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 5package mat 6 7import ( 8 "fmt" 9 "strconv" 10) 11 12// Formatted returns a fmt.Formatter for the matrix m using the given options. 13func Formatted(m Matrix, options ...FormatOption) fmt.Formatter { 14 f := formatter{ 15 matrix: m, 16 dot: '.', 17 } 18 for _, o := range options { 19 o(&f) 20 } 21 return f 22} 23 24type formatter struct { 25 matrix Matrix 26 prefix string 27 margin int 28 dot byte 29 squeeze bool 30} 31 32// FormatOption is a functional option for matrix formatting. 33type FormatOption func(*formatter) 34 35// Prefix sets the formatted prefix to the string p. Prefix is a string that is prepended to 36// each line of output. 37func Prefix(p string) FormatOption { 38 return func(f *formatter) { f.prefix = p } 39} 40 41// Excerpt sets the maximum number of rows and columns to print at the margins of the matrix 42// to m. If m is zero or less all elements are printed. 43func Excerpt(m int) FormatOption { 44 return func(f *formatter) { f.margin = m } 45} 46 47// DotByte sets the dot character to b. The dot character is used to replace zero elements 48// if the result is printed with the fmt ' ' verb flag. Without a DotByte option, the default 49// dot character is '.'. 50func DotByte(b byte) FormatOption { 51 return func(f *formatter) { f.dot = b } 52} 53 54// Squeeze sets the printing behaviour to minimise column width for each individual column. 55func Squeeze() FormatOption { 56 return func(f *formatter) { f.squeeze = true } 57} 58 59// Format satisfies the fmt.Formatter interface. 60func (f formatter) Format(fs fmt.State, c rune) { 61 if c == 'v' && fs.Flag('#') { 62 fmt.Fprintf(fs, "%#v", f.matrix) 63 return 64 } 65 format(f.matrix, f.prefix, f.margin, f.dot, f.squeeze, fs, c) 66} 67 68// format prints a pretty representation of m to the fs io.Writer. The format character c 69// specifies the numerical representation of elements; valid values are those for float64 70// specified in the fmt package, with their associated flags. In addition to this, a space 71// preceding a verb indicates that zero values should be represented by the dot character. 72// The printed range of the matrix can be limited by specifying a positive value for margin; 73// If margin is greater than zero, only the first and last margin rows/columns of the matrix 74// are output. If squeeze is true, column widths are determined on a per-column basis. 75// 76// format will not provide Go syntax output. 77func format(m Matrix, prefix string, margin int, dot byte, squeeze bool, fs fmt.State, c rune) { 78 rows, cols := m.Dims() 79 80 var printed int 81 if margin <= 0 { 82 printed = rows 83 if cols > printed { 84 printed = cols 85 } 86 } else { 87 printed = margin 88 } 89 90 prec, pOk := fs.Precision() 91 if !pOk { 92 prec = -1 93 } 94 95 var ( 96 maxWidth int 97 widths widther 98 buf, pad []byte 99 ) 100 if squeeze { 101 widths = make(columnWidth, cols) 102 } else { 103 widths = new(uniformWidth) 104 } 105 switch c { 106 case 'v', 'e', 'E', 'f', 'F', 'g', 'G': 107 if c == 'v' { 108 buf, maxWidth = maxCellWidth(m, 'g', printed, prec, widths) 109 } else { 110 buf, maxWidth = maxCellWidth(m, c, printed, prec, widths) 111 } 112 default: 113 fmt.Fprintf(fs, "%%!%c(%T=Dims(%d, %d))", c, m, rows, cols) 114 return 115 } 116 width, _ := fs.Width() 117 width = max(width, maxWidth) 118 pad = make([]byte, max(width, 2)) 119 for i := range pad { 120 pad[i] = ' ' 121 } 122 123 first := true 124 if rows > 2*printed || cols > 2*printed { 125 first = false 126 fmt.Fprintf(fs, "Dims(%d, %d)\n", rows, cols) 127 } 128 129 skipZero := fs.Flag(' ') 130 for i := 0; i < rows; i++ { 131 if !first { 132 fmt.Fprint(fs, prefix) 133 } 134 first = false 135 var el string 136 switch { 137 case rows == 1: 138 fmt.Fprint(fs, "[") 139 el = "]" 140 case i == 0: 141 fmt.Fprint(fs, "⎡") 142 el = "⎤\n" 143 case i < rows-1: 144 fmt.Fprint(fs, "⎢") 145 el = "⎥\n" 146 default: 147 fmt.Fprint(fs, "⎣") 148 el = "⎦" 149 } 150 151 for j := 0; j < cols; j++ { 152 if j >= printed && j < cols-printed { 153 j = cols - printed - 1 154 if i == 0 || i == rows-1 { 155 fmt.Fprint(fs, "... ... ") 156 } else { 157 fmt.Fprint(fs, " ") 158 } 159 continue 160 } 161 162 v := m.At(i, j) 163 if v == 0 && skipZero { 164 buf = buf[:1] 165 buf[0] = dot 166 } else { 167 if c == 'v' { 168 buf = strconv.AppendFloat(buf[:0], v, 'g', prec, 64) 169 } else { 170 buf = strconv.AppendFloat(buf[:0], v, byte(c), prec, 64) 171 } 172 } 173 if fs.Flag('-') { 174 fs.Write(buf) 175 fs.Write(pad[:widths.width(j)-len(buf)]) 176 } else { 177 fs.Write(pad[:widths.width(j)-len(buf)]) 178 fs.Write(buf) 179 } 180 181 if j < cols-1 { 182 fs.Write(pad[:2]) 183 } 184 } 185 186 fmt.Fprint(fs, el) 187 188 if i >= printed-1 && i < rows-printed && 2*printed < rows { 189 i = rows - printed - 1 190 fmt.Fprintf(fs, "%s .\n%[1]s .\n%[1]s .\n", prefix) 191 continue 192 } 193 } 194} 195 196func maxCellWidth(m Matrix, c rune, printed, prec int, w widther) ([]byte, int) { 197 var ( 198 buf = make([]byte, 0, 64) 199 rows, cols = m.Dims() 200 max int 201 ) 202 for i := 0; i < rows; i++ { 203 if i >= printed-1 && i < rows-printed && 2*printed < rows { 204 i = rows - printed - 1 205 continue 206 } 207 for j := 0; j < cols; j++ { 208 if j >= printed && j < cols-printed { 209 continue 210 } 211 212 buf = strconv.AppendFloat(buf, m.At(i, j), byte(c), prec, 64) 213 if len(buf) > max { 214 max = len(buf) 215 } 216 if len(buf) > w.width(j) { 217 w.setWidth(j, len(buf)) 218 } 219 buf = buf[:0] 220 } 221 } 222 return buf, max 223} 224 225type widther interface { 226 width(i int) int 227 setWidth(i, w int) 228} 229 230type uniformWidth int 231 232func (u *uniformWidth) width(_ int) int { return int(*u) } 233func (u *uniformWidth) setWidth(_, w int) { *u = uniformWidth(w) } 234 235type columnWidth []int 236 237func (c columnWidth) width(i int) int { return c[i] } 238func (c columnWidth) setWidth(i, w int) { c[i] = w } 239