Skip to content

feat: add temperature unit conversions #5315

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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))));
}
86 changes: 86 additions & 0 deletions src/main/java/com/thealgorithms/conversions/UnitsConverter.java
Original file line number Diff line number Diff line change
@@ -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<Pair<String, String>, AffineConverter> conversions;
private final Set<String> units;

private static void putIfNeeded(Map<Pair<String, String>, 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<Pair<String, String>, AffineConverter> addInversions(final Map<Pair<String, String>, AffineConverter> knownConversions) {
Map<Pair<String, String>, AffineConverter> res = new HashMap<Pair<String, String>, 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<Pair<String, String>, AffineConverter> addCompositions(final Map<Pair<String, String>, AffineConverter> knownConversions) {
Map<Pair<String, String>, AffineConverter> res = new HashMap<Pair<String, String>, 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<Pair<String, String>, AffineConverter> addAll(final Map<Pair<String, String>, AffineConverter> knownConversions) {
final var res = addInversions(knownConversions);
return addCompositions(res);
}

private static Map<Pair<String, String>, AffineConverter> computeAllConversions(final Map<Pair<String, String>, AffineConverter> basicConversions) {
var tmp = basicConversions;
var res = addAll(tmp);
while (res.size() != tmp.size()) {
tmp = res;
res = addAll(tmp);
}
return res;
}

private static Set<String> extractUnits(final Map<Pair<String, String>, AffineConverter> conversions) {
Set<String> res = new HashSet<>();
for (final var conversion : conversions.entrySet()) {
res.add(conversion.getKey().getKey());
}
return res;
}

public UnitsConverter(final Map<Pair<String, String>, 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<String> availableUnits() {
return units;
}
}
Original file line number Diff line number Diff line change
@@ -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<Arguments> builder, Map<String, Double> 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<Arguments> temperatureData() {
final Map<String, Double> 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<String, Double> 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<Arguments> 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<String> expectedUnits = Set.of("Celsius", "Fahrenheit", "Kelvin", "Réaumur", "Rankine", "Delisle");
assertEquals(expectedUnits, UnitConversions.TEMPERATURE.availableUnits());
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}