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