├── .gitignore ├── LICENSE ├── README.md ├── dub.sdl ├── dub.selections.json └── source └── pretty_array.d /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.o 3 | *.obj 4 | 5 | # Compiled Dynamic libraries 6 | *.so 7 | *.dylib 8 | *.dll 9 | 10 | # Compiled Static libraries 11 | *.a 12 | *.lib 13 | 14 | # Executables 15 | *.exe 16 | 17 | # DUB 18 | .dub 19 | docs.json 20 | __dummy.html 21 | docs/ 22 | *.pdb 23 | 24 | # Code coverage 25 | *.lst 26 | 27 | # custom 28 | app.d 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 tastyminerals 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pretty\_d\_array 2 | 3 | [Pretty-printing multidimensional D arrays](https://tastyminerals.github.io/tasty-blog/dlang/2020/06/25/pretty_printing_arrays.html). 4 | 5 | This small package uses awesome [mir-algorithm](https://github.com/libmir/mir-algorithm) library as a dependency. 6 | 7 | Simply put, it is a small dub package that turns your D arrays from this: 8 | 9 | ``` 10 | [[[1, 2, 3, 4, 5, 6, 7, 8], [9, 10, 11, 12, 13, 14, 15, 16], [17, 18, 19, 20, 21, 22, 23, 24], [25, 26, 27, 28, 29, 30, 31, 32], [33, 34, 35, 36, 37, 38, 39, 40], [41, 42, 43, 44, 45, 46, 47, 48], [49, 50, 51, 52, 53, 54, 55, 56]], 11 | [[57, 58, 59, 60, 61, 62, 63, 64], [65, 66, 67, 68, 69, 70, 71, 72], [73, 74, 75, 76, 77, 78, 79, 80], [81, 82, 83, 84, 85, 86, 87, 88], [89, 90, 91, 92, 93, 94, 95, 96], [97, 98, 99, 100, 101, 102, 103, 104], [105, 106, 107, 108, 109, 110, 111, 112]]] 12 | ``` 13 | 14 | into this 15 | 16 | ``` 17 | ┌ ┐ 18 | │ 1 2 3 4 5 6 7 8│ 19 | │ 9 10 11 12 13 14 15 16│ 20 | │ 17 18 19 20 21 22 23 24│ 21 | │ 25 26 27 28 29 30 31 32│ 22 | │ 33 34 35 36 37 38 39 40│ 23 | │ 41 42 43 44 45 46 47 48│ 24 | │ 49 50 51 52 53 54 55 56│ 25 | └ ┘ 26 | ┌ ┐ 27 | │ 57 58 59 60 61 62 63 64│ 28 | │ 65 66 67 68 69 70 71 72│ 29 | │ 73 74 75 76 77 78 79 80│ 30 | │ 81 82 83 84 85 86 87 88│ 31 | │ 89 90 91 92 93 94 95 96│ 32 | │ 97 98 99 100 101 102 103 104│ 33 | │105 106 107 108 109 110 111 112│ 34 | └ ┘ 35 | ``` 36 | 37 | I think it's much easier to reason about array structure using such simplified form. 38 | Let's see a code example. 39 | 40 | ```d 41 | import pretty_array; 42 | import std.stdio; 43 | import std.array; 44 | import std.range : chunks; 45 | 46 | void main() { 47 | auto arr = [10.4, 200.14, -40.203, 0.00523, 5, 2.56, 39.901, 56.12, 2.5, 1.2, -0.22103, 89091, 3, 5, 1, 0]; 48 | auto arr3D = darr.chunks(4).array.chunks(2).array; // convert it to [2 x 2 x 4] array 49 | arr3D.prettyArr.writeln; 50 | } 51 | ``` 52 | 53 | ``` 54 | ┌ ┐ 55 | │10.4 200.14 -40.203 0.00523│ 56 | │ 5 2.56 39.901 56.12│ 57 | └ ┘ 58 | ┌ ┐ 59 | │ 2.5 1.2 -0.22103 89091│ 60 | │ 3 5 1 0│ 61 | └ ┘ 62 | ``` 63 | 64 | Pretty-printing arrays with strings or chars is also possible. 65 | 66 | ```d 67 | auto charArr = [[['a', 'b', 'c', 'd'], ['e', 'f', 'g', 'h']]]; 68 | charArr.prettyArr.writeln; 69 | ``` 70 | ``` 71 | ┌ ┐ 72 | │a b c d│ 73 | │e f g h│ 74 | └ ┘ 75 | ``` 76 | 77 | ```d 78 | auto strArr = [[["abt", "bat"], ["dac", "eac"]], [["eab", "jua"], ["uia", "vma"]]]; 79 | strArr.prettyArr.writeln; 80 | ``` 81 | ``` 82 | ┌ ┐ 83 | │┌ ┐│ 84 | ││a b t││ 85 | ││b a t││ 86 | │└ ┘│ 87 | │┌ ┐│ 88 | ││d a c││ 89 | ││e a c││ 90 | │└ ┘│ 91 | └ ┘ 92 | ┌ ┐ 93 | │┌ ┐│ 94 | ││e a b││ 95 | ││j u a││ 96 | │└ ┘│ 97 | │┌ ┐│ 98 | ││u i a││ 99 | ││v m a││ 100 | │└ ┘│ 101 | └ ┘ 102 | ``` 103 | 104 | Standard array does not have `.shape` method like Mir slices. 105 | Therefore, `pretty_array` additionally provides a naive `getShape` function. 106 | 107 | ```d 108 | strArr.getShape.writeln; 109 | ``` 110 | ``` 111 | [2, 2, 2, 3] 112 | ``` 113 | 114 | `prettyArr` also **truncates** big enough arrays to save screen space. You can configure max number of elements allowed before truncation. 115 | 116 | ```d 117 | auto bigArr = [300, 600].iota!int(1).fuse; 118 | bigArr.prettyArr.writeln; 119 | ``` 120 | 121 | Will truncate the array into the following. 122 | 123 | ``` 124 | ┌ ┐ 125 | │ 1 2 3 ░ 598 599 600│ 126 | │ 601 602 603 ░ 1198 1199 1200│ 127 | │ 1201 1202 1203 ░ 1798 1799 1800│ 128 | │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ 129 | │178201 178202 178203 ░ 178798 178799 178800│ 130 | │178801 178802 178803 ░ 179398 179399 179400│ 131 | │179401 179402 179403 ░ 179998 179999 180000│ 132 | └ ┘ 133 | ``` 134 | 135 | `pretty_array` package contains 136 | 137 | * `prettyArr` -- converts an array into a pretty string. 138 | * `PrettyArrConfig` -- array formatting configuration. 139 | * `getShape` -- returns a shape of standard D array. 140 | 141 | ## Formatting Configuration 142 | 143 | You can configure some of the default formatting parameters via `PrettyArrConfig`. 144 | 145 | * `edgeItems` -- number of items preceding and following the truncation symbol (defaults to 3). 146 | * `lineWidth` -- max line width allowed without truncation (defaults to 120). 147 | * `precision` -- precision of floating point representations (defaults to 6). 148 | * `suppressExp` -- suppress scientific notation (defaults to true). 149 | * `threshold` -- max array size allowed without truncation (default is 1000 elements). 150 | * `withShape` -- additionally display array shape. 151 | 152 | Here are couple of usage examples. 153 | 154 | ```d 155 | auto a = [[0.000023, 1.234023, 13.443333], [479.311231, -100.001001, -0.412223]]; 156 | PrettyArrConfig.precision = 2; 157 | a.prettyArr.writeln; 158 | ``` 159 | 160 | Will reduce the default **floating precision** from 6 to 2. 161 | 162 | ``` 163 | ┌ ┐ 164 | │ 0.00 1.23 13.44│ 165 | │479.31 -100.00 -0.41│ 166 | └ ┘ 167 | ``` 168 | 169 | You can also enable **scientific notation** via _e_ suffix. 170 | 171 | ```d 172 | auto a = [[0.000023, 1.234023, 13.443333], [479.311231, -100.001001, -0.412223]]; 173 | PrettyArrConfig.suppressExp = false; 174 | PrettyArrConfig.withShape = true; 175 | a.prettyArr.writeln; 176 | ``` 177 | 178 | ``` 179 | ┌ ┐ 180 | │2.300000e-05 1.234023e+00 1.344333e+01│ 181 | │4.793112e+02 -1.000010e+02 -4.122230e-01│ 182 | └ ┘ 183 | [2 x 3] 184 | ``` 185 | 186 | ### Configuring Special Symbols 187 | 188 | If for some reason you don't like the awesome truncation symbol `░`, or pretty array frames, you can always edit them in the source code. 189 | 190 | Search `pretty_array.d` for 191 | 192 | ```d 193 | private enum Frame : string 194 | { 195 | ltAngle = "┌", 196 | lbAngle = "└", 197 | rtAngle = "┐", 198 | rbAngle = "┘", 199 | vBar = "│", 200 | newline = "\n", 201 | whitespace = " ", 202 | dash = "─", 203 | empty = "", 204 | dot = "·", 205 | truncStr = "░" // TIP: length of this string is 3! 206 | } 207 | ``` 208 | 209 | ### Building & Testing 210 | 211 | You'll need a D compiler (ldc, dmd, gdc) and dub. 212 | 213 | Build a library 214 | ``` 215 | dub build 216 | ``` 217 | 218 | Test a library 219 | ``` 220 | dub test 221 | ``` 222 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "pretty_array" 2 | description "Pretty D Arrays" 3 | authors "tastyminerals" 4 | copyright "Copyright © 2020, tastyminerals" 5 | license "MIT" 6 | 7 | targetType "library" 8 | dflags-ldc "-mcpu=native" 9 | 10 | buildType "release" { 11 | buildOptions "releaseMode" "inline" "optimize" 12 | dflags "-boundscheck=off" 13 | } 14 | 15 | buildType "debug" { 16 | buildOptions "debugMode" "debugInfo" "optimize" 17 | } 18 | 19 | buildType "debug-profile" { 20 | buildOptions "debugMode" "debugInfo" "profile" 21 | } 22 | 23 | buildType "tests" { 24 | buildOptions "unittests" 25 | } 26 | 27 | dependency "mir-random" version="~>2.2.19" 28 | dependency "mir-algorithm" version="~>3.12" 29 | -------------------------------------------------------------------------------- /dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "mir-algorithm": "3.12.35", 5 | "mir-core": "1.1.104", 6 | "mir-linux-kernel": "1.2.1", 7 | "mir-random": "2.2.19" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/pretty_array.d: -------------------------------------------------------------------------------- 1 | /** 2 | This module provides a function for pretty-printing D arrays of various dimensions. 3 | A multidimensional array is represented as a 2D matrix surrounded by nested square frames. 4 | If the array is too big, it will be truncated accordingly. 5 | Array formatting can be configured by changing configuration parameters. 6 | 7 | Usage examples: 8 | import pretty_array; 9 | import std.stdio; 10 | 11 | int[][] a = [[10, 5, -3], [34, -1, 0]]; 12 | a.prettyArr.writeln; 13 | 14 | import mir.ndslice; 15 | auto b = [2, 2, 3].iota!int.fuse; 16 | b.prettyArr.writeln; 17 | 18 | auto c = [ 19 | [0.000023, 1.234023, 13.443333], 20 | [479.311231, -100.001001, -0.412223] 21 | ]; 22 | PrettyArrConfig.precision = 2; 23 | PrettyArrConfig.suppressExp = false; 24 | c.prettyArr.writeln; 25 | */ 26 | module pretty_array; 27 | 28 | import std.array : join, array; 29 | import std.conv : to; 30 | import std.utf : byCodeUnit; 31 | import std.typecons : tuple, Tuple; 32 | import std.traits : isIntegral; 33 | import mir.ndslice; 34 | import std.stdio; 35 | 36 | /++ 37 | Array formatting configuration: 38 | 39 | edgeItems -- number of items preceding and following the truncation symbol (defaults to 3) 40 | lineWidth -- max line width allowed without truncation (defaults to 120) 41 | precision -- precision of floating point representations (defaults to 6) 42 | suppressExp -- suppress scientific notation (defaults to true) 43 | threshold -- max array size allowed without truncation (defaults to 1000 elements) 44 | withShape -- additionally display array shape 45 | +/ 46 | class PrettyArrConfig 47 | { 48 | static: 49 | int edgeItems = 3; 50 | int lineWidth = 120; 51 | int precision = 6; 52 | bool suppressExp = true; 53 | int threshold = 1000; 54 | bool withShape = false; 55 | } 56 | 57 | private enum Frame : string 58 | { 59 | ltAngle = "┌", 60 | lbAngle = "└", 61 | rtAngle = "┐", 62 | rbAngle = "┘", 63 | vBar = "│", 64 | newline = "\n", 65 | whitespace = " ", 66 | dash = "─", 67 | empty = "", 68 | dot = "·", 69 | truncStr = "░" // TIP: length of this string is 3! 70 | } 71 | 72 | ulong[] getShape(T : int)(T obj, ulong[] dims = null) 73 | { 74 | return dims; 75 | } 76 | 77 | ulong[] getShape(T : double)(T obj, ulong[] dims = null) 78 | { 79 | return dims; 80 | } 81 | 82 | /++ 83 | Get the shape of a plain D array. 84 | A standalone convenience function for getting array shape without converting to Mir Slices. 85 | The array must have correct dimensions otherwise the column index will not be consistent. 86 | In case of a "jagged" D array, the min column index will be displayed. 87 | +/ 88 | ulong[] getShape(T)(T obj, ulong[] dims = null) 89 | { 90 | import std.traits : isArray; 91 | import std.exception : enforce; 92 | 93 | enforce(isArray!(typeof(obj)), "Provided object is not plain D array!"); 94 | 95 | dims ~= obj.length.to!int; 96 | return getShape!(typeof(obj[0]))(obj[0], dims); 97 | } 98 | 99 | // Calculate the length of array elements converted to strings. 100 | private ulong getStrLength(T)(T arrSlice) 101 | { 102 | if (arrSlice.shape.length == 1) 103 | { 104 | return arrSlice.map!(a => a.toString).join.length; 105 | } 106 | else 107 | { 108 | auto slice2D = arrSlice.flattened.chunks(arrSlice.shape[$ - 1]); 109 | return slice2D[0].map!(a => a.toString).join.length; 110 | } 111 | } 112 | 113 | private string toString(T)(T obj) 114 | { 115 | import std.format : format; 116 | import std.traits : isIntegral, isSomeChar; 117 | 118 | if (isIntegral!T) 119 | { 120 | return obj.to!string; 121 | } 122 | else if (isSomeChar!T) 123 | { 124 | return obj.to!string; 125 | } 126 | else 127 | { 128 | string notation = PrettyArrConfig.suppressExp ? "f" : "e"; 129 | return format("%." ~ (cast(int)(PrettyArrConfig.precision)).to!string ~ notation, obj); 130 | } 131 | } 132 | 133 | // Convert truncated index to real array index. 134 | private ulong convertTruncIdx(ulong idx, ulong truncLen, ulong rowLen) 135 | { 136 | pragma(inline, true); 137 | return idx > PrettyArrConfig.edgeItems ? rowLen - (truncLen - idx) : idx; 138 | } 139 | 140 | /++ 141 | Get the longest string length of a row, construct a row with the longest string elements. 142 | We need to know the longest string length of the row to calculate the correct padding between the frames. 143 | We need to keep the row with longest string elements to correctly right-align all array elements. 144 | +/ 145 | private Tuple!(ulong, "strlen", string[], "row") getMaxStrLenAndMaxRow(T)(T arrSlice, bool truncate) 146 | { 147 | 148 | auto slice2D = arrSlice.flattened.chunks(arrSlice.shape[$ - 1]); 149 | const ulong truncLen = PrettyArrConfig.edgeItems * 2 + 1; 150 | const bool enoughRows = slice2D.shape[0] > truncLen; 151 | const bool encoughCols = slice2D[0].length > truncLen; 152 | ulong maxStrRowLen; 153 | string[] row, maxRow; 154 | 155 | // fill the empty array with the number of row elements 156 | // there probably a better way to do it 157 | for (int i; i < (truncate && encoughCols ? truncLen : slice2D[0].length); i++) 158 | { 159 | maxRow ~= "0"; 160 | row ~= ""; 161 | } 162 | 163 | // construct a row with longest string elements 164 | ulong rowi, colj; 165 | for (ulong i; i < (truncate && enoughRows ? truncLen : slice2D.shape[0]); i++) 166 | { 167 | rowi = truncate && enoughRows ? convertTruncIdx(i, truncLen, slice2D.shape[0]) : i; 168 | for (ulong j; j < (truncate && encoughCols ? truncLen : slice2D[rowi].length); 169 | j++) 170 | { 171 | colj = truncate && encoughCols ? convertTruncIdx(j, truncLen, slice2D[i].length) : j; 172 | row[j] = slice2D[rowi][colj].toString; 173 | } 174 | 175 | for (ulong k; k < row.length; k++) 176 | { 177 | if (truncate && encoughCols && (k == PrettyArrConfig.edgeItems)) 178 | { 179 | maxRow[k] = Frame.truncStr; 180 | continue; 181 | } 182 | maxRow[k] = maxRow[k].length < row[k].length ? row[k] : maxRow[k]; 183 | } 184 | } 185 | maxStrRowLen = truncate && encoughCols ? maxRow.join.length + truncLen - Frame.truncStr.length 186 | : maxRow.join.length + slice2D[0].length - 1; 187 | return Tuple!(ulong, "strlen", string[], "row")(maxStrRowLen, maxRow); 188 | } 189 | 190 | /++ 191 | Construct the padding between frame angles. 192 | Use white space if padding string is not provided. 193 | +/ 194 | private string getPadding(T)(T arrShape, ulong maxStrRowLen, string padStr = Frame.whitespace) 195 | { 196 | return padStr.byCodeUnit.repeat((arrShape.length < 2 197 | ? 0 : arrShape.length - 2) * 2 + maxStrRowLen).join; 198 | } 199 | 200 | private ulong lenDiff()(string a, string b) 201 | { 202 | return a.length > b.length ? a.length - b.length : 0; 203 | } 204 | 205 | /++ 206 | Build 1D array elements. 207 | +/ 208 | private string prettyFrame(T)(T arrSlice, bool truncate) 209 | if (arrSlice.shape.length == 1) 210 | { 211 | if (truncate) 212 | { 213 | string[] leftSlice = arrSlice[0 .. PrettyArrConfig.edgeItems].map!(a => a.toString).array; 214 | string[] rightSlice = arrSlice[$ - PrettyArrConfig.edgeItems .. $].map!( 215 | a => a.toString).array; 216 | return Frame.vBar ~ (leftSlice ~ Frame.truncStr ~ rightSlice) 217 | .join(" ") ~ Frame.vBar ~ Frame.newline; 218 | } 219 | else 220 | { 221 | return Frame.vBar ~ arrSlice.map!(a => a.toString).join(" ") ~ Frame.vBar ~ Frame.newline; 222 | } 223 | 224 | } 225 | 226 | /++ 227 | Build 2D matrix elements by iterating over rows and columns. 228 | "addedFrame" represents accumulated string that comes before each matrix row. 229 | For example, if we deal with 1D/2D matrix, "addedFrame" contains "|", otherwise it 230 | can contain "|||" strings accumulated by the recursive "prettyFrame" template. 231 | +/ 232 | private string prettyFrame(T)(T arrSlice, string addedFrame, Tuple!(ulong, 233 | "strlen", string[], "row") maxRow, bool truncate) 234 | if (arrSlice.shape.length == 2) 235 | { 236 | string arrStr; 237 | ulong rowi, colj; 238 | const ulong truncLen = PrettyArrConfig.edgeItems * 2 + 1; 239 | const bool enoughRows = arrSlice.shape[0] > truncLen; 240 | const bool enoughCols = arrSlice.shape[1] > truncLen; 241 | 242 | for (ulong i; i < (truncate && enoughRows ? truncLen : arrSlice.shape[0]); i++) 243 | { 244 | string[] newRow; 245 | rowi = truncate && enoughRows ? convertTruncIdx(i, truncLen, arrSlice.length) : i; 246 | for (ulong j; j < (truncate && enoughCols ? truncLen : arrSlice[rowi].length); 247 | j++) 248 | { 249 | colj = truncate && enoughCols ? convertTruncIdx(j, truncLen, arrSlice[i].length) : j; 250 | // insert white spaces before the element to right align it 251 | newRow ~= " ".repeat(lenDiff(maxRow.row[j], 252 | arrSlice[rowi][colj].toString)).join ~ arrSlice[rowi][colj].toString; 253 | 254 | if (truncate && enoughCols && (j == PrettyArrConfig.edgeItems)) 255 | { 256 | newRow[$ - 1] = Frame.truncStr; // overwrite last with truncation string 257 | } 258 | } 259 | 260 | if (truncate && enoughRows) 261 | { 262 | if (i != PrettyArrConfig.edgeItems) 263 | arrStr ~= addedFrame ~ newRow.join(" ") ~ addedFrame ~ Frame.newline; 264 | else 265 | arrStr ~= addedFrame ~ (cast(string) Frame.truncStr) 266 | .repeat(maxRow.strlen).join ~ addedFrame ~ Frame.newline; 267 | } 268 | else 269 | { 270 | arrStr ~= addedFrame ~ newRow.join(" ") ~ addedFrame ~ Frame.newline; 271 | } 272 | } 273 | 274 | return arrStr; 275 | } 276 | 277 | /++ 278 | Recursive template that constructs the outer frame for a multidimensional array. 279 | The outer frame does not include the closing frame header and footer. 280 | +/ 281 | private string prettyFrame(T)(T arrSlice, string addedFrame, Tuple!(ulong, 282 | "strlen", string[], "row") maxRow, bool truncate) 283 | if (arrSlice.shape.length > 2) 284 | { 285 | string arrStr; 286 | for (ulong i; i < arrSlice.shape[0]; i++) 287 | { 288 | string padding = getPadding!(typeof(arrSlice[i].shape))(arrSlice[i].shape, maxRow.strlen); 289 | arrStr ~= addedFrame ~ Frame.ltAngle ~ padding ~ Frame.rtAngle ~ addedFrame ~ Frame.newline; 290 | arrStr ~= prettyFrame!(typeof(arrSlice[i]))(arrSlice[i], 291 | addedFrame ~ Frame.vBar, maxRow, truncate); 292 | arrStr ~= addedFrame ~ Frame.lbAngle ~ padding ~ Frame.rbAngle ~ addedFrame ~ Frame.newline; 293 | } 294 | 295 | return arrStr; 296 | } 297 | 298 | // Check if an array can be truncated. 299 | private bool canTruncate(T)(T arrSlice) 300 | { 301 | return (arrSlice.flattened.length > PrettyArrConfig.threshold) || ((arrSlice.shape.length == 1) 302 | && (arrSlice.getStrLength > PrettyArrConfig.lineWidth)) ? true : false; 303 | } 304 | 305 | private string toStringShape(T)(T arrSlice) 306 | { 307 | return "[" ~ arrSlice.shape.map!(a => a.toString).join(" x ") ~ "]\n"; 308 | } 309 | 310 | /++ 311 | Pretty-print D array. 312 | The function starts from getting the shape of the multidimensional array. 313 | It then adds the closing header and footer frame and depending on its shape 1D/2D or >2D, 314 | calls one of the three "prettyFrame" templates. 315 | The template that runs on >2D arrays is recursive. It constructs the vertical outer frames first, 316 | dimension by dimension until the array becomes 1D or 2D. Afterwards the corresponding 317 | overloaded template is called to consturct the actual array elements. 318 | +/ 319 | string prettyArr(T)(T arr) 320 | in 321 | { 322 | assert(isConvertibleToSlice!(typeof(arr))); 323 | } 324 | do 325 | { 326 | string arrStr; 327 | auto arrSlice = arr.fuse; // convert to Mir Slice by GC allocating with fuse 328 | 329 | // check if we need array truncation 330 | const bool truncate = arrSlice.canTruncate; 331 | auto maxRow = arrSlice.getMaxStrLenAndMaxRow(truncate); 332 | string padding = getPadding!(typeof(arrSlice.shape))(arrSlice.shape, maxRow.strlen); 333 | 334 | static if (arrSlice.shape.length == 2) 335 | { 336 | arrStr ~= Frame.ltAngle ~ padding ~ Frame.rtAngle ~ Frame.newline; 337 | arrStr ~= prettyFrame!(typeof(arrSlice))(arrSlice, Frame.vBar, maxRow, truncate); 338 | arrStr ~= Frame.lbAngle ~ padding ~ Frame.rbAngle ~ Frame.newline; 339 | } 340 | else static if (arrSlice.shape.length > 1) 341 | { 342 | arrStr ~= prettyFrame!(typeof(arrSlice))(arrSlice, Frame.empty, maxRow, truncate); 343 | } 344 | else 345 | { 346 | arrStr ~= Frame.ltAngle ~ padding ~ Frame.rtAngle ~ Frame.newline; 347 | arrStr ~= prettyFrame!(typeof(arrSlice))(arrSlice, truncate); 348 | arrStr ~= Frame.lbAngle ~ padding ~ Frame.rbAngle ~ Frame.newline; 349 | } 350 | 351 | if (PrettyArrConfig.withShape) 352 | { 353 | arrStr ~= arrSlice.toStringShape; 354 | } 355 | 356 | return arrStr; 357 | } 358 | 359 | unittest 360 | { 361 | import std.range : chunks; 362 | import std.exception : assertThrown; 363 | 364 | int[] da0 = [1, 2, 3, 4, 5]; 365 | assert(da0.getShape == [5]); 366 | 367 | int[][] da1 = [[1, 0, 1], [-1, 0, 2]]; 368 | assert(da1.getShape == [2, 3]); 369 | 370 | int[][][] da2 = [[[1, 2, 3], [4, 5, 6]], [[1, 1, 2], [3, 1, 1]]]; 371 | assert(da2.getShape == [2, 2, 3]); 372 | 373 | auto ma0 = [3, 2].iota!int(1).fuse; 374 | assertThrown(ma0.getShape); 375 | 376 | int[] a0 = [200, 1, -3, 0, 0, 8501, 23]; 377 | string testa0 = "┌ ┐ 378 | │200 1 -3 0 0 8501 23│ 379 | └ ┘ 380 | "; 381 | assert(prettyArr!(typeof(a0))(a0) == testa0); 382 | 383 | auto a = [5, 2].iota!int(1).fuse; 384 | auto maxa = a.getMaxStrLenAndMaxRow(a.canTruncate); 385 | assert(getPadding!(typeof(a.shape))(a.shape, maxa.strlen).length == 4); 386 | string testa = "┌ ┐ 387 | │1 2│ 388 | │3 4│ 389 | │5 6│ 390 | │7 8│ 391 | │9 10│ 392 | └ ┘ 393 | "; 394 | assert(prettyArr!(typeof(a))(a) == testa); 395 | 396 | auto b = [2, 2, 6].iota!int(1).fuse; 397 | auto maxb = b.getMaxStrLenAndMaxRow(b.canTruncate); 398 | assert(getPadding!(typeof(b.shape))(b.shape, maxb.strlen).length == 19); 399 | string testb = "┌ ┐ 400 | │ 1 2 3 4 5 6│ 401 | │ 7 8 9 10 11 12│ 402 | └ ┘ 403 | ┌ ┐ 404 | │13 14 15 16 17 18│ 405 | │19 20 21 22 23 24│ 406 | └ ┘ 407 | "; 408 | assert(prettyArr!(typeof(b))(b) == testb); 409 | 410 | int[] carr = [ 411 | 1000, 21, 1232, 4, 5, 36, 1207, 18, 9, 10, -1, 12, 133, -14, 21915, 16 412 | ]; 413 | auto c = carr.chunks(2).array.chunks(4).array.chunks(2).array; // jagged D array 414 | string testc = "┌ ┐ 415 | │┌ ┐│ 416 | ││ 1000 21││ 417 | ││ 1232 4││ 418 | ││ 5 36││ 419 | ││ 1207 18││ 420 | │└ ┘│ 421 | │┌ ┐│ 422 | ││ 9 10││ 423 | ││ -1 12││ 424 | ││ 133 -14││ 425 | ││21915 16││ 426 | │└ ┘│ 427 | └ ┘ 428 | "; 429 | assert(prettyArr!(typeof(c))(c) == testc); 430 | 431 | auto d = [3, 1, 2, 1].iota!int(1).fuse; 432 | string testd = "┌ ┐ 433 | │┌ ┐│ 434 | ││1││ 435 | ││2││ 436 | │└ ┘│ 437 | └ ┘ 438 | ┌ ┐ 439 | │┌ ┐│ 440 | ││3││ 441 | ││4││ 442 | │└ ┘│ 443 | └ ┘ 444 | ┌ ┐ 445 | │┌ ┐│ 446 | ││5││ 447 | ││6││ 448 | │└ ┘│ 449 | └ ┘ 450 | "; 451 | assert(prettyArr!(typeof(d))(d) == testd); 452 | 453 | auto e = [2, 3, 6, 6].iota!int(1).fuse; 454 | string teste = "┌ ┐ 455 | │┌ ┐│ 456 | ││ 1 2 3 4 5 6││ 457 | ││ 7 8 9 10 11 12││ 458 | ││ 13 14 15 16 17 18││ 459 | ││ 19 20 21 22 23 24││ 460 | ││ 25 26 27 28 29 30││ 461 | ││ 31 32 33 34 35 36││ 462 | │└ ┘│ 463 | │┌ ┐│ 464 | ││ 37 38 39 40 41 42││ 465 | ││ 43 44 45 46 47 48││ 466 | ││ 49 50 51 52 53 54││ 467 | ││ 55 56 57 58 59 60││ 468 | ││ 61 62 63 64 65 66││ 469 | ││ 67 68 69 70 71 72││ 470 | │└ ┘│ 471 | │┌ ┐│ 472 | ││ 73 74 75 76 77 78││ 473 | ││ 79 80 81 82 83 84││ 474 | ││ 85 86 87 88 89 90││ 475 | ││ 91 92 93 94 95 96││ 476 | ││ 97 98 99 100 101 102││ 477 | ││103 104 105 106 107 108││ 478 | │└ ┘│ 479 | └ ┘ 480 | ┌ ┐ 481 | │┌ ┐│ 482 | ││109 110 111 112 113 114││ 483 | ││115 116 117 118 119 120││ 484 | ││121 122 123 124 125 126││ 485 | ││127 128 129 130 131 132││ 486 | ││133 134 135 136 137 138││ 487 | ││139 140 141 142 143 144││ 488 | │└ ┘│ 489 | │┌ ┐│ 490 | ││145 146 147 148 149 150││ 491 | ││151 152 153 154 155 156││ 492 | ││157 158 159 160 161 162││ 493 | ││163 164 165 166 167 168││ 494 | ││169 170 171 172 173 174││ 495 | ││175 176 177 178 179 180││ 496 | │└ ┘│ 497 | │┌ ┐│ 498 | ││181 182 183 184 185 186││ 499 | ││187 188 189 190 191 192││ 500 | ││193 194 195 196 197 198││ 501 | ││199 200 201 202 203 204││ 502 | ││205 206 207 208 209 210││ 503 | ││211 212 213 214 215 216││ 504 | │└ ┘│ 505 | └ ┘ 506 | "; 507 | 508 | assert(e.prettyArr == teste); 509 | 510 | auto f = [210, 5].iota!int(1).fuse; 511 | string testf = "┌ ┐ 512 | │ 1 2 3 4 5│ 513 | │ 6 7 8 9 10│ 514 | │ 11 12 13 14 15│ 515 | │░░░░░░░░░░░░░░░░░░░░░░░░│ 516 | │1036 1037 1038 1039 1040│ 517 | │1041 1042 1043 1044 1045│ 518 | │1046 1047 1048 1049 1050│ 519 | └ ┘ 520 | "; 521 | assert(f.prettyArr == testf); 522 | 523 | auto g = [100, 100].iota!int(1).fuse; 524 | string testg = "┌ ┐ 525 | │ 1 2 3 ░ 98 99 100│ 526 | │ 101 102 103 ░ 198 199 200│ 527 | │ 201 202 203 ░ 298 299 300│ 528 | │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ 529 | │9701 9702 9703 ░ 9798 9799 9800│ 530 | │9801 9802 9803 ░ 9898 9899 9900│ 531 | │9901 9902 9903 ░ 9998 9999 10000│ 532 | └ ┘ 533 | "; 534 | assert(g.prettyArr == testg); 535 | 536 | auto h = [500].iota!int(1).fuse; 537 | string testh = "┌ ┐ 538 | │1 2 3 ░ 498 499 500│ 539 | └ ┘ 540 | "; 541 | assert(h.prettyArr == testh); 542 | 543 | auto i = [2, 100, 500].iota!int(1).fuse; 544 | string testi = "┌ ┐ 545 | │ 1 2 3 ░ 498 499 500│ 546 | │ 501 502 503 ░ 998 999 1000│ 547 | │ 1001 1002 1003 ░ 1498 1499 1500│ 548 | │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ 549 | │48501 48502 48503 ░ 48998 48999 49000│ 550 | │49001 49002 49003 ░ 49498 49499 49500│ 551 | │49501 49502 49503 ░ 49998 49999 50000│ 552 | └ ┘ 553 | ┌ ┐ 554 | │50001 50002 50003 ░ 50498 50499 50500│ 555 | │50501 50502 50503 ░ 50998 50999 51000│ 556 | │51001 51002 51003 ░ 51498 51499 51500│ 557 | │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ 558 | │98501 98502 98503 ░ 98998 98999 99000│ 559 | │99001 99002 99003 ░ 99498 99499 99500│ 560 | │99501 99502 99503 ░ 99998 99999 100000│ 561 | └ ┘ 562 | "; 563 | assert(i.prettyArr == testi); 564 | 565 | auto j = [[1.23, real.nan, 13.44], [real.infinity, real.infinity, -0.412]]; 566 | PrettyArrConfig.suppressExp = false; 567 | string testj = "┌ ┐ 568 | │1.230000e+00 nan 1.344000e+01│ 569 | │ inf inf -4.120000e-01│ 570 | └ ┘ 571 | "; 572 | assert(j.prettyArr == testj); 573 | 574 | auto k = [ 575 | [0.000023, real.nan, 13.44], [real.infinity, real.infinity, -0.412] 576 | ]; 577 | string testk = "┌ ┐ 578 | │0.000023 nan 13.440000│ 579 | │ inf inf -0.412000│ 580 | └ ┘ 581 | "; 582 | PrettyArrConfig.suppressExp = true; 583 | assert(k.prettyArr == testk); 584 | 585 | auto l = [ 586 | [0.000023, 1.234023, 13.443333], [479.311231, -100.001001, -0.412223] 587 | ]; 588 | string testl = "┌ ┐ 589 | │ 0.00 1.23 13.44│ 590 | │479.31 -100.00 -0.41│ 591 | └ ┘ 592 | "; 593 | PrettyArrConfig.precision = 2; 594 | assert(l.prettyArr == testl); 595 | 596 | auto m = [[['a', 'b', 'c', 'd'], ['e', 'f', 'g', 'h']]]; 597 | string testm = "┌ ┐ 598 | │a b c d│ 599 | │e f g h│ 600 | └ ┘ 601 | "; 602 | assert(m.prettyArr == testm); 603 | 604 | PrettyArrConfig.withShape = true; 605 | string testmWithShape = "┌ ┐ 606 | │a b c d│ 607 | │e f g h│ 608 | └ ┘ 609 | [1 x 2 x 4] 610 | "; 611 | assert(m.prettyArr == testmWithShape); 612 | } 613 | --------------------------------------------------------------------------------