Skip to content
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

Unit conversions #3

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
52 changes: 52 additions & 0 deletions content/conversions.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
= Unit Conversions

== Question

Given a series of unit conversions of this form:

----
1 ft = 12 in
1 yd = 3 ft
1 cup = 8 oz
1 pint = 2 cups
----

Create a system that can convert qty of one unit to another unit. For example:

`2 yd = ___ in` would return 72

== Explanation

Let's go through the example in the problem.
We can convert `2 yd` to `ft` by multiplying the quantity 2 by 3 to get `6 ft`.
Similarly, we can convert `6 ft` to `in` by multiplying quantity 6 by 12 to get `72 in`.
If we know the intermediate units to convert our quantity to, getting our answer just requires some multiplication.
So how do we determine the intermediate units?
We have to search for a path between our start unit and our end unit.
Graph searching algorithms are good options for tasks like this.
Let each unit be a node in the graph and an edge between node `u` and node `v` means `u` can be converted to `v`.
The edges also have our scalar values for conversions.
Here's a graph for our example problem:

TODO
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • create graph


Now what should our graph search algorithm be?
Both DFS and BFS can work here, but we should discuss the trade-offs.
BFS is guaranteed to find the shortest path (i.e., fewest node conversions),
Fewer conversions means less floating point multiplication, so let's use that.
So our solution is now:

* Convert our units to a conversion graph
** Our graph is being directed, each edge can be reversed by dividing by the scalar rather than multiplying.
* Use BFS to find a path between the starting unit and the ending unit
** If no path exists, we cannot convert between these units and should notify the client somehow (throw an exception)
* When constructing the path, keep track of the scalars so we can multiply the initial quantity for our answer.


== Solution

.Conversions.java
[source,java]
----
include::src/Conversions.java[]
----
1 change: 1 addition & 0 deletions content/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ Explanations and solutions to interview questions
* xref:extreme_connect_4.adoc[Extreme Connect 4]
* xref:middle_queue.adoc[Front Middle Back Queue]
* xref:succession.adoc[Succession]
* xref:conversions.adoc[Unit Conversions]
56 changes: 56 additions & 0 deletions content/src/Conversions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import java.util.*;

class Conversions {

private final Map<String, List<Edge>> unitToConversions;

public Conversions() {
this.unitToConversions = new HashMap<>();
}

public void addConversion(String from, String to, double scalar) {
unitToConversions.computeIfAbsent(from, k -> new ArrayList<>()).add(new Edge(to, scalar));
unitToConversions.computeIfAbsent(to, k -> new ArrayList<>()).add(new Edge(from, 1 / scalar));
}

public double convert(ConversionQuery query) {
var q = new ArrayDeque<Edge>();
var visited = new HashSet<String>();
q.add(new Edge(query.from, query.qty));

while (!q.isEmpty()) {
var next = q.poll();
if (next.unit.equals(query.to)) {
return next.scalar;
}
if (visited.contains(next.unit)) {
continue;
}
for (var edge: unitToConversions.getOrDefault(next.unit, Collections.emptyList())) {
q.offer(new Edge(edge.unit, next.scalar * edge.scalar));
}
visited.add(next.unit);
}

throw new IllegalArgumentException("Impossible to convert: " + query);
}

private record ConversionQuery(String from, String to, double qty) {
}

private record Edge(String unit, double scalar) {
}

public static void main(String[] args) {
var conversions = new Conversions();

conversions.addConversion("ft", "in", 12.0);
conversions.addConversion("yd", "ft", 3.0);
conversions.addConversion("cup", "oz", 8.0);
conversions.addConversion("pint", "cup", 12.0);

System.out.println("72.0 == " + conversions.convert(new ConversionQuery("yd", "in", 2)));
System.out.println("2.0 == " + conversions.convert(new ConversionQuery("in", "yd", 72)));
}

}