gonum.org/v1/gonum@v0.14.0/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 }