diff options
Diffstat (limited to 'src/org/unitConverter/math/ObjectProduct.java')
-rw-r--r-- | src/org/unitConverter/math/ObjectProduct.java | 276 |
1 files changed, 276 insertions, 0 deletions
diff --git a/src/org/unitConverter/math/ObjectProduct.java b/src/org/unitConverter/math/ObjectProduct.java new file mode 100644 index 0000000..0cf89ec --- /dev/null +++ b/src/org/unitConverter/math/ObjectProduct.java @@ -0,0 +1,276 @@ +/**
+ * Copyright (C) 2018 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package org.unitConverter.math;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * An immutable product of multiple objects of a type, such as base units. The objects can be multiplied and
+ * exponentiated.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-16
+ */
+public final class ObjectProduct<T> {
+ /**
+ * Returns an empty ObjectProduct of a certain type
+ *
+ * @param <T>
+ * type of objects that can be multiplied
+ * @return empty product
+ * @since 2019-10-16
+ */
+ public static final <T> ObjectProduct<T> empty() {
+ return new ObjectProduct<>(new HashMap<>());
+ }
+
+ /**
+ * Gets an {@code ObjectProduct} from an object-to-integer mapping
+ *
+ * @param <T>
+ * type of object in product
+ * @param map
+ * map mapping objects to exponents
+ * @return object product
+ * @since 2019-10-16
+ */
+ public static final <T> ObjectProduct<T> fromExponentMapping(final Map<T, Integer> map) {
+ return new ObjectProduct<>(new HashMap<>(map));
+ }
+
+ /**
+ * Gets an ObjectProduct that has one of the inputted argument, and nothing else.
+ *
+ * @param object
+ * object that will be in the product
+ * @return product
+ * @since 2019-10-16
+ * @throws NullPointerException
+ * if object is null
+ */
+ public static final <T> ObjectProduct<T> oneOf(final T object) {
+ Objects.requireNonNull(object, "object must not be null.");
+ final Map<T, Integer> map = new HashMap<>();
+ map.put(object, 1);
+ return new ObjectProduct<>(map);
+ }
+
+ /**
+ * The objects that make up the product, mapped to their exponents. This map treats zero as null, and is immutable.
+ *
+ * @since 2019-10-16
+ */
+ final Map<T, Integer> exponents;
+
+ /**
+ * Creates the {@code ObjectProduct}.
+ *
+ * @param exponents
+ * objects that make up this product
+ * @since 2019-10-16
+ */
+ private ObjectProduct(final Map<T, Integer> exponents) {
+ this.exponents = Collections.unmodifiableMap(ConditionalExistenceCollections
+ .conditionalExistenceMap(new HashMap<>(exponents), e -> !Integer.valueOf(0).equals(e.getValue())));
+ }
+
+ /**
+ * Calculates the quotient of two products
+ *
+ * @param other
+ * other product
+ * @return quotient of two products
+ * @since 2019-10-16
+ * @throws NullPointerException
+ * if other is null
+ */
+ public ObjectProduct<T> dividedBy(final ObjectProduct<T> other) {
+ Objects.requireNonNull(other, "other must not be null.");
+ // get a list of all objects in both sets
+ final Set<T> objects = new HashSet<>();
+ objects.addAll(this.getBaseSet());
+ objects.addAll(other.getBaseSet());
+
+ // get a list of all exponents
+ final Map<T, Integer> map = new HashMap<>(objects.size());
+ for (final T key : objects) {
+ map.put(key, this.getExponent(key) - other.getExponent(key));
+ }
+
+ // create the product
+ return new ObjectProduct<>(map);
+ }
+
+ // this method relies on the use of ZeroIsNullMap
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj)
+ return true;
+ if (!(obj instanceof ObjectProduct))
+ return false;
+ final ObjectProduct<?> other = (ObjectProduct<?>) obj;
+ return Objects.equals(this.exponents, other.exponents);
+ }
+
+ /**
+ * @return immutable map mapping objects to exponents
+ * @since 2019-10-16
+ */
+ public Map<T, Integer> exponentMap() {
+ return this.exponents;
+ }
+
+ /**
+ * @return a set of all of the base objects with non-zero exponents that make up this dimension.
+ * @since 2018-12-12
+ * @since v0.1.0
+ */
+ public final Set<T> getBaseSet() {
+ final Set<T> dimensions = new HashSet<>();
+
+ // add all dimensions with a nonzero exponent - zero exponents shouldn't be there in the first place
+ for (final T dimension : this.exponents.keySet()) {
+ if (!this.exponents.get(dimension).equals(0)) {
+ dimensions.add(dimension);
+ }
+ }
+
+ return dimensions;
+ }
+
+ /**
+ * Gets the exponent for a specific dimension.
+ *
+ * @param dimension
+ * dimension to check
+ * @return exponent for that dimension
+ * @since 2018-12-12
+ * @since v0.1.0
+ */
+ public int getExponent(final T dimension) {
+ return this.exponents.getOrDefault(dimension, 0);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.exponents);
+ }
+
+ /**
+ * @return true if this product is a single object, i.e. it has one exponent of one and no other nonzero exponents
+ * @since 2019-10-16
+ */
+ public boolean isSingleObject() {
+ int oneCount = 0;
+ boolean twoOrMore = false; // has exponents of 2 or more
+ for (final T b : this.getBaseSet()) {
+ if (this.getExponent(b) == 1) {
+ oneCount++;
+ } else if (this.getExponent(b) != 0) {
+ twoOrMore = true;
+ }
+ }
+ return oneCount == 1 && !twoOrMore;
+ }
+
+ /**
+ * Multiplies this product by another
+ *
+ * @param other
+ * other product
+ * @return product of two products
+ * @since 2019-10-16
+ * @throws NullPointerException
+ * if other is null
+ */
+ public ObjectProduct<T> times(final ObjectProduct<T> other) {
+ Objects.requireNonNull(other, "other must not be null.");
+ // get a list of all objects in both sets
+ final Set<T> objects = new HashSet<>();
+ objects.addAll(this.getBaseSet());
+ objects.addAll(other.getBaseSet());
+
+ // get a list of all exponents
+ final Map<T, Integer> map = new HashMap<>(objects.size());
+ for (final T key : objects) {
+ map.put(key, this.getExponent(key) + other.getExponent(key));
+ }
+
+ // create the product
+ return new ObjectProduct<>(map);
+ }
+
+ /**
+ * Returns this product, but to an exponent
+ *
+ * @param exponent
+ * exponent
+ * @return result of exponentiation
+ * @since 2019-10-16
+ */
+ public ObjectProduct<T> toExponent(final int exponent) {
+ final Map<T, Integer> map = new HashMap<>(this.exponents);
+ for (final T key : this.exponents.keySet()) {
+ map.put(key, this.getExponent(key) * exponent);
+ }
+ return new ObjectProduct<>(map);
+ }
+
+ @Override
+ public String toString() {
+ return this.toString(Object::toString);
+ }
+
+ /**
+ * Converts this product to a string. The objects that make up this product are represented by
+ * {@code objectToString}
+ *
+ * @param objectToString
+ * function to convert objects to strings
+ * @return string representation of product
+ * @since 2019-10-16
+ */
+ public String toString(final Function<T, String> objectToString) {
+ final List<String> positiveStringComponents = new ArrayList<>();
+ final List<String> negativeStringComponents = new ArrayList<>();
+
+ // for each base object that makes up this object, add it and its exponent
+ for (final T object : this.getBaseSet()) {
+ final int exponent = this.exponents.get(object);
+ if (exponent > 0) {
+ positiveStringComponents.add(String.format("%s^%d", objectToString.apply(object), exponent));
+ } else if (exponent < 0) {
+ negativeStringComponents.add(String.format("%s^%d", objectToString.apply(object), -exponent));
+ }
+ }
+
+ final String positiveString = positiveStringComponents.isEmpty() ? "1"
+ : String.join(" * ", positiveStringComponents);
+ final String negativeString = negativeStringComponents.isEmpty() ? ""
+ : " / " + String.join(" * ", negativeStringComponents);
+
+ return positiveString + negativeString;
+ }
+}
|