diff --git a/src/main/java/com/thealgorithms/conversions/AffineConverter.java b/src/main/java/com/thealgorithms/conversions/AffineConverter.java new file mode 100644 index 000000000000..a580b23f90f9 --- /dev/null +++ b/src/main/java/com/thealgorithms/conversions/AffineConverter.java @@ -0,0 +1,23 @@ +package com.thealgorithms.conversions; + +public final class AffineConverter { + private final double slope; + private final double intercept; + public AffineConverter(final double inSlope, final double inIntercept) { + slope = inSlope; + intercept = inIntercept; + } + + public double convert(final double inValue) { + return slope * inValue + intercept; + } + + public AffineConverter invert() { + assert slope != 0.0; + return new AffineConverter(1.0 / slope, -intercept / slope); + } + + public AffineConverter compose(final AffineConverter other) { + return new AffineConverter(slope * other.slope, slope * other.intercept + intercept); + } +} diff --git a/src/main/java/com/thealgorithms/conversions/UnitConversions.java b/src/main/java/com/thealgorithms/conversions/UnitConversions.java new file mode 100644 index 000000000000..abc06a0f8863 --- /dev/null +++ b/src/main/java/com/thealgorithms/conversions/UnitConversions.java @@ -0,0 +1,14 @@ +package com.thealgorithms.conversions; + +import static java.util.Map.entry; + +import java.util.Map; +import org.apache.commons.lang3.tuple.Pair; + +public final class UnitConversions { + private UnitConversions() { + } + + public static final UnitsConverter TEMPERATURE = new UnitsConverter(Map.ofEntries(entry(Pair.of("Kelvin", "Celsius"), new AffineConverter(1.0, -273.15)), entry(Pair.of("Celsius", "Fahrenheit"), new AffineConverter(9.0 / 5.0, 32.0)), + entry(Pair.of("Réaumur", "Celsius"), new AffineConverter(5.0 / 4.0, 0.0)), entry(Pair.of("Delisle", "Celsius"), new AffineConverter(-2.0 / 3.0, 100.0)), entry(Pair.of("Rankine", "Kelvin"), new AffineConverter(5.0 / 9.0, 0.0)))); +} diff --git a/src/main/java/com/thealgorithms/conversions/UnitsConverter.java b/src/main/java/com/thealgorithms/conversions/UnitsConverter.java new file mode 100644 index 000000000000..a19d40285047 --- /dev/null +++ b/src/main/java/com/thealgorithms/conversions/UnitsConverter.java @@ -0,0 +1,86 @@ +package com.thealgorithms.conversions; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.apache.commons.lang3.tuple.Pair; + +public final class UnitsConverter { + private final Map, AffineConverter> conversions; + private final Set units; + + private static void putIfNeeded(Map, AffineConverter> conversions, final String inputUnit, final String outputUnit, final AffineConverter converter) { + if (!inputUnit.equals(outputUnit)) { + final var key = Pair.of(inputUnit, outputUnit); + conversions.putIfAbsent(key, converter); + } + } + + private static Map, AffineConverter> addInversions(final Map, AffineConverter> knownConversions) { + Map, AffineConverter> res = new HashMap, AffineConverter>(); + for (final var curConversion : knownConversions.entrySet()) { + final var inputUnit = curConversion.getKey().getKey(); + final var outputUnit = curConversion.getKey().getValue(); + putIfNeeded(res, inputUnit, outputUnit, curConversion.getValue()); + putIfNeeded(res, outputUnit, inputUnit, curConversion.getValue().invert()); + } + return res; + } + + private static Map, AffineConverter> addCompositions(final Map, AffineConverter> knownConversions) { + Map, AffineConverter> res = new HashMap, AffineConverter>(); + for (final var first : knownConversions.entrySet()) { + final var firstKey = first.getKey(); + putIfNeeded(res, firstKey.getKey(), firstKey.getValue(), first.getValue()); + for (final var second : knownConversions.entrySet()) { + final var secondKey = second.getKey(); + if (firstKey.getValue().equals(secondKey.getKey())) { + final var newConversion = second.getValue().compose(first.getValue()); + putIfNeeded(res, firstKey.getKey(), secondKey.getValue(), newConversion); + } + } + } + return res; + } + + private static Map, AffineConverter> addAll(final Map, AffineConverter> knownConversions) { + final var res = addInversions(knownConversions); + return addCompositions(res); + } + + private static Map, AffineConverter> computeAllConversions(final Map, AffineConverter> basicConversions) { + var tmp = basicConversions; + var res = addAll(tmp); + while (res.size() != tmp.size()) { + tmp = res; + res = addAll(tmp); + } + return res; + } + + private static Set extractUnits(final Map, AffineConverter> conversions) { + Set res = new HashSet<>(); + for (final var conversion : conversions.entrySet()) { + res.add(conversion.getKey().getKey()); + } + return res; + } + + public UnitsConverter(final Map, AffineConverter> basicConversions) { + conversions = computeAllConversions(basicConversions); + units = extractUnits(conversions); + } + + public double convert(final String inputUnit, final String outputUnit, final double value) { + if (inputUnit.equals(outputUnit)) { + throw new IllegalArgumentException("inputUnit must be different from outputUnit."); + } + final var conversionKey = Pair.of(inputUnit, outputUnit); + return conversions.get(conversionKey).convert(value); + } + + public Set availableUnits() { + return units; + } +} diff --git a/src/test/java/com/thealgorithms/conversions/UnitConversionsTest.java b/src/test/java/com/thealgorithms/conversions/UnitConversionsTest.java new file mode 100644 index 000000000000..073e7d6de2c6 --- /dev/null +++ b/src/test/java/com/thealgorithms/conversions/UnitConversionsTest.java @@ -0,0 +1,48 @@ +package com.thealgorithms.conversions; + +import static java.util.Map.entry; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class UnitConversionsTest { + private static void addData(Stream.Builder builder, Map values) { + for (final var first : values.entrySet()) { + for (final var second : values.entrySet()) { + if (!first.getKey().equals(second.getKey())) { + builder.add(Arguments.of(first.getKey(), second.getKey(), first.getValue(), second.getValue())); + } + } + } + } + + private static Stream temperatureData() { + final Map boilingPointOfWater = Map.ofEntries(entry("Celsius", 99.9839), entry("Fahrenheit", 211.97102), entry("Kelvin", 373.1339), entry("Réaumur", 79.98712), entry("Delisle", 0.02415), entry("Rankine", 671.64102)); + + final Map freezingPointOfWater = Map.ofEntries(entry("Celsius", 0.0), entry("Fahrenheit", 32.0), entry("Kelvin", 273.15), entry("Réaumur", 0.0), entry("Delisle", 150.0), entry("Rankine", 491.67)); + + Stream.Builder builder = Stream.builder(); + addData(builder, boilingPointOfWater); + addData(builder, freezingPointOfWater); + return builder.build(); + } + + @ParameterizedTest + @MethodSource("temperatureData") + void testTemperature(String inputUnit, String outputUnit, double value, double expected) { + final double result = UnitConversions.TEMPERATURE.convert(inputUnit, outputUnit, value); + assertEquals(expected, result, 0.00001); + } + + @Test + void testTemperatureUnits() { + final Set expectedUnits = Set.of("Celsius", "Fahrenheit", "Kelvin", "Réaumur", "Rankine", "Delisle"); + assertEquals(expectedUnits, UnitConversions.TEMPERATURE.availableUnits()); + } +} diff --git a/src/test/java/com/thealgorithms/conversions/UnitsConverterTest.java b/src/test/java/com/thealgorithms/conversions/UnitsConverterTest.java new file mode 100644 index 000000000000..76e48f144fd6 --- /dev/null +++ b/src/test/java/com/thealgorithms/conversions/UnitsConverterTest.java @@ -0,0 +1,18 @@ +package com.thealgorithms.conversions; + +import static java.util.Map.entry; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Map; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.Test; + +public class UnitsConverterTest { + + @Test + void testConvertThrowsForSameUnits() { + final UnitsConverter someConverter = new UnitsConverter(Map.ofEntries(entry(Pair.of("A", "B"), new AffineConverter(10.0, -20.0)))); + assertThrows(IllegalArgumentException.class, () -> someConverter.convert("A", "A", 20.0)); + assertThrows(IllegalArgumentException.class, () -> someConverter.convert("B", "B", 20.0)); + } +}