Skip to content
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
60 changes: 60 additions & 0 deletions src/main/java/com/thealgorithms/conversions/UnitsConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,43 @@
import java.util.Set;
import org.apache.commons.lang3.tuple.Pair;

/**
* A class that handles unit conversions using affine transformations.
*
* <p>The {@code UnitsConverter} allows converting values between different units using
* pre-defined affine conversion formulas. Each conversion is represented by an
* {@link AffineConverter} that defines the scaling and offset for the conversion.
*
* <p>For each unit, both direct conversions (e.g., Celsius to Fahrenheit) and inverse
* conversions (e.g., Fahrenheit to Celsius) are generated automatically. It also computes
* transitive conversions (e.g., Celsius to Kelvin via Fahrenheit if both conversions exist).
*
* <p>Key features include:
* <ul>
* <li>Automatic handling of inverse conversions (e.g., Fahrenheit to Celsius).</li>
* <li>Compositional conversions, meaning if conversions between A -> B and B -> C exist,
* it can automatically generate A -> C conversion.</li>
* <li>Supports multiple unit systems as long as conversions are provided in pairs.</li>
* </ul>
*
* <h2>Example Usage</h2>
* <pre>
* Map&lt;Pair&lt;String, String&gt;, AffineConverter&gt; basicConversions = Map.ofEntries(
* entry(Pair.of("Celsius", "Fahrenheit"), new AffineConverter(9.0 / 5.0, 32.0)),
* entry(Pair.of("Kelvin", "Celsius"), new AffineConverter(1.0, -273.15))
* );
*
* UnitsConverter converter = new UnitsConverter(basicConversions);
* double result = converter.convert("Celsius", "Fahrenheit", 100.0);
* // Output: 212.0 (Celsius to Fahrenheit conversion of 100°C)
* </pre>
*
* <h2>Exception Handling</h2>
* <ul>
* <li>If the input unit and output unit are the same, an {@link IllegalArgumentException} is thrown.</li>
* <li>If a conversion between the requested units does not exist, a {@link NoSuchElementException} is thrown.</li>
* </ul>
*/
public final class UnitsConverter {
private final Map<Pair<String, String>, AffineConverter> conversions;
private final Set<String> units;
Expand Down Expand Up @@ -68,11 +105,29 @@ private static Set<String> extractUnits(final Map<Pair<String, String>, AffineCo
return res;
}

/**
* Constructor for {@code UnitsConverter}.
*
* <p>Accepts a map of basic conversions and automatically generates inverse and
* transitive conversions.
*
* @param basicConversions the initial set of unit conversions to add.
*/
public UnitsConverter(final Map<Pair<String, String>, AffineConverter> basicConversions) {
conversions = computeAllConversions(basicConversions);
units = extractUnits(conversions);
}

/**
* Converts a value from one unit to another.
*
* @param inputUnit the unit of the input value.
* @param outputUnit the unit to convert the value into.
* @param value the value to convert.
* @return the converted value in the target unit.
* @throws IllegalArgumentException if inputUnit equals outputUnit.
* @throws NoSuchElementException if no conversion exists between the units.
*/
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.");
Expand All @@ -81,6 +136,11 @@ public double convert(final String inputUnit, final String outputUnit, final dou
return conversions.computeIfAbsent(conversionKey, k -> { throw new NoSuchElementException("No converter for: " + k); }).convert(value);
}

/**
* Retrieves the set of all units supported by this converter.
*
* @return a set of available units.
*/
public Set<String> availableUnits() {
return units;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.thealgorithms.conversions;

import static java.util.Map.entry;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import org.apache.commons.lang3.tuple.Pair;
import org.junit.jupiter.api.Test;

Expand All @@ -24,4 +26,19 @@ void testConvertThrowsForUnknownUnits() {
assertThrows(NoSuchElementException.class, () -> someConverter.convert("X", "A", 20.0));
assertThrows(NoSuchElementException.class, () -> someConverter.convert("X", "Y", 20.0));
}

@Test
void testAvailableUnits() {
final UnitsConverter someConverter = new UnitsConverter(Map.ofEntries(entry(Pair.of("Celsius", "Fahrenheit"), new AffineConverter(9.0 / 5.0, 32.0)), entry(Pair.of("Kelvin", "Celsius"), new AffineConverter(1.0, -273.15))));
assertEquals(Set.of("Celsius", "Fahrenheit", "Kelvin"), someConverter.availableUnits());
}

@Test
void testInvertConversion() {
final UnitsConverter someConverter = new UnitsConverter(Map.ofEntries(entry(Pair.of("A", "B"), new AffineConverter(2.0, 5.0))));
// Check conversion from A -> B
assertEquals(25.0, someConverter.convert("A", "B", 10.0), 0.0001);
// Check inverse conversion from B -> A
assertEquals(10.0, someConverter.convert("B", "A", 25.0), 0.0001);
}
}