| // Copyright ©2016 The Gonum Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package combin |
| |
| import ( |
| "math/big" |
| "reflect" |
| "strconv" |
| "testing" |
| "unsafe" |
| |
| "gonum.org/v1/gonum/floats/scalar" |
| ) |
| |
| // intSosMatch returns true if the two slices of slices are equal. |
| func intSosMatch(a, b [][]int) bool { |
| if len(a) != len(b) { |
| return false |
| } |
| for i, s := range a { |
| if len(s) != len(b[i]) { |
| return false |
| } |
| for j, v := range s { |
| if v != b[i][j] { |
| return false |
| } |
| } |
| } |
| return true |
| } |
| |
| var binomialTests = []struct { |
| n, k, ans int |
| }{ |
| {0, 0, 1}, |
| {5, 0, 1}, |
| {5, 1, 5}, |
| {5, 2, 10}, |
| {5, 3, 10}, |
| {5, 4, 5}, |
| {5, 5, 1}, |
| |
| {6, 0, 1}, |
| {6, 1, 6}, |
| {6, 2, 15}, |
| {6, 3, 20}, |
| {6, 4, 15}, |
| {6, 5, 6}, |
| {6, 6, 1}, |
| |
| {20, 0, 1}, |
| {20, 1, 20}, |
| {20, 2, 190}, |
| {20, 3, 1140}, |
| {20, 4, 4845}, |
| {20, 5, 15504}, |
| {20, 6, 38760}, |
| {20, 7, 77520}, |
| {20, 8, 125970}, |
| {20, 9, 167960}, |
| {20, 10, 184756}, |
| {20, 11, 167960}, |
| {20, 12, 125970}, |
| {20, 13, 77520}, |
| {20, 14, 38760}, |
| {20, 15, 15504}, |
| {20, 16, 4845}, |
| {20, 17, 1140}, |
| {20, 18, 190}, |
| {20, 19, 20}, |
| {20, 20, 1}, |
| } |
| |
| func TestBinomial(t *testing.T) { |
| for cas, test := range binomialTests { |
| ans := Binomial(test.n, test.k) |
| if ans != test.ans { |
| t.Errorf("Case %v: Binomial mismatch. Got %v, want %v.", cas, ans, test.ans) |
| } |
| } |
| var ( |
| // Ensure that we have enough space to represent results. |
| // TODO(kortschak): Replace the unsafe.Sizeof(int(0)) expression with |
| // sizeof.Int if https://github.com/golang/go/issues/29982 is |
| // implemented. See also https://github.com/golang/go/issues/40168. |
| n = int(unsafe.Sizeof(int(0))*8 - 3) |
| |
| want big.Int |
| got big.Int |
| ) |
| for k := 0; k <= n; k++ { |
| want.Binomial(int64(n), int64(k)) |
| got.SetInt64(int64(Binomial(n, k))) |
| if want.Cmp(&got) != 0 { |
| t.Errorf("Case n=%v,k=%v: Binomial mismatch for large n. Got %v, want %v.", n, k, got, want) |
| } |
| } |
| } |
| |
| func TestGeneralizedBinomial(t *testing.T) { |
| for cas, test := range binomialTests { |
| ans := GeneralizedBinomial(float64(test.n), float64(test.k)) |
| if !scalar.EqualWithinAbsOrRel(ans, float64(test.ans), 1e-14, 1e-14) { |
| t.Errorf("Case %v: Binomial mismatch. Got %v, want %v.", cas, ans, test.ans) |
| } |
| } |
| } |
| |
| func TestCombinations(t *testing.T) { |
| for cas, test := range []struct { |
| n, k int |
| data [][]int |
| }{ |
| { |
| n: 1, |
| k: 1, |
| data: [][]int{{0}}, |
| }, |
| { |
| n: 2, |
| k: 1, |
| data: [][]int{{0}, {1}}, |
| }, |
| { |
| n: 2, |
| k: 2, |
| data: [][]int{{0, 1}}, |
| }, |
| { |
| n: 3, |
| k: 1, |
| data: [][]int{{0}, {1}, {2}}, |
| }, |
| { |
| n: 3, |
| k: 2, |
| data: [][]int{{0, 1}, {0, 2}, {1, 2}}, |
| }, |
| { |
| n: 3, |
| k: 3, |
| data: [][]int{{0, 1, 2}}, |
| }, |
| { |
| n: 4, |
| k: 1, |
| data: [][]int{{0}, {1}, {2}, {3}}, |
| }, |
| { |
| n: 4, |
| k: 2, |
| data: [][]int{{0, 1}, {0, 2}, {0, 3}, {1, 2}, {1, 3}, {2, 3}}, |
| }, |
| { |
| n: 4, |
| k: 3, |
| data: [][]int{{0, 1, 2}, {0, 1, 3}, {0, 2, 3}, {1, 2, 3}}, |
| }, |
| { |
| n: 4, |
| k: 4, |
| data: [][]int{{0, 1, 2, 3}}, |
| }, |
| } { |
| data := Combinations(test.n, test.k) |
| if !intSosMatch(data, test.data) { |
| t.Errorf("Cas %v: Generated combinations mismatch. Got %v, want %v.", cas, data, test.data) |
| } |
| } |
| } |
| |
| func TestCombinationGenerator(t *testing.T) { |
| for n := 0; n <= 10; n++ { |
| for k := 1; k <= n; k++ { |
| combinations := Combinations(n, k) |
| cg := NewCombinationGenerator(n, k) |
| genCombs := make([][]int, 0, len(combinations)) |
| for cg.Next() { |
| genCombs = append(genCombs, cg.Combination(nil)) |
| } |
| if !intSosMatch(combinations, genCombs) { |
| t.Errorf("Combinations and generated combinations do not match. n = %v, k = %v", n, k) |
| } |
| } |
| } |
| } |
| |
| func TestCombinationIndex(t *testing.T) { |
| for cas, s := range []struct { |
| n, k int |
| }{ |
| {6, 3}, |
| {4, 4}, |
| {10, 1}, |
| {8, 2}, |
| } { |
| n := s.n |
| k := s.k |
| combs := make(map[string]struct{}) |
| for i := 0; i < Binomial(n, k); i++ { |
| comb := IndexToCombination(nil, i, n, k) |
| idx := CombinationIndex(comb, n, k) |
| if idx != i { |
| t.Errorf("Cas %d: combination mismatch. Want %d, got %d", cas, i, idx) |
| } |
| combs[intSliceToKey(comb)] = struct{}{} |
| } |
| if len(combs) != Binomial(n, k) { |
| t.Errorf("Case %d: not all generated combinations were unique", cas) |
| } |
| } |
| } |
| |
| func intSliceToKey(s []int) string { |
| var str string |
| for _, v := range s { |
| str += strconv.Itoa(v) + "_" |
| } |
| return str |
| } |
| |
| // TestCombinationOrder tests that the different Combinations methods |
| // agree on the iteration order. |
| func TestCombinationOrder(t *testing.T) { |
| n := 7 |
| k := 3 |
| list := Combinations(n, k) |
| for i, v := range list { |
| idx := CombinationIndex(v, n, k) |
| if idx != i { |
| t.Errorf("Combinations and CombinationIndex mismatch") |
| break |
| } |
| } |
| } |
| |
| func TestIdxSubFor(t *testing.T) { |
| for cas, dims := range [][]int{ |
| {2, 2}, |
| {3, 1, 6}, |
| {2, 4, 6, 7}, |
| } { |
| // Loop over all of the indexes. Confirm that the subscripts make sense |
| // and that IdxFor is the converse of SubFor. |
| maxIdx := 1 |
| for _, v := range dims { |
| maxIdx *= v |
| } |
| into := make([]int, len(dims)) |
| for idx := 0; idx < maxIdx; idx++ { |
| sub := SubFor(nil, idx, dims) |
| for i := range sub { |
| if sub[i] < 0 || sub[i] >= dims[i] { |
| t.Errorf("cas %v: bad subscript. dims: %v, sub: %v", cas, dims, sub) |
| } |
| } |
| SubFor(into, idx, dims) |
| if !reflect.DeepEqual(sub, into) { |
| t.Errorf("cas %v: subscript mismatch with supplied slice. Got %v, want %v", cas, into, sub) |
| } |
| idxOut := IdxFor(sub, dims) |
| if idxOut != idx { |
| t.Errorf("cas %v: returned index mismatch. Got %v, want %v", cas, idxOut, idx) |
| } |
| } |
| } |
| } |
| |
| func TestCartesian(t *testing.T) { |
| // First, test with a known return. |
| lens := []int{2, 3, 4} |
| want := [][]int{ |
| {0, 0, 0}, |
| {0, 0, 1}, |
| {0, 0, 2}, |
| {0, 0, 3}, |
| {0, 1, 0}, |
| {0, 1, 1}, |
| {0, 1, 2}, |
| {0, 1, 3}, |
| {0, 2, 0}, |
| {0, 2, 1}, |
| {0, 2, 2}, |
| {0, 2, 3}, |
| {1, 0, 0}, |
| {1, 0, 1}, |
| {1, 0, 2}, |
| {1, 0, 3}, |
| {1, 1, 0}, |
| {1, 1, 1}, |
| {1, 1, 2}, |
| {1, 1, 3}, |
| {1, 2, 0}, |
| {1, 2, 1}, |
| {1, 2, 2}, |
| {1, 2, 3}, |
| } |
| got := Cartesian(lens) |
| if !intSosMatch(want, got) { |
| t.Errorf("Cartesian data mismatch.\nwant:\n%v\ngot:\n%v", want, got) |
| } |
| } |
| |
| func TestNumCartesianProducts(t *testing.T) { |
| want := 6 |
| got := Card([]int{1, 2, 3}) |
| if want != got { |
| t.Errorf("number of Cartesian products mismatch.\nwant:\n%v\ngot:\n%v", want, got) |
| } |
| } |
| |
| func TestCartesianGenerator(t *testing.T) { |
| want := [][]int{ |
| {0, 0, 0}, |
| {0, 0, 1}, |
| {0, 0, 2}, |
| {0, 1, 0}, |
| {0, 1, 1}, |
| {0, 1, 2}, |
| } |
| gen := NewCartesianGenerator([]int{1, 2, 3}) |
| iterations := 0 |
| for gen.Next() { |
| got := gen.Product(nil) |
| if !reflect.DeepEqual(got, want[iterations]) { |
| t.Errorf("Cartesian product does not match. want: %v got: %v", want[iterations], got) |
| } |
| iterations++ |
| } |
| |
| if iterations != len(want) { |
| t.Errorf("Number of products does not match. want: %v got: %v", len(want), iterations) |
| } |
| } |
| |
| func TestPermutationIndex(t *testing.T) { |
| for cas, s := range []struct { |
| n, k int |
| }{ |
| {6, 3}, |
| {4, 4}, |
| {10, 1}, |
| {8, 2}, |
| } { |
| n := s.n |
| k := s.k |
| perms := make(map[string]struct{}) |
| for i := 0; i < NumPermutations(n, k); i++ { |
| perm := IndexToPermutation(nil, i, n, k) |
| idx := PermutationIndex(perm, n, k) |
| if idx != i { |
| t.Errorf("Cas %d: permutation mismatch. Want %d, got %d", cas, i, idx) |
| } |
| perms[intSliceToKey(perm)] = struct{}{} |
| } |
| if len(perms) != NumPermutations(n, k) { |
| t.Errorf("Case %d: not all generated combinations were unique", cas) |
| } |
| } |
| } |
| |
| func TestPermutationGenerator(t *testing.T) { |
| for n := 0; n <= 7; n++ { |
| for k := 1; k <= n; k++ { |
| permutations := Permutations(n, k) |
| pg := NewPermutationGenerator(n, k) |
| genPerms := make([][]int, 0, len(permutations)) |
| for pg.Next() { |
| genPerms = append(genPerms, pg.Permutation(nil)) |
| } |
| if !intSosMatch(permutations, genPerms) { |
| t.Errorf("Permutations and generated permutations do not match. n = %v, k = %v", n, k) |
| } |
| } |
| } |
| } |