package factors import ( "fmt" "maps" "math" "testing" ) // Run a test with a table of cases func tableTest[IN comparable, OUT any](t *testing.T, toTest func(IN) OUT, cases map[IN]OUT, equal func(OUT, OUT) bool, name string) { for input, expected := range cases { t.Run(fmt.Sprintf("%v", input), func(t *testing.T) { actual := toTest(input) if !equal(expected, actual) { t.Errorf("%s(%v) = %v, expect %v", name, input, actual, expected) } }) } } var primeFactorCases = map[uint]PrimeFactorization{ 0: PrimeFactorization{map[uint]uint{0: 1}}, 1: PrimeFactorization{map[uint]uint{}}, 2: PrimeFactorization{map[uint]uint{2: 1}}, 3: PrimeFactorization{map[uint]uint{3: 1}}, 4: PrimeFactorization{map[uint]uint{2: 2}}, 6: PrimeFactorization{map[uint]uint{2: 1, 3: 1}}, 10: PrimeFactorization{map[uint]uint{2: 1, 5: 1}}, 12: PrimeFactorization{map[uint]uint{2: 2, 3: 1}}, 33: PrimeFactorization{map[uint]uint{3: 1, 11: 1}}, 60: PrimeFactorization{map[uint]uint{2: 2, 3: 1, 5: 1}}, 86400: PrimeFactorization{map[uint]uint{2: 7, 3: 3, 5: 2}}, } func TestPrimeFactorize(t *testing.T) { equal := func(a, b PrimeFactorization) bool { return maps.Equal(a.exponents, b.exponents) } tableTest(t, PrimeFactorize, primeFactorCases, equal, "PrimeFactorize") } var factorCases = map[uint][]uint{ 1: []uint{1}, 2: []uint{1, 2}, 4: []uint{1, 2, 4}, 6: []uint{1, 2, 3, 6}, 10: []uint{1, 2, 5, 10}, 12: []uint{1, 2, 3, 4, 6, 12}, 13: []uint{1, 13}, 15: []uint{1, 3, 5, 15}, 18: []uint{1, 2, 3, 6, 9, 18}, 60: []uint{1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60}, } func TestFactors(t *testing.T) { tableTest(t, Factors, factorCases, setEquals, "Factors") } var totativeRatioCases = map[uint]float64{ 1: 1.0, 2: 0.5, 3: 2.0 / 3.0, 4: 0.5, 6: 1.0 / 3.0, 8: 0.5, 12: 1.0 / 3.0, } func TestTotativeRatio(t *testing.T) { equals := func(a, b float64) bool { return floatEquals(a, b, 1e-15) } tableTest(t, TotativeRatio, totativeRatioCases, equals, "TotativeRatio") } var factorScoreCases = map[uint]float64{ 1: 1.0, 2: 1.5, 3: 4.0 / 3.0, 4: 1.75, 6: 2.0, 8: 1.875, 10: 1.8, 12: 7.0 / 3.0, 120: 3.0, } func TestFactorScore(t *testing.T) { // factors.Score is accurate enough that we can test for exact floats! tableTest(t, Score, factorScoreCases, stdEquals[float64], "Score") } var basicRankCases = map[uint]string{ 2: "D-", 3: "E-", 4: "C~", 5: "F+", 6: "B~", 7: "F-", 8: "C-", 9: "E~", 10: "D+", 11: "F~", 12: "A-", 14: "D~", 15: "E+", 18: "B-", 20: "C+", 24: "A~", 30: "B+", 60: "A+", } func TestBasicRank(t *testing.T) { tableTest(t, BasicRank, basicRankCases, stdEquals[string], "BasicRank") } var gcdCases = map[struct{ a, b uint }]uint{ {0, 0}: 0, {1, 1}: 1, {6, 10}: 2, {10, 6}: 2, {12, 20}: 4, {20, 12}: 4, {20, 55}: 5, {55, 20}: 5, {60, 120}: 60, {120, 60}: 60, } // a version of gcd() for testing purposes func gcdTest(c struct{ a, b uint }) uint { return gcd(c.a, c.b) } func TestGCD(t *testing.T) { tableTest(t, gcdTest, gcdCases, stdEquals[uint], "gcd") } var mtcCases = map[uint]uint{ 2: 0, 3: 0, 4: 2, 5: 10, 6: 8, 7: 28, 8: 26, 9: 42, 10: 42, 11: 88, 12: 52, 13: 130, 14: 100, 15: 116, 16: 138, 18: 146, 20: 190, 24: 252, 28: 416, 30: 380, 32: 618, 36: 598, 48: 1100, 60: 1496, 64: 2602, 90: 3662, 120: 6080, 210: 18542, 360: 54362, 840: 270122, 2520: 2363528, 5040: 9409112, } func TestMTC(t *testing.T) { tableTest(t, MTC, mtcCases, stdEquals[uint], "MTC") } // to be used as the equal paramater for tableTest func stdEquals[T comparable](a, b T) bool { return a == b } func setEquals[E comparable](a, b []E) bool { // use maps to simulate sets // aSet[a] == true means set contains a, false means not aSet := make(map[E]bool) bSet := make(map[E]bool) for _, i := range a { aSet[i] = true } for _, j := range b { bSet[j] = true } return maps.Equal(aSet, bSet) } func floatEquals(a, b, maxDelta float64) bool { return math.Abs(a-b) <= maxDelta*math.Max(math.Abs(a), math.Abs(b)) }