517 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			517 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
| // Copyright ©2013 The Gonum Authors. All rights reserved.
 | |
| // Use of this source code is governed by a BSD-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| package mat
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| // Formatted returns a fmt.Formatter for the matrix m using the given options.
 | |
| func Formatted(m Matrix, options ...FormatOption) fmt.Formatter {
 | |
| 	f := formatter{
 | |
| 		matrix: m,
 | |
| 		dot:    '.',
 | |
| 	}
 | |
| 	for _, o := range options {
 | |
| 		o(&f)
 | |
| 	}
 | |
| 	return f
 | |
| }
 | |
| 
 | |
| type formatter struct {
 | |
| 	matrix  Matrix
 | |
| 	prefix  string
 | |
| 	margin  int
 | |
| 	dot     byte
 | |
| 	squeeze bool
 | |
| 
 | |
| 	format func(m Matrix, prefix string, margin int, dot byte, squeeze bool, fs fmt.State, c rune)
 | |
| }
 | |
| 
 | |
| // FormatOption is a functional option for matrix formatting.
 | |
| type FormatOption func(*formatter)
 | |
| 
 | |
| // Prefix sets the formatted prefix to the string p. Prefix is a string that is prepended to
 | |
| // each line of output after the first line.
 | |
| func Prefix(p string) FormatOption {
 | |
| 	return func(f *formatter) { f.prefix = p }
 | |
| }
 | |
| 
 | |
| // Excerpt sets the maximum number of rows and columns to print at the margins of the matrix
 | |
| // to m. If m is zero or less all elements are printed.
 | |
| func Excerpt(m int) FormatOption {
 | |
| 	return func(f *formatter) { f.margin = m }
 | |
| }
 | |
| 
 | |
| // DotByte sets the dot character to b. The dot character is used to replace zero elements
 | |
| // if the result is printed with the fmt ' ' verb flag. Without a DotByte option, the default
 | |
| // dot character is '.'.
 | |
| func DotByte(b byte) FormatOption {
 | |
| 	return func(f *formatter) { f.dot = b }
 | |
| }
 | |
| 
 | |
| // Squeeze sets the printing behavior to minimise column width for each individual column.
 | |
| func Squeeze() FormatOption {
 | |
| 	return func(f *formatter) { f.squeeze = true }
 | |
| }
 | |
| 
 | |
| // FormatMATLAB sets the printing behavior to output MATLAB syntax. If MATLAB syntax is
 | |
| // specified, the ' ' verb flag and Excerpt option are ignored. If the alternative syntax
 | |
| // verb flag, '#' is used the matrix is formatted in rows and columns.
 | |
| func FormatMATLAB() FormatOption {
 | |
| 	return func(f *formatter) { f.format = formatMATLAB }
 | |
| }
 | |
| 
 | |
| // FormatPython sets the printing behavior to output Python syntax. If Python syntax is
 | |
| // specified, the ' ' verb flag and Excerpt option are ignored. If the alternative syntax
 | |
| // verb flag, '#' is used the matrix is formatted in rows and columns.
 | |
| func FormatPython() FormatOption {
 | |
| 	return func(f *formatter) { f.format = formatPython }
 | |
| }
 | |
| 
 | |
| // Format satisfies the fmt.Formatter interface.
 | |
| func (f formatter) Format(fs fmt.State, c rune) {
 | |
| 	if c == 'v' && fs.Flag('#') && f.format == nil {
 | |
| 		fmt.Fprintf(fs, "%#v", f.matrix)
 | |
| 		return
 | |
| 	}
 | |
| 	if f.format == nil {
 | |
| 		f.format = format
 | |
| 	}
 | |
| 	f.format(f.matrix, f.prefix, f.margin, f.dot, f.squeeze, fs, c)
 | |
| }
 | |
| 
 | |
| // format prints a pretty representation of m to the fs io.Writer. The format character c
 | |
| // specifies the numerical representation of elements; valid values are those for float64
 | |
| // specified in the fmt package, with their associated flags. In addition to this, a space
 | |
| // preceding a verb indicates that zero values should be represented by the dot character.
 | |
| // The printed range of the matrix can be limited by specifying a positive value for margin;
 | |
| // If margin is greater than zero, only the first and last margin rows/columns of the matrix
 | |
| // are output. If squeeze is true, column widths are determined on a per-column basis.
 | |
| //
 | |
| // format will not provide Go syntax output.
 | |
| func format(m Matrix, prefix string, margin int, dot byte, squeeze bool, fs fmt.State, c rune) {
 | |
| 	rows, cols := m.Dims()
 | |
| 
 | |
| 	var printed int
 | |
| 	if margin <= 0 {
 | |
| 		printed = rows
 | |
| 		if cols > printed {
 | |
| 			printed = cols
 | |
| 		}
 | |
| 	} else {
 | |
| 		printed = margin
 | |
| 	}
 | |
| 
 | |
| 	prec, pOk := fs.Precision()
 | |
| 	if !pOk {
 | |
| 		prec = -1
 | |
| 	}
 | |
| 
 | |
| 	var (
 | |
| 		maxWidth int
 | |
| 		widths   widther
 | |
| 		buf, pad []byte
 | |
| 	)
 | |
| 	if squeeze {
 | |
| 		widths = make(columnWidth, cols)
 | |
| 	} else {
 | |
| 		widths = new(uniformWidth)
 | |
| 	}
 | |
| 	switch c {
 | |
| 	case 'v', 'e', 'E', 'f', 'F', 'g', 'G':
 | |
| 		if c == 'v' {
 | |
| 			buf, maxWidth = maxCellWidth(m, 'g', printed, prec, widths)
 | |
| 		} else {
 | |
| 			buf, maxWidth = maxCellWidth(m, c, printed, prec, widths)
 | |
| 		}
 | |
| 	default:
 | |
| 		fmt.Fprintf(fs, "%%!%c(%T=Dims(%d, %d))", c, m, rows, cols)
 | |
| 		return
 | |
| 	}
 | |
| 	width, _ := fs.Width()
 | |
| 	width = max(width, maxWidth)
 | |
| 	pad = make([]byte, max(width, 2))
 | |
| 	for i := range pad {
 | |
| 		pad[i] = ' '
 | |
| 	}
 | |
| 
 | |
| 	first := true
 | |
| 	if rows > 2*printed || cols > 2*printed {
 | |
| 		first = false
 | |
| 		fmt.Fprintf(fs, "Dims(%d, %d)\n", rows, cols)
 | |
| 	}
 | |
| 
 | |
| 	skipZero := fs.Flag(' ')
 | |
| 	for i := 0; i < rows; i++ {
 | |
| 		if !first {
 | |
| 			fmt.Fprint(fs, prefix)
 | |
| 		}
 | |
| 		first = false
 | |
| 		var el string
 | |
| 		switch {
 | |
| 		case rows == 1:
 | |
| 			fmt.Fprint(fs, "[")
 | |
| 			el = "]"
 | |
| 		case i == 0:
 | |
| 			fmt.Fprint(fs, "⎡")
 | |
| 			el = "⎤\n"
 | |
| 		case i < rows-1:
 | |
| 			fmt.Fprint(fs, "⎢")
 | |
| 			el = "⎥\n"
 | |
| 		default:
 | |
| 			fmt.Fprint(fs, "⎣")
 | |
| 			el = "⎦"
 | |
| 		}
 | |
| 
 | |
| 		for j := 0; j < cols; j++ {
 | |
| 			if j >= printed && j < cols-printed {
 | |
| 				j = cols - printed - 1
 | |
| 				if i == 0 || i == rows-1 {
 | |
| 					fmt.Fprint(fs, "...  ...  ")
 | |
| 				} else {
 | |
| 					fmt.Fprint(fs, "          ")
 | |
| 				}
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			v := m.At(i, j)
 | |
| 			if v == 0 && skipZero {
 | |
| 				buf = buf[:1]
 | |
| 				buf[0] = dot
 | |
| 			} else {
 | |
| 				if c == 'v' {
 | |
| 					buf = strconv.AppendFloat(buf[:0], v, 'g', prec, 64)
 | |
| 				} else {
 | |
| 					buf = strconv.AppendFloat(buf[:0], v, byte(c), prec, 64)
 | |
| 				}
 | |
| 			}
 | |
| 			if fs.Flag('-') {
 | |
| 				fs.Write(buf)
 | |
| 				fs.Write(pad[:widths.width(j)-len(buf)])
 | |
| 			} else {
 | |
| 				fs.Write(pad[:widths.width(j)-len(buf)])
 | |
| 				fs.Write(buf)
 | |
| 			}
 | |
| 
 | |
| 			if j < cols-1 {
 | |
| 				fs.Write(pad[:2])
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		fmt.Fprint(fs, el)
 | |
| 
 | |
| 		if i >= printed-1 && i < rows-printed && 2*printed < rows {
 | |
| 			i = rows - printed - 1
 | |
| 			fmt.Fprintf(fs, "%s .\n%[1]s .\n%[1]s .\n", prefix)
 | |
| 			continue
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // formatMATLAB prints a MATLAB representation of m to the fs io.Writer. The format character c
 | |
| // specifies the numerical representation of elements; valid values are those for float64
 | |
| // specified in the fmt package, with their associated flags.
 | |
| // The printed range of the matrix can be limited by specifying a positive value for margin;
 | |
| // If squeeze is true, column widths are determined on a per-column basis.
 | |
| //
 | |
| // formatMATLAB will not provide Go syntax output.
 | |
| func formatMATLAB(m Matrix, prefix string, _ int, _ byte, squeeze bool, fs fmt.State, c rune) {
 | |
| 	rows, cols := m.Dims()
 | |
| 
 | |
| 	prec, pOk := fs.Precision()
 | |
| 	width, _ := fs.Width()
 | |
| 	if !fs.Flag('#') {
 | |
| 		switch c {
 | |
| 		case 'v', 'e', 'E', 'f', 'F', 'g', 'G':
 | |
| 		default:
 | |
| 			fmt.Fprintf(fs, "%%!%c(%T=Dims(%d, %d))", c, m, rows, cols)
 | |
| 			return
 | |
| 		}
 | |
| 		format := fmtString(fs, c, prec, width)
 | |
| 		fs.Write([]byte{'['})
 | |
| 		for i := 0; i < rows; i++ {
 | |
| 			if i != 0 {
 | |
| 				fs.Write([]byte("; "))
 | |
| 			}
 | |
| 			for j := 0; j < cols; j++ {
 | |
| 				if j != 0 {
 | |
| 					fs.Write([]byte{' '})
 | |
| 				}
 | |
| 				fmt.Fprintf(fs, format, m.At(i, j))
 | |
| 			}
 | |
| 		}
 | |
| 		fs.Write([]byte{']'})
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if !pOk {
 | |
| 		prec = -1
 | |
| 	}
 | |
| 
 | |
| 	printed := rows
 | |
| 	if cols > printed {
 | |
| 		printed = cols
 | |
| 	}
 | |
| 
 | |
| 	var (
 | |
| 		maxWidth int
 | |
| 		widths   widther
 | |
| 		buf, pad []byte
 | |
| 	)
 | |
| 	if squeeze {
 | |
| 		widths = make(columnWidth, cols)
 | |
| 	} else {
 | |
| 		widths = new(uniformWidth)
 | |
| 	}
 | |
| 	switch c {
 | |
| 	case 'v', 'e', 'E', 'f', 'F', 'g', 'G':
 | |
| 		if c == 'v' {
 | |
| 			buf, maxWidth = maxCellWidth(m, 'g', printed, prec, widths)
 | |
| 		} else {
 | |
| 			buf, maxWidth = maxCellWidth(m, c, printed, prec, widths)
 | |
| 		}
 | |
| 	default:
 | |
| 		fmt.Fprintf(fs, "%%!%c(%T=Dims(%d, %d))", c, m, rows, cols)
 | |
| 		return
 | |
| 	}
 | |
| 	width = max(width, maxWidth)
 | |
| 	pad = make([]byte, max(width, 1))
 | |
| 	for i := range pad {
 | |
| 		pad[i] = ' '
 | |
| 	}
 | |
| 
 | |
| 	for i := 0; i < rows; i++ {
 | |
| 		var el string
 | |
| 		switch {
 | |
| 		case rows == 1:
 | |
| 			fmt.Fprint(fs, "[")
 | |
| 			el = "]"
 | |
| 		case i == 0:
 | |
| 			fmt.Fprint(fs, "[\n"+prefix+" ")
 | |
| 			el = "\n"
 | |
| 		case i < rows-1:
 | |
| 			fmt.Fprint(fs, prefix+" ")
 | |
| 			el = "\n"
 | |
| 		default:
 | |
| 			fmt.Fprint(fs, prefix+" ")
 | |
| 			el = "\n" + prefix + "]"
 | |
| 		}
 | |
| 
 | |
| 		for j := 0; j < cols; j++ {
 | |
| 			v := m.At(i, j)
 | |
| 			if c == 'v' {
 | |
| 				buf = strconv.AppendFloat(buf[:0], v, 'g', prec, 64)
 | |
| 			} else {
 | |
| 				buf = strconv.AppendFloat(buf[:0], v, byte(c), prec, 64)
 | |
| 			}
 | |
| 			if fs.Flag('-') {
 | |
| 				fs.Write(buf)
 | |
| 				fs.Write(pad[:widths.width(j)-len(buf)])
 | |
| 			} else {
 | |
| 				fs.Write(pad[:widths.width(j)-len(buf)])
 | |
| 				fs.Write(buf)
 | |
| 			}
 | |
| 
 | |
| 			if j < cols-1 {
 | |
| 				fs.Write(pad[:1])
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		fmt.Fprint(fs, el)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // formatPython prints a Python representation of m to the fs io.Writer. The format character c
 | |
| // specifies the numerical representation of elements; valid values are those for float64
 | |
| // specified in the fmt package, with their associated flags.
 | |
| // The printed range of the matrix can be limited by specifying a positive value for margin;
 | |
| // If squeeze is true, column widths are determined on a per-column basis.
 | |
| //
 | |
| // formatPython will not provide Go syntax output.
 | |
| func formatPython(m Matrix, prefix string, _ int, _ byte, squeeze bool, fs fmt.State, c rune) {
 | |
| 	rows, cols := m.Dims()
 | |
| 
 | |
| 	prec, pOk := fs.Precision()
 | |
| 	width, _ := fs.Width()
 | |
| 	if !fs.Flag('#') {
 | |
| 		switch c {
 | |
| 		case 'v', 'e', 'E', 'f', 'F', 'g', 'G':
 | |
| 		default:
 | |
| 			fmt.Fprintf(fs, "%%!%c(%T=Dims(%d, %d))", c, m, rows, cols)
 | |
| 			return
 | |
| 		}
 | |
| 		format := fmtString(fs, c, prec, width)
 | |
| 		fs.Write([]byte{'['})
 | |
| 		if rows > 1 {
 | |
| 			fs.Write([]byte{'['})
 | |
| 		}
 | |
| 		for i := 0; i < rows; i++ {
 | |
| 			if i != 0 {
 | |
| 				fs.Write([]byte("], ["))
 | |
| 			}
 | |
| 			for j := 0; j < cols; j++ {
 | |
| 				if j != 0 {
 | |
| 					fs.Write([]byte(", "))
 | |
| 				}
 | |
| 				fmt.Fprintf(fs, format, m.At(i, j))
 | |
| 			}
 | |
| 		}
 | |
| 		if rows > 1 {
 | |
| 			fs.Write([]byte{']'})
 | |
| 		}
 | |
| 		fs.Write([]byte{']'})
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if !pOk {
 | |
| 		prec = -1
 | |
| 	}
 | |
| 
 | |
| 	printed := rows
 | |
| 	if cols > printed {
 | |
| 		printed = cols
 | |
| 	}
 | |
| 
 | |
| 	var (
 | |
| 		maxWidth int
 | |
| 		widths   widther
 | |
| 		buf, pad []byte
 | |
| 	)
 | |
| 	if squeeze {
 | |
| 		widths = make(columnWidth, cols)
 | |
| 	} else {
 | |
| 		widths = new(uniformWidth)
 | |
| 	}
 | |
| 	switch c {
 | |
| 	case 'v', 'e', 'E', 'f', 'F', 'g', 'G':
 | |
| 		if c == 'v' {
 | |
| 			buf, maxWidth = maxCellWidth(m, 'g', printed, prec, widths)
 | |
| 		} else {
 | |
| 			buf, maxWidth = maxCellWidth(m, c, printed, prec, widths)
 | |
| 		}
 | |
| 	default:
 | |
| 		fmt.Fprintf(fs, "%%!%c(%T=Dims(%d, %d))", c, m, rows, cols)
 | |
| 		return
 | |
| 	}
 | |
| 	width = max(width, maxWidth)
 | |
| 	pad = make([]byte, max(width, 1))
 | |
| 	for i := range pad {
 | |
| 		pad[i] = ' '
 | |
| 	}
 | |
| 
 | |
| 	for i := 0; i < rows; i++ {
 | |
| 		if i != 0 {
 | |
| 			fmt.Fprint(fs, prefix)
 | |
| 		}
 | |
| 		var el string
 | |
| 		switch {
 | |
| 		case rows == 1:
 | |
| 			fmt.Fprint(fs, "[")
 | |
| 			el = "]"
 | |
| 		case i == 0:
 | |
| 			fmt.Fprint(fs, "[[")
 | |
| 			el = "],\n"
 | |
| 		case i < rows-1:
 | |
| 			fmt.Fprint(fs, " [")
 | |
| 			el = "],\n"
 | |
| 		default:
 | |
| 			fmt.Fprint(fs, " [")
 | |
| 			el = "]]"
 | |
| 		}
 | |
| 
 | |
| 		for j := 0; j < cols; j++ {
 | |
| 			v := m.At(i, j)
 | |
| 			if c == 'v' {
 | |
| 				buf = strconv.AppendFloat(buf[:0], v, 'g', prec, 64)
 | |
| 			} else {
 | |
| 				buf = strconv.AppendFloat(buf[:0], v, byte(c), prec, 64)
 | |
| 			}
 | |
| 			if fs.Flag('-') {
 | |
| 				fs.Write(buf)
 | |
| 				fs.Write(pad[:widths.width(j)-len(buf)])
 | |
| 			} else {
 | |
| 				fs.Write(pad[:widths.width(j)-len(buf)])
 | |
| 				fs.Write(buf)
 | |
| 			}
 | |
| 
 | |
| 			if j < cols-1 {
 | |
| 				fs.Write([]byte{','})
 | |
| 				fs.Write(pad[:1])
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		fmt.Fprint(fs, el)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // This is horrible, but it's what we have.
 | |
| func fmtString(fs fmt.State, c rune, prec, width int) string {
 | |
| 	var b strings.Builder
 | |
| 	b.WriteByte('%')
 | |
| 	for _, f := range "0+- " {
 | |
| 		if fs.Flag(int(f)) {
 | |
| 			b.WriteByte(byte(f))
 | |
| 		}
 | |
| 	}
 | |
| 	if width >= 0 {
 | |
| 		fmt.Fprint(&b, width)
 | |
| 	}
 | |
| 	if prec >= 0 {
 | |
| 		b.WriteByte('.')
 | |
| 		if prec > 0 {
 | |
| 			fmt.Fprint(&b, prec)
 | |
| 		}
 | |
| 	}
 | |
| 	b.WriteRune(c)
 | |
| 	return b.String()
 | |
| }
 | |
| 
 | |
| func maxCellWidth(m Matrix, c rune, printed, prec int, w widther) ([]byte, int) {
 | |
| 	var (
 | |
| 		buf        = make([]byte, 0, 64)
 | |
| 		rows, cols = m.Dims()
 | |
| 		max        int
 | |
| 	)
 | |
| 	for i := 0; i < rows; i++ {
 | |
| 		if i >= printed-1 && i < rows-printed && 2*printed < rows {
 | |
| 			i = rows - printed - 1
 | |
| 			continue
 | |
| 		}
 | |
| 		for j := 0; j < cols; j++ {
 | |
| 			if j >= printed && j < cols-printed {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			buf = strconv.AppendFloat(buf, m.At(i, j), byte(c), prec, 64)
 | |
| 			if len(buf) > max {
 | |
| 				max = len(buf)
 | |
| 			}
 | |
| 			if len(buf) > w.width(j) {
 | |
| 				w.setWidth(j, len(buf))
 | |
| 			}
 | |
| 			buf = buf[:0]
 | |
| 		}
 | |
| 	}
 | |
| 	return buf, max
 | |
| }
 | |
| 
 | |
| type widther interface {
 | |
| 	width(i int) int
 | |
| 	setWidth(i, w int)
 | |
| }
 | |
| 
 | |
| type uniformWidth int
 | |
| 
 | |
| func (u *uniformWidth) width(_ int) int   { return int(*u) }
 | |
| func (u *uniformWidth) setWidth(_, w int) { *u = uniformWidth(w) }
 | |
| 
 | |
| type columnWidth []int
 | |
| 
 | |
| func (c columnWidth) width(i int) int   { return c[i] }
 | |
| func (c columnWidth) setWidth(i, w int) { c[i] = w }
 |