001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.math4.legacy.linear; 019 020import java.text.FieldPosition; 021import java.text.NumberFormat; 022import java.text.ParsePosition; 023import java.util.ArrayList; 024import java.util.List; 025import java.util.Locale; 026 027import org.apache.commons.math4.legacy.exception.MathParseException; 028import org.apache.commons.math4.legacy.util.CompositeFormat; 029 030/** 031 * Formats a {@code nxm} matrix in components list format 032 * "{{a<sub>0</sub><sub>0</sub>,a<sub>0</sub><sub>1</sub>, ..., 033 * a<sub>0</sub><sub>m-1</sub>},{a<sub>1</sub><sub>0</sub>, 034 * a<sub>1</sub><sub>1</sub>, ..., a<sub>1</sub><sub>m-1</sub>},{...},{ 035 * a<sub>n-1</sub><sub>0</sub>, a<sub>n-1</sub><sub>1</sub>, ..., 036 * a<sub>n-1</sub><sub>m-1</sub>}}". 037 * <p>The prefix and suffix "{" and "}", the row prefix and suffix "{" and "}", 038 * the row separator "," and the column separator "," can be replaced by any 039 * user-defined strings. The number format for components can be configured.</p> 040 * 041 * <p>White space is ignored at parse time, even if it is in the prefix, suffix 042 * or separator specifications. So even if the default separator does include a space 043 * character that is used at format time, both input string "{{1,1,1}}" and 044 * " { { 1 , 1 , 1 } } " will be parsed without error and the same matrix will be 045 * returned. In the second case, however, the parse position after parsing will be 046 * just after the closing curly brace, i.e. just before the trailing space.</p> 047 * 048 * <p><b>Note:</b> the grouping functionality of the used {@link NumberFormat} is 049 * disabled to prevent problems when parsing (e.g. 1,345.34 would be a valid number 050 * but conflicts with the default column separator).</p> 051 * 052 * @since 3.1 053 */ 054public class RealMatrixFormat { 055 056 /** The default prefix: "{". */ 057 private static final String DEFAULT_PREFIX = "{"; 058 /** The default suffix: "}". */ 059 private static final String DEFAULT_SUFFIX = "}"; 060 /** The default row prefix: "{". */ 061 private static final String DEFAULT_ROW_PREFIX = "{"; 062 /** The default row suffix: "}". */ 063 private static final String DEFAULT_ROW_SUFFIX = "}"; 064 /** The default row separator: ",". */ 065 private static final String DEFAULT_ROW_SEPARATOR = ","; 066 /** The default column separator: ",". */ 067 private static final String DEFAULT_COLUMN_SEPARATOR = ","; 068 /** Prefix. */ 069 private final String prefix; 070 /** Suffix. */ 071 private final String suffix; 072 /** Row prefix. */ 073 private final String rowPrefix; 074 /** Row suffix. */ 075 private final String rowSuffix; 076 /** Row separator. */ 077 private final String rowSeparator; 078 /** Column separator. */ 079 private final String columnSeparator; 080 /** The format used for components. */ 081 private final NumberFormat format; 082 083 /** 084 * Create an instance with default settings. 085 * <p>The instance uses the default prefix, suffix and row/column separator: 086 * "[", "]", ";" and ", " and the default number format for components.</p> 087 */ 088 public RealMatrixFormat() { 089 this(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ROW_PREFIX, DEFAULT_ROW_SUFFIX, 090 DEFAULT_ROW_SEPARATOR, DEFAULT_COLUMN_SEPARATOR, CompositeFormat.getDefaultNumberFormat()); 091 } 092 093 /** 094 * Create an instance with a custom number format for components. 095 * @param format the custom format for components. 096 */ 097 public RealMatrixFormat(final NumberFormat format) { 098 this(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ROW_PREFIX, DEFAULT_ROW_SUFFIX, 099 DEFAULT_ROW_SEPARATOR, DEFAULT_COLUMN_SEPARATOR, format); 100 } 101 102 /** 103 * Create an instance with custom prefix, suffix and separator. 104 * @param prefix prefix to use instead of the default "{" 105 * @param suffix suffix to use instead of the default "}" 106 * @param rowPrefix row prefix to use instead of the default "{" 107 * @param rowSuffix row suffix to use instead of the default "}" 108 * @param rowSeparator tow separator to use instead of the default ";" 109 * @param columnSeparator column separator to use instead of the default ", " 110 */ 111 public RealMatrixFormat(final String prefix, final String suffix, 112 final String rowPrefix, final String rowSuffix, 113 final String rowSeparator, final String columnSeparator) { 114 this(prefix, suffix, rowPrefix, rowSuffix, rowSeparator, columnSeparator, 115 CompositeFormat.getDefaultNumberFormat()); 116 } 117 118 /** 119 * Create an instance with custom prefix, suffix, separator and format 120 * for components. 121 * @param prefix prefix to use instead of the default "{" 122 * @param suffix suffix to use instead of the default "}" 123 * @param rowPrefix row prefix to use instead of the default "{" 124 * @param rowSuffix row suffix to use instead of the default "}" 125 * @param rowSeparator tow separator to use instead of the default ";" 126 * @param columnSeparator column separator to use instead of the default ", " 127 * @param format the custom format for components. 128 */ 129 public RealMatrixFormat(final String prefix, final String suffix, 130 final String rowPrefix, final String rowSuffix, 131 final String rowSeparator, final String columnSeparator, 132 final NumberFormat format) { 133 this.prefix = prefix; 134 this.suffix = suffix; 135 this.rowPrefix = rowPrefix; 136 this.rowSuffix = rowSuffix; 137 this.rowSeparator = rowSeparator; 138 this.columnSeparator = columnSeparator; 139 this.format = format; 140 // disable grouping to prevent parsing problems 141 this.format.setGroupingUsed(false); 142 } 143 144 /** 145 * Get the set of locales for which real vectors formats are available. 146 * <p>This is the same set as the {@link NumberFormat} set.</p> 147 * @return available real vector format locales. 148 */ 149 public static Locale[] getAvailableLocales() { 150 return NumberFormat.getAvailableLocales(); 151 } 152 153 /** 154 * Get the format prefix. 155 * @return format prefix. 156 */ 157 public String getPrefix() { 158 return prefix; 159 } 160 161 /** 162 * Get the format suffix. 163 * @return format suffix. 164 */ 165 public String getSuffix() { 166 return suffix; 167 } 168 169 /** 170 * Get the format prefix. 171 * @return format prefix. 172 */ 173 public String getRowPrefix() { 174 return rowPrefix; 175 } 176 177 /** 178 * Get the format suffix. 179 * @return format suffix. 180 */ 181 public String getRowSuffix() { 182 return rowSuffix; 183 } 184 185 /** 186 * Get the format separator between rows of the matrix. 187 * @return format separator for rows. 188 */ 189 public String getRowSeparator() { 190 return rowSeparator; 191 } 192 193 /** 194 * Get the format separator between components. 195 * @return format separator between components. 196 */ 197 public String getColumnSeparator() { 198 return columnSeparator; 199 } 200 201 /** 202 * Get the components format. 203 * @return components format. 204 */ 205 public NumberFormat getFormat() { 206 return format; 207 } 208 209 /** 210 * Returns the default real vector format for the current locale. 211 * @return the default real vector format. 212 */ 213 public static RealMatrixFormat getInstance() { 214 return getInstance(Locale.getDefault()); 215 } 216 217 /** 218 * Returns the default real vector format for the given locale. 219 * @param locale the specific locale used by the format. 220 * @return the real vector format specific to the given locale. 221 */ 222 public static RealMatrixFormat getInstance(final Locale locale) { 223 return new RealMatrixFormat(CompositeFormat.getDefaultNumberFormat(locale)); 224 } 225 226 /** 227 * This method calls {@link #format(RealMatrix,StringBuffer,FieldPosition)}. 228 * 229 * @param m RealMatrix object to format. 230 * @return a formatted matrix. 231 */ 232 public String format(RealMatrix m) { 233 return format(m, new StringBuffer(), new FieldPosition(0)).toString(); 234 } 235 236 /** 237 * Formats a {@link RealMatrix} object to produce a string. 238 * @param matrix the object to format. 239 * @param toAppendTo where the text is to be appended 240 * @param pos On input: an alignment field, if desired. On output: the 241 * offsets of the alignment field 242 * @return the value passed in as toAppendTo. 243 */ 244 public StringBuffer format(RealMatrix matrix, StringBuffer toAppendTo, 245 FieldPosition pos) { 246 247 pos.setBeginIndex(0); 248 pos.setEndIndex(0); 249 250 // format prefix 251 toAppendTo.append(prefix); 252 253 // format rows 254 final int rows = matrix.getRowDimension(); 255 for (int i = 0; i < rows; ++i) { 256 toAppendTo.append(rowPrefix); 257 for (int j = 0; j < matrix.getColumnDimension(); ++j) { 258 if (j > 0) { 259 toAppendTo.append(columnSeparator); 260 } 261 CompositeFormat.formatDouble(matrix.getEntry(i, j), format, toAppendTo, pos); 262 } 263 toAppendTo.append(rowSuffix); 264 if (i < rows - 1) { 265 toAppendTo.append(rowSeparator); 266 } 267 } 268 269 // format suffix 270 toAppendTo.append(suffix); 271 272 return toAppendTo; 273 } 274 275 /** 276 * Parse a string to produce a {@link RealMatrix} object. 277 * 278 * @param source String to parse. 279 * @return the parsed {@link RealMatrix} object. 280 * @throws MathParseException if the beginning of the specified string 281 * cannot be parsed. 282 */ 283 public RealMatrix parse(String source) { 284 final ParsePosition parsePosition = new ParsePosition(0); 285 final RealMatrix result = parse(source, parsePosition); 286 if (parsePosition.getIndex() == 0) { 287 throw new MathParseException(source, 288 parsePosition.getErrorIndex(), 289 Array2DRowRealMatrix.class); 290 } 291 return result; 292 } 293 294 /** 295 * Parse a string to produce a {@link RealMatrix} object. 296 * 297 * @param source String to parse. 298 * @param pos input/output parsing parameter. 299 * @return the parsed {@link RealMatrix} object. 300 */ 301 public RealMatrix parse(String source, ParsePosition pos) { 302 int initialIndex = pos.getIndex(); 303 304 final String trimmedPrefix = prefix.trim(); 305 final String trimmedSuffix = suffix.trim(); 306 final String trimmedRowPrefix = rowPrefix.trim(); 307 final String trimmedRowSuffix = rowSuffix.trim(); 308 final String trimmedColumnSeparator = columnSeparator.trim(); 309 final String trimmedRowSeparator = rowSeparator.trim(); 310 311 // parse prefix 312 CompositeFormat.parseAndIgnoreWhitespace(source, pos); 313 if (!CompositeFormat.parseFixedstring(source, trimmedPrefix, pos)) { 314 return null; 315 } 316 317 // parse components 318 List<List<Number>> matrix = new ArrayList<>(); 319 List<Number> rowComponents = new ArrayList<>(); 320 for (boolean loop = true; loop;){ 321 322 if (!rowComponents.isEmpty()) { 323 CompositeFormat.parseAndIgnoreWhitespace(source, pos); 324 if (!CompositeFormat.parseFixedstring(source, trimmedColumnSeparator, pos)) { 325 if (!trimmedRowSuffix.isEmpty() && 326 !CompositeFormat.parseFixedstring(source, trimmedRowSuffix, pos)) { 327 return null; 328 } 329 CompositeFormat.parseAndIgnoreWhitespace(source, pos); 330 if (CompositeFormat.parseFixedstring(source, trimmedRowSeparator, pos)) { 331 matrix.add(rowComponents); 332 rowComponents = new ArrayList<>(); 333 continue; 334 } 335 loop = false; 336 } 337 } else { 338 CompositeFormat.parseAndIgnoreWhitespace(source, pos); 339 if (!trimmedRowPrefix.isEmpty() && 340 !CompositeFormat.parseFixedstring(source, trimmedRowPrefix, pos)) { 341 return null; 342 } 343 } 344 345 if (loop) { 346 CompositeFormat.parseAndIgnoreWhitespace(source, pos); 347 Number component = CompositeFormat.parseNumber(source, format, pos); 348 if (component != null) { 349 rowComponents.add(component); 350 } else { 351 if (rowComponents.isEmpty()) { 352 loop = false; 353 } else { 354 // invalid component 355 // set index back to initial, error index should already be set 356 pos.setIndex(initialIndex); 357 return null; 358 } 359 } 360 } 361 } 362 363 if (!rowComponents.isEmpty()) { 364 matrix.add(rowComponents); 365 } 366 367 // parse suffix 368 CompositeFormat.parseAndIgnoreWhitespace(source, pos); 369 if (!CompositeFormat.parseFixedstring(source, trimmedSuffix, pos)) { 370 return null; 371 } 372 373 // do not allow an empty matrix 374 if (matrix.isEmpty()) { 375 pos.setIndex(initialIndex); 376 return null; 377 } 378 379 // build vector 380 double[][] data = new double[matrix.size()][]; 381 int row = 0; 382 for (List<Number> rowList : matrix) { 383 data[row] = new double[rowList.size()]; 384 for (int i = 0; i < rowList.size(); i++) { 385 data[row][i] = rowList.get(i).doubleValue(); 386 } 387 row++; 388 } 389 return MatrixUtils.createRealMatrix(data); 390 } 391}