diff --git a/src/sage/matrix/matrix0.pyx b/src/sage/matrix/matrix0.pyx index 72feaee7a5d..3dc47b9df07 100644 --- a/src/sage/matrix/matrix0.pyx +++ b/src/sage/matrix/matrix0.pyx @@ -1796,7 +1796,9 @@ cdef class Matrix(sage.structure.element.Matrix): return self.str() def str(self, rep_mapping=None, zero=None, plus_one=None, minus_one=None, - *, unicode=False, shape=None, character_art=False): + *, unicode=False, shape=None, character_art=False, + left_border=None, right_border=None, + top_border=None, bottom_border=None): r""" Return a nice string representation of the matrix. @@ -1846,6 +1848,16 @@ cdef class Matrix(sage.structure.element.Matrix): :class:`~sage.typeset.unicode_art.UnicodeArt` which support line breaking of wide matrices that exceed the window width + - ``left_border``, ``right_border`` -- sequence (default: ``None``); + if not ``None``, call :func:`str` on the elements and use the + results as labels for the rows of the matrix. The labels appear + outside of the parentheses. + + - ``top_border``, ``bottom_border`` -- sequence (default: ``None``); + if not ``None``, call :func:`str` on the elements and use the + results as labels for the columns of the matrix. The labels appear + outside of the parentheses. + EXAMPLES:: sage: R = PolynomialRing(QQ,6,'z') @@ -1921,6 +1933,21 @@ cdef class Matrix(sage.structure.element.Matrix): [ 0.333333333333333 66.6666666666667] [ -3.00000000000000 1.00000000000000e6] + Matrices with borders:: + + sage: M = matrix([[1,2,3], [4,5,6], [7,8,9]]) + sage: M.subdivide(None, 2) + sage: print(M.str(unicode=True, + ....: top_border=['ab', 'cde', 'f'], + ....: bottom_border=['*', '', ''], + ....: left_border=[1, 10, 100], + ....: right_border=['', ' <', ''])) + ab cde f + 1⎛ 1 2│ 3⎞ + 10⎜ 4 5│ 6⎟ < + 100⎝ 7 8│ 9⎠ + * + TESTS: Prior to :issue:`11544` this could take a full minute to run (2011). :: @@ -1958,6 +1985,32 @@ cdef class Matrix(sage.structure.element.Matrix): sage: matrix(R, [[2/3 - 10^6 * x^3 + 3 * y + O(x, y)^4]]) [2/3 + 3*y - 1000000*x^3 + O(x, y)^4] sage: matrix.options._reset() + + Edge cases of matrices with borders:: + + sage: print(matrix(ZZ, 0, 0).str( + ....: top_border=[], bottom_border=[], left_border=[], right_border=[])) + [] + sage: print(matrix(ZZ, 0, 4).str( + ....: unicode=True, + ....: top_border='abcd', bottom_border=range(4))) + () + sage: print(matrix(ZZ, 1, 4).str( + ....: unicode=True, + ....: top_border='abcd', bottom_border=range(4))) + a b c d + (0 0 0 0) + 0 1 2 3 + sage: print(matrix(ZZ, 2, 4).str( + ....: unicode=True, + ....: top_border='abcd', bottom_border=range(4), left_border='uv')) + a b c d + u⎛0 0 0 0⎞ + v⎝0 0 0 0⎠ + 0 1 2 3 + sage: print(matrix(ZZ, 2, 0).str( + ....: top_border='', left_border='uv', right_border=['*', ''])) + [] """ cdef Py_ssize_t nr, nc, r, c nr = self._nrows @@ -2063,6 +2116,12 @@ cdef class Matrix(sage.structure.element.Matrix): # compute column widths S = [] + if top_border is not None: + for x in top_border: + S.append(str(x)) + top_count = 1 + else: + top_count = 0 for x in entries: # Override the usual representations with those specified if callable(rep_mapping): @@ -2075,39 +2134,83 @@ cdef class Matrix(sage.structure.element.Matrix): else: rep = repr(x) S.append(rep) + if bottom_border is not None: + for x in bottom_border: + S.append(str(x)) + bottom_count = 1 + else: + bottom_count = 0 width = max(map(len, S)) + left = [] rows = [] + right = [] hline = cl.join(hl * ((width + 1)*(b - a) - 1) - for a,b in zip([0] + col_divs, col_divs + [nc])) + for a,b in zip([0] + col_divs, col_divs + [nc])) # compute rows - for r from 0 <= r < nr: - rows += [hline] * row_divs.count(r) + for r in range(-top_count, nr + bottom_count): + if 0 <= r < nr: + n = row_divs.count(r) + if n: + left.extend([""] * n) + rows.extend([hline] * n) + right.extend([""] * n) + if left_border is not None and 0 <= r < nr: + left.append(str(left_border[r])) + else: + left.append("") s = "" for c from 0 <= c < nc: if col_div_counts[c]: - sep = vl * col_div_counts[c] + if 0 <= r < nr: + sep = vl * col_div_counts[c] + else: + sep = " " * col_div_counts[c] elif c == 0: sep = "" else: sep = " " - entry = S[r * nc + c] + entry = S[(r + top_count) * nc + c] entry = " " * (width - len(entry)) + entry s = s + sep + entry - s = s + vl * col_div_counts[nc] + else: + if 0 <= r < nr: + s = s + vl * col_div_counts[nc] + else: + s = s + " " * col_div_counts[nc] rows.append(s) - rows += [hline] * row_divs.count(nr) - - last_row = len(rows) - 1 - if last_row == 0: - rows[0] = slb + rows[0] + srb + if right_border is not None and 0 <= r < nr: + right.append(str(right_border[r])) + else: + right.append("") + else: + if nr == nr + bottom_count: + n = row_divs.count(nr) + if n: + left.extend([""] * n) + rows.extend([hline] * n) + right.extend([""] * n) + + # left and right brackets + for i in range(top_count): + rows[i] = " "*len(slb) + rows[i] + " "*len(srb) + if len(rows) == top_count + 1 + bottom_count: + rows[top_count] = slb + rows[top_count] + srb else: - rows[0] = tlb + rows[0] + trb - for r from 1 <= r < last_row: - rows[r] = mlb + rows[r] + mrb - rows[last_row] = blb + rows[last_row] + brb + rows[top_count] = tlb + rows[top_count] + trb + for i in range(top_count + 1, len(rows) - bottom_count - 1): + rows[i] = mlb + rows[i] + mrb + rows[-1 - bottom_count] = blb + rows[-1 - bottom_count] + brb + for i in range(bottom_count): + rows[-1 - i] = " "*len(slb) + rows[-1 - i] + " "*len(srb) + + # left and right border + left_width = max(len(s) for s in left) + right_width = max(len(s) for s in right) + for i in range(len(rows)): + rows[i] = left[i].rjust(left_width) + rows[i] + right[i].rjust(right_width) if character_art: breakpoints = [] diff --git a/src/sage/matrix/matrix_mod2_dense.pyx b/src/sage/matrix/matrix_mod2_dense.pyx index 9dd9ffcf6b8..f7689c60b07 100644 --- a/src/sage/matrix/matrix_mod2_dense.pyx +++ b/src/sage/matrix/matrix_mod2_dense.pyx @@ -346,7 +346,9 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse def str(self, rep_mapping=None, zero=None, plus_one=None, minus_one=None, - *, unicode=False, shape=None, character_art=False): + *, unicode=False, shape=None, character_art=False, + left_border=None, right_border=None, + top_border=None, bottom_border=None): r""" Return a nice string representation of the matrix. @@ -384,6 +386,16 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse :class:`~sage.typeset.unicode_art.UnicodeArt` which support line breaking of wide matrices that exceed the window width + - ``left_border``, ``right_border`` -- sequence (default: ``None``); + if not ``None``, call :func:`str` on the elements and use the + results as labels for the rows of the matrix. The labels appear + outside of the parentheses. + + - ``top_border``, ``bottom_border`` -- sequence (default: ``None``); + if not ``None``, call :func:`str` on the elements and use the + results as labels for the columns of the matrix. The labels appear + outside of the parentheses. + EXAMPLES:: sage: B = matrix(GF(2), 3, 3, [0, 1, 0, 0, 1, 1, 0, 0, 0]) @@ -416,13 +428,19 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse # Set the mapping based on keyword arguments # We ignore minus_one (it's only there for compatibility with Matrix) if (rep_mapping is not None or zero is not None or plus_one is not None - or unicode or shape is not None or character_art): + or unicode or shape is not None or character_art + or left_border is not None or right_border is not None + or top_border is not None or bottom_border is not None): # Shunt mappings off to the generic code since they might not be # single characters return matrix_dense.Matrix_dense.str(self, rep_mapping=rep_mapping, zero=zero, plus_one=plus_one, unicode=unicode, shape=shape, - character_art=character_art) + character_art=character_art, + left_border=left_border, + right_border=right_border, + top_border=top_border, + bottom_border=bottom_border) if self._nrows == 0 or self._ncols == 0: return "[]"