github.com/gopherd/gonum@v0.0.4/mat/format.go (about)

     1  // Copyright ©2013 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package mat
     6  
     7  import (
     8  	"fmt"
     9  	"strconv"
    10  	"strings"
    11  )
    12  
    13  // Formatted returns a fmt.Formatter for the matrix m using the given options.
    14  func Formatted(m Matrix, options ...FormatOption) fmt.Formatter {
    15  	f := formatter{
    16  		matrix: m,
    17  		dot:    '.',
    18  	}
    19  	for _, o := range options {
    20  		o(&f)
    21  	}
    22  	return f
    23  }
    24  
    25  type formatter struct {
    26  	matrix  Matrix
    27  	prefix  string
    28  	margin  int
    29  	dot     byte
    30  	squeeze bool
    31  
    32  	format func(m Matrix, prefix string, margin int, dot byte, squeeze bool, fs fmt.State, c rune)
    33  }
    34  
    35  // FormatOption is a functional option for matrix formatting.
    36  type FormatOption func(*formatter)
    37  
    38  // Prefix sets the formatted prefix to the string p. Prefix is a string that is prepended to
    39  // each line of output after the first line.
    40  func Prefix(p string) FormatOption {
    41  	return func(f *formatter) { f.prefix = p }
    42  }
    43  
    44  // Excerpt sets the maximum number of rows and columns to print at the margins of the matrix
    45  // to m. If m is zero or less all elements are printed.
    46  func Excerpt(m int) FormatOption {
    47  	return func(f *formatter) { f.margin = m }
    48  }
    49  
    50  // DotByte sets the dot character to b. The dot character is used to replace zero elements
    51  // if the result is printed with the fmt ' ' verb flag. Without a DotByte option, the default
    52  // dot character is '.'.
    53  func DotByte(b byte) FormatOption {
    54  	return func(f *formatter) { f.dot = b }
    55  }
    56  
    57  // Squeeze sets the printing behavior to minimise column width for each individual column.
    58  func Squeeze() FormatOption {
    59  	return func(f *formatter) { f.squeeze = true }
    60  }
    61  
    62  // FormatMATLAB sets the printing behavior to output MATLAB syntax. If MATLAB syntax is
    63  // specified, the ' ' verb flag and Excerpt option are ignored. If the alternative syntax
    64  // verb flag, '#' is used the matrix is formatted in rows and columns.
    65  func FormatMATLAB() FormatOption {
    66  	return func(f *formatter) { f.format = formatMATLAB }
    67  }
    68  
    69  // FormatPython sets the printing behavior to output Python syntax. If Python syntax is
    70  // specified, the ' ' verb flag and Excerpt option are ignored. If the alternative syntax
    71  // verb flag, '#' is used the matrix is formatted in rows and columns.
    72  func FormatPython() FormatOption {
    73  	return func(f *formatter) { f.format = formatPython }
    74  }
    75  
    76  // Format satisfies the fmt.Formatter interface.
    77  func (f formatter) Format(fs fmt.State, c rune) {
    78  	if c == 'v' && fs.Flag('#') && f.format == nil {
    79  		fmt.Fprintf(fs, "%#v", f.matrix)
    80  		return
    81  	}
    82  	if f.format == nil {
    83  		f.format = format
    84  	}
    85  	f.format(f.matrix, f.prefix, f.margin, f.dot, f.squeeze, fs, c)
    86  }
    87  
    88  // format prints a pretty representation of m to the fs io.Writer. The format character c
    89  // specifies the numerical representation of elements; valid values are those for float64
    90  // specified in the fmt package, with their associated flags. In addition to this, a space
    91  // preceding a verb indicates that zero values should be represented by the dot character.
    92  // The printed range of the matrix can be limited by specifying a positive value for margin;
    93  // If margin is greater than zero, only the first and last margin rows/columns of the matrix
    94  // are output. If squeeze is true, column widths are determined on a per-column basis.
    95  //
    96  // format will not provide Go syntax output.
    97  func format(m Matrix, prefix string, margin int, dot byte, squeeze bool, fs fmt.State, c rune) {
    98  	rows, cols := m.Dims()
    99  
   100  	var printed int
   101  	if margin <= 0 {
   102  		printed = rows
   103  		if cols > printed {
   104  			printed = cols
   105  		}
   106  	} else {
   107  		printed = margin
   108  	}
   109  
   110  	prec, pOk := fs.Precision()
   111  	if !pOk {
   112  		prec = -1
   113  	}
   114  
   115  	var (
   116  		maxWidth int
   117  		widths   widther
   118  		buf, pad []byte
   119  	)
   120  	if squeeze {
   121  		widths = make(columnWidth, cols)
   122  	} else {
   123  		widths = new(uniformWidth)
   124  	}
   125  	switch c {
   126  	case 'v', 'e', 'E', 'f', 'F', 'g', 'G':
   127  		if c == 'v' {
   128  			buf, maxWidth = maxCellWidth(m, 'g', printed, prec, widths)
   129  		} else {
   130  			buf, maxWidth = maxCellWidth(m, c, printed, prec, widths)
   131  		}
   132  	default:
   133  		fmt.Fprintf(fs, "%%!%c(%T=Dims(%d, %d))", c, m, rows, cols)
   134  		return
   135  	}
   136  	width, _ := fs.Width()
   137  	width = max(width, maxWidth)
   138  	pad = make([]byte, max(width, 2))
   139  	for i := range pad {
   140  		pad[i] = ' '
   141  	}
   142  
   143  	first := true
   144  	if rows > 2*printed || cols > 2*printed {
   145  		first = false
   146  		fmt.Fprintf(fs, "Dims(%d, %d)\n", rows, cols)
   147  	}
   148  
   149  	skipZero := fs.Flag(' ')
   150  	for i := 0; i < rows; i++ {
   151  		if !first {
   152  			fmt.Fprint(fs, prefix)
   153  		}
   154  		first = false
   155  		var el string
   156  		switch {
   157  		case rows == 1:
   158  			fmt.Fprint(fs, "[")
   159  			el = "]"
   160  		case i == 0:
   161  			fmt.Fprint(fs, "⎡")
   162  			el = "⎤\n"
   163  		case i < rows-1:
   164  			fmt.Fprint(fs, "⎢")
   165  			el = "⎥\n"
   166  		default:
   167  			fmt.Fprint(fs, "⎣")
   168  			el = "⎦"
   169  		}
   170  
   171  		for j := 0; j < cols; j++ {
   172  			if j >= printed && j < cols-printed {
   173  				j = cols - printed - 1
   174  				if i == 0 || i == rows-1 {
   175  					fmt.Fprint(fs, "...  ...  ")
   176  				} else {
   177  					fmt.Fprint(fs, "          ")
   178  				}
   179  				continue
   180  			}
   181  
   182  			v := m.At(i, j)
   183  			if v == 0 && skipZero {
   184  				buf = buf[:1]
   185  				buf[0] = dot
   186  			} else {
   187  				if c == 'v' {
   188  					buf = strconv.AppendFloat(buf[:0], v, 'g', prec, 64)
   189  				} else {
   190  					buf = strconv.AppendFloat(buf[:0], v, byte(c), prec, 64)
   191  				}
   192  			}
   193  			if fs.Flag('-') {
   194  				fs.Write(buf)
   195  				fs.Write(pad[:widths.width(j)-len(buf)])
   196  			} else {
   197  				fs.Write(pad[:widths.width(j)-len(buf)])
   198  				fs.Write(buf)
   199  			}
   200  
   201  			if j < cols-1 {
   202  				fs.Write(pad[:2])
   203  			}
   204  		}
   205  
   206  		fmt.Fprint(fs, el)
   207  
   208  		if i >= printed-1 && i < rows-printed && 2*printed < rows {
   209  			i = rows - printed - 1
   210  			fmt.Fprintf(fs, "%s .\n%[1]s .\n%[1]s .\n", prefix)
   211  			continue
   212  		}
   213  	}
   214  }
   215  
   216  // formatMATLAB prints a MATLAB representation of m to the fs io.Writer. The format character c
   217  // specifies the numerical representation of elements; valid values are those for float64
   218  // specified in the fmt package, with their associated flags.
   219  // The printed range of the matrix can be limited by specifying a positive value for margin;
   220  // If squeeze is true, column widths are determined on a per-column basis.
   221  //
   222  // formatMATLAB will not provide Go syntax output.
   223  func formatMATLAB(m Matrix, prefix string, _ int, _ byte, squeeze bool, fs fmt.State, c rune) {
   224  	rows, cols := m.Dims()
   225  
   226  	prec, pOk := fs.Precision()
   227  	width, _ := fs.Width()
   228  	if !fs.Flag('#') {
   229  		switch c {
   230  		case 'v', 'e', 'E', 'f', 'F', 'g', 'G':
   231  		default:
   232  			fmt.Fprintf(fs, "%%!%c(%T=Dims(%d, %d))", c, m, rows, cols)
   233  			return
   234  		}
   235  		format := fmtString(fs, c, prec, width)
   236  		fs.Write([]byte{'['})
   237  		for i := 0; i < rows; i++ {
   238  			if i != 0 {
   239  				fs.Write([]byte("; "))
   240  			}
   241  			for j := 0; j < cols; j++ {
   242  				if j != 0 {
   243  					fs.Write([]byte{' '})
   244  				}
   245  				fmt.Fprintf(fs, format, m.At(i, j))
   246  			}
   247  		}
   248  		fs.Write([]byte{']'})
   249  		return
   250  	}
   251  
   252  	if !pOk {
   253  		prec = -1
   254  	}
   255  
   256  	printed := rows
   257  	if cols > printed {
   258  		printed = cols
   259  	}
   260  
   261  	var (
   262  		maxWidth int
   263  		widths   widther
   264  		buf, pad []byte
   265  	)
   266  	if squeeze {
   267  		widths = make(columnWidth, cols)
   268  	} else {
   269  		widths = new(uniformWidth)
   270  	}
   271  	switch c {
   272  	case 'v', 'e', 'E', 'f', 'F', 'g', 'G':
   273  		if c == 'v' {
   274  			buf, maxWidth = maxCellWidth(m, 'g', printed, prec, widths)
   275  		} else {
   276  			buf, maxWidth = maxCellWidth(m, c, printed, prec, widths)
   277  		}
   278  	default:
   279  		fmt.Fprintf(fs, "%%!%c(%T=Dims(%d, %d))", c, m, rows, cols)
   280  		return
   281  	}
   282  	width = max(width, maxWidth)
   283  	pad = make([]byte, max(width, 1))
   284  	for i := range pad {
   285  		pad[i] = ' '
   286  	}
   287  
   288  	for i := 0; i < rows; i++ {
   289  		var el string
   290  		switch {
   291  		case rows == 1:
   292  			fmt.Fprint(fs, "[")
   293  			el = "]"
   294  		case i == 0:
   295  			fmt.Fprint(fs, "[\n"+prefix+" ")
   296  			el = "\n"
   297  		case i < rows-1:
   298  			fmt.Fprint(fs, prefix+" ")
   299  			el = "\n"
   300  		default:
   301  			fmt.Fprint(fs, prefix+" ")
   302  			el = "\n" + prefix + "]"
   303  		}
   304  
   305  		for j := 0; j < cols; j++ {
   306  			v := m.At(i, j)
   307  			if c == 'v' {
   308  				buf = strconv.AppendFloat(buf[:0], v, 'g', prec, 64)
   309  			} else {
   310  				buf = strconv.AppendFloat(buf[:0], v, byte(c), prec, 64)
   311  			}
   312  			if fs.Flag('-') {
   313  				fs.Write(buf)
   314  				fs.Write(pad[:widths.width(j)-len(buf)])
   315  			} else {
   316  				fs.Write(pad[:widths.width(j)-len(buf)])
   317  				fs.Write(buf)
   318  			}
   319  
   320  			if j < cols-1 {
   321  				fs.Write(pad[:1])
   322  			}
   323  		}
   324  
   325  		fmt.Fprint(fs, el)
   326  	}
   327  }
   328  
   329  // formatPython prints a Python representation of m to the fs io.Writer. The format character c
   330  // specifies the numerical representation of elements; valid values are those for float64
   331  // specified in the fmt package, with their associated flags.
   332  // The printed range of the matrix can be limited by specifying a positive value for margin;
   333  // If squeeze is true, column widths are determined on a per-column basis.
   334  //
   335  // formatPython will not provide Go syntax output.
   336  func formatPython(m Matrix, prefix string, _ int, _ byte, squeeze bool, fs fmt.State, c rune) {
   337  	rows, cols := m.Dims()
   338  
   339  	prec, pOk := fs.Precision()
   340  	width, _ := fs.Width()
   341  	if !fs.Flag('#') {
   342  		switch c {
   343  		case 'v', 'e', 'E', 'f', 'F', 'g', 'G':
   344  		default:
   345  			fmt.Fprintf(fs, "%%!%c(%T=Dims(%d, %d))", c, m, rows, cols)
   346  			return
   347  		}
   348  		format := fmtString(fs, c, prec, width)
   349  		fs.Write([]byte{'['})
   350  		if rows > 1 {
   351  			fs.Write([]byte{'['})
   352  		}
   353  		for i := 0; i < rows; i++ {
   354  			if i != 0 {
   355  				fs.Write([]byte("], ["))
   356  			}
   357  			for j := 0; j < cols; j++ {
   358  				if j != 0 {
   359  					fs.Write([]byte(", "))
   360  				}
   361  				fmt.Fprintf(fs, format, m.At(i, j))
   362  			}
   363  		}
   364  		if rows > 1 {
   365  			fs.Write([]byte{']'})
   366  		}
   367  		fs.Write([]byte{']'})
   368  		return
   369  	}
   370  
   371  	if !pOk {
   372  		prec = -1
   373  	}
   374  
   375  	printed := rows
   376  	if cols > printed {
   377  		printed = cols
   378  	}
   379  
   380  	var (
   381  		maxWidth int
   382  		widths   widther
   383  		buf, pad []byte
   384  	)
   385  	if squeeze {
   386  		widths = make(columnWidth, cols)
   387  	} else {
   388  		widths = new(uniformWidth)
   389  	}
   390  	switch c {
   391  	case 'v', 'e', 'E', 'f', 'F', 'g', 'G':
   392  		if c == 'v' {
   393  			buf, maxWidth = maxCellWidth(m, 'g', printed, prec, widths)
   394  		} else {
   395  			buf, maxWidth = maxCellWidth(m, c, printed, prec, widths)
   396  		}
   397  	default:
   398  		fmt.Fprintf(fs, "%%!%c(%T=Dims(%d, %d))", c, m, rows, cols)
   399  		return
   400  	}
   401  	width = max(width, maxWidth)
   402  	pad = make([]byte, max(width, 1))
   403  	for i := range pad {
   404  		pad[i] = ' '
   405  	}
   406  
   407  	for i := 0; i < rows; i++ {
   408  		if i != 0 {
   409  			fmt.Fprint(fs, prefix)
   410  		}
   411  		var el string
   412  		switch {
   413  		case rows == 1:
   414  			fmt.Fprint(fs, "[")
   415  			el = "]"
   416  		case i == 0:
   417  			fmt.Fprint(fs, "[[")
   418  			el = "],\n"
   419  		case i < rows-1:
   420  			fmt.Fprint(fs, " [")
   421  			el = "],\n"
   422  		default:
   423  			fmt.Fprint(fs, " [")
   424  			el = "]]"
   425  		}
   426  
   427  		for j := 0; j < cols; j++ {
   428  			v := m.At(i, j)
   429  			if c == 'v' {
   430  				buf = strconv.AppendFloat(buf[:0], v, 'g', prec, 64)
   431  			} else {
   432  				buf = strconv.AppendFloat(buf[:0], v, byte(c), prec, 64)
   433  			}
   434  			if fs.Flag('-') {
   435  				fs.Write(buf)
   436  				fs.Write(pad[:widths.width(j)-len(buf)])
   437  			} else {
   438  				fs.Write(pad[:widths.width(j)-len(buf)])
   439  				fs.Write(buf)
   440  			}
   441  
   442  			if j < cols-1 {
   443  				fs.Write([]byte{','})
   444  				fs.Write(pad[:1])
   445  			}
   446  		}
   447  
   448  		fmt.Fprint(fs, el)
   449  	}
   450  }
   451  
   452  // This is horrible, but it's what we have.
   453  func fmtString(fs fmt.State, c rune, prec, width int) string {
   454  	var b strings.Builder
   455  	b.WriteByte('%')
   456  	for _, f := range "0+- " {
   457  		if fs.Flag(int(f)) {
   458  			b.WriteByte(byte(f))
   459  		}
   460  	}
   461  	if width >= 0 {
   462  		fmt.Fprint(&b, width)
   463  	}
   464  	if prec >= 0 {
   465  		b.WriteByte('.')
   466  		if prec > 0 {
   467  			fmt.Fprint(&b, prec)
   468  		}
   469  	}
   470  	b.WriteRune(c)
   471  	return b.String()
   472  }
   473  
   474  func maxCellWidth(m Matrix, c rune, printed, prec int, w widther) ([]byte, int) {
   475  	var (
   476  		buf        = make([]byte, 0, 64)
   477  		rows, cols = m.Dims()
   478  		max        int
   479  	)
   480  	for i := 0; i < rows; i++ {
   481  		if i >= printed-1 && i < rows-printed && 2*printed < rows {
   482  			i = rows - printed - 1
   483  			continue
   484  		}
   485  		for j := 0; j < cols; j++ {
   486  			if j >= printed && j < cols-printed {
   487  				continue
   488  			}
   489  
   490  			buf = strconv.AppendFloat(buf, m.At(i, j), byte(c), prec, 64)
   491  			if len(buf) > max {
   492  				max = len(buf)
   493  			}
   494  			if len(buf) > w.width(j) {
   495  				w.setWidth(j, len(buf))
   496  			}
   497  			buf = buf[:0]
   498  		}
   499  	}
   500  	return buf, max
   501  }
   502  
   503  type widther interface {
   504  	width(i int) int
   505  	setWidth(i, w int)
   506  }
   507  
   508  type uniformWidth int
   509  
   510  func (u *uniformWidth) width(_ int) int   { return int(*u) }
   511  func (u *uniformWidth) setWidth(_, w int) { *u = uniformWidth(w) }
   512  
   513  type columnWidth []int
   514  
   515  func (c columnWidth) width(i int) int   { return c[i] }
   516  func (c columnWidth) setWidth(i, w int) { c[i] = w }