Skip to content

Commit

Permalink
Add new mathx package (#150)
Browse files Browse the repository at this point in the history
* Add new mathx package

* Fix docstring

* Move type

* Comments

* Remove field

* Change constant name
  • Loading branch information
cristinaleonr authored Jul 20, 2022
1 parent 91b9ef3 commit f08f1b0
Show file tree
Hide file tree
Showing 6 changed files with 285 additions and 0 deletions.
9 changes: 9 additions & 0 deletions mathx/compare.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package mathx

// Min returns the minimum of two ints.
func Min(a, b int) int {
if a < b {
return a
}
return b
}
52 changes: 52 additions & 0 deletions mathx/compare_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package mathx

import "testing"

func TestMin(t *testing.T) {
tests := []struct {
name string
a int
b int
expected int
}{
{
name: "pos-pos",
a: 2,
b: 3,
expected: 2,
},
{
name: "neg-neg",
a: -2,
b: -3,
expected: -3,
},
{
name: "pos-neg",
a: 2,
b: -3,
expected: -3,
},
{
name: "same",
a: 2,
b: 2,
expected: 2,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Min(tt.a, tt.b)

if got != tt.expected {
t.Errorf("Min() = %d, want %d", got, tt.expected)
}

got = Min(tt.b, tt.a)

if got != tt.expected {
t.Errorf("Min() = %d, want %d", got, tt.expected)
}
})
}
}
33 changes: 33 additions & 0 deletions mathx/haversine.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package mathx

import (
"math"
)

const earthRadiusKm = 6371

// GetHaversineDistance finds the distance (in km) between two latitude/longitude
// pairs using the Haversine formula.
// For more details, see http://en.wikipedia.org/wiki/Haversine_formula.
func GetHaversineDistance(lat1, lon1, lat2, lon2 float64) float64 {
dlat1 := degreesToRadian(lat1)
dlon1 := degreesToRadian(lon1)
dlat2 := degreesToRadian(lat2)
dlon2 := degreesToRadian(lon2)

diffLat := dlat2 - dlat1
diffLon := dlon2 - dlon1
sinDiffLat := math.Sin(diffLat / 2)
sinDiffLon := math.Sin(diffLon / 2)

a := sinDiffLat*sinDiffLat + math.Cos(dlat1)*
math.Cos(dlat2)*sinDiffLon*sinDiffLon
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
d := earthRadiusKm * c

return d
}

func degreesToRadian(d float64) float64 {
return d * math.Pi / 180
}
62 changes: 62 additions & 0 deletions mathx/haversine_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package mathx

import (
"math"
"testing"
)

func TestGetHaversineDistance(t *testing.T) {
tests := []struct {
name string
lat1 float64
lon1 float64
lat2 float64
lon2 float64
expected float64
}{
{
name: "US-UK",
lat1: 37.09024,
lon1: -95.712891,
lat2: 55.378051,
lon2: -3.435973,
expected: 6830.40,
},
{
name: "US-US",
lat1: 37.09024,
lon1: -95.712891,
lat2: 37.09024,
lon2: -95.712891,
expected: 0,
},
{
name: "0-UK",
lat1: 0,
lon1: 0,
lat2: 55.378051,
lon2: -3.435973,
expected: 6165.67,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := GetHaversineDistance(tt.lat1, tt.lon1, tt.lat2, tt.lon2)

if !compareFloats(got, tt.expected) {
t.Errorf("GetHaversineDistance() = %f, want %f", got, tt.expected)
}

got = GetHaversineDistance(tt.lat2, tt.lon2, tt.lat1, tt.lon1)

if !compareFloats(got, tt.expected) {
t.Errorf("GetHaversineDistance() = %f, want %f", got, tt.expected)
}
})
}
}

func compareFloats(f1 float64, f2 float64) bool {
diff := math.Abs(f1 - f2)
return diff < 0.01
}
36 changes: 36 additions & 0 deletions mathx/rand.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package mathx

import (
"math"
"math/rand"
)

// Random is a source of random values.
type Random struct {
src *rand.Rand
}

// NewRandom returns a new Random that uses the provided seed to generate
// random values.
func NewRandom(seed int64) Random {
src := rand.New(rand.NewSource(seed))
return Random{src: src}
}

// GetRandomInt returns a non-negative pseudo-random number in the interval [0, max).
// It returns 0 if max <= 0.
func (r *Random) GetRandomInt(max int) int {
if max <= 0 {
return 0
}
return r.src.Intn(max)
}

// GetExpDistributedInt returns a exponentially distributed number in the interval
// [0, +math.MaxFloat64), rounded to the nearest int. Callers can adjust the rate of the
// function through the rate parameter.
func (r *Random) GetExpDistributedInt(rate float64) int {
f := r.src.ExpFloat64() / rate
index := int(math.Round(f))
return index
}
93 changes: 93 additions & 0 deletions mathx/rand_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package mathx

import "testing"

const seed = 1658340109320624211

func TestRandom_GetRandomInt(t *testing.T) {
tests := []struct {
name string
max int
expected1 int
expected2 int
}{
{
name: "random",
max: 10,
expected1: 6,
expected2: 8,
},
{
name: "zero",
max: 0,
expected1: 0,
expected2: 0,
},
{
name: "negative",
max: -10,
expected1: 0,
expected2: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := NewRandom(seed)
got := r.GetRandomInt(tt.max)

if got != tt.expected1 {
t.Errorf("GetRandomInt() = %d, want %d", got, tt.expected1)
}

got = r.GetRandomInt(tt.max)

if got != tt.expected2 {
t.Errorf("GetRandomInt() = %d, want %d", got, tt.expected2)
}
})
}
}

func TestRandom_GetExpDistributedInt(t *testing.T) {
tests := []struct {
name string
rate float64
expected1 int
expected2 int
}{
{
name: "rate-1",
rate: 1,
expected1: 1,
expected2: 0,
},
{
name: "rate-0.1",
rate: 0.1,
expected1: 5,
expected2: 2,
},
{
name: "rate-5",
rate: 5,
expected1: 0,
expected2: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := NewRandom(seed)
got := r.GetExpDistributedInt(tt.rate)

if got != tt.expected1 {
t.Errorf("GetExpDistributedInt() = %d, want %d", got, tt.expected1)
}

got = r.GetExpDistributedInt(tt.rate)

if got != tt.expected2 {
t.Errorf("GetExpDistributedInt() = %d, want %d", got, tt.expected2)
}
})
}
}

0 comments on commit f08f1b0

Please sign in to comment.