From 69f2849d25a41ae7c0383636557deda1bc64247d Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Wed, 16 Oct 2019 12:29:19 -0400 Subject: Created more generalized objects for use in the new units. --- src/org/unitConverter/math/ObjectProduct.java | 261 +++++++++++++++++++++ src/org/unitConverter/math/ObjectProductTest.java | 79 +++++++ src/org/unitConverter/math/StandardDimensions.java | 86 +++++++ src/org/unitConverter/math/ZeroIsNullMap.java | 129 ++++++++++ src/org/unitConverter/math/ZeroIsNullMapTest.java | 113 +++++++++ 5 files changed, 668 insertions(+) create mode 100644 src/org/unitConverter/math/ObjectProduct.java create mode 100644 src/org/unitConverter/math/ObjectProductTest.java create mode 100644 src/org/unitConverter/math/StandardDimensions.java create mode 100644 src/org/unitConverter/math/ZeroIsNullMap.java create mode 100644 src/org/unitConverter/math/ZeroIsNullMapTest.java (limited to 'src/org/unitConverter') diff --git a/src/org/unitConverter/math/ObjectProduct.java b/src/org/unitConverter/math/ObjectProduct.java new file mode 100644 index 0000000..ec4d2d6 --- /dev/null +++ b/src/org/unitConverter/math/ObjectProduct.java @@ -0,0 +1,261 @@ +/** + * 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 . + */ +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; + +/** + * 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 { + /** + * Returns an empty ObjectProduct of a certain type + * + * @param + * type of objects that can be multiplied + * @return empty product + * @since 2019-10-16 + */ + public static final ObjectProduct empty() { + return new ObjectProduct<>(new HashMap<>()); + } + + /** + * Gets an {@code ObjectProduct} from an object-to-integer mapping + * + * @param + * type of object in product + * @param map + * map mapping objects to exponents + * @return object product + * @since 2019-10-16 + */ + public static final ObjectProduct fromExponentMapping(final Map 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 ObjectProduct oneOf(final T object) { + Objects.requireNonNull(object, "object must not be null."); + final Map 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 exponents; + + /** + * Creates the {@code ObjectProduct}. + * + * @param exponents + * objects that make up this product + * @since 2019-10-16 + */ + private ObjectProduct(final Map exponents) { + this.exponents = Collections.unmodifiableMap(ZeroIsNullMap.create(new HashMap<>(exponents))); + } + + /** + * 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 dividedBy(final ObjectProduct other) { + Objects.requireNonNull(other, "other must not be null."); + // get a list of all objects in both sets + final Set objects = new HashSet<>(); + objects.addAll(this.getBaseSet()); + objects.addAll(other.getBaseSet()); + + // get a list of all exponents + final Map 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 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 getBaseSet() { + final Set 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 "base", i.e. it has one exponent of one and no other nonzero exponents + * @since 2019-10-16 + */ + public boolean isBase() { + 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 times(final ObjectProduct other) { + Objects.requireNonNull(other, "other must not be null."); + // get a list of all objects in both sets + final Set objects = new HashSet<>(); + objects.addAll(this.getBaseSet()); + objects.addAll(other.getBaseSet()); + + // get a list of all exponents + final Map 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 toExponent(final int exponent) { + final Map 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() { + final List positiveStringComponents = new ArrayList<>(); + final List negativeStringComponents = new ArrayList<>(); + + // for each base dimension that makes up this dimension, add it and its exponent + for (final T dimension : this.getBaseSet()) { + final int exponent = this.exponents.get(dimension); + if (exponent > 0) { + positiveStringComponents.add(String.format("%s^%d", dimension, exponent)); + } else if (exponent < 0) { + negativeStringComponents.add(String.format("%s^%d", dimension, -exponent)); + } + } + + final String positiveString = positiveStringComponents.isEmpty() ? "1" + : String.join(" * ", positiveStringComponents); + final String negativeString = negativeStringComponents.isEmpty() ? "" + : " / " + String.join(" * ", negativeStringComponents); + + return positiveString + negativeString; + } +} diff --git a/src/org/unitConverter/math/ObjectProductTest.java b/src/org/unitConverter/math/ObjectProductTest.java new file mode 100644 index 0000000..03c767c --- /dev/null +++ b/src/org/unitConverter/math/ObjectProductTest.java @@ -0,0 +1,79 @@ +/** + * 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 . + */ +package org.unitConverter.math; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.unitConverter.dimension.StandardDimensions.AREA; +import static org.unitConverter.dimension.StandardDimensions.ENERGY; +import static org.unitConverter.dimension.StandardDimensions.LENGTH; +import static org.unitConverter.dimension.StandardDimensions.MASS; +import static org.unitConverter.dimension.StandardDimensions.MASS_DENSITY; +import static org.unitConverter.dimension.StandardDimensions.QUANTITY; +import static org.unitConverter.dimension.StandardDimensions.TIME; +import static org.unitConverter.dimension.StandardDimensions.VOLUME; + +import org.junit.jupiter.api.Test; +import org.unitConverter.dimension.SIBaseDimension; +import org.unitConverter.dimension.UnitDimension; + +/** + * Tests for {@link ObjectProduct} using BaseDimension as a test object. This is NOT part of this program's public API. + * + * @author Adrien Hopkins + * @since 2018-12-12 + * @since v0.1.0 + */ +class ObjectProductTest { + /** + * Tests {@link UnitDimension#equals} + * + * @since 2018-12-12 + * @since v0.1.0 + */ + @Test + public void testEquals() { + assertEquals(LENGTH, LENGTH); + assertFalse(LENGTH.equals(QUANTITY)); + } + + /** + * Tests {@code UnitDimension}'s exponentiation + * + * @since 2019-01-15 + * @since v0.1.0 + */ + @Test + public void testExponents() { + assertEquals(1, LENGTH.getExponent(SIBaseDimension.LENGTH)); + assertEquals(3, VOLUME.getExponent(SIBaseDimension.LENGTH)); + } + + /** + * Tests {@code UnitDimension}'s multiplication and division. + * + * @since 2018-12-12 + * @since v0.1.0 + */ + @Test + public void testMultiplicationAndDivision() { + assertEquals(AREA, LENGTH.times(LENGTH)); + assertEquals(MASS_DENSITY, MASS.dividedBy(VOLUME)); + assertEquals(ENERGY, AREA.times(MASS).dividedBy(TIME).dividedBy(TIME)); + assertEquals(LENGTH, LENGTH.times(TIME).dividedBy(TIME)); + } +} diff --git a/src/org/unitConverter/math/StandardDimensions.java b/src/org/unitConverter/math/StandardDimensions.java new file mode 100644 index 0000000..db5efc3 --- /dev/null +++ b/src/org/unitConverter/math/StandardDimensions.java @@ -0,0 +1,86 @@ +/** + * 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 . + */ +package org.unitConverter.math; + +import org.unitConverter.dimension.BaseDimension; +import org.unitConverter.dimension.OtherBaseDimension; +import org.unitConverter.dimension.SIBaseDimension; + +/** + * All of the dimensions that are used by the SI. (Test data for the ObjectProductTest) + * + * @author Adrien Hopkins + * @since 2018-12-11 + * @since v0.1.0 + */ +final class StandardDimensions { + // base dimensions + public static final ObjectProduct EMPTY = ObjectProduct.empty(); + public static final ObjectProduct LENGTH = ObjectProduct.oneOf(SIBaseDimension.LENGTH); + public static final ObjectProduct MASS = ObjectProduct.oneOf(SIBaseDimension.MASS); + public static final ObjectProduct TIME = ObjectProduct.oneOf(SIBaseDimension.TIME); + public static final ObjectProduct ELECTRIC_CURRENT = ObjectProduct + .oneOf(SIBaseDimension.ELECTRIC_CURRENT); + public static final ObjectProduct TEMPERATURE = ObjectProduct.oneOf(SIBaseDimension.TEMPERATURE); + public static final ObjectProduct QUANTITY = ObjectProduct.oneOf(SIBaseDimension.QUANTITY); + public static final ObjectProduct LUMINOUS_INTENSITY = ObjectProduct + .oneOf(SIBaseDimension.LUMINOUS_INTENSITY); + public static final ObjectProduct INFORMATION = ObjectProduct.oneOf(OtherBaseDimension.INFORMATION); + public static final ObjectProduct CURRENCY = ObjectProduct.oneOf(OtherBaseDimension.CURRENCY); + // derived dimensions without named SI units + public static final ObjectProduct AREA = LENGTH.times(LENGTH); + + public static final ObjectProduct VOLUME = AREA.times(LENGTH); + public static final ObjectProduct VELOCITY = LENGTH.dividedBy(TIME); + public static final ObjectProduct ACCELERATION = VELOCITY.dividedBy(TIME); + public static final ObjectProduct WAVENUMBER = EMPTY.dividedBy(LENGTH); + public static final ObjectProduct MASS_DENSITY = MASS.dividedBy(VOLUME); + public static final ObjectProduct SURFACE_DENSITY = MASS.dividedBy(AREA); + public static final ObjectProduct SPECIFIC_VOLUME = VOLUME.dividedBy(MASS); + public static final ObjectProduct CURRENT_DENSITY = ELECTRIC_CURRENT.dividedBy(AREA); + public static final ObjectProduct MAGNETIC_FIELD_STRENGTH = ELECTRIC_CURRENT.dividedBy(LENGTH); + public static final ObjectProduct CONCENTRATION = QUANTITY.dividedBy(VOLUME); + public static final ObjectProduct MASS_CONCENTRATION = CONCENTRATION.times(MASS); + public static final ObjectProduct LUMINANCE = LUMINOUS_INTENSITY.dividedBy(AREA); + public static final ObjectProduct REFRACTIVE_INDEX = VELOCITY.dividedBy(VELOCITY); + public static final ObjectProduct REFLACTIVE_PERMEABILITY = EMPTY.times(EMPTY); + public static final ObjectProduct ANGLE = LENGTH.dividedBy(LENGTH); + public static final ObjectProduct SOLID_ANGLE = AREA.dividedBy(AREA); + // derived dimensions with named SI units + public static final ObjectProduct FREQUENCY = EMPTY.dividedBy(TIME); + + public static final ObjectProduct FORCE = MASS.times(ACCELERATION); + public static final ObjectProduct ENERGY = FORCE.times(LENGTH); + public static final ObjectProduct POWER = ENERGY.dividedBy(TIME); + public static final ObjectProduct ELECTRIC_CHARGE = ELECTRIC_CURRENT.times(TIME); + public static final ObjectProduct VOLTAGE = ENERGY.dividedBy(ELECTRIC_CHARGE); + public static final ObjectProduct CAPACITANCE = ELECTRIC_CHARGE.dividedBy(VOLTAGE); + public static final ObjectProduct ELECTRIC_RESISTANCE = VOLTAGE.dividedBy(ELECTRIC_CURRENT); + public static final ObjectProduct ELECTRIC_CONDUCTANCE = ELECTRIC_CURRENT.dividedBy(VOLTAGE); + public static final ObjectProduct MAGNETIC_FLUX = VOLTAGE.times(TIME); + public static final ObjectProduct MAGNETIC_FLUX_DENSITY = MAGNETIC_FLUX.dividedBy(AREA); + public static final ObjectProduct INDUCTANCE = MAGNETIC_FLUX.dividedBy(ELECTRIC_CURRENT); + public static final ObjectProduct LUMINOUS_FLUX = LUMINOUS_INTENSITY.times(SOLID_ANGLE); + public static final ObjectProduct ILLUMINANCE = LUMINOUS_FLUX.dividedBy(AREA); + public static final ObjectProduct SPECIFIC_ENERGY = ENERGY.dividedBy(MASS); + public static final ObjectProduct CATALYTIC_ACTIVITY = QUANTITY.dividedBy(TIME); + + // You may NOT get StandardDimensions instances! + private StandardDimensions() { + throw new AssertionError(); + } +} diff --git a/src/org/unitConverter/math/ZeroIsNullMap.java b/src/org/unitConverter/math/ZeroIsNullMap.java new file mode 100644 index 0000000..10e4d52 --- /dev/null +++ b/src/org/unitConverter/math/ZeroIsNullMap.java @@ -0,0 +1,129 @@ +/** + * Copyright (C) 2019 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 . + */ +package org.unitConverter.math; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * A "wrapper" for an existing map that treats a zero value as equivalent to null. + * + * @author Adrien Hopkins + * @since 2019-10-16 + * @param + * type of key + */ +public final class ZeroIsNullMap extends AbstractMap { + /** + * Create a ZeroIsNullMap. This method always creates a new map. + * + * @param + * type of key + * @param map + * map to input + * @return map that treats zero as null + * @throws NullPointerException + * if map is null + * @since 2019-10-16 + */ + public static Map create(final Map map) { + return new ZeroIsNullMap<>(Objects.requireNonNull(map, "map must not be null.")); + } + + private final Map map; + + /** + * Creates the {@code ObjectProductMap}. + * + * @param map + * @since 2019-10-16 + */ + private ZeroIsNullMap(final Map map) { + this.map = map; + } + + @Override + public void clear() { + this.map.clear(); + } + + @Override + public boolean containsKey(final Object key) { + return this.map.containsKey(key) && this.map.get(key) != 0; + } + + @Override + public boolean containsValue(final Object value) { + return this.values().contains(value); + } + + @Override + public Set> entrySet() { + final Set> entrySet = new HashSet<>(this.map.entrySet()); + entrySet.removeIf(e -> e.getValue() == 0); + return entrySet; + } + + @Override + public Integer get(final Object key) { + final Integer i = this.map.get(key); + if (Objects.equals(i, 0)) + return null; + else + return i; + } + + @Override + public Set keySet() { + final Set keySet = new HashSet<>(this.map.keySet()); + keySet.removeIf(k -> this.map.get(k) == 0); + return keySet; + } + + @Override + public Integer put(final T key, final Integer value) { + if (value != 0) + return this.map.put(key, value); + else + return null; + } + + @Override + public void putAll(final Map m) { + for (final T key : m.keySet()) { + this.put(key, m.get(key)); + } + } + + @Override + public Integer remove(final Object key) { + return this.map.remove(key); + } + + @Override + public Collection values() { + final List values = new ArrayList<>(this.map.values()); + values.removeIf(i -> i == 0); + return values; + } +} \ No newline at end of file diff --git a/src/org/unitConverter/math/ZeroIsNullMapTest.java b/src/org/unitConverter/math/ZeroIsNullMapTest.java new file mode 100644 index 0000000..c3db951 --- /dev/null +++ b/src/org/unitConverter/math/ZeroIsNullMapTest.java @@ -0,0 +1,113 @@ +/** + * Copyright (C) 2019 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 . + */ +package org.unitConverter.math; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.junit.jupiter.api.Test; + +/** + * @author Adrien Hopkins + * @since 2019-10-16 + */ +class ZeroIsNullMapTest { + + /** + * @return map to be used for test data + * @since 2019-10-16 + */ + Map getTestMap() { + final Map map = new HashMap<>(); + map.put("one", 1); + map.put("two", 2); + map.put("zero", 0); + map.put("ten", 10); + return ZeroIsNullMap.create(map); + } + + /** + * Test method for {@link org.unitConverter.math.ZeroIsNullMap#containsKey(java.lang.Object)}. + */ + @Test + void testContainsKeyObject() { + final Map map = this.getTestMap(); + assertTrue(map.containsKey("one")); + assertTrue(map.containsKey("ten")); + assertFalse(map.containsKey("five")); + assertFalse(map.containsKey("zero")); + } + + /** + * Test method for {@link org.unitConverter.math.ZeroIsNullMap#containsValue(java.lang.Object)}. + */ + @Test + void testContainsValueObject() { + final Map map = this.getTestMap(); + assertTrue(map.containsValue(1)); + assertTrue(map.containsValue(10)); + assertFalse(map.containsValue(5)); + assertFalse(map.containsValue(0)); + } + + /** + * Test method for {@link org.unitConverter.math.ZeroIsNullMap#entrySet()}. + */ + @Test + void testEntrySet() { + final Map map = this.getTestMap(); + for (final Entry e : map.entrySet()) { + assertTrue(e.getValue() != 0); + } + } + + /** + * Test method for {@link org.unitConverter.math.ZeroIsNullMap#get(java.lang.Object)}. + */ + @Test + void testGetObject() { + final Map map = this.getTestMap(); + assertEquals(1, map.get("one")); + assertEquals(10, map.get("ten")); + assertEquals(null, map.get("five")); + assertEquals(null, map.get("zero")); + } + + /** + * Test method for {@link org.unitConverter.math.ZeroIsNullMap#keySet()}. + */ + @Test + void testKeySet() { + final Map map = this.getTestMap(); + assertFalse(map.keySet().contains("zero")); + } + + /** + * Test method for {@link org.unitConverter.math.ZeroIsNullMap#values()}. + */ + @Test + void testValues() { + final Map map = this.getTestMap(); + assertFalse(map.values().contains(0)); + } + +} -- cgit v1.2.3