diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-12-16 11:25:36 +0100 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-12-16 11:25:36 +0100 |
commit | 169c0cfffece0dd962c747dd981b47faad513f49 (patch) | |
tree | e0c1b1bcf9b40742a1cb75a78ad582f752d7f672 /vespajlib/src/main/java/com | |
parent | 7821d6affb977a8652d9be244ffd647b81db1789 (diff) |
Generify parsing
Diffstat (limited to 'vespajlib/src/main/java/com')
5 files changed, 118 insertions, 511 deletions
diff --git a/vespajlib/src/main/java/com/yahoo/tensor/IndexedTensor.java b/vespajlib/src/main/java/com/yahoo/tensor/IndexedTensor.java index 8b1f67158b7..67ed2180201 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/IndexedTensor.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/IndexedTensor.java @@ -82,116 +82,18 @@ public class IndexedTensor implements Tensor { return product; } + @Override + public TensorType type() { return type; } + /** * Returns the lenght of this in the nth dimension - * + * * @throws IndexOutOfBoundsException if the index is larger than the number of dimensions in this tensor minus one */ public int length(int dimension) { return dimensionSizes[dimension]; } - // TODO: Duplication with MixedTensor - static IndexedTensor from(TensorType type, String tensorString) { - tensorString = tensorString.trim(); - try { - if (tensorString.startsWith("{")) - return fromCellString(type, tensorString); - else - return fromNumber(Double.parseDouble(tensorString)); - } - catch (NumberFormatException e) { - throw new IllegalArgumentException("Excepted a number or a string starting by { or tensor(, got '" + - tensorString + "'"); - } - } - - // TODO: Duplication with MixedTensor - private static IndexedTensor fromCellString(TensorType type, String s) { - IndexedTensor.Builder builder = IndexedTensor.Builder.of(type); - s = s.trim().substring(1).trim(); - while (s.length() > 1) { - int keyOrTensorEnd = s.indexOf('}'); - int[] cellIndexes; - if (keyOrTensorEnd == s.length()-1) { // Tensor end: No keys: This is empty or contains a single number - cellIndexes = new int[0]; - } - else { - cellIndexes = indexesFrom(type, s.substring(0, keyOrTensorEnd + 1)); - s = s.substring(keyOrTensorEnd + 1).trim(); - if ( ! s.startsWith(":")) - throw new IllegalArgumentException("Expecting a ':' after " + s + ", got '" + s + "'"); - s = s.substring(1); - } - int valueEnd = s.indexOf(','); - if (valueEnd < 0) { // last value - valueEnd = s.indexOf("}"); - if (valueEnd < 0) - throw new IllegalArgumentException("A tensor string must end by '}'"); - } - Double value = asDouble(cellIndexes, s.substring(0, valueEnd).trim()); - - builder.cell(value, cellIndexes); - s = s.substring(valueEnd+1).trim(); - } - return builder.build(); - } - - // TODO: Duplication with MixedTensor - /** Creates a tenor address from a string on the form {dimension1:label1,dimension2:label2,...} */ - private static int[] indexesFrom(TensorType type, String mapAddressString) { - mapAddressString = mapAddressString.trim(); - if ( ! (mapAddressString.startsWith("{") && mapAddressString.endsWith("}"))) - throw new IllegalArgumentException("Expecting a tensor address enclosed in {}, got '" + mapAddressString + "'"); - - String addressBody = mapAddressString.substring(1, mapAddressString.length() - 1).trim(); - if (addressBody.isEmpty()) return new int[0]; - - int[] indexes = new int[type.dimensions().size()]; - for (String elementString : addressBody.split(",")) { - String[] pair = elementString.split(":"); - if (pair.length != 2) - throw new IllegalArgumentException("Expecting argument elements on the form dimension:label, " + - "got '" + elementString + "'"); - String dimension = pair[0].trim(); - Optional<Integer> dimensionIndex = type.indexOfDimension(dimension); - if ( ! dimensionIndex.isPresent()) - throw new IllegalArgumentException("Dimension '" + dimension + "' is not present in " + type); - indexes[dimensionIndex.get()] = asInteger(pair[1].trim(), dimension); - } - return indexes; - } - - // TODO: Duplication with MixedTensor - private static int asInteger(String value, String dimension) { - try { - return Integer.parseInt(value); - } - catch (NumberFormatException e) { - throw new IllegalArgumentException("Expected an integer value for dimension '" + dimension + "', " + - "but got '" + value + "'"); - } - - } - - private static IndexedTensor fromNumber(double number) { - return new IndexedTensor(TensorType.empty, new int[] {}, new double[] { number }); - } - - // TODO: Duplication with MixedTensor - private static Double asDouble(int[] indexes, String s) { - try { - return Double.valueOf(s); - } - catch (NumberFormatException e) { - throw new IllegalArgumentException("At " + Arrays.toString(indexes) + - ": Expected a floating point number, got '" + s + "'"); - } - } - - @Override - public TensorType type() { return type; } - @Override // TODO: Replace this with iterator public Map<TensorAddress, Double> cells() { @@ -274,7 +176,10 @@ public class IndexedTensor implements Tensor { productSize *= dimensionSize; return new double[productSize]; } - + + @Override + public TensorType type() { return type; } + @Override public abstract IndexedTensor build(); @@ -341,7 +246,7 @@ public class IndexedTensor implements Tensor { } @Override - public Tensor.Builder cell(TensorAddress address, double value) { + public Builder cell(TensorAddress address, double value) { values[toValueIndex(address, dimensionSizes)] = value; return this; } @@ -423,6 +328,26 @@ public class IndexedTensor implements Tensor { } } + @Override + public IndexedCellBuilder cell() { + return new IndexedCellBuilder(); + } + + @Override + public Builder cell(TensorAddress address, double value) { + int[] indexes = new int[address.labels().size()]; + for (int i = 0; i < address.labels().size(); i++) { + try { + indexes[i] = Integer.parseInt(address.labels().get(i)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Labels in an indexed tensor must be integers, not '" + + address.labels().get(i) + "'"); + } + } + cell(value, indexes); + return this; + } + /** * Set a value using an index API. The number of indexes must be the same as the dimensions in the type of this. * Values can be written in any order but all values needed to make this dense must be provided @@ -431,6 +356,7 @@ public class IndexedTensor implements Tensor { * @return this for chaining */ @SuppressWarnings("unchecked") + @Override public Builder cell(double value, int... indexes) { if (indexes.length != type.dimensions().size()) throw new IllegalArgumentException("Wrong number of indexes (" + indexes.length + ") for " + type); @@ -456,26 +382,6 @@ public class IndexedTensor implements Tensor { return this; } - @Override - public IndexedCellBuilder cell() { - return new IndexedCellBuilder(); - } - - @Override - public Builder cell(TensorAddress address, double value) { - int[] indexes = new int[address.labels().size()]; - for (int i = 0; i < address.labels().size(); i++) { - try { - indexes[i] = Integer.parseInt(address.labels().get(i)); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Labels in an indexed tensor must be integers, not '" + - address.labels().get(i) + "'"); - } - } - cell(value, indexes); - return this; - } - /** Fill the given list with nulls if necessary to make sure it has a (possibly null) value at the given index */ private void ensureCapacity(int index, List<Object> list) { while (list.size() <= index) diff --git a/vespajlib/src/main/java/com/yahoo/tensor/MappedTensor.java b/vespajlib/src/main/java/com/yahoo/tensor/MappedTensor.java index 3c609acff45..a5a4e29e44c 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/MappedTensor.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/MappedTensor.java @@ -113,13 +113,22 @@ public class MappedTensor implements Tensor { public MappedCellBuilder cell() { return new MappedCellBuilder(); } - + + @Override + public TensorType type() { return type; } + @Override public Builder cell(TensorAddress address, double value) { cells.put(address, value); return this; } - + + @Override + public Builder cell(double value, int... labels) { + cells.put(new TensorAddress(labels), value); + return this; + } + @Override public MappedTensor build() { return new MappedTensor(type, cells.build()); diff --git a/vespajlib/src/main/java/com/yahoo/tensor/MixedTensor.java b/vespajlib/src/main/java/com/yahoo/tensor/MixedTensor.java deleted file mode 100644 index 89eb93e8fbb..00000000000 --- a/vespajlib/src/main/java/com/yahoo/tensor/MixedTensor.java +++ /dev/null @@ -1,373 +0,0 @@ -// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.tensor; - -import com.google.common.annotations.Beta; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -/** - * Sketch of a mixed dimension tensor which retains efficiency for indexed dimensions - * - * @author bratseth - */ -@Beta -public class MixedTensor implements Tensor { - - private final TensorType type; - - private final IndexedDimension firstDimension; - - private MixedTensor(TensorType type, IndexedDimension firstDimension) { - this.type = type; - this.firstDimension = firstDimension; - } - - /** Construct an indexed tensor having a single dimension with the given values */ - // TODO: Privatize - public MixedTensor(TensorType type, List<Object> values) { - if (type.dimensions().size() != 1 || ! type.dimensions().get(0).isIndexed()) - throw new IllegalArgumentException("Expected a single-dimensional indexed tensor but got " + type); - this.type = type; - this.firstDimension = new IndexedDimension(values); - } - - /** - * Returns the value at the given indexes - * - * @param indexes the indexes into the dimensions of this. Must be one number per dimension of this - * @throws IndexOutOfBoundsException if any of the indexes are out of bound or a wrong number of indexes are given - */ - public Double get(int ... indexes) { - IndexedDimension currentDimension = firstDimension; - for (int i = 0; i < indexes.length; i++) { - if (i == indexes.length -1) - return (Double)currentDimension.values().get(indexes[i]); - else - currentDimension = (IndexedDimension)currentDimension.values().get(indexes[i]); - } - return Double.NaN; // empty - } - - /** - * Returns the lenght of this in the nth dimension - * - * @throws IndexOutOfBoundsException if the index is larger than the number of dimensions in this tensor minus one - */ - public int length(int dimension) { - if (firstDimension.values().isEmpty()) return 0; // empty tensor - - IndexedDimension currentDimension = firstDimension; - while (dimension > 0) - currentDimension = (IndexedDimension)currentDimension.values().get(0); // get the first as all (should) have the same size - return currentDimension.values().size(); - } - - static MixedTensor from(TensorType type, String tensorString) { - tensorString = tensorString.trim(); - try { - if (tensorString.startsWith("{")) - return fromCellString(type, tensorString); - else - return fromNumber(Double.parseDouble(tensorString)); - } - catch (NumberFormatException e) { - throw new IllegalArgumentException("Excepted a number or a string starting by { or tensor(, got '" + - tensorString + "'"); - } - } - - private static MixedTensor fromCellString(TensorType type, String s) { - MixedTensor.Builder builder = new MixedTensor.Builder(type); - s = s.trim().substring(1).trim(); - while (s.length() > 1) { - int keyEnd = s.indexOf('}'); - int[] cellIndexes = indexesFrom(type, s.substring(0, keyEnd+1)); - s = s.substring(keyEnd + 1).trim(); - if ( ! s.startsWith(":")) - throw new IllegalArgumentException("Expecting a ':' after " + s + ", got '" + s + "'"); - int valueEnd = s.indexOf(','); - if (valueEnd < 0) { // last value - valueEnd = s.indexOf("}"); - if (valueEnd < 0) - throw new IllegalArgumentException("A tensor string must end by '}'"); - } - Double value = asDouble(cellIndexes, s.substring(1, valueEnd).trim()); - - builder.set(value, cellIndexes); - s = s.substring(valueEnd+1).trim(); - } - return builder.build(); - } - - /** Creates a tenor address from a string on the form {dimension1:label1,dimension2:label2,...} */ - private static int[] indexesFrom(TensorType type, String mapAddressString) { - mapAddressString = mapAddressString.trim(); - if ( ! (mapAddressString.startsWith("{") && mapAddressString.endsWith("}"))) - throw new IllegalArgumentException("Expecting a tensor address to be enclosed in {}, got '" + mapAddressString + "'"); - - String addressBody = mapAddressString.substring(1, mapAddressString.length() - 1).trim(); - if (addressBody.isEmpty()) return new int[0]; - - int[] indexes = new int[type.dimensions().size()]; - for (String elementString : addressBody.split(",")) { - String[] pair = elementString.split(":"); - if (pair.length != 2) - throw new IllegalArgumentException("Expecting argument elements to be on the form dimension:label, " + - "got '" + elementString + "'"); - String dimension = pair[0].trim(); - Optional<Integer> dimensionIndex = type.indexOfDimension(dimension); - if ( ! dimensionIndex.isPresent()) - throw new IllegalArgumentException("Dimension '" + dimension + "' is not present in " + type); - indexes[dimensionIndex.get()] = asInteger(pair[1].trim(), dimension); - } - return indexes; - } - - private static int asInteger(String value, String dimension) { - try { - return Integer.parseInt(value); - } - catch (NumberFormatException e) { - throw new IllegalArgumentException("Expected an integer value for dimension '" + dimension + "', " + - "but got '" + value + "'"); - } - - } - - private static MixedTensor fromNumber(double number) { - return new MixedTensor(TensorType.empty, new IndexedDimension(number)); - } - - private static Double asDouble(int[] indexes, String s) { - try { - return Double.valueOf(s); - } - catch (NumberFormatException e) { - throw new IllegalArgumentException("At " + Arrays.toString(indexes) + - ": Expected a floating point number, got '" + s + "'"); - } - } - - @Override - public TensorType type() { return type; } - - @Override - public Map<TensorAddress, Double> cells() { - ImmutableMap.Builder<TensorAddress, Double> builder = new ImmutableMap.Builder<>(); - populateRecursively(builder, firstDimension, new TensorAddress.Builder(type), new ArrayList<>(type.dimensions())); - return builder.build(); - } - - private void populateRecursively(ImmutableMap.Builder<TensorAddress, Double> valueBuilder, IndexedDimension dimensionValues, - TensorAddress.Builder partialAddress, List<TensorType.Dimension> remainingDimensions) { - if (remainingDimensions.size() == 0) { - if ( ! dimensionValues.values().isEmpty()) // either empty or a single value - valueBuilder.put(TensorAddress.empty, (Double)dimensionValues.values().get(0)); - } - else if (remainingDimensions.size() == 1) { - for (int i = 0; i < dimensionValues.values().size(); i++) - valueBuilder.put(partialAddress.copy().add(remainingDimensions.get(0).name(), String.valueOf(i)).build(), - (Double)dimensionValues.values().get(i)); - } - else { - List<TensorType.Dimension> nestedRemainingDimensions = new ArrayList<>(remainingDimensions); - TensorType.Dimension currentDimension = nestedRemainingDimensions.remove(0); - for (int i = 0; i < dimensionValues.values().size(); i++) { - populateRecursively(valueBuilder, (IndexedDimension)dimensionValues.values().get(i), - partialAddress.copy().add(currentDimension.name(), String.valueOf(i)), - nestedRemainingDimensions); - } - } - } - - /** Returns the value at this address, or NaN if there is no value at this address */ - @Override - public double get(TensorAddress address) { - if (type.dimensions().isEmpty()) // either empty or a sinle value - return firstDimension.values().isEmpty() ? Double.NaN : (double)firstDimension.values().get(0); - - IndexedDimension currentDimension = firstDimension; - for (int i = 0; i < address.labels().size(); i++) { - int index = Integer.parseInt(address.labels().get(i)); - if (index >= currentDimension.values().size()) return Double.NaN; - - Object value = currentDimension.values().get(index); - if (value == null) return Double.NaN; - - if (i == address.labels().size() -1) // last dimension - return (double)value; - else - currentDimension = (IndexedDimension)value; - } - return Double.NaN; - } - - @Override - public int hashCode() { return firstDimension.hashCode(); } - - @Override - public String toString() { return Tensor.toStandardString(this); } - - @Override - public boolean equals(Object o) { - if ( ! (o instanceof Tensor)) return false; - return Tensor.equals(this, (Tensor)o); - } - - /** An indexed dimension containing another indexed dimension */ - private static class IndexedDimension { - - private final ImmutableList<Object> values; - - public IndexedDimension() { values = ImmutableList.of(); } - - public IndexedDimension(Double value) { values = ImmutableList.of(value); } - - public IndexedDimension(List<Object> values) { - this.values = ImmutableList.copyOf(values); - } - - public ImmutableList<Object> values() { return values; } - - } - - public static class Builder implements Tensor.Builder { - - private final TensorType type; - - /** List of List or Double */ - private List<Object> firstDimension = null; - - public Builder(TensorType type) { - this.type = type; - } - - @Override - public MixedTensor build() { - // TODO: Enforce that all values in all dimensions are equally large - if (firstDimension == null) // empty - return new MixedTensor(type, new IndexedDimension()); - if (type.dimensions().isEmpty()) // single number - return new MixedTensor(type, - new IndexedDimension((Double)firstDimension.get(0))); - - List<TensorType.Dimension> dimensions = new ArrayList<>(type.dimensions()); - IndexedDimension firstDimension = buildRecursively(dimensions, this.firstDimension); - return new MixedTensor(type, firstDimension); - } - - @SuppressWarnings("unchecked") - private IndexedDimension buildRecursively(List<TensorType.Dimension> remainingDimensions, - List<Object> currentDimensionValues) { - if (remainingDimensions.size() == 1) { // last dimension - for (int i = 0; i < currentDimensionValues.size(); i++) - if (currentDimensionValues.get(i) == null) - throw new IllegalArgumentException("Missing a value at index " + i + " in dimension " + - remainingDimensions.get(0) + " for tensor of type " + type); - return new IndexedDimension(currentDimensionValues); - } - else { - List<TensorType.Dimension> nestedRemainingDimensions = new ArrayList<>(remainingDimensions); - TensorType.Dimension currentDimension = nestedRemainingDimensions.remove(0); - ImmutableList.Builder<Object> values = new ImmutableList.Builder<>(); - for (int i = 0; i < currentDimensionValues.size(); i++) { - if (currentDimensionValues.get(i) == null) - throw new IllegalArgumentException("Missing a value at index " + i + " in dimension " + - currentDimension + " for tensor of type " + type); - values.add(buildRecursively(nestedRemainingDimensions, (List<Object>)currentDimensionValues.get(i))); - } - return new IndexedDimension(values.build()); - } - } - - /** - * Set a value using an index API. The number of indexes must be the same as the dimensions in the type of this. - * Values can be written in any order but all values needed to make this dense must be provided - * before building this. - * - * @return this for chaining - */ - @SuppressWarnings("unchecked") - public Builder set(double value, int ... indexes) { - if (indexes.length != type.dimensions().size()) - throw new IllegalArgumentException("Wrong number of indexes (" + indexes.length + ") for " + type); - - if (indexes.length == 0) { - firstDimension = Collections.singletonList(value); - return this; - } - - if (firstDimension == null) - firstDimension = new ArrayList<>(); - List<Object> currentValues = firstDimension; - for (int dimensionIndex = 0; dimensionIndex < indexes.length; dimensionIndex++) { - ensureCapacity(indexes[dimensionIndex], currentValues); - if (dimensionIndex == indexes.length - 1) { // last dimension - currentValues.set(indexes[dimensionIndex], value); - } - else { - if (currentValues.get(indexes[dimensionIndex]) == null) - currentValues.set(indexes[dimensionIndex], new ArrayList<>()); - currentValues = (List<Object>)currentValues.get(indexes[dimensionIndex]); - } - } - return this; - } - - @Override - public IndexedCellBuilder cell() { - return new IndexedCellBuilder(); - } - - @Override - public Builder cell(TensorAddress address, double value) { - int[] indexes = new int[address.labels().size()]; - for (int i = 0; i < address.labels().size(); i++) { - try { - indexes[i] = Integer.parseInt(address.labels().get(i)); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Labels in an indexed tensor must be integers, not '" + - address.labels().get(i) + "'"); - } - } - set(value, indexes); - return this; - } - - /** Fill the given list with nulls if necessary to make sure it has a (possibly null) value at the given index */ - private void ensureCapacity(int index, List<Object> list) { - while (list.size() <= index) - list.add(list.size(), null); - } - - public class IndexedCellBuilder implements CellBuilder { - - private final TensorAddress.Builder addressBuilder = new TensorAddress.Builder(MixedTensor.Builder.this.type); - - @Override - public MixedTensor.Builder.IndexedCellBuilder label(String dimension, String label) { - addressBuilder.add(dimension, label); - return this; - } - - @Override - public MixedTensor.Builder.IndexedCellBuilder label(String dimension, int label) { - return label(dimension, String.valueOf(label)); - } - - @Override - public MixedTensor.Builder value(double cellValue) { - return MixedTensor.Builder.this.cell(addressBuilder.build(), cellValue); - } - - } - - } -} diff --git a/vespajlib/src/main/java/com/yahoo/tensor/Tensor.java b/vespajlib/src/main/java/com/yahoo/tensor/Tensor.java index f548e551788..34e36d9fb8b 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/Tensor.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/Tensor.java @@ -251,11 +251,17 @@ public interface Tensor { return IndexedTensor.Builder.of(type); } + /** Returns the type this is building */ + TensorType type(); + /** Return a cell builder */ CellBuilder cell(); - /** Add a built cell */ + /** Add a cell */ Builder cell(TensorAddress address, double value); + + /** Add a cell */ + Builder cell(double value, int ... labels); Tensor build(); diff --git a/vespajlib/src/main/java/com/yahoo/tensor/TensorParser.java b/vespajlib/src/main/java/com/yahoo/tensor/TensorParser.java index 625a799ace3..5990e633266 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/TensorParser.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/TensorParser.java @@ -39,17 +39,6 @@ class TensorParser { } } - private static Tensor tensorFromValueString(String tensorCellString, TensorType type) { - boolean containsIndexedDimensions = type.dimensions().stream().anyMatch(d -> d.isIndexed()); - boolean containsMappedDimensions = type.dimensions().stream().anyMatch(d -> !d.isIndexed()); - if (containsIndexedDimensions && containsMappedDimensions) - throw new IllegalArgumentException("Mixed dimension types are not supported, got: " + type); - if (containsMappedDimensions) - return MappedTensor.from(type, tensorCellString); - else // indexed or none - return IndexedTensor.from(type, tensorCellString); - } - /** Derive the tensor type from the first address string in the given tensor string */ private static TensorType typeFromValueString(String s) { s = s.substring(1).trim(); // remove tensor start @@ -73,4 +62,74 @@ class TensorParser { return builder.build(); } + private static Tensor tensorFromValueString(String tensorValueString, TensorType type) { + Tensor.Builder builder = Tensor.Builder.of(type); + tensorValueString = tensorValueString.trim(); + try { + if (tensorValueString.startsWith("{")) + return fromCellString(builder, tensorValueString); + else + return builder.cell(Double.parseDouble(tensorValueString)).build(); + } + catch (NumberFormatException e) { + throw new IllegalArgumentException("Excepted a number or a string starting by { or tensor(, got '" + + tensorValueString + "'"); + } + } + + private static Tensor fromCellString(Tensor.Builder builder, String s) { + s = s.trim().substring(1).trim(); + while (s.length() > 1) { + int keyOrTensorEnd = s.indexOf('}'); + TensorAddress.Builder addressBuilder = new TensorAddress.Builder(builder.type()); + if (keyOrTensorEnd < s.length() - 1) { // Key end: This has a key - otherwise TensorAdress is empty + addLabels(s.substring(0, keyOrTensorEnd + 1), addressBuilder); + s = s.substring(keyOrTensorEnd + 1).trim(); + if ( ! s.startsWith(":")) + throw new IllegalArgumentException("Expecting a ':' after " + s + ", got '" + s + "'"); + s = s.substring(1); + } + int valueEnd = s.indexOf(','); + if (valueEnd < 0) { // last value + valueEnd = s.indexOf("}"); + if (valueEnd < 0) + throw new IllegalArgumentException("A tensor string must end by '}'"); + } + + TensorAddress address = addressBuilder.build(); + Double value = asDouble(address, s.substring(0, valueEnd).trim()); + builder.cell(address, value); + s = s.substring(valueEnd+1).trim(); + } + return builder.build(); + } + + /** Creates a tenor address from a string on the form {dimension1:label1,dimension2:label2,...} */ + private static void addLabels(String mapAddressString, TensorAddress.Builder builder) { + mapAddressString = mapAddressString.trim(); + if ( ! (mapAddressString.startsWith("{") && mapAddressString.endsWith("}"))) + throw new IllegalArgumentException("Expecting a tensor address enclosed in {}, got '" + mapAddressString + "'"); + + String addressBody = mapAddressString.substring(1, mapAddressString.length() - 1).trim(); + if (addressBody.isEmpty()) return; + + for (String elementString : addressBody.split(",")) { + String[] pair = elementString.split(":"); + if (pair.length != 2) + throw new IllegalArgumentException("Expecting argument elements on the form dimension:label, " + + "got '" + elementString + "'"); + String dimension = pair[0].trim(); + builder.add(dimension, pair[1].trim()); + } + } + + private static Double asDouble(TensorAddress address, String s) { + try { + return Double.valueOf(s); + } + catch (NumberFormatException e) { + throw new IllegalArgumentException("At " + address + ": Expected a floating point number, got '" + s + "'"); + } + } + } |