Skip to content

Commit

Permalink
Additional compact() tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-kulcsar committed Oct 19, 2024
1 parent a5451e5 commit 1850aef
Show file tree
Hide file tree
Showing 6 changed files with 357 additions and 14 deletions.
12 changes: 0 additions & 12 deletions approx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,6 @@ namespace Approx {
return allPassed
}

function areEquivalent(a: string[], b: string[]): boolean {
if (a.length != b.length) {
return false
}
for (let s of a) {
if (b.indexOf(s) == -1) {
return false
}
}
return true
}

function arrangements(): boolean {
let test: TernaryStringSet
let allPassed: boolean = true
Expand Down
73 changes: 73 additions & 0 deletions balance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
namespace Balance {
export function run(): boolean {
let test: TernaryStringSet
let allPassed: boolean = true

// balance() improves bad tree structure
test = new TernaryStringSet()
for (let s of ShortEnglishList.words) {
test.add(s)
}
const badDepth: number = test.stats.depth
test.balance()
const goodDepth: number = test.stats.depth

if (!areEquivalent(ShortEnglishList.words, test.toArray())) {
game.splash("Balance test 1a failed.")
allPassed = false
}
if (goodDepth >= badDepth) {
game.splash(`Balance test 1b failed. Bad depth: ${badDepth}, good depth: ${goodDepth}.`)
allPassed = false
}
if (test.has("")) {
game.splash("Balance test 1c failed.")
allPassed = false
}

// balance() preserves contents
test = new TernaryStringSet()
const words: string[] = [
"",
"a",
"ape",
"aphid",
"aphids",
"bee",
"bees",
"cat",
"cats",
]

// Add words in the worst possible order.
for (let s of words) {
test.add(s)
}
test.balance()
if (!areEquivalent(words, test.toArray())) {
game.splash("Balance test 2 failed.")
allPassed = false
}

// balance() of empty tree is safe.
test = new TernaryStringSet()
try {
test.balance()
if (test.size != 0) {
game.splash("Balance test 3b failed.")
allPassed = false
}
test.add("")
test.balance()
if (test.size != 1) {
game.splash("Balance test 3c failed.")
allPassed = false
}
} catch {
game.splash("Balance test 3a failed.")
allPassed = false
}

return allPassed
}
}
11 changes: 11 additions & 0 deletions common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
function areEquivalent(a: string[], b: string[]): boolean {
if (a.length != b.length) {
return false
}
for (let s of a) {
if (b.indexOf(s) == -1) {
return false
}
}
return true
}
260 changes: 260 additions & 0 deletions compact.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
namespace Compact {
export function run(): boolean {
let test: TernaryStringSet
let allPassed: boolean = true

const compactWords: TernaryStringSet = new TernaryStringSet(ShortEnglishList.words)
compactWords.compact()

// Getter matches state.
test = new TernaryStringSet()
if (test.compacted) {
game.splash("Compact test 1a failed.")
allPassed = false
}
test.addAll(["add", "bad", "mad",])
test.compact()
if (!test.compacted) {
game.splash("Compact test 1b failed.")
allPassed = false
}

// Empty set
test = new TernaryStringSet()
test.compact()
if (test.stats.nodes != 0) {
game.splash("Compact test 2a failed.")
allPassed = false
}
test.add("")
test.compact()
if (test.stats.nodes != 0) {
game.splash("Compact test 2b failed.")
allPassed = false
}
if (!test.has("")) {
game.splash("Compact test 2c failed.")
allPassed = false
}

// Trivial compaction of tree with no duplicates.
// For compaction to have an effect,
// there must be strings with common suffixes.
test = new TernaryStringSet()
test.clear()
test.add("A")
test.compact()
if (test.stats.nodes != 1) {
game.splash("Compact test 3a failed.")
allPassed = false
}
test.add("B")
test.compact()
if (test.stats.nodes != 2) {
game.splash("Compact test 3b failed.")
allPassed = false
}
if (!test.has("A")) {
game.splash("Compact test 3c failed.")
allPassed = false
}
if (!test.has("B")) {
game.splash("Compact test 3d failed.")
allPassed = false
}

/**
* Basic suffix sharing.
* Here, we add strings with a common suffix, "ing,"
* but different prefixes. In the original tree,
* each string will have its own subtree for its "ing,"
* but in the compacted tree, they will share one subtree.
*/
test = new TernaryStringSet()
test.clear()
test.addAll(["abcing", "defing", "ghiing", "jkling", "mnoing",])
const preCompactSize: number = test.stats.nodes
test.compact()
if (3 * (test.size - 1) != preCompactSize - test.stats.nodes) {
game.splash("Compact test 4 failed.")
allPassed = false
}

/**
* Non-trivial suffix sharing.
* A more complex example than the basic test.
* In this case, each string has a unique prefix, "a" -- "d."
* All have a common suffix, "ing,"
* but two share the suffix "_aing"
* and two share the suffix "_bing."
* Before compactio0n, there is a node for each letter
* of each string (6 chars * 4 strings) since there are
* no shared prefixes. After compaction, we expect a node each
* for the prefixes, three nodes for the shared "ing,"
* two nodes for the shared "_a," and two for the "_b"
* (4 + 3 + 2 + 2)
*/
test = new TernaryStringSet()
const words: string[] = ["a_aing", "b_aing", "c_bing", "d_bing",]
test.addAll(words)
if (test.stats.nodes != 24) {
game.splash("Compact test 5a failed.")
allPassed = false
}
test.compact()
if (test.stats.nodes != 11) {
game.splash("Compact test 5b failed.")
allPassed = false
}

/**
* If we add "e_ai," this will add 4 nodes since the prefix "e"
* is unique and "ai" was previously infix, not a suffix!
*/
test.add("e_ai")
test.compact()
if (test.stats.nodes != 15) {
game.splash("Compact test 5c failed.")
allPassed = false
}

/**
* Whereas if we add "fng," only one more node is needed
* (for the "f"), since the suffix "ng" is part of the
* existing "ing" subtree.
*/
test.add("fng")
test.compact()
if (test.stats.nodes != 16) {
game.splash("Compact test 5d failed.")
allPassed = false
}

// Dictionary compaction.
test = compactWords
if (test.size != ShortEnglishList.words.length) {
game.splash("Compact test 6a failed.")
allPassed = false
}
/**
* Non-trivial compacted set should have fewer nodes.
* In fact, for our word list, it is leff than half as many nodes!
*/
let wordSet: TernaryStringSet = new TernaryStringSet(ShortEnglishList.words)
// game.splash(`Loose nodes: ${wordSet.stats.nodes}, compact nodes: ${test.stats.nodes}`)
if (test.stats.nodes >= wordSet.stats.nodes) {
game.splash("Compact test 6b failed.")
allPassed = false
}
// But it should contain all of the same words.
for (let s of ShortEnglishList.words) {
if (!test.has(s)) {
game.splash(`Compact test 6c failed word ${s}.`)
allPassed = false
}
}

/**
* Automatic decompaction on mutation.
* Attempting to mutate a compact set must leave it uncompacted.
*/
const compactOriginal: TernaryStringSet = new TernaryStringSet()
compactOriginal.addAll([
"alligator",
"bass",
"crane",
"dove",
"eagle",
"flea",
"porcupine",
])
compactOriginal.compact()
if (!compactOriginal.compacted) {
game.splash("Compact test 7a failed.")
allPassed = false
}

// Copying a compact set yields a compact copy.
test = new TernaryStringSet(compactOriginal)
if (!test.compacted) {
game.splash("Compact test 7b failed.")
allPassed = false
}

// Adding a string already in the set has no effect.
test.add("alligator")
if (!test.compacted) {
game.splash("Compact test 7c failed.")
allPassed = false
}

// Adding a string not in the set undoes compaction.
test.add("dragonfly")
if (test.compacted) {
game.splash("Compact test 7d failed.")
allPassed = false
}

// As does adding via addAll().
test = new TernaryStringSet(compactOriginal)
test.addAll(["dragonfly",])
if (test.compacted) {
game.splash("Compact test 7e failed.")
allPassed = false
}

/**
* Likewise, deleting a string not in the set has no effect,
* while actually deleting a string undoes compaction.
*/
test = new TernaryStringSet(compactOriginal)
test.delete("zedonk")
if (!test.compacted) {
game.splash("Compact test 7f failed.")
allPassed = false
}
test.delete("alligator")
if (test.compacted) {
game.splash("Compact test 7g failed.")
allPassed = false
}

// balance() undoes compaction.
test = new TernaryStringSet(compactOriginal)
test.balance()
if (test.compacted) {
game.splash("Compact test 7h failed.")
allPassed = false
}

// clear() trivially resets the compaction state.
test = new TernaryStringSet(compactOriginal)
test.clear()
if (test.compacted) {
game.splash("Compact test 7i failed.")
allPassed = false
}

// Non-mutating methods do not decompact.
const rhs: TernaryStringSet = new TernaryStringSet(["list", "wrestle",])
test = compactWords
// test.equals(rhs)
test.forEach((s) => s)
test.getArrangementsOf("coat")
test.getCompletionsOf("win")
test.getPartialMatchesOf("cu.")
test.getWithinHammingDistanceOf("cat", 1)
test.has("cat")
// test.isSubsetOf(rhs)
// test.isSupersetOf(rhs)
// test.keys
test.size
// test.values
if (!test.compacted) {
game.splash("Compact test 8 failed.")
allPassed = false
}

return allPassed
}
}
8 changes: 8 additions & 0 deletions main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ if (!Approx.run()) {
allPassed = false
}

if (!Balance.run()) {
allPassed = false
}

if (!Compact.run()) {
allPassed = false
}

// Show summary.
if (allPassed) {
game.splash("All tests passed!")
Expand Down
Loading

0 comments on commit 1850aef

Please sign in to comment.