-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add immutable ordered finite 'map' and 'set' to the stdlib (#561)
I've been sitting on this code for almost ~~9~~ _13_ months now (started on Xmas 2023), it's about time we finalise it. This PR adds immutable ordered maps and sets based on weight balanced trees into the standard library. The `map` and `set` now work on all main backends: JS, LLVM, Chez. Progress ~~is **blocked**~~ was blocked on: 1. ~~very annoying inliner bug(s?) [#733]~~ _resolved_ 🥳 2. ~~missing comparisons on strings on LLVM [#748]~~ _resolved_ 🎉
- Loading branch information
Showing
19 changed files
with
1,640 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Get key1: value1 | ||
Get key2: value2 | ||
Get key1: newValue1 | ||
Get key2: value2 | ||
After cleaning: | ||
Get key1: newValue1 | ||
Get key2: Not found |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import map | ||
import bytearray | ||
|
||
record CacheEntry[V](value: V, timestamp: Int) | ||
|
||
effect time(): Int | ||
|
||
def main() = { | ||
var currentTime = 0 | ||
try { | ||
var cache: Map[String, CacheEntry[String]] = map::empty(compareStringBytes) | ||
val maxAge = 8 | ||
|
||
def cachePut(key: String, value: String): Unit = | ||
cache = cache.put(key, CacheEntry(value, do time())) | ||
|
||
def cacheGet(key: String): Option[String] = | ||
cache.get(key) match { | ||
case Some(entry) and do time() - entry.timestamp < maxAge => Some(entry.value) | ||
case _ => None() | ||
} | ||
|
||
def cleanExpiredEntries(): Unit = { | ||
val currentTime = do time() | ||
cache = cache.filter { (_, entry) => | ||
currentTime - entry.timestamp < maxAge | ||
} | ||
} | ||
|
||
cachePut("key1", "value1") | ||
cachePut("key2", "value2") | ||
|
||
println("Get key1: " ++ cacheGet("key1").getOrElse { "Not found" }) | ||
println("Get key2: " ++ cacheGet("key2").getOrElse { "Not found" }) | ||
|
||
cachePut("key1", "newValue1") | ||
|
||
println("Get key1: " ++ cacheGet("key1").getOrElse { "Not found" }) | ||
println("Get key2: " ++ cacheGet("key2").getOrElse { "Not found" }) | ||
|
||
cleanExpiredEntries() | ||
|
||
println("After cleaning:") | ||
println("Get key1: " ++ cacheGet("key1").getOrElse { "Not found" }) | ||
println("Get key2: " ++ cacheGet("key2").getOrElse { "Not found" }) | ||
} with time { | ||
currentTime = currentTime + 1 | ||
resume(currentTime) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
ask: 3 | ||
can: 3 | ||
you: 3 | ||
see: 0 | ||
do: 4 | ||
Effekt: 2 | ||
[Effekt → 2, and → 1, ask → 3, but → 1, can → 3, do → 4, fellow → 2, for → 4, language → 2, man → 1, my → 2, not → 2, of → 2, programmers → 2, programs → 1, so → 1, the → 2, together → 1, we → 1, what → 4, will → 1, world → 1, you → 3, your → 2] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import map | ||
import bytearray | ||
|
||
def counter(words: List[String]): Map[String, Int] = { | ||
var m: Map[String, Int] = map::empty(compareStringBytes); | ||
|
||
list::foreach(words) { word => | ||
m = m.putWithKey(word, 1) { (_, n1, n2) => n1 + n2 } | ||
} | ||
|
||
m | ||
} | ||
|
||
def main() = { | ||
// John F. Kennedy's Inaugural Address, Jan 20, 1961; modified for Effekt | ||
val speech: List[String] = [ | ||
"and", "so", "my", "fellow", "Effekt", "programmers", | ||
"ask", "not", "what", "your", "language", "can", "do", "for", "you", | ||
"ask", "what", "you", "can", "do", "for", "your", "language", | ||
"my", "fellow", "programmers", "of", "the", "world", | ||
"ask", "not", "what", "Effekt", "will", "do", "for", "you", | ||
"but", "what", "together", "we", "can", "do", "for", "the", "programs", "of", "man" | ||
] | ||
|
||
val ctr: Map[String, Int] = counter(speech) | ||
|
||
def test(word: String) = { | ||
val count = ctr.getOrElse(word) { 0 } | ||
println(word ++ ": " ++ count.show) | ||
} | ||
|
||
test("ask") | ||
test("can") | ||
test("you") | ||
test("see") | ||
test("do") | ||
test("Effekt") | ||
|
||
println(map::internal::prettyPairs(ctr.toList) { s => show(s) } { n => show(n) }) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[0 → Hello, 1 → World, 2 → Woo!] | ||
[-1 → Hullo, 0 → Hello, 1 → World, 2 → Woo!] | ||
[-10 → EY, -1 → Hullo, 0 → Hello, 1 → World, 2 → Woo!] | ||
[0 → Hello, 2 → Woo!, 42 → Whole new world!, 100 → Big, 1000 → Bigger, 10000 → Biggest!] | ||
[-1 → Huh?!, 0 → Hello, 1 → Foo, 2 → Woo!, 42 → Whole new world!, 100 → Big, 1000 → Bigger, 10000 → Biggest!] | ||
[-1 → Huh?!, 0 → Hello, 1 → Foo, 2 → Woo!, 42 → Whole new world!, 100 → Big, 1000 → Bigger, 10000 → Biggest!] | ||
[0 → Hello, 1 → World, 2 → Woo!] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import map | ||
|
||
def main() = { | ||
val l = [(0, "Hello"), (1, "World"), (2, "Woo!")] | ||
|
||
val m = map::fromList(l, compareInt) | ||
println(map::internal::prettyPairs(m.toList) { n => show(n) } { s => show(s) }) | ||
|
||
val m2 = m.put(-1, "Hullo") | ||
println(map::internal::prettyPairs(m2.toList) { n => show(n) } { s => show(s) }) | ||
|
||
val m3 = m2.put(-10, "EY") | ||
println(map::internal::prettyPairs(m3.toList) { n => show(n) } { s => show(s) }) | ||
|
||
// ... | ||
|
||
val m4 = m.delete(1).put(42, "Whole new world!").put(100, "Big").put(1000, "Bigger").put(10000, "Biggest!") | ||
println(map::internal::prettyPairs(m4.toList) { n => show(n) } { s => show(s) }) | ||
|
||
val m5 = map::fromList(Cons((1, "Foo"), Cons((-1, "Huh?!"), m4.toList.reverse)), compareInt) | ||
println(map::internal::prettyPairs(m5.toList) { n => show(n) } { s => show(s) }) | ||
|
||
val m6: Map[Int, String] = m5.toList.fromList(compareInt) | ||
println(map::internal::prettyPairs(m6.toList) { n => show(n) } { s => show(s) }) | ||
|
||
val nuMap = map::fromList(l.reverse, compareInt) | ||
println(map::internal::prettyPairs(nuMap.toList) { n => show(n) } { s => show(s) }) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Min: 5 -> Five | ||
Max: 30 -> Thirty |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import map | ||
|
||
def show(opt: Option[(Int, String)]): String = opt match { | ||
case Some((k, v)) => k.show ++ " -> " ++ v | ||
case None() => "X" | ||
} | ||
|
||
def main() = { | ||
val m = map::fromList([(10, "Ten"), (20, "Twenty"), (5, "Five"), (30, "Thirty")], compareInt) | ||
|
||
val min: Option[(Int, String)] = m.getMin | ||
val max: Option[(Int, String)] = m.getMax | ||
|
||
println("Min: " ++ min.show) | ||
println("Max: " ++ max.show) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
Union (keeping first value): | ||
[1 → apple, 2 → banana, 3 → cherry, 4 → elderberry] | ||
|
||
Union (combining values): | ||
[1 → apple, 2 → banana/berry, 3 → cherry/date, 4 → elderberry] | ||
|
||
Intersection (combining keys): | ||
[2 → banana-berry, 3 → cherry-date] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import map | ||
|
||
def main() = { | ||
// Create multiple maps | ||
val map1 = map::fromList([(1, "apple"), (2, "banana"), (3, "cherry")], compareInt) | ||
val map2 = map::fromList([(2, "berry"), (3, "date"), (4, "elderberry")], compareInt) | ||
|
||
// Test union with different combine strategies | ||
println("Union (keeping first value):") | ||
println(map::internal::prettyPairs(map1.union(map2).toList) { n => show(n) } { s => show(s) }) | ||
|
||
println("\nUnion (combining values):") | ||
val combinedMap = map1.union(map2, compareInt) { (k, v1, v2) => v1 ++ "/" ++ v2 } | ||
println(map::internal::prettyPairs(combinedMap.toList) { n => show(n) } { s => show(s) }) | ||
|
||
// Test intersection | ||
println("\nIntersection (combining keys):") | ||
val intersectedMap = map1.intersection(map2, compareInt) { (k, v1, v2) => v1 ++ "-" ++ v2 } | ||
println(map::internal::prettyPairs(intersectedMap.toList) { n => show(n) } { s => show(s) }) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
ask: true | ||
can: true | ||
you: true | ||
see: false | ||
do: true | ||
Effekt: true | ||
|
||
Cons(Effekt, Cons(and, Cons(ask, Cons(but, Cons(can, Cons(do, Cons(fellow, Cons(for, Cons(language, Cons(man, Cons(my, Cons(not, Cons(of, Cons(programmers, Cons(programs, Cons(so, Cons(the, Cons(together, Cons(we, Cons(what, Cons(will, Cons(world, Cons(you, Cons(your, Nil())))))))))))))))))))))))) | ||
sorted | ||
Cons(Effekt, Cons(and, Cons(ask, Cons(but, Cons(can, Cons(do, Cons(fellow, Cons(for, Cons(language, Cons(man, Cons(my, Cons(not, Cons(of, Cons(programmers, Cons(programs, Cons(so, Cons(the, Cons(together, Cons(we, Cons(what, Cons(will, Cons(world, Cons(you, Cons(your, Nil())))))))))))))))))))))))) | ||
sorted | ||
|
||
Cons(after, Cons(around, Cons(better, Cons(do, Cons(ever, Cons(faster, Cons(harder, Cons(hour, Cons(is, Cons(it, Cons(make, Cons(makes, Cons(more, Cons(never, Cons(over, Cons(stronger, Cons(than, Cons(the, Cons(us, Cons(work, Cons(world, Nil()))))))))))))))))))))) | ||
sorted | ||
|
||
lyrics / speech: | ||
Cons(after, Cons(around, Cons(better, Cons(ever, Cons(faster, Cons(harder, Cons(hour, Cons(is, Cons(it, Cons(make, Cons(makes, Cons(more, Cons(never, Cons(over, Cons(stronger, Cons(than, Cons(us, Cons(work, Nil())))))))))))))))))) | ||
speech / lyrics: | ||
Cons(Effekt, Cons(and, Cons(ask, Cons(but, Cons(can, Cons(fellow, Cons(for, Cons(language, Cons(man, Cons(my, Cons(not, Cons(of, Cons(programmers, Cons(programs, Cons(so, Cons(together, Cons(we, Cons(what, Cons(will, Cons(you, Cons(your, Nil()))))))))))))))))))))) | ||
speech n lyrics: | ||
Cons(do, Cons(the, Cons(world, Nil()))) | ||
speech u lyrics: | ||
Cons(Effekt, Cons(after, Cons(and, Cons(around, Cons(ask, Cons(better, Cons(but, Cons(can, Cons(do, Cons(ever, Cons(faster, Cons(fellow, Cons(for, Cons(harder, Cons(hour, Cons(is, Cons(it, Cons(language, Cons(make, Cons(makes, Cons(man, Cons(more, Cons(my, Cons(never, Cons(not, Cons(of, Cons(over, Cons(programmers, Cons(programs, Cons(so, Cons(stronger, Cons(than, Cons(the, Cons(together, Cons(us, Cons(we, Cons(what, Cons(will, Cons(work, Cons(world, Cons(you, Cons(your, Nil())))))))))))))))))))))))))))))))))))))))))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import set | ||
import bytearray | ||
|
||
def unique(words: List[String]): Set[String] = { | ||
var s: Set[String] = set::empty(compareStringBytes); | ||
|
||
list::foreach(words) { word => | ||
s = s.insert(word) | ||
} | ||
|
||
s | ||
} | ||
|
||
def main() = { | ||
// John F. Kennedy's Inaugural Address Jan 20 1961; modified for Effekt | ||
val speech: List[String] = [ | ||
"and", "so", "my", "fellow", "Effekt", "programmers", | ||
"ask", "not", "what", "your", "language", "can", "do", "for", "you", | ||
"ask", "what", "you", "can", "do", "for", "your", "language", | ||
"my", "fellow", "programmers", "of", "the", "world", | ||
"ask", "not", "what", "Effekt", "will", "do", "for", "you", | ||
"but", "what", "together", "we", "can", "do", "for", "the", "programs", "of", "man" | ||
] | ||
|
||
val uniqueSpeech: Set[String] = unique(speech) | ||
|
||
def test(word: String) = { | ||
val present = uniqueSpeech.contains(word) | ||
println(word ++ ": " ++ present.show) | ||
} | ||
|
||
test("ask") | ||
test("can") | ||
test("you") | ||
test("see") | ||
test("do") | ||
test("Effekt") | ||
|
||
// --- | ||
println("") | ||
|
||
def testSorted(s: Set[String]) = { | ||
val sorted = s.toList.isSortedBy { (x, y) => | ||
x.compareStringBytes(y) match { | ||
case Equal() => true | ||
case Less() => true | ||
case Greater() => false | ||
} | ||
} | ||
if (sorted) { | ||
println("sorted") | ||
} else { | ||
println("unsorted") | ||
} | ||
} | ||
|
||
println(uniqueSpeech.toList) | ||
testSorted(uniqueSpeech) | ||
|
||
println(set::fromList(speech, compareStringBytes).toList) | ||
testSorted(set::fromList(speech, compareStringBytes)) | ||
|
||
// --- | ||
println("") | ||
|
||
// Around the World / Harder, Better, Faster, Stronger by Daft Punk (Alive 2007) | ||
val lyrics: List[String] = [ | ||
"around", "the", "world", "around", "the", "world", | ||
"around", "the", "world", "around", "the", "world", | ||
"around", "the", "world", "around", "the", "world", | ||
"around", "the", "world", "around", "the", "world", | ||
"around", "the", "world", "around", "the", "world", | ||
"around", "the", "world", "around", "the", "world", | ||
"around", "the", "world", "around", "the", "world", | ||
"around", "the", "world", "around", "the", "world", | ||
|
||
"work", "it", "make", "it", "do", "it", "makes", "us", | ||
"harder", "better", "faster", "stronger", | ||
"more", "than", "hour", "hour", "never", | ||
"ever", "after", "work", "is", "over", | ||
|
||
"work", "it", "make", "it", "do", "it", "makes", "us", | ||
"around", "the", "world", "around", "the", "world", | ||
"around", "the", "world", "around", "the", "world", | ||
|
||
"harder", "better", "faster", "stronger", | ||
"around", "the", "world", "around", "the", "world", | ||
"around", "the", "world", "around", "the", "world" | ||
] | ||
|
||
val uniqueLyrics = unique(lyrics) | ||
|
||
println(uniqueLyrics.toList) | ||
testSorted(uniqueLyrics) | ||
|
||
// --- | ||
println("") | ||
|
||
println("lyrics / speech:") | ||
println(uniqueLyrics.difference(uniqueSpeech).toList) | ||
|
||
println("speech / lyrics:") | ||
println(uniqueSpeech.difference(uniqueLyrics).toList) | ||
|
||
println("speech n lyrics:") | ||
println(uniqueLyrics.intersection(uniqueSpeech).toList) | ||
|
||
println("speech u lyrics:") | ||
println(uniqueLyrics.union(uniqueSpeech).toList) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
1: H (72) | ||
2: e (101) | ||
3: l (108) | ||
4: l (108) | ||
5: o (111) | ||
[1 → H, 2 → e, 3 → l, 4 → l, 5 → o] | ||
Hello |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import stream | ||
import map | ||
|
||
def main() = { | ||
val m = map::fromList([(1, 'H'), (2, 'e'), (3, 'l'), (4, 'l'), (5, 'o')], compareInt) | ||
|
||
for[(Int, Char)] { each(m) } { case (k, v) => | ||
println(show(k) ++ ": " ++ show(v) ++ " (" ++ show(v.toInt) ++ ")") | ||
} | ||
|
||
val newMap = collectMap[Int, Char](compareInt) { each(m) } | ||
println(map::internal::prettyPairs(newMap.toList) { n => show(n) } { c => show(c) }) | ||
|
||
val hello: String = collectString { eachValue(m) } | ||
println(hello) | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.