-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
145 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package day325 | ||
|
||
import ( | ||
"errors" | ||
) | ||
|
||
// Unit represents a unit of measure. | ||
type Unit string | ||
|
||
// Converter knows how to convert from/to different | ||
// units of measure. | ||
type Converter struct { | ||
table map[Unit]map[Unit]float64 | ||
} | ||
|
||
// ErrConversionImpossible returns the error given when a conversion | ||
// isn't possible. | ||
func ErrConversionImpossible() error { | ||
return errConversionImpossible | ||
} | ||
|
||
var errConversionImpossible = errors.New("conversion is impossible between these units") | ||
|
||
// Convert attempts to convert a value from one unit to another. | ||
// Uses a DFS algorithm to find a path to that conversion. | ||
func (c *Converter) Convert(val float64, from, to Unit) (float64, error) { | ||
visited := make(map[Unit]struct{}) | ||
return c.convert(val, from, to, visited) | ||
} | ||
|
||
func (c *Converter) convert(val float64, from, to Unit, visited map[Unit]struct{}) (float64, error) { | ||
if factor, found := c.table[from][to]; found { | ||
return val * factor, nil | ||
} | ||
for nextTo, factor := range c.table[from] { | ||
if _, seen := visited[nextTo]; seen { | ||
continue | ||
} | ||
visited[nextTo] = struct{}{} | ||
if result, err := c.convert(val*factor, nextTo, to, visited); err == nil { | ||
return result, nil | ||
} | ||
delete(visited, nextTo) | ||
} | ||
return 0.0, ErrConversionImpossible() | ||
} | ||
|
||
// AddConversion adds a new conversion rule to the table in both directions. | ||
func (c *Converter) AddConversion(factor float64, from, to Unit) { | ||
if _, exists := c.table[from]; !exists { | ||
c.table[from] = make(map[Unit]float64) | ||
} | ||
if _, exists := c.table[to]; !exists { | ||
c.table[to] = make(map[Unit]float64) | ||
} | ||
c.table[from][to] = factor | ||
c.table[to][from] = 1.0 / factor | ||
} | ||
|
||
// NewConverter returns a pointer to a new Converter. | ||
func NewConverter() *Converter { | ||
return &Converter{make(map[Unit]map[Unit]float64)} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package day325 | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
// nolint | ||
var testcases = []struct { | ||
val float64 | ||
from, to Unit | ||
expected float64 | ||
err error | ||
tolerance float64 | ||
}{ | ||
{ | ||
10.0, | ||
"in", "cm", | ||
25.4, | ||
nil, | ||
0.05, | ||
}, | ||
{ | ||
10.0, | ||
"in", "km", | ||
0.000254, | ||
nil, | ||
0.0000005, | ||
}, | ||
{ | ||
10.0, | ||
"in", "km", | ||
0.000254, | ||
nil, | ||
0.0000005, | ||
}, | ||
{ | ||
10.0, | ||
"in", "ml", | ||
0.0, | ||
ErrConversionImpossible(), | ||
0.0, | ||
}, | ||
} | ||
|
||
func TestConverter(t *testing.T) { | ||
t.Parallel() | ||
c := NewConverter() | ||
c.AddConversion(12.0, "ft", "in") | ||
c.AddConversion(5280.0, "mi", "ft") | ||
c.AddConversion(1.609, "mi", "km") | ||
c.AddConversion(1000, "km", "m") | ||
c.AddConversion(100, "m", "cm") | ||
c.AddConversion(3.0, "yd", "ft") | ||
c.AddConversion(22.0, "chain", "yd") | ||
for _, tc := range testcases { | ||
if result, err := c.Convert(tc.val, tc.from, tc.to); err != tc.err || abs(result-tc.expected) > tc.tolerance { | ||
t.Errorf("Expected (%v,%v), got (%v,%v)", tc.expected, tc.err, result, err) | ||
} | ||
} | ||
} | ||
|
||
func BenchmarkConverter(b *testing.B) { | ||
c := NewConverter() | ||
c.AddConversion(12.0, "in", "ft") | ||
c.AddConversion(5280.0, "ft", "mi") | ||
c.AddConversion(1.609, "mi", "km") | ||
c.AddConversion(0.001, "km", "m") | ||
c.AddConversion(0.01, "m", "cm") | ||
for i := 0; i < b.N; i++ { | ||
for _, tc := range testcases { | ||
c.Convert(tc.val, tc.from, tc.to) // nolint | ||
} | ||
} | ||
} | ||
|
||
func abs(a float64) float64 { | ||
if a < 0 { | ||
return -a | ||
} | ||
return a | ||
} |