blob: 9886915cd4880dccad5c440f6f90857880d60a2b [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/attribution_reporting/combinatorics.h"
#include <set>
#include <vector>
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
TEST(CombinatoricsTest, BinomialCoefficient) {
// Test cases generated via a python program using scipy.special.comb.
struct {
int n;
int k;
int expected;
} kTestCases[]{
// All cases for n and k in [0, 10).
// clang-format off
{0, 0, 1}, {0, 1, 0}, {0, 2, 0}, {0, 3, 0}, {0, 4, 0}, {0, 5, 0},
{0, 6, 0}, {0, 7, 0}, {0, 8, 0}, {0, 9, 0}, {1, 0, 1}, {1, 1, 1},
{1, 2, 0}, {1, 3, 0}, {1, 4, 0}, {1, 5, 0}, {1, 6, 0}, {1, 7, 0},
{1, 8, 0}, {1, 9, 0}, {2, 0, 1}, {2, 1, 2}, {2, 2, 1}, {2, 3, 0},
{2, 4, 0}, {2, 5, 0}, {2, 6, 0}, {2, 7, 0}, {2, 8, 0}, {2, 9, 0},
{3, 0, 1}, {3, 1, 3}, {3, 2, 3}, {3, 3, 1}, {3, 4, 0}, {3, 5, 0},
{3, 6, 0}, {3, 7, 0}, {3, 8, 0}, {3, 9, 0}, {4, 0, 1}, {4, 1, 4},
{4, 2, 6}, {4, 3, 4}, {4, 4, 1}, {4, 5, 0}, {4, 6, 0}, {4, 7, 0},
{4, 8, 0}, {4, 9, 0}, {5, 0, 1}, {5, 1, 5}, {5, 2, 10}, {5, 3, 10},
{5, 4, 5}, {5, 5, 1}, {5, 6, 0}, {5, 7, 0}, {5, 8, 0}, {5, 9, 0},
{6, 0, 1}, {6, 1, 6}, {6, 2, 15}, {6, 3, 20}, {6, 4, 15}, {6, 5, 6},
{6, 6, 1}, {6, 7, 0}, {6, 8, 0}, {6, 9, 0}, {7, 0, 1}, {7, 1, 7},
{7, 2, 21}, {7, 3, 35}, {7, 4, 35}, {7, 5, 21}, {7, 6, 7}, {7, 7, 1},
{7, 8, 0}, {7, 9, 0}, {8, 0, 1}, {8, 1, 8}, {8, 2, 28}, {8, 3, 56},
{8, 4, 70}, {8, 5, 56}, {8, 6, 28}, {8, 7, 8}, {8, 8, 1}, {8, 9, 0},
{9, 0, 1}, {9, 1, 9}, {9, 2, 36}, {9, 3, 84}, {9, 4, 126}, {9, 5, 126},
{9, 6, 84}, {9, 7, 36}, {9, 8, 9}, {9, 9, 1},
// clang-format on
// A few larger cases:
{30, 3, 4060},
{100, 2, 4950},
{100, 5, 75287520},
};
for (const auto& test_case : kTestCases) {
EXPECT_EQ(BinomialCoefficient(test_case.n, test_case.k), test_case.expected)
<< "n=" << test_case.n << ", k=" << test_case.k;
}
}
TEST(CombinatoricsTest, GetKCombinationAtIndex) {
// Test cases vetted via an equivalent calculator:
// https://planetcalc.com/8592/
struct {
int index;
int k;
std::vector<int> expected;
} kTestCases[]{
{0, 0, {}},
// clang-format off
{0, 1, {0}}, {1, 1, {1}}, {2, 1, {2}},
{3, 1, {3}}, {4, 1, {4}}, {5, 1, {5}},
{6, 1, {6}}, {7, 1, {7}}, {8, 1, {8}},
{9, 1, {9}}, {10, 1, {10}}, {11, 1, {11}},
{12, 1, {12}}, {13, 1, {13}}, {14, 1, {14}},
{15, 1, {15}}, {16, 1, {16}}, {17, 1, {17}},
{18, 1, {18}}, {19, 1, {19}},
{0, 2, {1, 0}}, {1, 2, {2, 0}}, {2, 2, {2, 1}},
{3, 2, {3, 0}}, {4, 2, {3, 1}}, {5, 2, {3, 2}},
{6, 2, {4, 0}}, {7, 2, {4, 1}}, {8, 2, {4, 2}},
{9, 2, {4, 3}}, {10, 2, {5, 0}}, {11, 2, {5, 1}},
{12, 2, {5, 2}}, {13, 2, {5, 3}}, {14, 2, {5, 4}},
{15, 2, {6, 0}}, {16, 2, {6, 1}}, {17, 2, {6, 2}},
{18, 2, {6, 3}}, {19, 2, {6, 4}},
{0, 3, {2, 1, 0}}, {1, 3, {3, 1, 0}}, {2, 3, {3, 2, 0}},
{3, 3, {3, 2, 1}}, {4, 3, {4, 1, 0}}, {5, 3, {4, 2, 0}},
{6, 3, {4, 2, 1}}, {7, 3, {4, 3, 0}}, {8, 3, {4, 3, 1}},
{9, 3, {4, 3, 2}}, {10, 3, {5, 1, 0}}, {11, 3, {5, 2, 0}},
{12, 3, {5, 2, 1}}, {13, 3, {5, 3, 0}}, {14, 3, {5, 3, 1}},
{15, 3, {5, 3, 2}}, {16, 3, {5, 4, 0}}, {17, 3, {5, 4, 1}},
{18, 3, {5, 4, 2}}, {19, 3, {5, 4, 3}},
// clang-format on
{2924, 3, {26, 25, 24}},
};
for (const auto& test_case : kTestCases) {
EXPECT_EQ(GetKCombinationAtIndex(test_case.index, test_case.k),
test_case.expected)
<< "index=" << test_case.index << ", k=" << test_case.k;
}
}
// Simple stress test to make sure that GetKCombinationAtIndex is returning
// combinations uniquely indexed by the given index, i.e. there are never any
// repeats.
TEST(CombinatoricsTest, GetKCombination_NoRepeats) {
for (int k = 1; k < 5; k++) {
std::set<std::vector<int>> seen_combinations;
for (int index = 0; index < 3000; index++) {
EXPECT_TRUE(
seen_combinations.insert(GetKCombinationAtIndex(index, k)).second)
<< "index=" << index << ", k=" << k;
}
}
}
// The k-combination at a given index is the unique set of k positive integers
// a_k > a_{k-1} > ... > a_2 > a_1 >= 0 such that
// `index` = \sum_{i=1}^k {a_i}\choose{i}
TEST(CombinatoricsTest, GetKCombination_MatchesDefinition) {
for (int k = 1; k < 5; k++) {
for (int index = 0; index < 3000; index++) {
std::vector<int> combination = GetKCombinationAtIndex(index, k);
int sum = 0;
for (int i = 0; i < k; i++) {
sum += BinomialCoefficient(combination[i], k - i);
}
EXPECT_EQ(index, sum);
}
}
}
TEST(CombinatoricsTest, GetNumberOfStarsAndBarsSequences) {
EXPECT_EQ(3,
GetNumberOfStarsAndBarsSequences(/*num_stars=*/1, /*num_bars=*/2));
EXPECT_EQ(2925,
GetNumberOfStarsAndBarsSequences(/*num_stars=*/3, /*num_bars=*/24));
}
TEST(CombinatoricsTest, GetStarIndices) {
const struct {
int num_stars;
int num_bars;
int sequence_index;
std::vector<int> expected;
} kTestCases[] = {
{1, 2, 2, {2}},
{3, 24, 23, {6, 3, 0}},
};
for (const auto& test_case : kTestCases) {
EXPECT_EQ(GetStarIndices(test_case.num_stars, test_case.num_bars,
test_case.sequence_index),
test_case.expected);
}
}
TEST(CombinatoricsTest, GetBarsPrecedingEachStar) {
const struct {
std::vector<int> star_indices;
std::vector<int> expected;
} kTestCases[] = {
{{2}, {2}},
{{6, 3, 0}, {4, 2, 0}},
};
for (const auto& test_case : kTestCases) {
EXPECT_EQ(GetBarsPrecedingEachStar(test_case.star_indices),
test_case.expected);
}
}
} // namespace content