From 76080ef85ed831292901c14f528104d6430e4824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Fri, 30 Aug 2024 10:49:29 +0200 Subject: [PATCH 01/29] Initial draft of (tree) maps & sets --- libraries/common/map.effekt | 718 ++++++++++++++++++++++++++++++++++++ libraries/common/set.effekt | 208 +++++++++++ 2 files changed, 926 insertions(+) create mode 100644 libraries/common/map.effekt create mode 100644 libraries/common/set.effekt diff --git a/libraries/common/map.effekt b/libraries/common/map.effekt new file mode 100644 index 000000000..f54adbd44 --- /dev/null +++ b/libraries/common/map.effekt @@ -0,0 +1,718 @@ +module map + +// Ordered finite immutable map, backed by balanced binary trees of logarithmic depth. +// Currently use `genericCompare` as a comparison primitive, which means the map is now working only in the JS backend. +// +// Please don't use the internal constructors `Bin` & `Tip` directly, +// they might change down the line and are not considered stable. +type Map[K, V] { + Bin(size: Int, k: K, v: V, left: Map[K, V], right: Map[K, V]); + Tip() +} + +// Create a new empty map. +// +// O(1) +def empty[K, V](): Map[K, V] = { + Tip() +} + +// Check if map `m` is empty. +// +// O(1) +def isEmpty[K, V](m: Map[K, V]): Bool = { + m match { + case Tip() => true + case _ => false + } +} + +// Check if map `m` is nonempty. +// +// O(1) +def nonEmpty[K, V](m: Map[K, V]): Bool = { + m match { + case Tip() => false + case _ => true + } +} + +// Create a new map containing only the mapping from `k` to `v`. +// +// O(1) +def singleton[K, V](k: K, v: V): Map[K, V] = { + Bin(1, k, v, Tip(), Tip()) +} + +// Get the size of the map (the number of keys/values). +// +// O(1) +def size[K, V](m: Map[K, V]): Int = { + m match { + case Tip() => 0 + case Bin(size, _, _, _, _) => size + } +} + +// Insert a new key `k` and value `v` into the map `m`. +// If the key `k` is already present in `m`, its associated value is replaced with `v`. +// +// O(log N) +def put[K, V](m: Map[K, V], k: K, v: V): Map[K, V] = m match { + case Tip() => singleton(k, v) + case Bin(size, k2, v2, l, r) => + genericCompare(k, k2) match { + case Less() => balance(k2, v2, put(l, k, v), r) + case Greater() => balance(k2, v2, l, put(r, k, v)) + case Equal() => Bin(size, k, v, l, r) + } +} + +// Insert a new key `k` and value `v` into the map `m`. +// If the key `k` is already present in `m` with value `v2`, the function `combine` is called on `k`, `v`, `v2`. +// +// O(log N) +def putWithKey[K, V](m: Map[K, V], k: K, v: V) { combine: (K, V, V) => V } : Map[K, V] = m match { + case Tip() => singleton(k, v) + case Bin(size, k2, v2, l, r) => + genericCompare(k, k2) match { + case Less() => balance(k2, v2, l.putWithKey(k, v){combine}, r) + case Greater() => balance(k2, v2, l, r.putWithKey(k, v){combine}) + case Equal() => Bin(size, k, combine(k, v, v2), l, r) + } +} + +// Lookup the value at a key `k` in the map `m`. +// +// O(log N) +def get[K, V](m: Map[K, V], k: K): Option[V] = { + m match { + case Tip() => None() + case Bin(size, k2, v, l, r) => + genericCompare(k, k2) match { + case Less() => get(l, k) + case Greater() => get(r, k) + case Equal() => Some(v) + } + } +} + +// Lookup the value at a key `k` in the map `m`. +// If there is no key, use the `default` block to retrieve a default value. +// +// O(log N) +def getOrElse[K, V](m: Map[K, V], k: K) { default : => V }: V = { + get(m, k) match { + case None() => default() + case Some(v) => v + } +} + +// Check if map `m` contains a key `k`. +// +// O(log N) +def contains[K, V](m: Map[K, V], k: K): Bool = { + get(m, k) match { + case None() => false + case Some(v) => true + } +} + +// Get minimum in the map `m`. +// +// O(log N) +def getMin[K, V](m: Map[K, V]): Option[(K, V)] = { + def go(k: K, v: V, m: Map[K, V]): (K, V) = { + m match { + case Tip() => (k, v) + case Bin(_, k2, v2, l, _) => go(k2, v2, l) + } + } + + m match { + case Tip() => None() + case Bin(_, k, v, l, _) => Some(go(k, v, l)) + } +} + +// Get maximum in the map `m`. +// +// O(log N) +def getMax[K, V](m: Map[K, V]): Option[(K, V)] = { + def go(k: K, v: V, m: Map[K, V]): (K, V) = { + m match { + case Tip() => (k, v) + case Bin(_, k2, v2, _, r) => go(k2, v2, r) + } + } + + m match { + case Tip() => None() + case Bin(_, k, v, _, r) => Some(go(k, v, r)) + } +} + +// Map a function `f` over values in map `m`. +// +// O(N) +def map[K, V1, V2](m: Map[K, V1]) { f : (K, V1) => V2 }: Map[K, V2] = { + m match { + case Tip() => Tip() + case Bin(size, k, v, l, r) => + Bin(size, k, f(k, v), l.map {f}, r.map {f}) + } +} + +// Map a function `f` over values in map `m`. +// +// O(N) +def map[K, V1, V2](m: Map[K, V1]) { f : V1 => V2 }: Map[K, V2] = { + m.map { (_k, v) => f(v) } +} + +// Traverse all keys and their associated values in map `m` in order, +// running the function `action` on a key and its associated value. +// +// Law: `m.foreach { action } === m.toList.foreach { action }` +// +// O(N) +// +// TODO: Support {Control} for early exits. +def foreach[K, V](m: Map[K, V]) { action: (K, V) => Unit }: Unit = { + def go(m: Map[K, V]): Unit = { + m match { + case Tip() => () + case Bin(_, k, v, l, r) => + go(l) + action(k, v) + go(r) + } + } + go(m) +} + +// Convert a map `m` into a list of (key, value) pairs. +// +// O(N) +def toList[K, V](m: Map[K, V]): List[(K, V)] = { + var acc = Nil() + m.foreach { (k, v) => + acc = Cons((k, v), acc) + } + acc.reverse +} + +// Get a list of keys of the map `m`. +// +// O(N) +def keys[K, V](m: Map[K, V]): List[K] = { + var acc = Nil() + m.foreach { (k, _v) => + acc = Cons(k, acc) + } + acc.reverse +} + +// Get a list of values of the map `m`. +// +// O(N) +def values[K, V](m: Map[K, V]): List[V] = { + var acc = Nil() + m.foreach { (_k, v) => + acc = Cons(v, acc) + } + acc.reverse +} + +// Create a map from a list of (key, value) pairs. +// If the list contains more than one value for the same key, +// only the last value is used in the map. +// +// O(N) if the list is sorted by key, +// O(N log N) otherwise +def fromList[K, V](pairs: List[(K, V)]): Map[K, V] = { + pairs match { + case Nil() => Tip() + case Cons((k, v), Nil()) => singleton(k, v) + case Cons((k, v), rest) => + // TODO: this function should really, **really** get inlined! + def notOrdered(k: K, pairs: List[(K, V)]) = { + pairs match { + case Nil() => false + case Cons((k2, _), _) => // k >= k2 + genericCompare(k, k2) match { + case Less() => false + case Greater() => true + case Equal() => true + } + } + } + + // Naive insertion, used for the worst-case scenario when the list is not sorted by key + def insertMany(m: Map[K, V], pairs: List[(K, V)]) = { + var mapSoFar = m + pairs.foreach { case (k, v) => + mapSoFar = mapSoFar.put(k, v) + } + mapSoFar + } + + // Returns a triple `(map, xs, ys)` + // + // Invariant: At least one of `xs`, `ys` is empty. + // Moreover, if `ys` is nonempty, its keys are **not** ordered! + // Otherwise, all of the seen keys have been ordered so far. + // + // TODO: Possibly use a better type to encode the invariant? + def create(level: Int, pairs: List[(K, V)]): (Map[K, V], List[(K, V)], List[(K, V)]) = { + pairs match { + case Nil() => (Tip(), [], []) + case Cons((k, v), rest) => + if (level == 1) { + val singleton = Bin(1, k, v, Tip(), Tip()) + if (notOrdered(k, rest)) { + (singleton, [], rest) + } else { + (singleton, rest, []) + } + } else { + val res = create(level.bitwiseShr(1), pairs) + res match { + case (_, Nil(), _) => res + case (l, Cons((k2, v2), Nil()), zs) => (l.putMax(k2, v2), [], zs) + case (l, Cons((k2, v2), rest2), _) => + val xs = Cons((k2, v2), rest2) // @-pattern + + if (notOrdered(k2, rest2)) { (l, [], xs) } + else { + val (r, zs, ws) = create(level.bitwiseShr(1), rest2); + (link(k2, v2, l, r), zs, ws) + } + } + } + } + } + + def go(level: Int, m: Map[K, V], pairs: List[(K, V)]): Map[K, V] = { + pairs match { + case Nil() => m + case Cons((k, v), Nil()) => m.putMax(k, v) + case Cons((k, v), rest) => + if (notOrdered(k, rest)) { insertMany(m, pairs) } + else { + val l = m; // m is the left subtree here + val cr = create(level, rest) + cr match { + case (r, xs, Nil()) => go(level.bitwiseShl(1), link(k, v, l, r), xs) + case (r, Nil(), ys) => insertMany(link(k, v, l, r), ys) + case _ => panic("create: go: cannot happen, invariant broken!") + } + } + } + } + + if (notOrdered(k, rest)) { insertMany(singleton(k, v), rest) } + else { go(1, singleton(k, v), rest) } + } +} + +// Remove a key `k` from a map `m`. +// If `k` is not in `m`, `m` is returned. +// +// O(log N) +def delete[K, V](m: Map[K, V], k: K): Map[K, V] = { + m match { + case Tip() => Tip() + case Bin(_, k2, v2, l, r) => + genericCompare(k, k2) match { + case Less() => balance(k2, v2, l.delete(k), r) + case Greater() => balance(k2, v2, l, r.delete(k)) + case Equal() => glue(l, r) + } + } +} + +// Can be used to insert, delete, or update a value. +// Law: `get(m.alter(k){f}, k) === f(get(m, k))` +// +// O(log N) +def alter[K, V](m: Map[K, V], k: K) { f : Option[V] => Option[V] }: Map[K, V] = { + m match { + case Tip() => + f(None()) match { + case None() => Tip() + case Some(v) => singleton(k, v) + } + case Bin(size, k2, v2, l, r) => + genericCompare(k, k2) match { + case Less() => balance(k2, v2, l.alter(k){f}, r) + case Greater() => balance(k2, v2, l, r.alter(k){f}) + case Equal() => + f(Some(v2)) match { + case Some(v) => Bin(size, k2, v, l, r) + case None() => glue(l, r) + } + } + } +} + +// Update or delete a value associated with key `k` in map `m`. +// +// O(log N) +def update[K, V](m: Map[K, V], k: K) { f: (K, V) => Option[V] }: Map[K, V] = { + m match { + case Tip() => Tip() + case Bin(size, k2, v2, l, r) => + genericCompare(k, k2) match { + case Less() => balance(k2, v2, l.update(k){f}, r) + case Greater() => balance(k2, v2, l, r.update(k){f}) + case Equal() => + f(k2, v2) match { + case Some(v) => Bin(size, k2, v, l, r) + case None() => glue(l, r) + } + } + } +} + +// Update or delete a value associated with key `k` in map `m`. +// +// O(log N) +def update[K, V](m: Map[K, V], k: K) { f : V => Option[V] }: Map[K, V] = { + m.update(k) { (_k, v) => f(v) } +} + +// Get `n`-th (key, value) pair in the map `m`. +// +// O(log N) +def getIndex[K, V](m: Map[K, V], n: Int): Option[(K, V)] = { + m match { + case Tip() => None() + case Bin(size, k, v, l, r) => + val sizeL = l.size() + genericCompare(sizeL, n) match { + case Less() => r.getIndex(n - (sizeL + 1)) + case Greater() => l.getIndex(n) + case Equal() => Some((k, v)) + } + } +} + +// Construct a new map which contains all elements of `m1` +// except those where the key is found in `m2`. +// +// O(???) +def difference[K, V](m1: Map[K, V], m2: Map[K, V]): Map[K, V] = { + (m1, m2) match { + case (Tip(), m2) => Tip() + case (m1, Tip()) => m1 + case (m1, Bin(_, k, _, l2, r2)) => + val (l1, r1) = m1.split(k) + val leftDiff = l1.difference(l2) + val rightDiff = r1.difference(r2) + if ((leftDiff.size() + rightDiff.size()) == m1.size()) { m1 } + else { link2(leftDiff, rightDiff) } + } +} + +// Construct a new map which contains the elements of both `m1` and `m2`. +// When a key is associated with a value in both `m1` and `m2`, the new value is determined using the `combine` function. +// +// O(???) +def union[K, V](m1: Map[K, V], m2: Map[K, V]) { combine : (K, V, V) => V }: Map[K, V] = { + // Internal function similar to `putWithKey`, but right-biased. + // Only used here, recursively. + def putWithKeyR(m: Map[K, V], k: K, v: V): Map[K, V] = { + m match { + case Tip() => singleton(k, v) + case Bin(size, k2, v2, l, r) => + genericCompare(k, k2) match { + case Less() => balance(k2, v2, l.putWithKeyR(k, v), r) + case Greater() => balance(k2, v2, l, r.putWithKeyR(k, v)) + case Equal() => Bin(size, k, combine(k2, v2, v), l, r) + } + } + } + + (m1, m2) match { + case (_, Tip()) => m1 + case (_, Bin(_, k, v, Tip(), Tip())) => m1.putWithKeyR(k, v) + case (Bin(_, k, v, Tip(), Tip()), _) => m2.putWithKey(k, v){combine} + case (Tip(), _) => m2 + case (Bin(_, k1, v1, l1, r1), _) => + val (l2, optMid, r2) = m2.splitLookup(k1) + val leftUnion = union(l1, l2){combine} + val rightUnion = union(r1, r2){combine} + optMid match { + case None() => link(k1, v1, leftUnion, rightUnion) + case Some(v2) => link(k1, combine(k1, v1, v2), leftUnion, rightUnion) + } + } +} + +// Construct a new map which contains the elements of both `m1` and `m2`. +// When a key is associated with a value in both `m1` and `m2`, the new value is determined using the `combine` function. +// +// O(???) +def union[K, V](m1: Map[K, V], m2: Map[K, V]) { combine : (V, V) => V }: Map[K, V] = { + union(m1, m2) { (k, v1, v2) => combine(v1, v2) } +} + +// Construct a new map which contains the elements of both `m1` and `m2`. +// Left-biased: Uses values from `m1` if there are duplicate keys. +// +// O(???) +def union[K, V](m1: Map[K, V], m2: Map[K, V]): Map[K, V] = { + union(m1, m2) { (k, v1, v2) => v1 } +} + +// ------------- +// Internal + +val ratio = 2 +val delta = 3 + +def bin[K, V](k: K, v: V, l: Map[K, V], r: Map[K, V]): Map[K, V] = { + Bin(l.size() + r.size() + 1, k, v, l, r) +} + +def balance[K, V](k: K, v: V, l: Map[K, V], r: Map[K, V]): Map[K, V] = { + /* + k1->v1 + / \ + t1 m k2->v2 + = / \ + k2->v2 ~> k1->v1 t3 + / \ / \ + t2 t3 t1 t2 + */ + def singleL[A, B](k1: A, v1: B, t1: Map[A, B], m: Map[A, B]): Map[A, B] = { + m match { + case Bin(_, k2, v2, t2, t3) => bin(k2, v2, bin(k1, v1, t1, t2), t3) + case _ => panic("impossible: singleL: Tip") + } + } + + /* + k1->v1 + / \ + m t3 k2->v2 + = / \ + k2->v2 ~> t1 k1->v1 + / \ / / \ + t1 t2 t1 t2 t3 + */ + def singleR[A, B](k1: A, v1: B, m: Map[A, B], t3: Map[A, B]): Map[A, B] = { + m match { + case Bin(_, k2, v2, t1, t2) => bin(k2, v2, t1, bin(k1, v1, t2, t3)) + case _ => panic("impossible: singleR: Tip") + } + } + + /* + k1->v1 k3->v3 + / \ / \ + t1 m k1->v1 k2->v2 + = / \ / \ + k2->v2 ~> t1 t2 t3 t4 + / \ + k3->v3 t4 + / \ + t2 t3 + */ + def doubleL[A, B](k1: A, v1: B, t1: Map[A, B], m: Map[A, B]): Map[A, B] = { + m match { + case Bin(_, k2, v2, Bin(_, k3, v3, t2, t3), t4) => + bin(k3, v3, bin(k1, v1, t1, t2), bin(k2, v2, t3, t4)) + case _ => panic("impossible: doubleL: Tip") + } + } + + /* + k1->v1 k3->v3 + / \ / \ + m t4 k2->v2 k1->v1 + = / \ / \ + k2->v2 ~> t1 t2 t3 t4 + / \ + t1 k3->v3 + / \ + t2 t3 + */ + def doubleR[A, B](k1: A, v1: B, m: Map[A, B], t4: Map[A, B]): Map[A, B] = { + m match { + case Bin(_, k2, v2, t1, Bin(_, k3, v3, t2, t3)) => + bin(k3, v3, bin(k2, v2, t1, t2), bin(k1, v1, t3, t4)) + case _ => + panic("impossible: doubleR: Tip") + } + } + + def rotateL[A, B](k: A, v: B, l: Map[A, B], r: Map[A, B]): Map[A, B] = { + r match { + case Bin(_, _, _, rl, rr) => + if (rl.size() < ratio * rr.size()) { singleL(k, v, l, r) } + else { doubleL(k, v, l, r) } + case Tip() => doubleL(k, v, l, r) + } + } + def rotateR[A, B](k: A, v: B, l: Map[A, B], r: Map[A, B]): Map[A, B] = { + l match { + case Bin(_, _, _, ll, lr) => + if (ll.size() < ratio * lr.size()) { singleR(k, v, l, r) } + else { doubleR(k, v, l, r) } + case Tip() => doubleR(k, v, l, r) + } + } + + val sizeL = l.size() + val sizeR = r.size() + val sizeCombined = sizeL + sizeR + 1 + + if ((sizeL + sizeR) <= 1) { Bin(sizeCombined, k, v, l, r) } + else if (sizeR > (delta * sizeL)) { rotateL(k, v, l, r) } + else if (sizeL > (delta * sizeR)) { rotateR(k, v, l, r) } + else { Bin(sizeCombined, k, v, l, r)} +} + +record MaxView[K, V](k: K, v: V, m: Map[K, V]) +record MinView[K, V](k: K, v: V, m: Map[K, V]) + +def maxViewSure[K, V](k: K, v: V, l: Map[K, V], r: Map[K, V]): MaxView[K, V] = { + (l, r) match { + case (l, Tip()) => MaxView(k, v, l) + case (l, Bin(_, kr, vr, rl, rr)) => + val MaxView(km, vm, r2) = maxViewSure(kr, vr, rl, rr) + MaxView(km, vm, balance(k, v, l, r2)) + } +} + +def minViewSure[K, V](k: K, v: V, l: Map[K, V], r: Map[K, V]): MinView[K, V] = { + (l, r) match { + case (Tip(), r) => MinView(k, v, r) + case (Bin(_, kl, vl, ll, lr), r) => + val MinView(km, vm, l2) = minViewSure(kl, vl, ll, lr) + MinView(km, vm, balance(k, v, l2, r)) + } +} + +// Internal: Glues two balanced trees (with respect to each other) together. +def glue[K, V](l: Map[K, V], r: Map[K, V]): Map[K, V] = { + (l, r) match { + case (Tip(), r) => r + case (l, Tip()) => l + case (Bin(sizeL, kl, vl, ll, lr), Bin(sizeR, kr, vr, rl, rr)) => + if (sizeL > sizeR) { + val MaxView(km, m, l2) = maxViewSure(kl, vl, ll, lr) + balance(km, m, l2, r) + } else { + val MinView(km, m, r2) = minViewSure(kr, vr, rl, rr) + balance(km, m, l, r2) + } + } +} + +def splitLookup[K, V](m: Map[K, V], k: K): Tuple3[Map[K, V], Option[V], Map[K, V]] = { + m match { + case Tip() => (Tip(), None(), Tip()) + case Bin(_, k2, v2, l, r) => + genericCompare(k, k2) match { + case Less() => + val (lessThan, mid, greaterThan) = l.splitLookup(k); + (lessThan, mid, link(k2, v2, greaterThan, r)) + case Greater() => + val (lessThan, mid, greaterThan) = r.splitLookup(k); + (link(k2, v2, l, lessThan), mid, greaterThan) + case Equal() => (l, Some(v2), r) + } + } +} + +def split[K, V](m: Map[K, V], k: K): (Map[K, V], Map[K, V]) = { + val (l, _, r) = m.splitLookup(k); + (l, r) +} + +def link[K, V](k: K, v: V, l: Map[K, V], r: Map[K, V]): Map[K, V] = { + (l, r) match { + case (Tip(), r) => r.putMin(k, v) + case (l, Tip()) => l.putMax(k, v) + case (Bin(sizeL, kl, vl, ll, lr), Bin(sizeR, kr, vr, rl, rr)) => + if ((delta * sizeL) < sizeR) { balance(kr, vr, link(k, v, l, rl), rr) } + else if ((delta * sizeR) < sizeL) { balance(kl, vl, ll, link(k, v, lr, r)) } + else { bin(k, v, l, r) } + } +} + +// Internal: merge two trees +def link2[K, V](l: Map[K, V], r: Map[K, V]): Map[K, V] = { + (l, r) match { + case (Tip(), r) => r + case (l, Tip()) => l + case (Bin(sizeL, kl, vl, ll, lr), Bin(sizeR, kr, vr, rl, rr)) => + if ((delta * sizeL) < sizeR) { balance(kr, vr, link2(l, lr), rr) } + else if ((delta * sizeR) < sizeL) { balance(kl, vl, ll, link2(lr, r)) } + else { glue(l, r) } + } +} + +def putMin[K, V](m: Map[K, V], k: K, v: V): Map[K, V] = { + m match { + case Tip() => singleton(k, v) + case Bin(_, k2, v2, l, r) => + balance(k2, v2, l.putMin(k, v), r) + } +} + +def putMax[K, V](m: Map[K, V], k: K, v: V): Map[K, V] = { + m match { + case Tip() => singleton(k, v) + case Bin(_, k2, v2, l, r) => + balance(k2, v2, l, r.putMax(k, v)) + } +} + +// Section: internal utilities for tests: + +// Check if a map `m` is balanced. +def isBalanced[K, V](m: Map[K, V]): Bool = { + m match { + case Tip() => true + case Bin(_, _, _, l, r) => + val bothSmall = l.size() + r.size() <= 1 + val leftSmallEnough = l.size() <= delta * r.size() + val rightSmallEnough = r.size() <= delta * l.size() + (bothSmall || (leftSmallEnough && rightSmallEnough)) && isBalanced(l) && isBalanced(r) + } +} + +// Section: genericShow for tree maps and list maps + +def genericShow[K, V](m: Map[K, V]): String = { + // Helper function to recursively build the string representation of the tree + def go(t: Map[K, V], prefix: String, isTail: Bool): String = { + t match { + case Tip() => "" + case Bin(_, k, v, l, r) => + val pair = k.genericShow ++ " → " ++ v.genericShow + val currentLine = prefix ++ (if (isTail) "└── " else "├── ") ++ pair ++ "\n" + + val newPrefix = prefix ++ (if (isTail) " " else "│ ") + val leftStr = go(l, newPrefix, false) + val rightStr = go(r, newPrefix, true) + + currentLine ++ leftStr ++ rightStr + } + } + + // Start the recursion with the initial map, an empty prefix, and true for the root being the tail + go(m, "", true) +} + +def genericShow[K, V](list: List[(K, V)]): String = { + val res: String = + list.map { case (k, v) => k.genericShow ++ " → " ++ v.genericShow } + .join(", ") + + "[" ++ res ++ "]" +} diff --git a/libraries/common/set.effekt b/libraries/common/set.effekt new file mode 100644 index 000000000..973ccd8bb --- /dev/null +++ b/libraries/common/set.effekt @@ -0,0 +1,208 @@ +module set + +import map + +// Ordered finite set, backed by a `Map`. +record Set[A](internal: Map[A, Unit]) + +// Create a new empty set. +// +// O(1) +def empty[A](): Set[A] = { + Set(empty()) +} + +// Check if set `s` is empty. +// +// O(1) +def isEmpty[A](s: Set[A]): Bool = { + s.viaInternal { m => + m.isEmpty() + } +} + +// Check if set `s` is nonempty. +// +// O(1) +def nonEmpty[A](s: Set[A]): Bool = { + s.viaInternal { m => + m.nonEmpty() + } +} + +// Create a new set containing only the given element `a`. +// +// O(1) +def singleton[A](a: A): Set[A] = { + val s: Set[A] = empty() + s.insert(a) +} + +// Insert a new element `a` into the set `s`. +// +// O(log N) +def insert[A](s: Set[A], a: A): Set[A] = { + s.modifyInternal { m => + put(m, a, ()) + } +} + +// Create a set from a given list. +// +// O(N log N) +// O(N) if the list is sorted +def fromList[A](list: List[A]): Set[A] = { + val m: Map[A, Unit] = list.map { k => (k, ()) }.fromList + Set(m) +} + +// Create a list from a given set. +// +// O(N) +def toList[A](s: Set[A]): List[A] = { + var acc = Nil() + s.foreach { a => + acc = Cons(a, acc) + } + acc.reverse +} + +// Check if a predicate holds for all elements in a given set. +// +// O(N) +def all[A](s: Set[A]) { predicate : A => Bool }: Bool = { + var result = true + s.foreach { a => + if (not(predicate(a))) { result = false } + } + result +} + +// Check if a predicate holds for at least one element in a given set. +// +// O(N) +def any[A](s: Set[A]) { predicate: A => Bool }: Bool = { + var result = false + s.foreach { a => + if (predicate(a)) { result = true } + } + result +} + +// Check if a set contains a given element. +// +// O(log N) +def contains[A](s: Set[A], a: A): Bool = { + s.viaInternal { m => + m.contains(a) + } +} + +// Check if set `s1` is a subset of set `s2`. +// +// O(N log N) +def subset[A](s1: Set[A], s2: Set[A]): Bool = { + s1.all { a => + s2.contains(a) + } +} + +// Check if set `s1` is a superset of set `s2`. +// +// O(N log N) +def superset[A](s1: Set[A], s2: Set[A]): Bool = { + s2.subset(s1) +} + +// Remove an element from a set. +// If the element is not in the set, the original set is returned. +// +// O(log N) +def delete[A](s: Set[A], a: A): Set[A] = { + s.modifyInternal { m => + m.delete(a) + } +} + +// Remove many elements from a set. +// +// O(M log N) where M is the size of the list. +def deleteMany[A](s: Set[A], list: List[A]): Set[A] = { + var tmp = s + list.foreach { a => + tmp = tmp.delete(a) + } + tmp +} + +// Traverse all elements in order, running the function `action` on each element. +// +// Law: `s.foreach { action } === s.toList.foreach { action }` +// +// O(N) +def foreach[A](s: Set[A]) { action : A => Unit }: Unit = { + s.viaInternal { m => + m.foreach { (k, _v) => + action(k) + } + } +} + +// Get the size of a set. +// +// O(N) +def size[A](s: Set[A]): Int = { + s.viaInternal { m => + m.size() + } +} + +// Convert a given set into a map. +// Uses a function `value` to give a value for each of the keys (elements of the set). +// +// O(N) +def toMap[K, V](keys: Set[K]) { value : K => V }: Map[K, V] = { + keys.viaInternal { m => + m.map { (k, _v) => value(k) } + } +} + +// Construct a new set which contains all elements of `s1` +// except those where the element is in `s2`. +// +// O(???) +def difference[A](s1: Set[A], s2: Set[A]): Set[A] = { + s1.modifyInternal { m1 => + s2.viaInternal { m2 => + m1.difference(m2) + } + } +} + +// Construct a new set which contains both elements of `s1` and `s2`. +// +// O(???) +def union[A](s1: Set[A], s2: Set[A]): Set[A] = { + s1.modifyInternal { m1 => + s2.viaInternal { m2 => + m1.union(m2) + } + } +} + +// ------------- +// Internal + +// Internal: inline at all costs +// Function `f` is only used linearly, the set `s` is dropped. +def viaInternal[A, R](s: Set[A]) { f : Map[A, Unit] => R }: R = { + val Set(internal) = s + f(internal) +} + +// Internal: inline at all costs +// Uses `viaInternal` inside and wraps the result in a `Set`. +// Ideally, this would be in-place (when possible) since we don't change the type? +def modifyInternal[A](s: Set[A]) { f : Map[A, Unit] => Map[A, Unit] }: Set[A] = { + s.viaInternal { m => Set(f(m)) } +} From 418dc4fbe9036a3aa3ac5c4f2a429ffe188de23c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Fri, 30 Aug 2024 11:28:59 +0200 Subject: [PATCH 02/29] Add a quick unit test --- .../src/test/scala/effekt/StdlibTests.scala | 6 +++++- examples/stdlib/map/map.check | 4 ++++ examples/stdlib/map/map.effekt | 21 +++++++++++++++++++ libraries/common/map.effekt | 6 +++--- 4 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 examples/stdlib/map/map.check create mode 100644 examples/stdlib/map/map.effekt diff --git a/effekt/jvm/src/test/scala/effekt/StdlibTests.scala b/effekt/jvm/src/test/scala/effekt/StdlibTests.scala index f74cb0e21..ff07b1fea 100644 --- a/effekt/jvm/src/test/scala/effekt/StdlibTests.scala +++ b/effekt/jvm/src/test/scala/effekt/StdlibTests.scala @@ -25,7 +25,8 @@ abstract class StdlibChezTests extends StdlibTests { examplesDir / "stdlib" / "bytearray", examplesDir / "stdlib" / "io", examplesDir / "stdlib" / "stream" / "characters.effekt", - examplesDir / "stdlib" / "stream" / "fuse_newlines.effekt" + examplesDir / "stdlib" / "stream" / "fuse_newlines.effekt", + examplesDir / "stdlib" / "map" ) } class StdlibChezSchemeMonadicTests extends StdlibChezTests { @@ -65,5 +66,8 @@ class StdlibLLVMTests extends StdlibTests { examplesDir / "stdlib" / "list" / "build.effekt", examplesDir / "stdlib" / "string" / "strings.effekt", examplesDir / "stdlib" / "string" / "unicode.effekt", + + // Not implemented yet + examplesDir / "stdlib" / "map" ) } diff --git a/examples/stdlib/map/map.check b/examples/stdlib/map/map.check new file mode 100644 index 000000000..1e0551d66 --- /dev/null +++ b/examples/stdlib/map/map.check @@ -0,0 +1,4 @@ +[0 → Hello, 1 → World, 2 → Woo!] +[0 → Hello, 2 → Woo!, 42 → Whole new world!, 100 → Big, 1000 → Bigger, 10000 → Biggest!] +[0 → Hello, 1 → Foo, 2 → Woo!, 42 → Whole new world!, 100 → Big, 1000 → Bigger, 10000 → Biggest!] +[0 → Hello, 1 → Foo, 2 → Woo!, 42 → Whole new world!, 100 → Big, 1000 → Bigger, 10000 → Biggest!] diff --git a/examples/stdlib/map/map.effekt b/examples/stdlib/map/map.effekt new file mode 100644 index 000000000..906fb69b4 --- /dev/null +++ b/examples/stdlib/map/map.effekt @@ -0,0 +1,21 @@ +import map + +def main() = { + val l = [(0, "Hello"), (1, "World"), (2, "Woo!")] + + val m = map::fromList(l) + println(m.toList.prettyPairs) + + val m2 = m.delete(1).put(42, "Whole new world!").put(100, "Big").put(1000, "Bigger").put(10000, "Biggest!") + println(m2.toList.prettyPairs) + + // TODO: BUG! + // val m3 = map::fromList(Cons((1, "Foo"), Cons((-1, "Huh?!"), m2.toList.reverse))) + // println(m3.toList.prettyPairs) + + val m3 = map::fromList(Cons((1, "Foo"), m2.toList.reverse)) + println(m3.toList.prettyPairs) + + val m4: Map[Int, String] = m3.toList.fromList + println(m4.toList.prettyPairs) +} diff --git a/libraries/common/map.effekt b/libraries/common/map.effekt index f54adbd44..e68f88fa9 100644 --- a/libraries/common/map.effekt +++ b/libraries/common/map.effekt @@ -686,9 +686,9 @@ def isBalanced[K, V](m: Map[K, V]): Bool = { } } -// Section: genericShow for tree maps and list maps +// Section: prettyprinting for tree maps and list maps -def genericShow[K, V](m: Map[K, V]): String = { +def prettyMap[K, V](m: Map[K, V]): String = { // Helper function to recursively build the string representation of the tree def go(t: Map[K, V], prefix: String, isTail: Bool): String = { t match { @@ -709,7 +709,7 @@ def genericShow[K, V](m: Map[K, V]): String = { go(m, "", true) } -def genericShow[K, V](list: List[(K, V)]): String = { +def prettyPairs[K, V](list: List[(K, V)]): String = { val res: String = list.map { case (k, v) => k.genericShow ++ " → " ++ v.genericShow } .join(", ") From 992e4c47a75aaa80e58df39dbc38b8662425eb4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Fri, 30 Aug 2024 11:41:40 +0200 Subject: [PATCH 03/29] Use pattern guards in rotateL, rotateR --- libraries/common/map.effekt | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/libraries/common/map.effekt b/libraries/common/map.effekt index e68f88fa9..1d0e5ce0e 100644 --- a/libraries/common/map.effekt +++ b/libraries/common/map.effekt @@ -550,18 +550,14 @@ def balance[K, V](k: K, v: V, l: Map[K, V], r: Map[K, V]): Map[K, V] = { def rotateL[A, B](k: A, v: B, l: Map[A, B], r: Map[A, B]): Map[A, B] = { r match { - case Bin(_, _, _, rl, rr) => - if (rl.size() < ratio * rr.size()) { singleL(k, v, l, r) } - else { doubleL(k, v, l, r) } - case Tip() => doubleL(k, v, l, r) + case Bin(_, _, _, rl, rr) and (rl.size() < ratio * rr.size()) => singleL(k, v, l, r) + case _ => doubleL(k, v, l, r) } } def rotateR[A, B](k: A, v: B, l: Map[A, B], r: Map[A, B]): Map[A, B] = { l match { - case Bin(_, _, _, ll, lr) => - if (ll.size() < ratio * lr.size()) { singleR(k, v, l, r) } - else { doubleR(k, v, l, r) } - case Tip() => doubleR(k, v, l, r) + case Bin(_, _, _, ll, lr) and (ll.size() < ratio * lr.size()) => singleR(k, v, l, r) + case _ => doubleR(k, v, l, r) } } From 9236b292c313faeff31630e6ef77fc78ca0eac4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Fri, 30 Aug 2024 13:04:13 +0200 Subject: [PATCH 04/29] Fix wrong right rotation --- libraries/common/map.effekt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/common/map.effekt b/libraries/common/map.effekt index 1d0e5ce0e..3db75085e 100644 --- a/libraries/common/map.effekt +++ b/libraries/common/map.effekt @@ -556,7 +556,7 @@ def balance[K, V](k: K, v: V, l: Map[K, V], r: Map[K, V]): Map[K, V] = { } def rotateR[A, B](k: A, v: B, l: Map[A, B], r: Map[A, B]): Map[A, B] = { l match { - case Bin(_, _, _, ll, lr) and (ll.size() < ratio * lr.size()) => singleR(k, v, l, r) + case Bin(_, _, _, ll, lr) and (lr.size() < ratio * ll.size()) => singleR(k, v, l, r) case _ => doubleR(k, v, l, r) } } From a1995da174d51eb958d66a4664081718b6cfb6cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Fri, 30 Aug 2024 13:09:57 +0200 Subject: [PATCH 05/29] Add a few more regression tests --- examples/stdlib/map/map.check | 7 +++++-- examples/stdlib/map/map.effekt | 19 ++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/examples/stdlib/map/map.check b/examples/stdlib/map/map.check index 1e0551d66..60d02e23e 100644 --- a/examples/stdlib/map/map.check +++ b/examples/stdlib/map/map.check @@ -1,4 +1,7 @@ [0 → Hello, 1 → World, 2 → Woo!] +[-1 → Hullo, 0 → Hello, 1 → World, 2 → Woo!] +[-10 → EY, 0 → Hello, 1 → World, 2 → Woo!] [0 → Hello, 2 → Woo!, 42 → Whole new world!, 100 → Big, 1000 → Bigger, 10000 → Biggest!] -[0 → Hello, 1 → Foo, 2 → Woo!, 42 → Whole new world!, 100 → Big, 1000 → Bigger, 10000 → Biggest!] -[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!] +[-10 → EY, 0 → Hello, 1 → World, 2 → Woo!] +[0 → Hello, 1 → World, 2 → Woo!] diff --git a/examples/stdlib/map/map.effekt b/examples/stdlib/map/map.effekt index 906fb69b4..b36d024fc 100644 --- a/examples/stdlib/map/map.effekt +++ b/examples/stdlib/map/map.effekt @@ -6,16 +6,21 @@ def main() = { val m = map::fromList(l) println(m.toList.prettyPairs) - val m2 = m.delete(1).put(42, "Whole new world!").put(100, "Big").put(1000, "Bigger").put(10000, "Biggest!") + val m2 = m.put(-1, "Hullo") println(m2.toList.prettyPairs) - // TODO: BUG! - // val m3 = map::fromList(Cons((1, "Foo"), Cons((-1, "Huh?!"), m2.toList.reverse))) - // println(m3.toList.prettyPairs) - - val m3 = map::fromList(Cons((1, "Foo"), m2.toList.reverse)) + val m3 = m.put(-10, "EY") println(m3.toList.prettyPairs) - val m4: Map[Int, String] = m3.toList.fromList + val m4 = m.delete(1).put(42, "Whole new world!").put(100, "Big").put(1000, "Bigger").put(10000, "Biggest!") println(m4.toList.prettyPairs) + + val m5 = map::fromList(Cons((1, "Foo"), Cons((-1, "Huh?!"), m4.toList.reverse))) + println(m5.toList.prettyPairs) + + val m6: Map[Int, String] = m3.toList.fromList + println(m6.toList.prettyPairs) + + val nuMap = map::fromList(l.reverse) + println(nuMap.toList.prettyPairs) } From 55f2a50f9a0de9eaaec69730728d52c9629468d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Fri, 30 Aug 2024 13:13:11 +0200 Subject: [PATCH 06/29] Update tests once again --- examples/stdlib/map/map.check | 4 ++-- examples/stdlib/map/map.effekt | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/stdlib/map/map.check b/examples/stdlib/map/map.check index 60d02e23e..652b0adc0 100644 --- a/examples/stdlib/map/map.check +++ b/examples/stdlib/map/map.check @@ -1,7 +1,7 @@ [0 → Hello, 1 → World, 2 → Woo!] [-1 → Hullo, 0 → Hello, 1 → World, 2 → Woo!] -[-10 → EY, 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!] -[-10 → EY, 0 → Hello, 1 → World, 2 → Woo!] +[-1 → Huh?!, 0 → Hello, 1 → Foo, 2 → Woo!, 42 → Whole new world!, 100 → Big, 1000 → Bigger, 10000 → Biggest!] [0 → Hello, 1 → World, 2 → Woo!] diff --git a/examples/stdlib/map/map.effekt b/examples/stdlib/map/map.effekt index b36d024fc..494507e36 100644 --- a/examples/stdlib/map/map.effekt +++ b/examples/stdlib/map/map.effekt @@ -9,16 +9,18 @@ def main() = { val m2 = m.put(-1, "Hullo") println(m2.toList.prettyPairs) - val m3 = m.put(-10, "EY") + val m3 = m2.put(-10, "EY") println(m3.toList.prettyPairs) + // ... + val m4 = m.delete(1).put(42, "Whole new world!").put(100, "Big").put(1000, "Bigger").put(10000, "Biggest!") println(m4.toList.prettyPairs) val m5 = map::fromList(Cons((1, "Foo"), Cons((-1, "Huh?!"), m4.toList.reverse))) println(m5.toList.prettyPairs) - val m6: Map[Int, String] = m3.toList.fromList + val m6: Map[Int, String] = m5.toList.fromList println(m6.toList.prettyPairs) val nuMap = map::fromList(l.reverse) From e94e51f5fbd4dbbdb5aa61ecdd85204b82337c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Fri, 30 Aug 2024 13:33:49 +0200 Subject: [PATCH 07/29] Add a 'counter' example --- examples/stdlib/map/counter.check | 7 ++++++ examples/stdlib/map/counter.effekt | 39 ++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 examples/stdlib/map/counter.check create mode 100644 examples/stdlib/map/counter.effekt diff --git a/examples/stdlib/map/counter.check b/examples/stdlib/map/counter.check new file mode 100644 index 000000000..c5eb3a954 --- /dev/null +++ b/examples/stdlib/map/counter.check @@ -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] diff --git a/examples/stdlib/map/counter.effekt b/examples/stdlib/map/counter.effekt new file mode 100644 index 000000000..612519cd8 --- /dev/null +++ b/examples/stdlib/map/counter.effekt @@ -0,0 +1,39 @@ +import map + +def counter(words: List[String]): Map[String, Int] = { + var m: Map[String, Int] = map::empty(); + + 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 words: 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(words) + + 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(ctr.toList.prettyPairs) +} From c2ad2c0cef07253bd8c51e2efea93c968c7839bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Wed, 2 Oct 2024 18:09:02 +0200 Subject: [PATCH 08/29] Use '///' for doc comments --- libraries/common/map.effekt | 218 ++++++++++++++++++------------------ libraries/common/set.effekt | 138 +++++++++++------------ 2 files changed, 178 insertions(+), 178 deletions(-) diff --git a/libraries/common/map.effekt b/libraries/common/map.effekt index 3db75085e..4c999da73 100644 --- a/libraries/common/map.effekt +++ b/libraries/common/map.effekt @@ -1,25 +1,25 @@ module map -// Ordered finite immutable map, backed by balanced binary trees of logarithmic depth. -// Currently use `genericCompare` as a comparison primitive, which means the map is now working only in the JS backend. -// -// Please don't use the internal constructors `Bin` & `Tip` directly, -// they might change down the line and are not considered stable. +/// Ordered finite immutable map, backed by balanced binary trees of logarithmic depth. +/// Currently use `genericCompare` as a comparison primitive, which means the map is now working only in the JS backend. +/// +/// Please don't use the internal constructors `Bin` & `Tip` directly, +/// they might change down the line and are not considered stable. type Map[K, V] { Bin(size: Int, k: K, v: V, left: Map[K, V], right: Map[K, V]); Tip() } -// Create a new empty map. -// -// O(1) +/// Create a new empty map. +/// +/// O(1) def empty[K, V](): Map[K, V] = { Tip() } -// Check if map `m` is empty. -// -// O(1) +/// Check if map `m` is empty. +/// +/// O(1) def isEmpty[K, V](m: Map[K, V]): Bool = { m match { case Tip() => true @@ -27,9 +27,9 @@ def isEmpty[K, V](m: Map[K, V]): Bool = { } } -// Check if map `m` is nonempty. -// -// O(1) +/// Check if map `m` is nonempty. +/// +/// O(1) def nonEmpty[K, V](m: Map[K, V]): Bool = { m match { case Tip() => false @@ -37,16 +37,16 @@ def nonEmpty[K, V](m: Map[K, V]): Bool = { } } -// Create a new map containing only the mapping from `k` to `v`. -// -// O(1) +/// Create a new map containing only the mapping from `k` to `v`. +/// +/// O(1) def singleton[K, V](k: K, v: V): Map[K, V] = { Bin(1, k, v, Tip(), Tip()) } -// Get the size of the map (the number of keys/values). -// -// O(1) +/// Get the size of the map (the number of keys/values). +/// +/// O(1) def size[K, V](m: Map[K, V]): Int = { m match { case Tip() => 0 @@ -54,10 +54,10 @@ def size[K, V](m: Map[K, V]): Int = { } } -// Insert a new key `k` and value `v` into the map `m`. -// If the key `k` is already present in `m`, its associated value is replaced with `v`. -// -// O(log N) +/// Insert a new key `k` and value `v` into the map `m`. +/// If the key `k` is already present in `m`, its associated value is replaced with `v`. +/// +/// O(log N) def put[K, V](m: Map[K, V], k: K, v: V): Map[K, V] = m match { case Tip() => singleton(k, v) case Bin(size, k2, v2, l, r) => @@ -68,10 +68,10 @@ def put[K, V](m: Map[K, V], k: K, v: V): Map[K, V] = m match { } } -// Insert a new key `k` and value `v` into the map `m`. -// If the key `k` is already present in `m` with value `v2`, the function `combine` is called on `k`, `v`, `v2`. -// -// O(log N) +/// Insert a new key `k` and value `v` into the map `m`. +/// If the key `k` is already present in `m` with value `v2`, the function `combine` is called on `k`, `v`, `v2`. +/// +/// O(log N) def putWithKey[K, V](m: Map[K, V], k: K, v: V) { combine: (K, V, V) => V } : Map[K, V] = m match { case Tip() => singleton(k, v) case Bin(size, k2, v2, l, r) => @@ -82,9 +82,9 @@ def putWithKey[K, V](m: Map[K, V], k: K, v: V) { combine: (K, V, V) => V } : Map } } -// Lookup the value at a key `k` in the map `m`. -// -// O(log N) +/// Lookup the value at a key `k` in the map `m`. +/// +/// O(log N) def get[K, V](m: Map[K, V], k: K): Option[V] = { m match { case Tip() => None() @@ -97,10 +97,10 @@ def get[K, V](m: Map[K, V], k: K): Option[V] = { } } -// Lookup the value at a key `k` in the map `m`. -// If there is no key, use the `default` block to retrieve a default value. -// -// O(log N) +/// Lookup the value at a key `k` in the map `m`. +/// If there is no key, use the `default` block to retrieve a default value. +/// +/// O(log N) def getOrElse[K, V](m: Map[K, V], k: K) { default : => V }: V = { get(m, k) match { case None() => default() @@ -108,9 +108,9 @@ def getOrElse[K, V](m: Map[K, V], k: K) { default : => V }: V = { } } -// Check if map `m` contains a key `k`. -// -// O(log N) +/// Check if map `m` contains a key `k`. +/// +/// O(log N) def contains[K, V](m: Map[K, V], k: K): Bool = { get(m, k) match { case None() => false @@ -118,9 +118,9 @@ def contains[K, V](m: Map[K, V], k: K): Bool = { } } -// Get minimum in the map `m`. -// -// O(log N) +/// Get minimum in the map `m`. +/// +/// O(log N) def getMin[K, V](m: Map[K, V]): Option[(K, V)] = { def go(k: K, v: V, m: Map[K, V]): (K, V) = { m match { @@ -135,9 +135,9 @@ def getMin[K, V](m: Map[K, V]): Option[(K, V)] = { } } -// Get maximum in the map `m`. -// -// O(log N) +/// Get maximum in the map `m`. +/// +/// O(log N) def getMax[K, V](m: Map[K, V]): Option[(K, V)] = { def go(k: K, v: V, m: Map[K, V]): (K, V) = { m match { @@ -152,9 +152,9 @@ def getMax[K, V](m: Map[K, V]): Option[(K, V)] = { } } -// Map a function `f` over values in map `m`. -// -// O(N) +/// Map a function `f` over values in map `m`. +/// +/// O(N) def map[K, V1, V2](m: Map[K, V1]) { f : (K, V1) => V2 }: Map[K, V2] = { m match { case Tip() => Tip() @@ -163,21 +163,21 @@ def map[K, V1, V2](m: Map[K, V1]) { f : (K, V1) => V2 }: Map[K, V2] = { } } -// Map a function `f` over values in map `m`. -// -// O(N) +/// Map a function `f` over values in map `m`. +/// +/// O(N) def map[K, V1, V2](m: Map[K, V1]) { f : V1 => V2 }: Map[K, V2] = { m.map { (_k, v) => f(v) } } -// Traverse all keys and their associated values in map `m` in order, -// running the function `action` on a key and its associated value. -// -// Law: `m.foreach { action } === m.toList.foreach { action }` -// -// O(N) -// -// TODO: Support {Control} for early exits. +/// Traverse all keys and their associated values in map `m` in order, +/// running the function `action` on a key and its associated value. +/// +/// Law: `m.foreach { action } === m.toList.foreach { action }` +/// +/// O(N) +/// +/// TODO: Support {Control} for early exits. def foreach[K, V](m: Map[K, V]) { action: (K, V) => Unit }: Unit = { def go(m: Map[K, V]): Unit = { m match { @@ -191,9 +191,9 @@ def foreach[K, V](m: Map[K, V]) { action: (K, V) => Unit }: Unit = { go(m) } -// Convert a map `m` into a list of (key, value) pairs. -// -// O(N) +/// Convert a map `m` into a list of (key, value) pairs. +/// +/// O(N) def toList[K, V](m: Map[K, V]): List[(K, V)] = { var acc = Nil() m.foreach { (k, v) => @@ -202,9 +202,9 @@ def toList[K, V](m: Map[K, V]): List[(K, V)] = { acc.reverse } -// Get a list of keys of the map `m`. -// -// O(N) +/// Get a list of keys of the map `m`. +/// +/// O(N) def keys[K, V](m: Map[K, V]): List[K] = { var acc = Nil() m.foreach { (k, _v) => @@ -213,9 +213,9 @@ def keys[K, V](m: Map[K, V]): List[K] = { acc.reverse } -// Get a list of values of the map `m`. -// -// O(N) +/// Get a list of values of the map `m`. +/// +/// O(N) def values[K, V](m: Map[K, V]): List[V] = { var acc = Nil() m.foreach { (_k, v) => @@ -224,12 +224,12 @@ def values[K, V](m: Map[K, V]): List[V] = { acc.reverse } -// Create a map from a list of (key, value) pairs. -// If the list contains more than one value for the same key, -// only the last value is used in the map. -// -// O(N) if the list is sorted by key, -// O(N log N) otherwise +/// Create a map from a list of (key, value) pairs. +/// If the list contains more than one value for the same key, +/// only the last value is used in the map. +/// +/// O(N) if the list is sorted by key, +/// O(N log N) otherwise def fromList[K, V](pairs: List[(K, V)]): Map[K, V] = { pairs match { case Nil() => Tip() @@ -316,10 +316,10 @@ def fromList[K, V](pairs: List[(K, V)]): Map[K, V] = { } } -// Remove a key `k` from a map `m`. -// If `k` is not in `m`, `m` is returned. -// -// O(log N) +/// Remove a key `k` from a map `m`. +/// If `k` is not in `m`, `m` is returned. +/// +/// O(log N) def delete[K, V](m: Map[K, V], k: K): Map[K, V] = { m match { case Tip() => Tip() @@ -332,10 +332,10 @@ def delete[K, V](m: Map[K, V], k: K): Map[K, V] = { } } -// Can be used to insert, delete, or update a value. -// Law: `get(m.alter(k){f}, k) === f(get(m, k))` -// -// O(log N) +/// Can be used to insert, delete, or update a value. +/// Law: `get(m.alter(k){f}, k) === f(get(m, k))` +/// +/// O(log N) def alter[K, V](m: Map[K, V], k: K) { f : Option[V] => Option[V] }: Map[K, V] = { m match { case Tip() => @@ -356,9 +356,9 @@ def alter[K, V](m: Map[K, V], k: K) { f : Option[V] => Option[V] }: Map[K, V] = } } -// Update or delete a value associated with key `k` in map `m`. -// -// O(log N) +/// Update or delete a value associated with key `k` in map `m`. +/// +/// O(log N) def update[K, V](m: Map[K, V], k: K) { f: (K, V) => Option[V] }: Map[K, V] = { m match { case Tip() => Tip() @@ -375,16 +375,16 @@ def update[K, V](m: Map[K, V], k: K) { f: (K, V) => Option[V] }: Map[K, V] = { } } -// Update or delete a value associated with key `k` in map `m`. -// -// O(log N) +/// Update or delete a value associated with key `k` in map `m`. +/// +/// O(log N) def update[K, V](m: Map[K, V], k: K) { f : V => Option[V] }: Map[K, V] = { m.update(k) { (_k, v) => f(v) } } -// Get `n`-th (key, value) pair in the map `m`. -// -// O(log N) +/// Get `n`-th (key, value) pair in the map `m`. +/// +/// O(log N) def getIndex[K, V](m: Map[K, V], n: Int): Option[(K, V)] = { m match { case Tip() => None() @@ -398,10 +398,10 @@ def getIndex[K, V](m: Map[K, V], n: Int): Option[(K, V)] = { } } -// Construct a new map which contains all elements of `m1` -// except those where the key is found in `m2`. -// -// O(???) +/// Construct a new map which contains all elements of `m1` +/// except those where the key is found in `m2`. +/// +/// O(???) def difference[K, V](m1: Map[K, V], m2: Map[K, V]): Map[K, V] = { (m1, m2) match { case (Tip(), m2) => Tip() @@ -415,10 +415,10 @@ def difference[K, V](m1: Map[K, V], m2: Map[K, V]): Map[K, V] = { } } -// Construct a new map which contains the elements of both `m1` and `m2`. -// When a key is associated with a value in both `m1` and `m2`, the new value is determined using the `combine` function. -// -// O(???) +/// Construct a new map which contains the elements of both `m1` and `m2`. +/// When a key is associated with a value in both `m1` and `m2`, the new value is determined using the `combine` function. +/// +/// O(???) def union[K, V](m1: Map[K, V], m2: Map[K, V]) { combine : (K, V, V) => V }: Map[K, V] = { // Internal function similar to `putWithKey`, but right-biased. // Only used here, recursively. @@ -450,18 +450,18 @@ def union[K, V](m1: Map[K, V], m2: Map[K, V]) { combine : (K, V, V) => V }: Map[ } } -// Construct a new map which contains the elements of both `m1` and `m2`. -// When a key is associated with a value in both `m1` and `m2`, the new value is determined using the `combine` function. -// -// O(???) +/// Construct a new map which contains the elements of both `m1` and `m2`. +/// When a key is associated with a value in both `m1` and `m2`, the new value is determined using the `combine` function. +/// +/// O(???) def union[K, V](m1: Map[K, V], m2: Map[K, V]) { combine : (V, V) => V }: Map[K, V] = { union(m1, m2) { (k, v1, v2) => combine(v1, v2) } } -// Construct a new map which contains the elements of both `m1` and `m2`. -// Left-biased: Uses values from `m1` if there are duplicate keys. -// -// O(???) +/// Construct a new map which contains the elements of both `m1` and `m2`. +/// Left-biased: Uses values from `m1` if there are duplicate keys. +/// +/// O(???) def union[K, V](m1: Map[K, V], m2: Map[K, V]): Map[K, V] = { union(m1, m2) { (k, v1, v2) => v1 } } @@ -592,7 +592,7 @@ def minViewSure[K, V](k: K, v: V, l: Map[K, V], r: Map[K, V]): MinView[K, V] = { } } -// Internal: Glues two balanced trees (with respect to each other) together. +/// Internal: Glues two balanced trees (with respect to each other) together. def glue[K, V](l: Map[K, V], r: Map[K, V]): Map[K, V] = { (l, r) match { case (Tip(), r) => r @@ -640,7 +640,7 @@ def link[K, V](k: K, v: V, l: Map[K, V], r: Map[K, V]): Map[K, V] = { } } -// Internal: merge two trees +/// Internal: merge two trees def link2[K, V](l: Map[K, V], r: Map[K, V]): Map[K, V] = { (l, r) match { case (Tip(), r) => r @@ -670,7 +670,7 @@ def putMax[K, V](m: Map[K, V], k: K, v: V): Map[K, V] = { // Section: internal utilities for tests: -// Check if a map `m` is balanced. +/// Check if a map `m` is balanced. def isBalanced[K, V](m: Map[K, V]): Bool = { m match { case Tip() => true diff --git a/libraries/common/set.effekt b/libraries/common/set.effekt index 973ccd8bb..9a2de15a3 100644 --- a/libraries/common/set.effekt +++ b/libraries/common/set.effekt @@ -2,63 +2,63 @@ module set import map -// Ordered finite set, backed by a `Map`. +/// Ordered finite set, backed by a `Map`. record Set[A](internal: Map[A, Unit]) -// Create a new empty set. -// -// O(1) +/// Create a new empty set. +/// +/// O(1) def empty[A](): Set[A] = { Set(empty()) } -// Check if set `s` is empty. -// -// O(1) +/// Check if set `s` is empty. +/// +/// O(1) def isEmpty[A](s: Set[A]): Bool = { s.viaInternal { m => m.isEmpty() } } -// Check if set `s` is nonempty. -// -// O(1) +/// Check if set `s` is nonempty. +/// +/// O(1) def nonEmpty[A](s: Set[A]): Bool = { s.viaInternal { m => m.nonEmpty() } } -// Create a new set containing only the given element `a`. -// -// O(1) +/// Create a new set containing only the given element `a`. +/// +/// O(1) def singleton[A](a: A): Set[A] = { val s: Set[A] = empty() s.insert(a) } -// Insert a new element `a` into the set `s`. -// -// O(log N) +/// Insert a new element `a` into the set `s`. +/// +/// O(log N) def insert[A](s: Set[A], a: A): Set[A] = { s.modifyInternal { m => put(m, a, ()) } } -// Create a set from a given list. -// -// O(N log N) -// O(N) if the list is sorted +/// Create a set from a given list. +/// +/// O(N log N) +/// O(N) if the list is sorted def fromList[A](list: List[A]): Set[A] = { val m: Map[A, Unit] = list.map { k => (k, ()) }.fromList Set(m) } -// Create a list from a given set. -// -// O(N) +/// Create a list from a given set. +/// +/// O(N) def toList[A](s: Set[A]): List[A] = { var acc = Nil() s.foreach { a => @@ -67,9 +67,9 @@ def toList[A](s: Set[A]): List[A] = { acc.reverse } -// Check if a predicate holds for all elements in a given set. -// -// O(N) +/// Check if a predicate holds for all elements in a given set. +/// +/// O(N) def all[A](s: Set[A]) { predicate : A => Bool }: Bool = { var result = true s.foreach { a => @@ -78,9 +78,9 @@ def all[A](s: Set[A]) { predicate : A => Bool }: Bool = { result } -// Check if a predicate holds for at least one element in a given set. -// -// O(N) +/// Check if a predicate holds for at least one element in a given set. +/// +/// O(N) def any[A](s: Set[A]) { predicate: A => Bool }: Bool = { var result = false s.foreach { a => @@ -89,44 +89,44 @@ def any[A](s: Set[A]) { predicate: A => Bool }: Bool = { result } -// Check if a set contains a given element. -// -// O(log N) +/// Check if a set contains a given element. +/// +/// O(log N) def contains[A](s: Set[A], a: A): Bool = { s.viaInternal { m => m.contains(a) } } -// Check if set `s1` is a subset of set `s2`. -// -// O(N log N) +/// Check if set `s1` is a subset of set `s2`. +/// +/// O(N log N) def subset[A](s1: Set[A], s2: Set[A]): Bool = { s1.all { a => s2.contains(a) } } -// Check if set `s1` is a superset of set `s2`. -// -// O(N log N) +/// Check if set `s1` is a superset of set `s2`. +/// +/// O(N log N) def superset[A](s1: Set[A], s2: Set[A]): Bool = { s2.subset(s1) } -// Remove an element from a set. -// If the element is not in the set, the original set is returned. -// -// O(log N) +/// Remove an element from a set. +/// If the element is not in the set, the original set is returned. +/// +/// O(log N) def delete[A](s: Set[A], a: A): Set[A] = { s.modifyInternal { m => m.delete(a) } } -// Remove many elements from a set. -// -// O(M log N) where M is the size of the list. +/// Remove many elements from a set. +/// +/// O(M log N) where M is the size of the list. def deleteMany[A](s: Set[A], list: List[A]): Set[A] = { var tmp = s list.foreach { a => @@ -135,11 +135,11 @@ def deleteMany[A](s: Set[A], list: List[A]): Set[A] = { tmp } -// Traverse all elements in order, running the function `action` on each element. -// -// Law: `s.foreach { action } === s.toList.foreach { action }` -// -// O(N) +/// Traverse all elements in order, running the function `action` on each element. +/// +/// Law: `s.foreach { action } === s.toList.foreach { action }` +/// +/// O(N) def foreach[A](s: Set[A]) { action : A => Unit }: Unit = { s.viaInternal { m => m.foreach { (k, _v) => @@ -148,29 +148,29 @@ def foreach[A](s: Set[A]) { action : A => Unit }: Unit = { } } -// Get the size of a set. -// -// O(N) +/// Get the size of a set. +/// +/// O(N) def size[A](s: Set[A]): Int = { s.viaInternal { m => m.size() } } -// Convert a given set into a map. -// Uses a function `value` to give a value for each of the keys (elements of the set). -// -// O(N) +/// Convert a given set into a map. +/// Uses a function `value` to give a value for each of the keys (elements of the set). +/// +/// O(N) def toMap[K, V](keys: Set[K]) { value : K => V }: Map[K, V] = { keys.viaInternal { m => m.map { (k, _v) => value(k) } } } -// Construct a new set which contains all elements of `s1` -// except those where the element is in `s2`. -// -// O(???) +/// Construct a new set which contains all elements of `s1` +/// except those where the element is in `s2`. +/// +/// O(???) def difference[A](s1: Set[A], s2: Set[A]): Set[A] = { s1.modifyInternal { m1 => s2.viaInternal { m2 => @@ -179,9 +179,9 @@ def difference[A](s1: Set[A], s2: Set[A]): Set[A] = { } } -// Construct a new set which contains both elements of `s1` and `s2`. -// -// O(???) +/// Construct a new set which contains both elements of `s1` and `s2`. +/// +/// O(???) def union[A](s1: Set[A], s2: Set[A]): Set[A] = { s1.modifyInternal { m1 => s2.viaInternal { m2 => @@ -193,16 +193,16 @@ def union[A](s1: Set[A], s2: Set[A]): Set[A] = { // ------------- // Internal -// Internal: inline at all costs -// Function `f` is only used linearly, the set `s` is dropped. +/// Internal: inline at all costs +/// Function `f` is only used linearly, the set `s` is dropped. def viaInternal[A, R](s: Set[A]) { f : Map[A, Unit] => R }: R = { val Set(internal) = s f(internal) } -// Internal: inline at all costs -// Uses `viaInternal` inside and wraps the result in a `Set`. -// Ideally, this would be in-place (when possible) since we don't change the type? +/// Internal: inline at all costs +/// Uses `viaInternal` inside and wraps the result in a `Set`. +/// Ideally, this would be in-place (when possible) since we don't change the type? def modifyInternal[A](s: Set[A]) { f : Map[A, Unit] => Map[A, Unit] }: Set[A] = { s.viaInternal { m => Set(f(m)) } } From bc6bf339a2764146b50957d39c70d0af4ebdceb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Wed, 2 Oct 2024 18:12:29 +0200 Subject: [PATCH 09/29] Move internal tools into their own namespace --- examples/stdlib/map/counter.effekt | 2 +- examples/stdlib/map/map.effekt | 14 +++--- libraries/common/map.effekt | 70 +++++++++++++++--------------- 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/examples/stdlib/map/counter.effekt b/examples/stdlib/map/counter.effekt index 612519cd8..87ab092b3 100644 --- a/examples/stdlib/map/counter.effekt +++ b/examples/stdlib/map/counter.effekt @@ -35,5 +35,5 @@ def main() = { test("do") test("Effekt") - println(ctr.toList.prettyPairs) + println(map::internal::prettyPairs(ctr.toList)) } diff --git a/examples/stdlib/map/map.effekt b/examples/stdlib/map/map.effekt index 494507e36..7762d30c8 100644 --- a/examples/stdlib/map/map.effekt +++ b/examples/stdlib/map/map.effekt @@ -4,25 +4,25 @@ def main() = { val l = [(0, "Hello"), (1, "World"), (2, "Woo!")] val m = map::fromList(l) - println(m.toList.prettyPairs) + println(map::internal::prettyPairs(m.toList)) val m2 = m.put(-1, "Hullo") - println(m2.toList.prettyPairs) + println(map::internal::prettyPairs(m2.toList)) val m3 = m2.put(-10, "EY") - println(m3.toList.prettyPairs) + println(map::internal::prettyPairs(m3.toList)) // ... val m4 = m.delete(1).put(42, "Whole new world!").put(100, "Big").put(1000, "Bigger").put(10000, "Biggest!") - println(m4.toList.prettyPairs) + println(map::internal::prettyPairs(m4.toList)) val m5 = map::fromList(Cons((1, "Foo"), Cons((-1, "Huh?!"), m4.toList.reverse))) - println(m5.toList.prettyPairs) + println(map::internal::prettyPairs(m5.toList)) val m6: Map[Int, String] = m5.toList.fromList - println(m6.toList.prettyPairs) + println(map::internal::prettyPairs(m6.toList)) val nuMap = map::fromList(l.reverse) - println(nuMap.toList.prettyPairs) + println(map::internal::prettyPairs(nuMap.toList)) } diff --git a/libraries/common/map.effekt b/libraries/common/map.effekt index 4c999da73..815a49c49 100644 --- a/libraries/common/map.effekt +++ b/libraries/common/map.effekt @@ -668,47 +668,49 @@ def putMax[K, V](m: Map[K, V], k: K, v: V): Map[K, V] = { } } -// Section: internal utilities for tests: +/// Section: internal utilities +namespace internal { + // Section: for tests and invariants: -/// Check if a map `m` is balanced. -def isBalanced[K, V](m: Map[K, V]): Bool = { - m match { - case Tip() => true - case Bin(_, _, _, l, r) => - val bothSmall = l.size() + r.size() <= 1 - val leftSmallEnough = l.size() <= delta * r.size() - val rightSmallEnough = r.size() <= delta * l.size() - (bothSmall || (leftSmallEnough && rightSmallEnough)) && isBalanced(l) && isBalanced(r) + /// Check if a map `m` is balanced. + def isBalanced[K, V](m: Map[K, V]): Bool = { + m match { + case Tip() => true + case Bin(_, _, _, l, r) => + val bothSmall = l.size() + r.size() <= 1 + val leftSmallEnough = l.size() <= delta * r.size() + val rightSmallEnough = r.size() <= delta * l.size() + (bothSmall || (leftSmallEnough && rightSmallEnough)) && isBalanced(l) && isBalanced(r) + } } -} -// Section: prettyprinting for tree maps and list maps + // Section: prettyprinting for tree maps and list maps: -def prettyMap[K, V](m: Map[K, V]): String = { - // Helper function to recursively build the string representation of the tree - def go(t: Map[K, V], prefix: String, isTail: Bool): String = { - t match { - case Tip() => "" - case Bin(_, k, v, l, r) => - val pair = k.genericShow ++ " → " ++ v.genericShow - val currentLine = prefix ++ (if (isTail) "└── " else "├── ") ++ pair ++ "\n" + def prettyMap[K, V](m: Map[K, V]): String = { + // Helper function to recursively build the string representation of the tree + def go(t: Map[K, V], prefix: String, isTail: Bool): String = { + t match { + case Tip() => "" + case Bin(_, k, v, l, r) => + val pair = k.genericShow ++ " → " ++ v.genericShow + val currentLine = prefix ++ (if (isTail) "└── " else "├── ") ++ pair ++ "\n" - val newPrefix = prefix ++ (if (isTail) " " else "│ ") - val leftStr = go(l, newPrefix, false) - val rightStr = go(r, newPrefix, true) + val newPrefix = prefix ++ (if (isTail) " " else "│ ") + val leftStr = go(l, newPrefix, false) + val rightStr = go(r, newPrefix, true) - currentLine ++ leftStr ++ rightStr + currentLine ++ leftStr ++ rightStr + } } - } - // Start the recursion with the initial map, an empty prefix, and true for the root being the tail - go(m, "", true) -} + // Start the recursion with the initial map, an empty prefix, and true for the root being the tail + go(m, "", true) + } -def prettyPairs[K, V](list: List[(K, V)]): String = { - val res: String = - list.map { case (k, v) => k.genericShow ++ " → " ++ v.genericShow } - .join(", ") + def prettyPairs[K, V](list: List[(K, V)]): String = { + val res: String = + list.map { case (k, v) => k.genericShow ++ " → " ++ v.genericShow } + .join(", ") - "[" ++ res ++ "]" -} + "[" ++ res ++ "]" + } From 5cb7ae816e016f4fcb0a71bdca04d46747fcc8b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Fri, 4 Oct 2024 13:18:28 +0200 Subject: [PATCH 10/29] Add internal namespace for set, fix missing brace --- libraries/common/map.effekt | 1 + libraries/common/set.effekt | 50 +++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/libraries/common/map.effekt b/libraries/common/map.effekt index 815a49c49..bd07713a8 100644 --- a/libraries/common/map.effekt +++ b/libraries/common/map.effekt @@ -714,3 +714,4 @@ namespace internal { "[" ++ res ++ "]" } +} \ No newline at end of file diff --git a/libraries/common/set.effekt b/libraries/common/set.effekt index 9a2de15a3..1e4da7e85 100644 --- a/libraries/common/set.effekt +++ b/libraries/common/set.effekt @@ -16,7 +16,7 @@ def empty[A](): Set[A] = { /// /// O(1) def isEmpty[A](s: Set[A]): Bool = { - s.viaInternal { m => + s.internal::via { m => m.isEmpty() } } @@ -25,7 +25,7 @@ def isEmpty[A](s: Set[A]): Bool = { /// /// O(1) def nonEmpty[A](s: Set[A]): Bool = { - s.viaInternal { m => + s.internal::via { m => m.nonEmpty() } } @@ -42,7 +42,7 @@ def singleton[A](a: A): Set[A] = { /// /// O(log N) def insert[A](s: Set[A], a: A): Set[A] = { - s.modifyInternal { m => + s.internal::viaInplace { m => put(m, a, ()) } } @@ -93,7 +93,7 @@ def any[A](s: Set[A]) { predicate: A => Bool }: Bool = { /// /// O(log N) def contains[A](s: Set[A], a: A): Bool = { - s.viaInternal { m => + s.internal::via { m => m.contains(a) } } @@ -119,7 +119,7 @@ def superset[A](s1: Set[A], s2: Set[A]): Bool = { /// /// O(log N) def delete[A](s: Set[A], a: A): Set[A] = { - s.modifyInternal { m => + s.internal::viaInplace { m => m.delete(a) } } @@ -141,7 +141,7 @@ def deleteMany[A](s: Set[A], list: List[A]): Set[A] = { /// /// O(N) def foreach[A](s: Set[A]) { action : A => Unit }: Unit = { - s.viaInternal { m => + s.internal::via { m => m.foreach { (k, _v) => action(k) } @@ -152,7 +152,7 @@ def foreach[A](s: Set[A]) { action : A => Unit }: Unit = { /// /// O(N) def size[A](s: Set[A]): Int = { - s.viaInternal { m => + s.internal::via { m => m.size() } } @@ -162,7 +162,7 @@ def size[A](s: Set[A]): Int = { /// /// O(N) def toMap[K, V](keys: Set[K]) { value : K => V }: Map[K, V] = { - keys.viaInternal { m => + keys.internal::via { m => m.map { (k, _v) => value(k) } } } @@ -172,8 +172,8 @@ def toMap[K, V](keys: Set[K]) { value : K => V }: Map[K, V] = { /// /// O(???) def difference[A](s1: Set[A], s2: Set[A]): Set[A] = { - s1.modifyInternal { m1 => - s2.viaInternal { m2 => + s1.internal::viaInplace { m1 => + s2.internal::via { m2 => m1.difference(m2) } } @@ -183,8 +183,8 @@ def difference[A](s1: Set[A], s2: Set[A]): Set[A] = { /// /// O(???) def union[A](s1: Set[A], s2: Set[A]): Set[A] = { - s1.modifyInternal { m1 => - s2.viaInternal { m2 => + s1.internal::viaInplace { m1 => + s2.internal::via { m2 => m1.union(m2) } } @@ -193,16 +193,18 @@ def union[A](s1: Set[A], s2: Set[A]): Set[A] = { // ------------- // Internal -/// Internal: inline at all costs -/// Function `f` is only used linearly, the set `s` is dropped. -def viaInternal[A, R](s: Set[A]) { f : Map[A, Unit] => R }: R = { - val Set(internal) = s - f(internal) -} +namespace internal { + /// Internal: inline at all costs + /// Function `f` is only used linearly, the set `s` is dropped. + def via[A, R](s: Set[A]) { f : Map[A, Unit] => R }: R = { + val Set(internal) = s + f(internal) + } -/// Internal: inline at all costs -/// Uses `viaInternal` inside and wraps the result in a `Set`. -/// Ideally, this would be in-place (when possible) since we don't change the type? -def modifyInternal[A](s: Set[A]) { f : Map[A, Unit] => Map[A, Unit] }: Set[A] = { - s.viaInternal { m => Set(f(m)) } -} + /// Internal: inline at all costs + /// Uses `via` inside and wraps the result in a `Set`. + /// Ideally, this would be in-place (when possible) since we don't change the type? + def viaInplace[A](s: Set[A]) { f : Map[A, Unit] => Map[A, Unit] }: Set[A] = { + s.via { m => Set(f(m)) } + } +} \ No newline at end of file From 32658376ea641867b7ceeb3b5920bf6f88643c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Fri, 4 Oct 2024 13:53:41 +0200 Subject: [PATCH 11/29] Re-add forgotten unit test for treesets --- examples/stdlib/set/unique.check | 18 +++++++ examples/stdlib/set/unique.effekt | 85 +++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 examples/stdlib/set/unique.check create mode 100644 examples/stdlib/set/unique.effekt diff --git a/examples/stdlib/set/unique.check b/examples/stdlib/set/unique.check new file mode 100644 index 000000000..72ee25591 --- /dev/null +++ b/examples/stdlib/set/unique.check @@ -0,0 +1,18 @@ +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())))))))))))))))))))))))) +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())))))))))))))))))))))))) +true +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()))))))))))))))))))))) +true +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(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 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())))))))))))))))))))))))))))))))))))))))))) \ No newline at end of file diff --git a/examples/stdlib/set/unique.effekt b/examples/stdlib/set/unique.effekt new file mode 100644 index 000000000..55ae008c9 --- /dev/null +++ b/examples/stdlib/set/unique.effekt @@ -0,0 +1,85 @@ +import set + +def unique(words: List[String]): Set[String] = { + var s: Set[String] = set::empty(); + + list::foreach(words) { word => + s = s.insert(word) + } + + s +} + +def main() = { + // John F. Kennedy's Inaugural Address Jan 20 1961; modified for Effekt + val words: 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 uniqueWords: Set[String] = unique(words) + + def test(word: String) = { + val present = uniqueWords.contains(word) + println(word ++ ": " ++ present.show) + } + + test("ask") + test("can") + test("you") + test("see") + test("do") + test("Effekt") + + // --- + + println(uniqueWords.toList) + println(uniqueWords.toList.isSortedBy { (x, y) => x <= y }) + + println(set::fromList(words).toList) + println(set::fromList(words).toList.isSortedBy { (x, y) => x <= y }) + + // --- + + // around the World / Harder, Better, Faster, Stronger by Daft Punk from Alive 2007 + val alive2007: 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(alive2007) + println(uniqueLyrics.toList) + println(uniqueLyrics.toList.isSortedBy { (a, b) => a <= b }) + + println("lyrics / speech:") + println(uniqueLyrics.difference(uniqueWords).toList) + + println("speech / lyrics:") + println(uniqueLyrics.difference(uniqueWords).toList) + + println("speech u lyrics:") + println(uniqueLyrics.union(uniqueWords).toList) +} From dac0e5e1be9e5782412137406f0631ff09e33f03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Fri, 4 Oct 2024 15:38:42 +0200 Subject: [PATCH 12/29] Ignore map&set stdlib tests on Chez and LLVM --- effekt/jvm/src/test/scala/effekt/StdlibTests.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/effekt/jvm/src/test/scala/effekt/StdlibTests.scala b/effekt/jvm/src/test/scala/effekt/StdlibTests.scala index ff07b1fea..58fd5cce4 100644 --- a/effekt/jvm/src/test/scala/effekt/StdlibTests.scala +++ b/effekt/jvm/src/test/scala/effekt/StdlibTests.scala @@ -26,7 +26,8 @@ abstract class StdlibChezTests extends StdlibTests { examplesDir / "stdlib" / "io", examplesDir / "stdlib" / "stream" / "characters.effekt", examplesDir / "stdlib" / "stream" / "fuse_newlines.effekt", - examplesDir / "stdlib" / "map" + examplesDir / "stdlib" / "map", + examplesDir / "stdlib" / "set", ) } class StdlibChezSchemeMonadicTests extends StdlibChezTests { @@ -68,6 +69,7 @@ class StdlibLLVMTests extends StdlibTests { examplesDir / "stdlib" / "string" / "unicode.effekt", // Not implemented yet - examplesDir / "stdlib" / "map" + examplesDir / "stdlib" / "map", + examplesDir / "stdlib" / "set", ) } From 678e4d6a453bab08a0889f0b2bdcad333e7fabdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Fri, 4 Oct 2024 15:39:48 +0200 Subject: [PATCH 13/29] Reformat treemap source --- libraries/common/map.effekt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/libraries/common/map.effekt b/libraries/common/map.effekt index bd07713a8..fa0bc7e4f 100644 --- a/libraries/common/map.effekt +++ b/libraries/common/map.effekt @@ -420,8 +420,7 @@ def difference[K, V](m1: Map[K, V], m2: Map[K, V]): Map[K, V] = { /// /// O(???) def union[K, V](m1: Map[K, V], m2: Map[K, V]) { combine : (K, V, V) => V }: Map[K, V] = { - // Internal function similar to `putWithKey`, but right-biased. - // Only used here, recursively. + /// Internal function similar to `putWithKey`, but right-biased. Only used here, recursively. def putWithKeyR(m: Map[K, V], k: K, v: V): Map[K, V] = { m match { case Tip() => singleton(k, v) @@ -454,9 +453,8 @@ def union[K, V](m1: Map[K, V], m2: Map[K, V]) { combine : (K, V, V) => V }: Map[ /// When a key is associated with a value in both `m1` and `m2`, the new value is determined using the `combine` function. /// /// O(???) -def union[K, V](m1: Map[K, V], m2: Map[K, V]) { combine : (V, V) => V }: Map[K, V] = { +def union[K, V](m1: Map[K, V], m2: Map[K, V]) { combine : (V, V) => V }: Map[K, V] = union(m1, m2) { (k, v1, v2) => combine(v1, v2) } -} /// Construct a new map which contains the elements of both `m1` and `m2`. /// Left-biased: Uses values from `m1` if there are duplicate keys. @@ -608,7 +606,7 @@ def glue[K, V](l: Map[K, V], r: Map[K, V]): Map[K, V] = { } } -def splitLookup[K, V](m: Map[K, V], k: K): Tuple3[Map[K, V], Option[V], Map[K, V]] = { +def splitLookup[K, V](m: Map[K, V], k: K): (Map[K, V], Option[V], Map[K, V]) = { m match { case Tip() => (Tip(), None(), Tip()) case Bin(_, k2, v2, l, r) => From 0f15df3de13309ff34c9df7caa41b3648e8bf6e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Fri, 4 Oct 2024 15:40:31 +0200 Subject: [PATCH 14/29] Rename speech in tests --- examples/stdlib/map/counter.effekt | 4 ++-- examples/stdlib/set/unique.effekt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/stdlib/map/counter.effekt b/examples/stdlib/map/counter.effekt index 87ab092b3..e108e5983 100644 --- a/examples/stdlib/map/counter.effekt +++ b/examples/stdlib/map/counter.effekt @@ -12,7 +12,7 @@ def counter(words: List[String]): Map[String, Int] = { def main() = { // John F. Kennedy's Inaugural Address, Jan 20, 1961; modified for Effekt - val words: List[String] = [ + 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", @@ -21,7 +21,7 @@ def main() = { "but", "what", "together", "we", "can", "do", "for", "the", "programs", "of", "man" ] - val ctr: Map[String, Int] = counter(words) + val ctr: Map[String, Int] = counter(speech) def test(word: String) = { val count = ctr.getOrElse(word) { 0 } diff --git a/examples/stdlib/set/unique.effekt b/examples/stdlib/set/unique.effekt index 55ae008c9..8a0c6fc64 100644 --- a/examples/stdlib/set/unique.effekt +++ b/examples/stdlib/set/unique.effekt @@ -12,7 +12,7 @@ def unique(words: List[String]): Set[String] = { def main() = { // John F. Kennedy's Inaugural Address Jan 20 1961; modified for Effekt - val words: List[String] = [ + 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", @@ -21,10 +21,10 @@ def main() = { "but", "what", "together", "we", "can", "do", "for", "the", "programs", "of", "man" ] - val uniqueWords: Set[String] = unique(words) + val uniqueSpeech: Set[String] = unique(speech) def test(word: String) = { - val present = uniqueWords.contains(word) + val present = uniqueSpeech.contains(word) println(word ++ ": " ++ present.show) } From 7f6559c94566841283867e2cf5634616624bdc33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Fri, 4 Oct 2024 15:40:51 +0200 Subject: [PATCH 15/29] Add intersection for treeset and treemap, incl. tests --- examples/stdlib/set/unique.check | 12 ++++++---- examples/stdlib/set/unique.effekt | 40 ++++++++++++++++++++++--------- libraries/common/map.effekt | 35 ++++++++++++++++++++++++--- libraries/common/set.effekt | 11 +++++++++ 4 files changed, 80 insertions(+), 18 deletions(-) diff --git a/examples/stdlib/set/unique.check b/examples/stdlib/set/unique.check index 72ee25591..819832611 100644 --- a/examples/stdlib/set/unique.check +++ b/examples/stdlib/set/unique.check @@ -4,15 +4,19 @@ 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())))))))))))))))))))))))) -true +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())))))))))))))))))))))))) -true +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()))))))))))))))))))))) -true +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(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())))))))))))))))))) +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())))))))))))))))))))))))))))))))))))))))))) \ No newline at end of file diff --git a/examples/stdlib/set/unique.effekt b/examples/stdlib/set/unique.effekt index 8a0c6fc64..a7b000b40 100644 --- a/examples/stdlib/set/unique.effekt +++ b/examples/stdlib/set/unique.effekt @@ -36,17 +36,28 @@ def main() = { test("Effekt") // --- + println("") + + def testSorted(s: Set[String]) = { + val sorted = s.toList.isSortedBy { (x, y) => x <= y } + if (sorted) { + println("sorted") + } else { + println("unsorted") + } + } - println(uniqueWords.toList) - println(uniqueWords.toList.isSortedBy { (x, y) => x <= y }) + println(uniqueSpeech.toList) + testSorted(uniqueSpeech) - println(set::fromList(words).toList) - println(set::fromList(words).toList.isSortedBy { (x, y) => x <= y }) + println(set::fromList(speech).toList) + testSorted(set::fromList(speech)) // --- + println("") - // around the World / Harder, Better, Faster, Stronger by Daft Punk from Alive 2007 - val alive2007: List[String] = [ + // 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", @@ -70,16 +81,23 @@ def main() = { "around", "the", "world", "around", "the", "world" ] - val uniqueLyrics = unique(alive2007) + val uniqueLyrics = unique(lyrics) + println(uniqueLyrics.toList) - println(uniqueLyrics.toList.isSortedBy { (a, b) => a <= b }) + testSorted(uniqueLyrics) + + // --- + println("") println("lyrics / speech:") - println(uniqueLyrics.difference(uniqueWords).toList) + println(uniqueLyrics.difference(uniqueSpeech).toList) println("speech / lyrics:") - println(uniqueLyrics.difference(uniqueWords).toList) + println(uniqueSpeech.difference(uniqueLyrics).toList) + + println("speech n lyrics:") + println(uniqueLyrics.intersection(uniqueSpeech).toList) println("speech u lyrics:") - println(uniqueLyrics.union(uniqueWords).toList) + println(uniqueLyrics.union(uniqueSpeech).toList) } diff --git a/libraries/common/map.effekt b/libraries/common/map.effekt index fa0bc7e4f..63457c50a 100644 --- a/libraries/common/map.effekt +++ b/libraries/common/map.effekt @@ -460,9 +460,38 @@ def union[K, V](m1: Map[K, V], m2: Map[K, V]) { combine : (V, V) => V }: Map[K, /// Left-biased: Uses values from `m1` if there are duplicate keys. /// /// O(???) -def union[K, V](m1: Map[K, V], m2: Map[K, V]): Map[K, V] = { - union(m1, m2) { (k, v1, v2) => v1 } -} +def union[K, V](m1: Map[K, V], m2: Map[K, V]): Map[K, V] = + union[K, V](m1, m2) { (k, v1, v2) => v1 } + +/// Construct a new map which combines all elements that are in both `m1` and `m2` using the `combine` function. +/// +/// O(???) +def intersection[K, A, B, C](m1: Map[K, A], m2: Map[K, B]) { combine: (K, A, B) => C }: Map[K, C] = + (m1, m2) match { + case (Tip(), _) => Tip() + case (_, Tip()) => Tip() + case (Bin(_, k, v1, l1, r1), _) => + val (l2, mid, r2) = m2.splitLookup(k) + val left = l1.intersection(l2) { combine } + val right = r1.intersection(r2) { combine } + mid match { + case Some(v2) => link(k, combine(k, v1, v2), left, right) + case None() => link2(left, right) + } + } + +/// Construct a new map which combines all elements that are in both `m1` and `m2` using the `combine` function. +/// +/// O(???) +def intersection[K, A, B, C](m1: Map[K, A], m2: Map[K, B]) { combine: (A, B) => C }: Map[K, C] = + m1.intersection[K, A, B, C](m2) { (k, v1, v2) => combine(v1, v2) } + +/// Construct a new map which combines all elements that are in both `m1` and `m2`. +/// Left-biased: Always uses values from `m1`. +/// +/// O(???) +def intersection[K, A, B](m1: Map[K, A], m2: Map[K, B]): Map[K, A] = + m1.intersection[K, A, B, A](m2) { (k, v1, v2) => v1 } // ------------- // Internal diff --git a/libraries/common/set.effekt b/libraries/common/set.effekt index 1e4da7e85..7cee1d789 100644 --- a/libraries/common/set.effekt +++ b/libraries/common/set.effekt @@ -190,6 +190,17 @@ def union[A](s1: Set[A], s2: Set[A]): Set[A] = { } } +/// Construct a new set which contains only elements which are in both of `s1` and `s2`. +/// +/// O(???) +def intersection[A](s1: Set[A], s2: Set[A]): Set[A] = { + s1.internal::viaInplace { m1 => + s2.internal::via { m2 => + m1.intersection(m2) + } + } +} + // ------------- // Internal From 209bfc808e2ceeea8ee846709792ce33fe4f70a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Mon, 25 Nov 2024 17:24:09 +0100 Subject: [PATCH 16/29] Plug map&set into 'stream' --- examples/stdlib/stream/map.check | 7 +++++ examples/stdlib/stream/map.effekt | 17 ++++++++++++ libraries/common/stream.effekt | 45 +++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 examples/stdlib/stream/map.check create mode 100644 examples/stdlib/stream/map.effekt diff --git a/examples/stdlib/stream/map.check b/examples/stdlib/stream/map.check new file mode 100644 index 000000000..2a7d25cb9 --- /dev/null +++ b/examples/stdlib/stream/map.check @@ -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 \ No newline at end of file diff --git a/examples/stdlib/stream/map.effekt b/examples/stdlib/stream/map.effekt new file mode 100644 index 000000000..74a3cb119 --- /dev/null +++ b/examples/stdlib/stream/map.effekt @@ -0,0 +1,17 @@ +import stream +import map + +def main() = { + val m = map::fromList([(1, 'H'), (2, 'e'), (3, 'l'), (4, 'l'), (5, 'o')]) + + for[(Int, Char)] { each(m) } { case (k, v) => + println(show(k) ++ ": " ++ show(v) ++ " (" ++ show(v.toInt) ++ ")") + } + + val newMap = collectMap[Int, Char] { each(m) } + println(map::internal::prettyPairs(newMap.toList)) + + val hello: String = collectString { eachValue(m) } + println(hello) +} + diff --git a/libraries/common/stream.effekt b/libraries/common/stream.effekt index 6337c1f1c..e501d03a8 100644 --- a/libraries/common/stream.effekt +++ b/libraries/common/stream.effekt @@ -2,6 +2,9 @@ module stream import array import bytearray +import map +import set + import io/filesystem import io/error @@ -74,6 +77,26 @@ def each(bytes: ByteArray): Unit / emit[Byte] = { go(0) } +/// Turns a `map` into a producer of a push stream +/// of `(key, value)` pairs by emitting each contained *in order*. +def each[K, V](map: Map[K, V]): Unit / emit[(K, V)] = + map.foreach { (k, v) => do emit((k, v)) } + +/// Turns a `map` into a producer of a push stream +/// of its keys by emitting each contained *in order*. +def eachKey[K, V](map: Map[K, V]): Unit / emit[K] = + map.foreach { (k, _v) => do emit(k) } + +/// Turns a `map` into a producer of a push stream +/// of its values by emitting each contained. +def eachValue[K, V](map: Map[K, V]): Unit / emit[V] = + map.foreach { (_k, v) => do emit(v) } + +/// Turns a `set` into a producer of a push stream +/// by emitting each contained *in order*. +def each[A](set: Set[A]): Unit / emit[A] = + set.foreach { x => do emit(x) } + // not option // not dequeue // not queue @@ -238,6 +261,28 @@ def feed[T, R](list: List[T]) { reader: () => R / read[T] }: R = { } } +def collectMap[K, V, R] { stream: () => R / emit[(K, V)] }: (R, Map[K, V]) = + try { + (stream(), map::empty()) + } with emit[(K, V)] { case (k, v) => + val (r, map) = resume(()); + (r, map.put(k, v)) + } + +def collectMap[K, V] { stream: () => Any / emit[(K, V)] }: Map[K, V] = + collectMap[K, V, Any]{stream}.second + +def collectSet[A, R] { stream: () => R / emit[A] }: (R, Set[A]) = + try { + (stream(), set::empty()) + } with emit[A] { (v) => + val (r, set) = resume(()); + (r, set.insert(v)) + } + +def collectSet[A] { stream: () => Any / emit[A] }: Set[A] = + collectSet[A, Any]{stream}.second + def feed[T, R](array: Array[T]) { reader: () => R / read[T] }: R = { var i = 0 try { From 6bfdbb3b1cb90deeb0bcda4325f01103987eeb52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Tue, 10 Dec 2024 12:29:35 +0100 Subject: [PATCH 17/29] Fix set/unique test --- examples/stdlib/set/unique.check | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/stdlib/set/unique.check b/examples/stdlib/set/unique.check index 819832611..ccce73c03 100644 --- a/examples/stdlib/set/unique.check +++ b/examples/stdlib/set/unique.check @@ -12,6 +12,7 @@ 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: @@ -19,4 +20,4 @@ Cons(Effekt, Cons(and, Cons(ask, Cons(but, Cons(can, Cons(fellow, Cons(for, Cons 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())))))))))))))))))))))))))))))))))))))))))) \ No newline at end of file +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())))))))))))))))))))))))))))))))))))))))))) From 537419e1f564db56ec4f897df291953df0b11e78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Tue, 10 Dec 2024 12:31:39 +0100 Subject: [PATCH 18/29] Ignore added stream tests on Chez & LLVM --- effekt/jvm/src/test/scala/effekt/StdlibTests.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/effekt/jvm/src/test/scala/effekt/StdlibTests.scala b/effekt/jvm/src/test/scala/effekt/StdlibTests.scala index 58fd5cce4..f0d12dbdb 100644 --- a/effekt/jvm/src/test/scala/effekt/StdlibTests.scala +++ b/effekt/jvm/src/test/scala/effekt/StdlibTests.scala @@ -26,6 +26,7 @@ abstract class StdlibChezTests extends StdlibTests { examplesDir / "stdlib" / "io", examplesDir / "stdlib" / "stream" / "characters.effekt", examplesDir / "stdlib" / "stream" / "fuse_newlines.effekt", + examplesDir / "stdlib" / "stream" / "map.effekt", examplesDir / "stdlib" / "map", examplesDir / "stdlib" / "set", ) @@ -50,6 +51,7 @@ class StdlibLLVMTests extends StdlibTests { examplesDir / "stdlib" / "bytearray" / "bytearray.effekt", examplesDir / "stdlib" / "stream" / "characters.effekt", examplesDir / "stdlib" / "stream" / "fuse_newlines.effekt", + examplesDir / "stdlib" / "stream" / "map.effekt", examplesDir / "stdlib" / "io" / "filesystem" / "async_file_io.effekt", examplesDir / "stdlib" / "io" / "filesystem" / "files.effekt", examplesDir / "stdlib" / "io" / "filesystem" / "wordcount.effekt", From 62560d11c25f1922e7ad38b98369f8887a72d35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Tue, 10 Dec 2024 12:33:10 +0100 Subject: [PATCH 19/29] Fix expected output in stream/map --- examples/stdlib/stream/map.check | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/stdlib/stream/map.check b/examples/stdlib/stream/map.check index 2a7d25cb9..c4a76188b 100644 --- a/examples/stdlib/stream/map.check +++ b/examples/stdlib/stream/map.check @@ -4,4 +4,4 @@ 4: l (108) 5: o (111) [1 → H, 2 → e, 3 → l, 4 -> l, 5 -> o] -hello \ No newline at end of file +Hello From 47f5e300af1be9f46b65b96ff90db00a3c8c8553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Tue, 10 Dec 2024 13:16:48 +0100 Subject: [PATCH 20/29] Fix expected output in stream/map (again) --- examples/stdlib/stream/map.check | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/stdlib/stream/map.check b/examples/stdlib/stream/map.check index c4a76188b..4f7e2bb11 100644 --- a/examples/stdlib/stream/map.check +++ b/examples/stdlib/stream/map.check @@ -3,5 +3,5 @@ 3: l (108) 4: l (108) 5: o (111) -[1 → H, 2 → e, 3 → l, 4 -> l, 5 -> o] +[1 → 72, 2 → 101, 3 → 108, 4 → 108, 5 → 111] Hello From f534faab3ebd232f2f3441da8967a91e60378914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Thu, 12 Dec 2024 11:28:35 +0100 Subject: [PATCH 21/29] Add a few more useful functions --- libraries/common/map.effekt | 27 +++++++++++++++++++++++++++ libraries/common/set.effekt | 14 ++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/libraries/common/map.effekt b/libraries/common/map.effekt index 63457c50a..2ffd6e363 100644 --- a/libraries/common/map.effekt +++ b/libraries/common/map.effekt @@ -152,6 +152,20 @@ def getMax[K, V](m: Map[K, V]): Option[(K, V)] = { } } +/// Forgets the values of a map, setting them all to `(): Unit`. +/// Used by `set`s internally. +/// +/// Law: `m.forget === m.map { (_k, _v) => () }` +/// +/// O(N) +def forget[K, V](m: Map[K, V]): Map[K, Unit] = { + m match { + case Tip() => Tip() + case Bin(size, k, v, l, r) => + Bin(size, k, (), l.forget, r.forget) + } +} + /// Map a function `f` over values in map `m`. /// /// O(N) @@ -170,6 +184,19 @@ def map[K, V1, V2](m: Map[K, V1]) { f : V1 => V2 }: Map[K, V2] = { m.map { (_k, v) => f(v) } } +/// Map a function `f` over values in map `m`, keeping only the values where `f` returns `Some(...)` +/// +/// O(N) +def mapMaybe[K, V1, V2](m: Map[K, V1]) { f : (K, V1) => Option[V2] }: Map[K, V2] = + m match { + case Tip() => Tip() + case Bin(size, k, v, l, r) => f(k, v) match { + case Some(v2) => link(k, v2, l.mapMaybe {f}, r.mapMaybe {f}) + case None() => link2(l.mapMaybe {f}, r.mapMaybe {f}) + } + } + + /// Traverse all keys and their associated values in map `m` in order, /// running the function `action` on a key and its associated value. /// diff --git a/libraries/common/set.effekt b/libraries/common/set.effekt index 7cee1d789..52afc3932 100644 --- a/libraries/common/set.effekt +++ b/libraries/common/set.effekt @@ -47,6 +47,20 @@ def insert[A](s: Set[A], a: A): Set[A] = { } } +/// Create a set from a given map by ignoring the values. +/// +/// O(N) +def fromMapKeys[K, V](map: Map[K, V]): Set[K] = + Set(map.forget) + +/// Create a map from a set and a function. +/// +/// O(N) +def toMap[K, V](keys: Set[K]) { valueOf: K => V }: Map[K, V] = + keys.internal::via { m => + m.map { (k, _v) => valueOf(k) } + } + /// Create a set from a given list. /// /// O(N log N) From daf398c561a75a2d59c82af0fbf17a7567a9102b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Thu, 12 Dec 2024 12:40:14 +0100 Subject: [PATCH 22/29] Add a few more tests for weird/complex operations --- examples/stdlib/map/minmax.check | 2 ++ examples/stdlib/map/minmax.effekt | 16 ++++++++++++++++ examples/stdlib/map/setops.check | 8 ++++++++ examples/stdlib/map/setops.effekt | 20 ++++++++++++++++++++ 4 files changed, 46 insertions(+) create mode 100644 examples/stdlib/map/minmax.check create mode 100644 examples/stdlib/map/minmax.effekt create mode 100644 examples/stdlib/map/setops.check create mode 100644 examples/stdlib/map/setops.effekt diff --git a/examples/stdlib/map/minmax.check b/examples/stdlib/map/minmax.check new file mode 100644 index 000000000..5ff1cc121 --- /dev/null +++ b/examples/stdlib/map/minmax.check @@ -0,0 +1,2 @@ +Min: 5 -> Five +Max: 30 -> Thirty diff --git a/examples/stdlib/map/minmax.effekt b/examples/stdlib/map/minmax.effekt new file mode 100644 index 000000000..ede299a41 --- /dev/null +++ b/examples/stdlib/map/minmax.effekt @@ -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")]) + + val min: Option[(Int, String)] = m.getMin + val max: Option[(Int, String)] = m.getMax + + println("Min: " ++ min.show) + println("Max: " ++ max.show) +} diff --git a/examples/stdlib/map/setops.check b/examples/stdlib/map/setops.check new file mode 100644 index 000000000..7ad007279 --- /dev/null +++ b/examples/stdlib/map/setops.check @@ -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] diff --git a/examples/stdlib/map/setops.effekt b/examples/stdlib/map/setops.effekt new file mode 100644 index 000000000..a8658b3d5 --- /dev/null +++ b/examples/stdlib/map/setops.effekt @@ -0,0 +1,20 @@ +import map + +def main() = { + // Create multiple maps + val map1 = map::fromList([(1, "apple"), (2, "banana"), (3, "cherry")]) + val map2 = map::fromList([(2, "berry"), (3, "date"), (4, "elderberry")]) + + // Test union with different combine strategies + println("Union (keeping first value):") + println(map::internal::prettyPairs(map1.union(map2).toList)) + + println("\nUnion (combining values):") + val combinedMap = map1.union(map2) { (k, v1, v2) => v1 ++ "/" ++ v2 } + println(map::internal::prettyPairs(combinedMap.toList)) + + // Test intersection + println("\nIntersection (combining keys):") + val intersectedMap = map1.intersection(map2) { (k, v1, v2) => v1 ++ "-" ++ v2 } + println(map::internal::prettyPairs(intersectedMap.toList)) +} From cdd3e6b8b8866d08a5e6aba0a4f30fda43f9f674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Thu, 12 Dec 2024 14:09:25 +0100 Subject: [PATCH 23/29] Add map::filter & a caching test --- examples/stdlib/map/cache.check | 7 +++++ examples/stdlib/map/cache.effekt | 49 ++++++++++++++++++++++++++++++++ libraries/common/map.effekt | 34 ++++++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 examples/stdlib/map/cache.check create mode 100644 examples/stdlib/map/cache.effekt diff --git a/examples/stdlib/map/cache.check b/examples/stdlib/map/cache.check new file mode 100644 index 000000000..c817f5786 --- /dev/null +++ b/examples/stdlib/map/cache.check @@ -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 diff --git a/examples/stdlib/map/cache.effekt b/examples/stdlib/map/cache.effekt new file mode 100644 index 000000000..dcdfff816 --- /dev/null +++ b/examples/stdlib/map/cache.effekt @@ -0,0 +1,49 @@ +import map + +record CacheEntry[V](value: V, timestamp: Int) + +effect time(): Int + +def main() = { + var currentTime = 0 + try { + var cache: Map[String, CacheEntry[String]] = map::empty() + 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) + } +} diff --git a/libraries/common/map.effekt b/libraries/common/map.effekt index 2ffd6e363..92a63cda6 100644 --- a/libraries/common/map.effekt +++ b/libraries/common/map.effekt @@ -196,6 +196,40 @@ def mapMaybe[K, V1, V2](m: Map[K, V1]) { f : (K, V1) => Option[V2] }: Map[K, V2] } } +/// Filters a map `m` with a `shouldKeep` function, +/// keeping only the elements where `shouldKeep` returns `true`. +/// +/// Law: `m.filter { f } === m.mapMaybe { (k, v) => if (f(k, v)) Some(v) else None() }` +/// +/// O(N) +def filter[K, V](m: Map[K, V]) { shouldKeep: (K, V) => Bool }: Map[K, V] = { + def go(tree: Map[K, V]): (Map[K, V], Bool) = tree match { + case Tip() => (Tip(), false) + case Bin(size, k, v, l, r) => + val (l2, lchanged) = go(l) + val (r2, rchanged) = go(r) + if (shouldKeep(k, v)) { + val changed = lchanged || rchanged + val tree2 = if (changed) { + link(k, v, l2, r2) + } else { + tree + } + (tree2, changed) + } else { + (link2(l2, r2), true) + } + } + + go(m).first +} + +/// Filters a map `m` with a `shouldKeep` function, +/// keeping only the values where `shouldKeep` returns `true`. +/// +/// O(N) +def filter[K, V](m: Map[K, V]) { shouldKeep: V => Bool }: Map[K, V] = + m.filter { (_k, v) => shouldKeep(v) } /// Traverse all keys and their associated values in map `m` in order, /// running the function `action` on a key and its associated value. From 127c9826185b0eef0fe384567c3fdffa0aeb2cbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Fri, 13 Dec 2024 20:05:38 +0100 Subject: [PATCH 24/29] Use an explicit comparison function --- libraries/common/effekt.effekt | 5 + libraries/common/map.effekt | 1241 ++++++++++++++++++-------------- libraries/common/set.effekt | 211 +++--- 3 files changed, 823 insertions(+), 634 deletions(-) diff --git a/libraries/common/effekt.effekt b/libraries/common/effekt.effekt index 65505277f..4b9c0f19f 100644 --- a/libraries/common/effekt.effekt +++ b/libraries/common/effekt.effekt @@ -156,6 +156,11 @@ type Ordering { Greater() } +def compareInt(n: Int, m: Int) = + if (n == m) Equal() + else if (n < m) Less() + else Greater() + extern pure def genericCompareImpl[R](x: R, y: R): Int = js "$effekt.compare(${x}, ${y})" diff --git a/libraries/common/map.effekt b/libraries/common/map.effekt index 92a63cda6..efe8847e4 100644 --- a/libraries/common/map.effekt +++ b/libraries/common/map.effekt @@ -1,200 +1,129 @@ module map /// Ordered finite immutable map, backed by balanced binary trees of logarithmic depth. -/// Currently use `genericCompare` as a comparison primitive, which means the map is now working only in the JS backend. +record Map[K, V](tree: internal::Tree[K, V], compare: (K, K) => Ordering at {}) + +/// Create a new empty map using a pure, first-class comparison function. /// -/// Please don't use the internal constructors `Bin` & `Tip` directly, -/// they might change down the line and are not considered stable. -type Map[K, V] { - Bin(size: Int, k: K, v: V, left: Map[K, V], right: Map[K, V]); - Tip() -} +/// O(1) +def empty[K, V](compare: (K, K) => Ordering at {}): Map[K, V] = + Map(internal::empty(), compare) -/// Create a new empty map. +/// Create a new empty map using a generic comparison function. +/// Only available on JavaScript backends! /// /// O(1) -def empty[K, V](): Map[K, V] = { - Tip() -} +def emptyGeneric[K, V](): Map[K, V] = + Map( + internal::empty(), + box { (left: K, right: K) => genericCompare(left, right) } + ) /// Check if map `m` is empty. /// /// O(1) -def isEmpty[K, V](m: Map[K, V]): Bool = { - m match { - case Tip() => true - case _ => false - } -} +def isEmpty[K, V](m: Map[K, V]): Bool = internal::isEmpty(m.tree) /// Check if map `m` is nonempty. /// /// O(1) -def nonEmpty[K, V](m: Map[K, V]): Bool = { - m match { - case Tip() => false - case _ => true - } -} +def nonEmpty[K, V](m: Map[K, V]): Bool = internal::nonEmpty(m.tree) -/// Create a new map containing only the mapping from `k` to `v`. +/// Create a new map containing the mapping from `k` to `v` and a pure, first-class comparison function. /// /// O(1) -def singleton[K, V](k: K, v: V): Map[K, V] = { - Bin(1, k, v, Tip(), Tip()) -} +def singleton[K, V](k: K, v: V, compare: (K, K) => Ordering at {}): Map[K, V] = + Map(internal::singleton(k, v), compare) + +/// Create a new map containing the mapping from `k` to `v` using a generic comparison function. +/// Only available on the JavaScript backends! +/// +/// O(1) +def singletonGeneric[K, V](k: K, v: V): Map[K, V] = + Map( + internal::singleton(k, v), + box { (left: K, right: K) => genericCompare(left, right) } + ) /// Get the size of the map (the number of keys/values). /// /// O(1) -def size[K, V](m: Map[K, V]): Int = { - m match { - case Tip() => 0 - case Bin(size, _, _, _, _) => size - } -} +def size[K, V](m: Map[K, V]): Int = internal::size(m.tree) /// Insert a new key `k` and value `v` into the map `m`. /// If the key `k` is already present in `m`, its associated value is replaced with `v`. /// /// O(log N) -def put[K, V](m: Map[K, V], k: K, v: V): Map[K, V] = m match { - case Tip() => singleton(k, v) - case Bin(size, k2, v2, l, r) => - genericCompare(k, k2) match { - case Less() => balance(k2, v2, put(l, k, v), r) - case Greater() => balance(k2, v2, l, put(r, k, v)) - case Equal() => Bin(size, k, v, l, r) - } +def put[K, V](m: Map[K, V], k: K, v: V): Map[K, V] = { + val newTree = internal::put(m.tree, m.compare, k, v) + Map(newTree, m.compare) } /// Insert a new key `k` and value `v` into the map `m`. /// If the key `k` is already present in `m` with value `v2`, the function `combine` is called on `k`, `v`, `v2`. /// /// O(log N) -def putWithKey[K, V](m: Map[K, V], k: K, v: V) { combine: (K, V, V) => V } : Map[K, V] = m match { - case Tip() => singleton(k, v) - case Bin(size, k2, v2, l, r) => - genericCompare(k, k2) match { - case Less() => balance(k2, v2, l.putWithKey(k, v){combine}, r) - case Greater() => balance(k2, v2, l, r.putWithKey(k, v){combine}) - case Equal() => Bin(size, k, combine(k, v, v2), l, r) - } +def putWithKey[K, V](m: Map[K, V], k: K, v: V) { combine: (K, V, V) => V }: Map[K, V] = { + val newTree = internal::putWithKey(m.tree, m.compare, k, v) {combine} + Map(newTree, m.compare) } /// Lookup the value at a key `k` in the map `m`. /// /// O(log N) -def get[K, V](m: Map[K, V], k: K): Option[V] = { - m match { - case Tip() => None() - case Bin(size, k2, v, l, r) => - genericCompare(k, k2) match { - case Less() => get(l, k) - case Greater() => get(r, k) - case Equal() => Some(v) - } - } -} +def get[K, V](m: Map[K, V], k: K): Option[V] = internal::get(m.tree, m.compare, k) /// Lookup the value at a key `k` in the map `m`. /// If there is no key, use the `default` block to retrieve a default value. /// /// O(log N) -def getOrElse[K, V](m: Map[K, V], k: K) { default : => V }: V = { - get(m, k) match { +def getOrElse[K, V](m: Map[K, V], k: K) { default: => V }: V = + internal::get(m.tree, m.compare, k) match { case None() => default() case Some(v) => v - } -} + } /// Check if map `m` contains a key `k`. /// /// O(log N) -def contains[K, V](m: Map[K, V], k: K): Bool = { - get(m, k) match { +def contains[K, V](m: Map[K, V], k: K): Bool = + internal::get(m.tree, m.compare, k) match { case None() => false case Some(v) => true } -} /// Get minimum in the map `m`. /// /// O(log N) -def getMin[K, V](m: Map[K, V]): Option[(K, V)] = { - def go(k: K, v: V, m: Map[K, V]): (K, V) = { - m match { - case Tip() => (k, v) - case Bin(_, k2, v2, l, _) => go(k2, v2, l) - } - } - - m match { - case Tip() => None() - case Bin(_, k, v, l, _) => Some(go(k, v, l)) - } -} +def getMin[K, V](m: Map[K, V]): Option[(K, V)] = internal::getMin(m.tree) /// Get maximum in the map `m`. /// /// O(log N) -def getMax[K, V](m: Map[K, V]): Option[(K, V)] = { - def go(k: K, v: V, m: Map[K, V]): (K, V) = { - m match { - case Tip() => (k, v) - case Bin(_, k2, v2, _, r) => go(k2, v2, r) - } - } - - m match { - case Tip() => None() - case Bin(_, k, v, _, r) => Some(go(k, v, r)) - } -} - -/// Forgets the values of a map, setting them all to `(): Unit`. -/// Used by `set`s internally. -/// -/// Law: `m.forget === m.map { (_k, _v) => () }` -/// -/// O(N) -def forget[K, V](m: Map[K, V]): Map[K, Unit] = { - m match { - case Tip() => Tip() - case Bin(size, k, v, l, r) => - Bin(size, k, (), l.forget, r.forget) - } -} +def getMax[K, V](m: Map[K, V]): Option[(K, V)] = internal::getMax(m.tree) -/// Map a function `f` over values in map `m`. +/// Tree a function `f` over values in map `m`. /// /// O(N) -def map[K, V1, V2](m: Map[K, V1]) { f : (K, V1) => V2 }: Map[K, V2] = { - m match { - case Tip() => Tip() - case Bin(size, k, v, l, r) => - Bin(size, k, f(k, v), l.map {f}, r.map {f}) - } +def map[K, V1, V2](m: Map[K, V1]) { f: (K, V1) => V2 }: Map[K, V2] = { + val newTree = internal::map(m.tree) {f} + Map(newTree, m.compare) } -/// Map a function `f` over values in map `m`. +/// Tree a function `f` over values in map `m`. /// /// O(N) -def map[K, V1, V2](m: Map[K, V1]) { f : V1 => V2 }: Map[K, V2] = { +def map[K, V1, V2](m: Map[K, V1]) { f: V1 => V2 }: Map[K, V2] = { m.map { (_k, v) => f(v) } } -/// Map a function `f` over values in map `m`, keeping only the values where `f` returns `Some(...)` +/// Tree a function `f` over values in map `m`, keeping only the values where `f` returns `Some(...)` /// /// O(N) -def mapMaybe[K, V1, V2](m: Map[K, V1]) { f : (K, V1) => Option[V2] }: Map[K, V2] = - m match { - case Tip() => Tip() - case Bin(size, k, v, l, r) => f(k, v) match { - case Some(v2) => link(k, v2, l.mapMaybe {f}, r.mapMaybe {f}) - case None() => link2(l.mapMaybe {f}, r.mapMaybe {f}) - } - } +def mapMaybe[K, V1, V2](m: Map[K, V1]) { f: (K, V1) => Option[V2] }: Map[K, V2] = { + val newTree = internal::mapMaybe(m.tree) {f} + Map(newTree, m.compare) +} /// Filters a map `m` with a `shouldKeep` function, /// keeping only the elements where `shouldKeep` returns `true`. @@ -203,25 +132,8 @@ def mapMaybe[K, V1, V2](m: Map[K, V1]) { f : (K, V1) => Option[V2] }: Map[K, V2] /// /// O(N) def filter[K, V](m: Map[K, V]) { shouldKeep: (K, V) => Bool }: Map[K, V] = { - def go(tree: Map[K, V]): (Map[K, V], Bool) = tree match { - case Tip() => (Tip(), false) - case Bin(size, k, v, l, r) => - val (l2, lchanged) = go(l) - val (r2, rchanged) = go(r) - if (shouldKeep(k, v)) { - val changed = lchanged || rchanged - val tree2 = if (changed) { - link(k, v, l2, r2) - } else { - tree - } - (tree2, changed) - } else { - (link2(l2, r2), true) - } - } - - go(m).first + val newTree = internal::filter(m.tree) {shouldKeep} + Map(newTree, m.compare) } /// Filters a map `m` with a `shouldKeep` function, @@ -239,18 +151,7 @@ def filter[K, V](m: Map[K, V]) { shouldKeep: V => Bool }: Map[K, V] = /// O(N) /// /// TODO: Support {Control} for early exits. -def foreach[K, V](m: Map[K, V]) { action: (K, V) => Unit }: Unit = { - def go(m: Map[K, V]): Unit = { - m match { - case Tip() => () - case Bin(_, k, v, l, r) => - go(l) - action(k, v) - go(r) - } - } - go(m) -} +def foreach[K, V](m: Map[K, V]) { action: (K, V) => Unit }: Unit = internal::foreach(m.tree) {action} /// Convert a map `m` into a list of (key, value) pairs. /// @@ -271,7 +172,7 @@ def keys[K, V](m: Map[K, V]): List[K] = { m.foreach { (k, _v) => acc = Cons(k, acc) } - acc.reverse + acc.reverse } /// Get a list of values of the map `m`. @@ -282,99 +183,28 @@ def values[K, V](m: Map[K, V]): List[V] = { m.foreach { (_k, v) => acc = Cons(v, acc) } - acc.reverse + acc.reverse } -/// Create a map from a list of (key, value) pairs. +/// Create a map from a list of (key, value) pairs and a pure, first-class comparison function /// If the list contains more than one value for the same key, /// only the last value is used in the map. /// /// O(N) if the list is sorted by key, /// O(N log N) otherwise -def fromList[K, V](pairs: List[(K, V)]): Map[K, V] = { - pairs match { - case Nil() => Tip() - case Cons((k, v), Nil()) => singleton(k, v) - case Cons((k, v), rest) => - // TODO: this function should really, **really** get inlined! - def notOrdered(k: K, pairs: List[(K, V)]) = { - pairs match { - case Nil() => false - case Cons((k2, _), _) => // k >= k2 - genericCompare(k, k2) match { - case Less() => false - case Greater() => true - case Equal() => true - } - } - } - - // Naive insertion, used for the worst-case scenario when the list is not sorted by key - def insertMany(m: Map[K, V], pairs: List[(K, V)]) = { - var mapSoFar = m - pairs.foreach { case (k, v) => - mapSoFar = mapSoFar.put(k, v) - } - mapSoFar - } +def fromList[K, V](pairs: List[(K, V)], compare: (K, K) => Ordering at {}): Map[K, V] = + Map(internal::fromList(pairs, compare), compare) - // Returns a triple `(map, xs, ys)` - // - // Invariant: At least one of `xs`, `ys` is empty. - // Moreover, if `ys` is nonempty, its keys are **not** ordered! - // Otherwise, all of the seen keys have been ordered so far. - // - // TODO: Possibly use a better type to encode the invariant? - def create(level: Int, pairs: List[(K, V)]): (Map[K, V], List[(K, V)], List[(K, V)]) = { - pairs match { - case Nil() => (Tip(), [], []) - case Cons((k, v), rest) => - if (level == 1) { - val singleton = Bin(1, k, v, Tip(), Tip()) - if (notOrdered(k, rest)) { - (singleton, [], rest) - } else { - (singleton, rest, []) - } - } else { - val res = create(level.bitwiseShr(1), pairs) - res match { - case (_, Nil(), _) => res - case (l, Cons((k2, v2), Nil()), zs) => (l.putMax(k2, v2), [], zs) - case (l, Cons((k2, v2), rest2), _) => - val xs = Cons((k2, v2), rest2) // @-pattern - - if (notOrdered(k2, rest2)) { (l, [], xs) } - else { - val (r, zs, ws) = create(level.bitwiseShr(1), rest2); - (link(k2, v2, l, r), zs, ws) - } - } - } - } - } - - def go(level: Int, m: Map[K, V], pairs: List[(K, V)]): Map[K, V] = { - pairs match { - case Nil() => m - case Cons((k, v), Nil()) => m.putMax(k, v) - case Cons((k, v), rest) => - if (notOrdered(k, rest)) { insertMany(m, pairs) } - else { - val l = m; // m is the left subtree here - val cr = create(level, rest) - cr match { - case (r, xs, Nil()) => go(level.bitwiseShl(1), link(k, v, l, r), xs) - case (r, Nil(), ys) => insertMany(link(k, v, l, r), ys) - case _ => panic("create: go: cannot happen, invariant broken!") - } - } - } - } - - if (notOrdered(k, rest)) { insertMany(singleton(k, v), rest) } - else { go(1, singleton(k, v), rest) } - } +/// Create a map from a list of (key, value) pairs and a generic comparison. +/// If the list contains more than one value for the same key, +/// only the last value is used in the map. +/// Works only on JavaScript backends! +/// +/// O(N) if the list is sorted by key, +/// O(N log N) otherwise +def fromList[K, V](pairs: List[(K, V)], compare: (K, K) => Ordering at {}): Map[K, V] = { + val compare: (K, K) => Ordering at {} = box { (left, right) => genericCompare(left, right) } + Map(internal::fromList(pairs, compare), compare) } /// Remove a key `k` from a map `m`. @@ -382,386 +212,742 @@ def fromList[K, V](pairs: List[(K, V)]): Map[K, V] = { /// /// O(log N) def delete[K, V](m: Map[K, V], k: K): Map[K, V] = { - m match { - case Tip() => Tip() - case Bin(_, k2, v2, l, r) => - genericCompare(k, k2) match { - case Less() => balance(k2, v2, l.delete(k), r) - case Greater() => balance(k2, v2, l, r.delete(k)) - case Equal() => glue(l, r) - } - } + val newTree = internal::delete(m.tree, m.compare, k) + Map(newTree, m.compare) } /// Can be used to insert, delete, or update a value. /// Law: `get(m.alter(k){f}, k) === f(get(m, k))` /// /// O(log N) -def alter[K, V](m: Map[K, V], k: K) { f : Option[V] => Option[V] }: Map[K, V] = { - m match { - case Tip() => - f(None()) match { - case None() => Tip() - case Some(v) => singleton(k, v) - } - case Bin(size, k2, v2, l, r) => - genericCompare(k, k2) match { - case Less() => balance(k2, v2, l.alter(k){f}, r) - case Greater() => balance(k2, v2, l, r.alter(k){f}) - case Equal() => - f(Some(v2)) match { - case Some(v) => Bin(size, k2, v, l, r) - case None() => glue(l, r) - } - } - } +def alter[K, V](m: Map[K, V], k: K) { f: Option[V] => Option[V] }: Map[K, V] = { + val newTree = internal::alter(m.tree, m.compare, k) {f} + Map(newTree, m.compare) } /// Update or delete a value associated with key `k` in map `m`. /// /// O(log N) def update[K, V](m: Map[K, V], k: K) { f: (K, V) => Option[V] }: Map[K, V] = { - m match { - case Tip() => Tip() - case Bin(size, k2, v2, l, r) => - genericCompare(k, k2) match { - case Less() => balance(k2, v2, l.update(k){f}, r) - case Greater() => balance(k2, v2, l, r.update(k){f}) - case Equal() => - f(k2, v2) match { - case Some(v) => Bin(size, k2, v, l, r) - case None() => glue(l, r) - } - } - } + val newTree = internal::update(m.tree, m.compare, k) {f} + Map(newTree, m.compare) } /// Update or delete a value associated with key `k` in map `m`. /// /// O(log N) -def update[K, V](m: Map[K, V], k: K) { f : V => Option[V] }: Map[K, V] = { +def update[K, V](m: Map[K, V], k: K) { f: V => Option[V] }: Map[K, V] = m.update(k) { (_k, v) => f(v) } -} /// Get `n`-th (key, value) pair in the map `m`. /// /// O(log N) -def getIndex[K, V](m: Map[K, V], n: Int): Option[(K, V)] = { - m match { - case Tip() => None() - case Bin(size, k, v, l, r) => - val sizeL = l.size() - genericCompare(sizeL, n) match { - case Less() => r.getIndex(n - (sizeL + 1)) - case Greater() => l.getIndex(n) - case Equal() => Some((k, v)) - } - } +def getIndex[K, V](m: Map[K, V], n: Int): Option[(K, V)] = + internal::getIndex(m.tree, n) + +/// Construct a new map which contains all elements of `m1` +/// except those where the key is found in `m2`. +/// Uses an explicit pure, first-class comparison function. +/// +/// O(???) +def difference[K, V](m1: Map[K, V], m2: Map[K, V], compare: (K, K) => Ordering at {}) = { + val newTree = internal::difference(m1.tree, m2.tree, compare) + Map(newTree, compare) } /// Construct a new map which contains all elements of `m1` /// except those where the key is found in `m2`. +/// Uses the comparison function from `m1`. /// /// O(???) -def difference[K, V](m1: Map[K, V], m2: Map[K, V]): Map[K, V] = { - (m1, m2) match { - case (Tip(), m2) => Tip() - case (m1, Tip()) => m1 - case (m1, Bin(_, k, _, l2, r2)) => - val (l1, r1) = m1.split(k) - val leftDiff = l1.difference(l2) - val rightDiff = r1.difference(r2) - if ((leftDiff.size() + rightDiff.size()) == m1.size()) { m1 } - else { link2(leftDiff, rightDiff) } - } +def difference[K, V](m1: Map[K, V], m2: Map[K, V]) = { + val newTree = internal::difference(m1.tree, m2.tree, m1.compare) + Map(newTree, m1.compare) } /// Construct a new map which contains the elements of both `m1` and `m2`. /// When a key is associated with a value in both `m1` and `m2`, the new value is determined using the `combine` function. +/// Uses an explicit pure, first-class comparison function. /// /// O(???) -def union[K, V](m1: Map[K, V], m2: Map[K, V]) { combine : (K, V, V) => V }: Map[K, V] = { - /// Internal function similar to `putWithKey`, but right-biased. Only used here, recursively. - def putWithKeyR(m: Map[K, V], k: K, v: V): Map[K, V] = { - m match { - case Tip() => singleton(k, v) - case Bin(size, k2, v2, l, r) => - genericCompare(k, k2) match { - case Less() => balance(k2, v2, l.putWithKeyR(k, v), r) - case Greater() => balance(k2, v2, l, r.putWithKeyR(k, v)) - case Equal() => Bin(size, k, combine(k2, v2, v), l, r) - } - } - } - - (m1, m2) match { - case (_, Tip()) => m1 - case (_, Bin(_, k, v, Tip(), Tip())) => m1.putWithKeyR(k, v) - case (Bin(_, k, v, Tip(), Tip()), _) => m2.putWithKey(k, v){combine} - case (Tip(), _) => m2 - case (Bin(_, k1, v1, l1, r1), _) => - val (l2, optMid, r2) = m2.splitLookup(k1) - val leftUnion = union(l1, l2){combine} - val rightUnion = union(r1, r2){combine} - optMid match { - case None() => link(k1, v1, leftUnion, rightUnion) - case Some(v2) => link(k1, combine(k1, v1, v2), leftUnion, rightUnion) - } - } +def union[K, V](m1: Map[K, V], m2: Map[K, V], compare: (K, K) => Ordering at {}) { combine: (K, V, V) => V }: Map[K, V] = { + val newTree = internal::union(m1.tree, m2.tree, compare) {combine} + Map(newTree, compare) } /// Construct a new map which contains the elements of both `m1` and `m2`. /// When a key is associated with a value in both `m1` and `m2`, the new value is determined using the `combine` function. +/// Uses an explicit pure, first-class comparison function. /// /// O(???) -def union[K, V](m1: Map[K, V], m2: Map[K, V]) { combine : (V, V) => V }: Map[K, V] = - union(m1, m2) { (k, v1, v2) => combine(v1, v2) } +def union[K, V](m1: Map[K, V], m2: Map[K, V], compare: (K, K) => Ordering at {}) { combine: (V, V) => V }: Map[K, V] = + union(m1, m2, compare) { (k, v1, v2) => combine(v1, v2) } /// Construct a new map which contains the elements of both `m1` and `m2`. -/// Left-biased: Uses values from `m1` if there are duplicate keys. +/// Left-biased: Uses values from `m1` if there are duplicate keys and +/// uses the comparison function from `m1`. /// /// O(???) def union[K, V](m1: Map[K, V], m2: Map[K, V]): Map[K, V] = - union[K, V](m1, m2) { (k, v1, v2) => v1 } + union[K, V](m1, m2, m1.compare) { (k, v1, v2) => v1 } /// Construct a new map which combines all elements that are in both `m1` and `m2` using the `combine` function. /// /// O(???) -def intersection[K, A, B, C](m1: Map[K, A], m2: Map[K, B]) { combine: (K, A, B) => C }: Map[K, C] = - (m1, m2) match { - case (Tip(), _) => Tip() - case (_, Tip()) => Tip() - case (Bin(_, k, v1, l1, r1), _) => - val (l2, mid, r2) = m2.splitLookup(k) - val left = l1.intersection(l2) { combine } - val right = r1.intersection(r2) { combine } - mid match { - case Some(v2) => link(k, combine(k, v1, v2), left, right) - case None() => link2(left, right) - } - } +def intersection[K, A, B, C](m1: Map[K, A], m2: Map[K, B], compare: (K, K) => Ordering at {}) { combine: (K, A, B) => C }: Map[K, C] = { + val newTree = internal::intersection(m1.tree, m2.tree, compare) {combine} + Map(newTree, compare) +} /// Construct a new map which combines all elements that are in both `m1` and `m2` using the `combine` function. /// /// O(???) -def intersection[K, A, B, C](m1: Map[K, A], m2: Map[K, B]) { combine: (A, B) => C }: Map[K, C] = - m1.intersection[K, A, B, C](m2) { (k, v1, v2) => combine(v1, v2) } +def intersection[K, A, B, C](m1: Map[K, A], m2: Map[K, B], compare: (K, K) => Ordering at {}) { combine: (A, B) => C }: Map[K, C] = + m1.intersection[K, A, B, C](m2, compare) { (k, v1, v2) => combine(v1, v2) } /// Construct a new map which combines all elements that are in both `m1` and `m2`. -/// Left-biased: Always uses values from `m1`. +/// Left-biased: Always uses values from `m1` and the comparison function from `m1`. /// /// O(???) def intersection[K, A, B](m1: Map[K, A], m2: Map[K, B]): Map[K, A] = - m1.intersection[K, A, B, A](m2) { (k, v1, v2) => v1 } + m1.intersection[K, A, B, A](m2, m1.compare) { (k, v1, v2) => v1 } -// ------------- -// Internal +/// Please don't directly use: +/// - the `Tree` type +/// - its internal constructors `Bin` & `Tip`, +/// - and these functions. +/// As they might change down the line and are not considered stable / public. +namespace internal { + /// Balanced binary trees of logarithmic depth. + type Tree[K, V] { + Bin(size: Int, k: K, v: V, left: Tree[K, V], right: Tree[K, V]); + Tip() + } -val ratio = 2 -val delta = 3 + /// Create a new empty tree. + /// + /// O(1) + def empty[K, V](): Tree[K, V] = Tip() -def bin[K, V](k: K, v: V, l: Map[K, V], r: Map[K, V]): Map[K, V] = { - Bin(l.size() + r.size() + 1, k, v, l, r) -} + /// Check if tree `m` is empty. + /// + /// O(1) + def isEmpty[K, V](m: Tree[K, V]): Bool = + m match { + case Tip() => true + case _ => false + } -def balance[K, V](k: K, v: V, l: Map[K, V], r: Map[K, V]): Map[K, V] = { - /* - k1->v1 - / \ - t1 m k2->v2 - = / \ - k2->v2 ~> k1->v1 t3 - / \ / \ - t2 t3 t1 t2 - */ - def singleL[A, B](k1: A, v1: B, t1: Map[A, B], m: Map[A, B]): Map[A, B] = { + /// Check if tree `m` is nonempty. + /// + /// O(1) + def nonEmpty[K, V](m: Tree[K, V]): Bool = m match { - case Bin(_, k2, v2, t2, t3) => bin(k2, v2, bin(k1, v1, t1, t2), t3) - case _ => panic("impossible: singleL: Tip") + case Tip() => false + case _ => true } - } - /* - k1->v1 - / \ - m t3 k2->v2 - = / \ - k2->v2 ~> t1 k1->v1 - / \ / / \ - t1 t2 t1 t2 t3 - */ - def singleR[A, B](k1: A, v1: B, m: Map[A, B], t3: Map[A, B]): Map[A, B] = { + /// Create a new tree containing only the mapping from `k` to `v`. + /// + /// O(1) + def singleton[K, V](k: K, v: V): Tree[K, V] = + Bin(1, k, v, Tip(), Tip()) + + /// Get the size of the tree (the number of keys/values). + /// + /// O(1) + def size[K, V](m: Tree[K, V]): Int = m match { - case Bin(_, k2, v2, t1, t2) => bin(k2, v2, t1, bin(k1, v1, t2, t3)) - case _ => panic("impossible: singleR: Tip") + case Tip() => 0 + case Bin(size, _, _, _, _) => size } - } - /* - k1->v1 k3->v3 - / \ / \ - t1 m k1->v1 k2->v2 - = / \ / \ - k2->v2 ~> t1 t2 t3 t4 - / \ - k3->v3 t4 - / \ - t2 t3 - */ - def doubleL[A, B](k1: A, v1: B, t1: Map[A, B], m: Map[A, B]): Map[A, B] = { + /// Insert a new key `k` and value `v` into the tree `m`. + /// If the key `k` is already present in `m`, its associated value is replaced with `v`. + /// + /// O(log N) + def put[K, V](m: Tree[K, V], compare: (K, K) => Ordering at {}, k: K, v: V): Tree[K, V] = m match { - case Bin(_, k2, v2, Bin(_, k3, v3, t2, t3), t4) => - bin(k3, v3, bin(k1, v1, t1, t2), bin(k2, v2, t3, t4)) - case _ => panic("impossible: doubleL: Tip") + case Tip() => singleton(k, v) + case Bin(size, k2, v2, l, r) => + compare(k, k2) match { + case Less() => balance(k2, v2, put(l, compare, k, v), r) + case Greater() => balance(k2, v2, l, put(r, compare, k, v)) + case Equal() => Bin(size, k, v, l, r) + } } - } - /* - k1->v1 k3->v3 - / \ / \ - m t4 k2->v2 k1->v1 - = / \ / \ - k2->v2 ~> t1 t2 t3 t4 - / \ - t1 k3->v3 - / \ - t2 t3 - */ - def doubleR[A, B](k1: A, v1: B, m: Map[A, B], t4: Map[A, B]): Map[A, B] = { + /// Insert a new key `k` and value `v` into the tree `m`. + /// If the key `k` is already present in `m` with value `v2`, the function `combine` is called on `k`, `v`, `v2`. + /// + /// O(log N) + def putWithKey[K, V](m: Tree[K, V], compare: (K, K) => Ordering at {}, k: K, v: V) { combine: (K, V, V) => V } : Tree[K, V] = m match { - case Bin(_, k2, v2, t1, Bin(_, k3, v3, t2, t3)) => - bin(k3, v3, bin(k2, v2, t1, t2), bin(k1, v1, t3, t4)) - case _ => - panic("impossible: doubleR: Tip") + case Tip() => singleton(k, v) + case Bin(size, k2, v2, l, r) => + compare(k, k2) match { + case Less() => balance(k2, v2, l.putWithKey(compare, k, v){combine}, r) + case Greater() => balance(k2, v2, l, r.putWithKey(compare, k, v){combine}) + case Equal() => Bin(size, k, combine(k, v, v2), l, r) + } } - } - def rotateL[A, B](k: A, v: B, l: Map[A, B], r: Map[A, B]): Map[A, B] = { - r match { - case Bin(_, _, _, rl, rr) and (rl.size() < ratio * rr.size()) => singleL(k, v, l, r) - case _ => doubleL(k, v, l, r) + /// Lookup the value at a key `k` in the tree `m`. + /// + /// O(log N) + def get[K, V](m: Tree[K, V], compare: (K, K) => Ordering at {}, k: K): Option[V] = + m match { + case Tip() => None() + case Bin(size, k2, v, l, r) => + compare(k, k2) match { + case Less() => get(l, compare, k) + case Greater() => get(r, compare, k) + case Equal() => Some(v) + } + } + + /// Get minimum in the tree `m`. + /// + /// O(log N) + def getMin[K, V](m: Tree[K, V]): Option[(K, V)] = { + def go(k: K, v: V, m: Tree[K, V]): (K, V) = { + m match { + case Tip() => (k, v) + case Bin(_, k2, v2, l, _) => go(k2, v2, l) + } + } + + m match { + case Tip() => None() + case Bin(_, k, v, l, _) => Some(go(k, v, l)) } } - def rotateR[A, B](k: A, v: B, l: Map[A, B], r: Map[A, B]): Map[A, B] = { - l match { - case Bin(_, _, _, ll, lr) and (lr.size() < ratio * ll.size()) => singleR(k, v, l, r) - case _ => doubleR(k, v, l, r) + + /// Get maximum in the tree `m`. + /// + /// O(log N) + def getMax[K, V](m: Tree[K, V]): Option[(K, V)] = { + def go(k: K, v: V, m: Tree[K, V]): (K, V) = { + m match { + case Tip() => (k, v) + case Bin(_, k2, v2, _, r) => go(k2, v2, r) + } + } + + m match { + case Tip() => None() + case Bin(_, k, v, _, r) => Some(go(k, v, r)) } } - val sizeL = l.size() - val sizeR = r.size() - val sizeCombined = sizeL + sizeR + 1 + /// Forgets the values of a tree, setting them all to `(): Unit`. + /// Used by `set`s internally. + /// + /// Law: `m.forget === m.map { (_k, _v) => () }` + /// + /// O(N) + def forget[K, V](m: Tree[K, V]): Tree[K, Unit] = + m match { + case Tip() => Tip() + case Bin(size, k, v, l, r) => + Bin(size, k, (), l.forget, r.forget) + } - if ((sizeL + sizeR) <= 1) { Bin(sizeCombined, k, v, l, r) } - else if (sizeR > (delta * sizeL)) { rotateL(k, v, l, r) } - else if (sizeL > (delta * sizeR)) { rotateR(k, v, l, r) } - else { Bin(sizeCombined, k, v, l, r)} -} + /// Tree a function `f` over values in tree `m`. + /// + /// O(N) + def map[K, V1, V2](m: Tree[K, V1]) { f : (K, V1) => V2 }: Tree[K, V2] = + m match { + case Tip() => Tip() + case Bin(size, k, v, l, r) => + Bin(size, k, f(k, v), l.map {f}, r.map {f}) + } -record MaxView[K, V](k: K, v: V, m: Map[K, V]) -record MinView[K, V](k: K, v: V, m: Map[K, V]) + /// Tree a function `f` over values in tree `m`, keeping only the values where `f` returns `Some(...)` + /// + /// O(N) + def mapMaybe[K, V1, V2](m: Tree[K, V1]) { f : (K, V1) => Option[V2] }: Tree[K, V2] = + m match { + case Tip() => Tip() + case Bin(size, k, v, l, r) => f(k, v) match { + case Some(v2) => link(k, v2, l.mapMaybe {f}, r.mapMaybe {f}) + case None() => link2(l.mapMaybe {f}, r.mapMaybe {f}) + } + } -def maxViewSure[K, V](k: K, v: V, l: Map[K, V], r: Map[K, V]): MaxView[K, V] = { - (l, r) match { - case (l, Tip()) => MaxView(k, v, l) - case (l, Bin(_, kr, vr, rl, rr)) => - val MaxView(km, vm, r2) = maxViewSure(kr, vr, rl, rr) - MaxView(km, vm, balance(k, v, l, r2)) - } -} + /// Filters a tree `m` with a `shouldKeep` function, + /// keeping only the elements where `shouldKeep` returns `true`. + /// + /// Law: `m.filter { f } === m.mapMaybe { (k, v) => if (f(k, v)) Some(v) else None() }` + /// + /// O(N) + def filter[K, V](m: Tree[K, V]) { shouldKeep: (K, V) => Bool }: Tree[K, V] = { + def go(tree: Tree[K, V]): (Tree[K, V], Bool) = tree match { + case Tip() => (Tip(), false) + case Bin(size, k, v, l, r) => + val (l2, lchanged) = go(l) + val (r2, rchanged) = go(r) + if (shouldKeep(k, v)) { + val changed = lchanged || rchanged + val tree2 = if (changed) { + link(k, v, l2, r2) + } else { + tree + } + (tree2, changed) + } else { + (link2(l2, r2), true) + } + } + + go(m).first + } + + /// Traverse all keys and their associated values in tree `m` in order, + /// running the function `action` on a key and its associated value. + /// + /// Law: `m.foreach { action } === m.toList.foreach { action }` + /// + /// O(N) + /// + /// TODO: Support {Control} for early exits. + def foreach[K, V](m: Tree[K, V]) { action: (K, V) => Unit }: Unit = { + def go(m: Tree[K, V]): Unit = { + m match { + case Tip() => () + case Bin(_, k, v, l, r) => + go(l) + action(k, v) + go(r) + } + } + go(m) + } + + /// Create a tree from a list of (key, value) pairs. + /// If the list contains more than one value for the same key, + /// only the last value is used in the tree. + /// + /// O(N) if the list is sorted by key, + /// O(N log N) otherwise + def fromList[K, V](pairs: List[(K, V)], compare: (K, K) => Ordering at {}): Tree[K, V] = { + pairs match { + case Nil() => Tip() + case Cons((k, v), Nil()) => singleton(k, v) + case Cons((k, v), rest) => + // TODO: this function should really, **really** get inlined! + def notOrdered(k: K, pairs: List[(K, V)]) = { + pairs match { + case Nil() => false + case Cons((k2, _), _) => // k >= k2 + compare(k, k2) match { + case Less() => false + case Greater() => true + case Equal() => true + } + } + } -def minViewSure[K, V](k: K, v: V, l: Map[K, V], r: Map[K, V]): MinView[K, V] = { - (l, r) match { - case (Tip(), r) => MinView(k, v, r) - case (Bin(_, kl, vl, ll, lr), r) => - val MinView(km, vm, l2) = minViewSure(kl, vl, ll, lr) - MinView(km, vm, balance(k, v, l2, r)) + // Naive insertion, used for the worst-case scenario when the list is not sorted by key + def insertMany(m: Tree[K, V], pairs: List[(K, V)]) = { + var treeSoFar = m + pairs.foreach { case (k, v) => + treeSoFar = treeSoFar.put(compare, k, v) + } + treeSoFar + } + + // Returns a triple `(tree, xs, ys)` + // + // Invariant: At least one of `xs`, `ys` is empty. + // Moreover, if `ys` is nonempty, its keys are **not** ordered! + // Otherwise, all of the seen keys have been ordered so far. + // + // TODO: Possibly use a better type to encode the invariant? + def create(level: Int, pairs: List[(K, V)]): (Tree[K, V], List[(K, V)], List[(K, V)]) = { + pairs match { + case Nil() => (Tip(), [], []) + case Cons((k, v), rest) => + if (level == 1) { + val singleton = Bin(1, k, v, Tip(), Tip()) + if (notOrdered(k, rest)) { + (singleton, [], rest) + } else { + (singleton, rest, []) + } + } else { + val res = create(level.bitwiseShr(1), pairs) + res match { + case (_, Nil(), _) => res + case (l, Cons((k2, v2), Nil()), zs) => (l.putMax(k2, v2), [], zs) + case (l, Cons((k2, v2), rest2), _) => + val xs = Cons((k2, v2), rest2) // @-pattern + + if (notOrdered(k2, rest2)) { (l, [], xs) } + else { + val (r, zs, ws) = create(level.bitwiseShr(1), rest2); + (link(k2, v2, l, r), zs, ws) + } + } + } + } + } + + def go(level: Int, m: Tree[K, V], pairs: List[(K, V)]): Tree[K, V] = { + pairs match { + case Nil() => m + case Cons((k, v), Nil()) => m.putMax(k, v) + case Cons((k, v), rest) => + if (notOrdered(k, rest)) { insertMany(m, pairs) } + else { + val l = m; // m is the left subtree here + val cr = create(level, rest) + cr match { + case (r, xs, Nil()) => go(level.bitwiseShl(1), link(k, v, l, r), xs) + case (r, Nil(), ys) => insertMany(link(k, v, l, r), ys) + case _ => panic("create: go: cannot happen, invariant broken!") + } + } + } + } + + if (notOrdered(k, rest)) { insertMany(singleton(k, v), rest) } + else { go(1, singleton(k, v), rest) } + } } -} -/// Internal: Glues two balanced trees (with respect to each other) together. -def glue[K, V](l: Map[K, V], r: Map[K, V]): Map[K, V] = { - (l, r) match { - case (Tip(), r) => r - case (l, Tip()) => l - case (Bin(sizeL, kl, vl, ll, lr), Bin(sizeR, kr, vr, rl, rr)) => - if (sizeL > sizeR) { - val MaxView(km, m, l2) = maxViewSure(kl, vl, ll, lr) - balance(km, m, l2, r) - } else { - val MinView(km, m, r2) = minViewSure(kr, vr, rl, rr) - balance(km, m, l, r2) + /// Remove a key `k` from a tree `m`. + /// If `k` is not in `m`, `m` is returned. + /// + /// O(log N) + def delete[K, V](m: Tree[K, V], compare: (K, K) => Ordering at {}, k: K): Tree[K, V] = + m match { + case Tip() => Tip() + case Bin(_, k2, v2, l, r) => + compare(k, k2) match { + case Less() => balance(k2, v2, l.delete(compare, k), r) + case Greater() => balance(k2, v2, l, r.delete(compare, k)) + case Equal() => glue(l, r) + } + } + + /// Can be used to insert, delete, or update a value. + /// Law: `get(m.alter(k){f}, k) === f(get(m, k))` + /// + /// O(log N) + def alter[K, V](m: Tree[K, V], compare: (K, K) => Ordering at {}, k: K) { f: Option[V] => Option[V] }: Tree[K, V] = + m match { + case Tip() => + f(None()) match { + case None() => Tip() + case Some(v) => singleton(k, v) + } + case Bin(size, k2, v2, l, r) => + compare(k, k2) match { + case Less() => balance(k2, v2, l.alter(compare, k){f}, r) + case Greater() => balance(k2, v2, l, r.alter(compare, k){f}) + case Equal() => + f(Some(v2)) match { + case Some(v) => Bin(size, k2, v, l, r) + case None() => glue(l, r) + } + } + } + + /// Update or delete a value associated with key `k` in tree `m`. + /// + /// O(log N) + def update[K, V](m: Tree[K, V], compare: (K, K) => Ordering at {}, k: K) { f: (K, V) => Option[V] }: Tree[K, V] = + m match { + case Tip() => Tip() + case Bin(size, k2, v2, l, r) => + compare(k, k2) match { + case Less() => balance(k2, v2, l.update(compare, k){f}, r) + case Greater() => balance(k2, v2, l, r.update(compare, k){f}) + case Equal() => + f(k2, v2) match { + case Some(v) => Bin(size, k2, v, l, r) + case None() => glue(l, r) + } + } + } + + /// Get `n`-th (key, value) pair in the tree `m`. + /// + /// O(log N) + def getIndex[K, V](m: Tree[K, V], n: Int): Option[(K, V)] = + m match { + case Tip() => None() + case Bin(size, k, v, l, r) => + val sizeL = l.size() + compareInt(sizeL, n) match { + case Less() => r.getIndex(n - (sizeL + 1)) + case Greater() => l.getIndex(n) + case Equal() => Some((k, v)) + } + } + + /// Construct a new tree which contains all elements of `m1` + /// except those where the key is found in `m2`. + /// + /// O(???) + def difference[K, V](m1: Tree[K, V], m2: Tree[K, V], compare: (K, K) => Ordering at {}): Tree[K, V] = + (m1, m2) match { + case (Tip(), m2) => Tip() + case (m1, Tip()) => m1 + case (m1, Bin(_, k, _, l2, r2)) => + val (l1, _, r1) = m1.splitLookup(compare, k) + val leftDiff = l1.difference(l2, compare) + val rightDiff = r1.difference(r2, compare) + if ((leftDiff.size() + rightDiff.size()) == m1.size()) { m1 } + else { link2(leftDiff, rightDiff) } + } + + /// Construct a new tree which contains the elements of both `m1` and `m2`. + /// When a key is associated with a value in both `m1` and `m2`, the new value is determined using the `combine` function. + /// + /// O(???) + def union[K, V](m1: Tree[K, V], m2: Tree[K, V], compare: (K, K) => Ordering at {}) { combine : (K, V, V) => V }: Tree[K, V] = { + /// Internal function similar to `putWithKey`, but right-biased. Only used here, recursively. + def putWithKeyR(m: Tree[K, V], k: K, v: V): Tree[K, V] = { + m match { + case Tip() => singleton(k, v) + case Bin(size, k2, v2, l, r) => + genericCompare(k, k2) match { + case Less() => balance(k2, v2, l.putWithKeyR(k, v), r) + case Greater() => balance(k2, v2, l, r.putWithKeyR(k, v)) + case Equal() => Bin(size, k, combine(k2, v2, v), l, r) + } } + } + + (m1, m2) match { + case (_, Tip()) => m1 + case (_, Bin(_, k, v, Tip(), Tip())) => m1.putWithKeyR(k, v) + case (Bin(_, k, v, Tip(), Tip()), _) => m2.putWithKey(compare, k, v){combine} + case (Tip(), _) => m2 + case (Bin(_, k1, v1, l1, r1), _) => + val (l2, optMid, r2) = m2.splitLookup(compare, k1) + val leftUnion = union(l1, l2, compare){combine} + val rightUnion = union(r1, r2, compare){combine} + optMid match { + case None() => link(k1, v1, leftUnion, rightUnion) + case Some(v2) => link(k1, combine(k1, v1, v2), leftUnion, rightUnion) + } + } } -} -def splitLookup[K, V](m: Map[K, V], k: K): (Map[K, V], Option[V], Map[K, V]) = { - m match { - case Tip() => (Tip(), None(), Tip()) - case Bin(_, k2, v2, l, r) => - genericCompare(k, k2) match { - case Less() => - val (lessThan, mid, greaterThan) = l.splitLookup(k); - (lessThan, mid, link(k2, v2, greaterThan, r)) - case Greater() => - val (lessThan, mid, greaterThan) = r.splitLookup(k); - (link(k2, v2, l, lessThan), mid, greaterThan) - case Equal() => (l, Some(v2), r) + /// Construct a new tree which combines all elements that are in both `m1` and `m2` using the `combine` function. + /// + /// O(???) + def intersection[K, A, B, C](m1: Tree[K, A], m2: Tree[K, B], compare: (K, K) => Ordering at {}) { combine: (K, A, B) => C }: Tree[K, C] = + (m1, m2) match { + case (Tip(), _) => Tip() + case (_, Tip()) => Tip() + case (Bin(_, k, v1, l1, r1), _) => + val (l2, mid, r2) = m2.splitLookup(compare, k) + val left = l1.intersection(l2, compare) { combine } + val right = r1.intersection(r2, compare) { combine } + mid match { + case Some(v2) => link(k, combine(k, v1, v2), left, right) + case None() => link2(left, right) + } + } + + // ------------- + // Internal + + val ratio = 2 + val delta = 3 + + def bin[K, V](k: K, v: V, l: Tree[K, V], r: Tree[K, V]): Tree[K, V] = { + Bin(l.size() + r.size() + 1, k, v, l, r) + } + + def balance[K, V](k: K, v: V, l: Tree[K, V], r: Tree[K, V]): Tree[K, V] = { + /* + k1->v1 + / \ + t1 m k2->v2 + = / \ + k2->v2 ~> k1->v1 t3 + / \ / \ + t2 t3 t1 t2 + */ + def singleL[A, B](k1: A, v1: B, t1: Tree[A, B], m: Tree[A, B]): Tree[A, B] = { + m match { + case Bin(_, k2, v2, t2, t3) => bin(k2, v2, bin(k1, v1, t1, t2), t3) + case _ => panic("impossible: singleL: Tip") } - } -} + } -def split[K, V](m: Map[K, V], k: K): (Map[K, V], Map[K, V]) = { - val (l, _, r) = m.splitLookup(k); - (l, r) -} + /* + k1->v1 + / \ + m t3 k2->v2 + = / \ + k2->v2 ~> t1 k1->v1 + / \ / / \ + t1 t2 t1 t2 t3 + */ + def singleR[A, B](k1: A, v1: B, m: Tree[A, B], t3: Tree[A, B]): Tree[A, B] = { + m match { + case Bin(_, k2, v2, t1, t2) => bin(k2, v2, t1, bin(k1, v1, t2, t3)) + case _ => panic("impossible: singleR: Tip") + } + } -def link[K, V](k: K, v: V, l: Map[K, V], r: Map[K, V]): Map[K, V] = { - (l, r) match { - case (Tip(), r) => r.putMin(k, v) - case (l, Tip()) => l.putMax(k, v) - case (Bin(sizeL, kl, vl, ll, lr), Bin(sizeR, kr, vr, rl, rr)) => - if ((delta * sizeL) < sizeR) { balance(kr, vr, link(k, v, l, rl), rr) } - else if ((delta * sizeR) < sizeL) { balance(kl, vl, ll, link(k, v, lr, r)) } - else { bin(k, v, l, r) } - } -} + /* + k1->v1 k3->v3 + / \ / \ + t1 m k1->v1 k2->v2 + = / \ / \ + k2->v2 ~> t1 t2 t3 t4 + / \ + k3->v3 t4 + / \ + t2 t3 + */ + def doubleL[A, B](k1: A, v1: B, t1: Tree[A, B], m: Tree[A, B]): Tree[A, B] = { + m match { + case Bin(_, k2, v2, Bin(_, k3, v3, t2, t3), t4) => + bin(k3, v3, bin(k1, v1, t1, t2), bin(k2, v2, t3, t4)) + case _ => panic("impossible: doubleL: Tip") + } + } -/// Internal: merge two trees -def link2[K, V](l: Map[K, V], r: Map[K, V]): Map[K, V] = { - (l, r) match { - case (Tip(), r) => r - case (l, Tip()) => l - case (Bin(sizeL, kl, vl, ll, lr), Bin(sizeR, kr, vr, rl, rr)) => - if ((delta * sizeL) < sizeR) { balance(kr, vr, link2(l, lr), rr) } - else if ((delta * sizeR) < sizeL) { balance(kl, vl, ll, link2(lr, r)) } - else { glue(l, r) } - } -} + /* + k1->v1 k3->v3 + / \ / \ + m t4 k2->v2 k1->v1 + = / \ / \ + k2->v2 ~> t1 t2 t3 t4 + / \ + t1 k3->v3 + / \ + t2 t3 + */ + def doubleR[A, B](k1: A, v1: B, m: Tree[A, B], t4: Tree[A, B]): Tree[A, B] = { + m match { + case Bin(_, k2, v2, t1, Bin(_, k3, v3, t2, t3)) => + bin(k3, v3, bin(k2, v2, t1, t2), bin(k1, v1, t3, t4)) + case _ => + panic("impossible: doubleR: Tip") + } + } -def putMin[K, V](m: Map[K, V], k: K, v: V): Map[K, V] = { - m match { - case Tip() => singleton(k, v) - case Bin(_, k2, v2, l, r) => - balance(k2, v2, l.putMin(k, v), r) + def rotateL[A, B](k: A, v: B, l: Tree[A, B], r: Tree[A, B]): Tree[A, B] = { + r match { + case Bin(_, _, _, rl, rr) and (rl.size() < ratio * rr.size()) => singleL(k, v, l, r) + case _ => doubleL(k, v, l, r) + } + } + def rotateR[A, B](k: A, v: B, l: Tree[A, B], r: Tree[A, B]): Tree[A, B] = { + l match { + case Bin(_, _, _, ll, lr) and (lr.size() < ratio * ll.size()) => singleR(k, v, l, r) + case _ => doubleR(k, v, l, r) + } + } + + val sizeL = l.size() + val sizeR = r.size() + val sizeCombined = sizeL + sizeR + 1 + + if ((sizeL + sizeR) <= 1) { Bin(sizeCombined, k, v, l, r) } + else if (sizeR > (delta * sizeL)) { rotateL(k, v, l, r) } + else if (sizeL > (delta * sizeR)) { rotateR(k, v, l, r) } + else { Bin(sizeCombined, k, v, l, r)} } -} -def putMax[K, V](m: Map[K, V], k: K, v: V): Map[K, V] = { - m match { - case Tip() => singleton(k, v) - case Bin(_, k2, v2, l, r) => - balance(k2, v2, l, r.putMax(k, v)) + record MaxView[K, V](k: K, v: V, m: Tree[K, V]) + record MinView[K, V](k: K, v: V, m: Tree[K, V]) + + def maxViewSure[K, V](k: K, v: V, l: Tree[K, V], r: Tree[K, V]): MaxView[K, V] = + (l, r) match { + case (l, Tip()) => MaxView(k, v, l) + case (l, Bin(_, kr, vr, rl, rr)) => + val MaxView(km, vm, r2) = maxViewSure(kr, vr, rl, rr) + MaxView(km, vm, balance(k, v, l, r2)) + } + + def minViewSure[K, V](k: K, v: V, l: Tree[K, V], r: Tree[K, V]): MinView[K, V] = + (l, r) match { + case (Tip(), r) => MinView(k, v, r) + case (Bin(_, kl, vl, ll, lr), r) => + val MinView(km, vm, l2) = minViewSure(kl, vl, ll, lr) + MinView(km, vm, balance(k, v, l2, r)) + } + + /// Internal: Glues two balanced trees (with respect to each other) together. + def glue[K, V](l: Tree[K, V], r: Tree[K, V]): Tree[K, V] = + (l, r) match { + case (Tip(), r) => r + case (l, Tip()) => l + case (Bin(sizeL, kl, vl, ll, lr), Bin(sizeR, kr, vr, rl, rr)) => + if (sizeL > sizeR) { + val MaxView(km, m, l2) = maxViewSure(kl, vl, ll, lr) + balance(km, m, l2, r) + } else { + val MinView(km, m, r2) = minViewSure(kr, vr, rl, rr) + balance(km, m, l, r2) + } + } + + def splitLookup[K, V](m: Tree[K, V], compare: (K, K) => Ordering at {}, k: K): (Tree[K, V], Option[V], Tree[K, V]) = + m match { + case Tip() => (Tip(), None(), Tip()) + case Bin(_, k2, v2, l, r) => + compare(k, k2) match { + case Less() => + val (lessThan, mid, greaterThan) = l.splitLookup(compare, k); + (lessThan, mid, link(k2, v2, greaterThan, r)) + case Greater() => + val (lessThan, mid, greaterThan) = r.splitLookup(compare, k); + (link(k2, v2, l, lessThan), mid, greaterThan) + case Equal() => (l, Some(v2), r) + } + } + + def link[K, V](k: K, v: V, l: Tree[K, V], r: Tree[K, V]): Tree[K, V] = { + (l, r) match { + case (Tip(), r) => r.putMin(k, v) + case (l, Tip()) => l.putMax(k, v) + case (Bin(sizeL, kl, vl, ll, lr), Bin(sizeR, kr, vr, rl, rr)) => + if ((delta * sizeL) < sizeR) { balance(kr, vr, link(k, v, l, rl), rr) } + else if ((delta * sizeR) < sizeL) { balance(kl, vl, ll, link(k, v, lr, r)) } + else { bin(k, v, l, r) } + } } -} -/// Section: internal utilities -namespace internal { + /// Internal: merge two trees + def link2[K, V](l: Tree[K, V], r: Tree[K, V]): Tree[K, V] = + (l, r) match { + case (Tip(), r) => r + case (l, Tip()) => l + case (Bin(sizeL, kl, vl, ll, lr), Bin(sizeR, kr, vr, rl, rr)) => + if ((delta * sizeL) < sizeR) { balance(kr, vr, link2(l, lr), rr) } + else if ((delta * sizeR) < sizeL) { balance(kl, vl, ll, link2(lr, r)) } + else { glue(l, r) } + } + + def putMin[K, V](m: Tree[K, V], k: K, v: V): Tree[K, V] = + m match { + case Tip() => singleton(k, v) + case Bin(_, k2, v2, l, r) => + balance(k2, v2, l.putMin(k, v), r) + } + + def putMax[K, V](m: Tree[K, V], k: K, v: V): Tree[K, V] = + m match { + case Tip() => singleton(k, v) + case Bin(_, k2, v2, l, r) => + balance(k2, v2, l, r.putMax(k, v)) + } + // Section: for tests and invariants: - /// Check if a map `m` is balanced. - def isBalanced[K, V](m: Map[K, V]): Bool = { + /// Check if a tree `m` is balanced. + def isBalanced[K, V](m: Tree[K, V]): Bool = m match { case Tip() => true case Bin(_, _, _, l, r) => @@ -770,13 +956,12 @@ namespace internal { val rightSmallEnough = r.size() <= delta * l.size() (bothSmall || (leftSmallEnough && rightSmallEnough)) && isBalanced(l) && isBalanced(r) } - } - // Section: prettyprinting for tree maps and list maps: + // Section: prettyprinting for trees: - def prettyMap[K, V](m: Map[K, V]): String = { + def prettyTree[K, V](m: Tree[K, V]): String = { // Helper function to recursively build the string representation of the tree - def go(t: Map[K, V], prefix: String, isTail: Bool): String = { + def go(t: Tree[K, V], prefix: String, isTail: Bool): String = { t match { case Tip() => "" case Bin(_, k, v, l, r) => @@ -791,7 +976,7 @@ namespace internal { } } - // Start the recursion with the initial map, an empty prefix, and true for the root being the tail + // Start the recursion with the initial tree, an empty prefix, and true for the root being the tail go(m, "", true) } diff --git a/libraries/common/set.effekt b/libraries/common/set.effekt index 52afc3932..c2dcb2a1c 100644 --- a/libraries/common/set.effekt +++ b/libraries/common/set.effekt @@ -2,74 +2,108 @@ module set import map -/// Ordered finite set, backed by a `Map`. -record Set[A](internal: Map[A, Unit]) +/// Ordered finite immutable set, backed by balanced binary trees of logarithmic depth. +record Set[A](tree: internal::Tree[A, Unit], compare: (A, A) => Ordering at {}) -/// Create a new empty set. +/// Create a new empty set using a pure, first-class comparison function. /// /// O(1) -def empty[A](): Set[A] = { - Set(empty()) -} +def empty[A](compare: (A, A) => Ordering at {}): Set[A] = + Set(internal::empty(), compare) + +/// Create a new empty set using a generic comparison function. +/// Only available on JavaScript backends! +/// +/// O(1) +def emptyGeneric[A](): Set[A] = + Set( + internal::empty(), + box { (left: A, right: A) => genericCompare(left, right) } + ) /// Check if set `s` is empty. /// /// O(1) -def isEmpty[A](s: Set[A]): Bool = { - s.internal::via { m => - m.isEmpty() - } -} +def isEmpty[A](s: Set[A]): Bool = internal::isEmpty(s.tree) /// Check if set `s` is nonempty. /// /// O(1) -def nonEmpty[A](s: Set[A]): Bool = { - s.internal::via { m => - m.nonEmpty() - } -} +def nonEmpty[A](s: Set[A]): Bool = internal::nonEmpty(s.tree) -/// Create a new set containing only the given element `a`. +/// Create a new set containing a single `element`. +/// Requires a pure, first-class comparison function. /// /// O(1) -def singleton[A](a: A): Set[A] = { - val s: Set[A] = empty() - s.insert(a) -} +def singleton[A](element: A, compare: (A, A) => Ordering at {}): Set[A] = + Set(internal::singleton(element, ()), compare) + +/// Create a new set containing a single `element` and a generic comparison function. +/// Only available on the JavaScript backends! +/// +/// O(1) +def singletonGeneric[A](element: A): Set[A] = + Set( + internal::singleton(element, ()), + box { (left: A, right: A) => genericCompare(left, right) } + ) + +/// Get the size of the set (the number of its elements). +/// +/// O(1) +def size[A](s: Set[A]): Int = internal::size(s.tree) /// Insert a new element `a` into the set `s`. /// /// O(log N) def insert[A](s: Set[A], a: A): Set[A] = { - s.internal::viaInplace { m => - put(m, a, ()) - } + val newTree = internal::put(s.tree, s.compare, a, ()) + Set(newTree, s.compare) } /// Create a set from a given map by ignoring the values. /// /// O(N) def fromMapKeys[K, V](map: Map[K, V]): Set[K] = - Set(map.forget) + Set(internal::forget(map.tree), map.compare) -/// Create a map from a set and a function. +/// Create a map from a set and a function, +/// reusing the set's comparison. /// /// O(N) -def toMap[K, V](keys: Set[K]) { valueOf: K => V }: Map[K, V] = - keys.internal::via { m => - m.map { (k, _v) => valueOf(k) } - } +def toMap[K, V](keys: Set[K]) { valueOf: K => V }: Map[K, V] = { + val newTree = keys.tree.map { (k, _v) => valueOf(k) } + Set(newTree, keys.compare) +} + +/// Create a set from a given list and a pure, first-class comparison function. +/// +/// O(N log N) +/// O(N) if the list is sorted +def fromList[A](list: List[A], compare: (A, A) => Ordering at {}): Set[A] = { + val tree: internal::Tree[A, Unit] = list.map { k => (k, ()) }.fromList(compare) + Set(tree, compare) +} -/// Create a set from a given list. +/// Create a set from a given list with a generic comparison function. +/// Works only on the JavaScript backends! /// /// O(N log N) /// O(N) if the list is sorted def fromList[A](list: List[A]): Set[A] = { - val m: Map[A, Unit] = list.map { k => (k, ()) }.fromList - Set(m) + val compare: (A, A) => Ordering at {} = box { (left, right) => genericCompare(left, right) } + val tree: internal::Tree[A, Unit] = list.map { k => (k, ()) }.fromList(compare) + Set(tree, compare) } +/// Traverse all elements in order, running the function `action` on each element. +/// +/// Law: `s.foreach { action } === s.toList.foreach { action }` +/// +/// O(N) +def foreach[A](s: Set[A]) { action: A => Unit }: Unit = + s.tree.foreach {action} + /// Create a list from a given set. /// /// O(N) @@ -100,17 +134,18 @@ def any[A](s: Set[A]) { predicate: A => Bool }: Bool = { s.foreach { a => if (predicate(a)) { result = true } } - result + result } /// Check if a set contains a given element. /// /// O(log N) -def contains[A](s: Set[A], a: A): Bool = { - s.internal::via { m => - m.contains(a) +def contains[A](s: Set[A], a: A): Bool = + internal::get(s.tree, s.compare, a) match { + case None() => false + case Some(v) => true } -} + /// Check if set `s1` is a subset of set `s2`. /// @@ -133,9 +168,8 @@ def superset[A](s1: Set[A], s2: Set[A]): Bool = { /// /// O(log N) def delete[A](s: Set[A], a: A): Set[A] = { - s.internal::viaInplace { m => - m.delete(a) - } + val newTree = internal::delete(s.tree, s.compare, a) + Set(newTree, s.compare) } /// Remove many elements from a set. @@ -149,87 +183,52 @@ def deleteMany[A](s: Set[A], list: List[A]): Set[A] = { tmp } -/// Traverse all elements in order, running the function `action` on each element. -/// -/// Law: `s.foreach { action } === s.toList.foreach { action }` -/// -/// O(N) -def foreach[A](s: Set[A]) { action : A => Unit }: Unit = { - s.internal::via { m => - m.foreach { (k, _v) => - action(k) - } - } -} - -/// Get the size of a set. -/// -/// O(N) -def size[A](s: Set[A]): Int = { - s.internal::via { m => - m.size() - } -} - -/// Convert a given set into a map. -/// Uses a function `value` to give a value for each of the keys (elements of the set). +/// Construct a new set which contains all elements of `s1` +/// except those where the element is in `s2`. +/// Uses an explicit comparison function. /// -/// O(N) -def toMap[K, V](keys: Set[K]) { value : K => V }: Map[K, V] = { - keys.internal::via { m => - m.map { (k, _v) => value(k) } - } +/// O(???) +def difference[A](s1: Set[A], s2: Set[A], compare: (A, A) => Ordering at {}): Set[A] = { + val newTree = internal::difference(s1.tree, s2.tree, compare) + Set(newTree, compare) } /// Construct a new set which contains all elements of `s1` /// except those where the element is in `s2`. +/// Uses a comparison function from `s1`. /// /// O(???) -def difference[A](s1: Set[A], s2: Set[A]): Set[A] = { - s1.internal::viaInplace { m1 => - s2.internal::via { m2 => - m1.difference(m2) - } - } -} +def difference[A](s1: Set[A], s2: Set[A]): Set[A] = + s1.difference(s2, s1.compare) /// Construct a new set which contains both elements of `s1` and `s2`. +/// Uses an explicit comparison function. /// /// O(???) -def union[A](s1: Set[A], s2: Set[A]): Set[A] = { - s1.internal::viaInplace { m1 => - s2.internal::via { m2 => - m1.union(m2) - } - } +def union[A](s1: Set[A], s2: Set[A], compare: (A, A) => Ordering at {}): Set[A] = { + val newTree = internal::union(s1.tree, s2.tree, compare) + Set(newTree, compare) } +/// Construct a new set which contains both elements of `s1` and `s2`. +/// Uses a comparison function from `s1`. +/// +/// O(???) +def union[A](s1: Set[A], s2: Set[A]): Set[A] = + s1.union(s2, s1.compare) + /// Construct a new set which contains only elements which are in both of `s1` and `s2`. +/// Uses an explicit comparison function. /// /// O(???) def intersection[A](s1: Set[A], s2: Set[A]): Set[A] = { - s1.internal::viaInplace { m1 => - s2.internal::via { m2 => - m1.intersection(m2) - } - } + val newTree = internal::intersection(s1.tree, s2.tree, compare) + Set(newTree, compare) } -// ------------- -// Internal - -namespace internal { - /// Internal: inline at all costs - /// Function `f` is only used linearly, the set `s` is dropped. - def via[A, R](s: Set[A]) { f : Map[A, Unit] => R }: R = { - val Set(internal) = s - f(internal) - } - - /// Internal: inline at all costs - /// Uses `via` inside and wraps the result in a `Set`. - /// Ideally, this would be in-place (when possible) since we don't change the type? - def viaInplace[A](s: Set[A]) { f : Map[A, Unit] => Map[A, Unit] }: Set[A] = { - s.via { m => Set(f(m)) } - } -} \ No newline at end of file +/// Construct a new set which contains only elements which are in both of `s1` and `s2`. +/// Uses a comparison function from `s1`. +/// +/// O(???) +def intersection[A](s1: Set[A], s2: Set[A]): Set[A] = + s1.intersection(s2, s1.compare) \ No newline at end of file From eff01b57ba38c4ed24a218df065356678a7ffc43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Fri, 13 Dec 2024 20:14:08 +0100 Subject: [PATCH 25/29] Fix subtle, yet obvious bugs, duh --- libraries/common/map.effekt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/common/map.effekt b/libraries/common/map.effekt index efe8847e4..f9f2349b6 100644 --- a/libraries/common/map.effekt +++ b/libraries/common/map.effekt @@ -202,7 +202,7 @@ def fromList[K, V](pairs: List[(K, V)], compare: (K, K) => Ordering at {}): Map[ /// /// O(N) if the list is sorted by key, /// O(N log N) otherwise -def fromList[K, V](pairs: List[(K, V)], compare: (K, K) => Ordering at {}): Map[K, V] = { +def fromList[K, V](pairs: List[(K, V)]): Map[K, V] = { val compare: (K, K) => Ordering at {} = box { (left, right) => genericCompare(left, right) } Map(internal::fromList(pairs, compare), compare) } @@ -713,7 +713,7 @@ namespace internal { m match { case Tip() => singleton(k, v) case Bin(size, k2, v2, l, r) => - genericCompare(k, k2) match { + compare(k, k2) match { case Less() => balance(k2, v2, l.putWithKeyR(k, v), r) case Greater() => balance(k2, v2, l, r.putWithKeyR(k, v)) case Equal() => Bin(size, k, combine(k2, v2, v), l, r) From ed9ca1106d0e22ed05116303e8746f2ec068e815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Fri, 13 Dec 2024 20:20:40 +0100 Subject: [PATCH 26/29] Attempt to fix tests --- examples/stdlib/map/cache.effekt | 6 +++--- examples/stdlib/map/counter.effekt | 2 +- examples/stdlib/map/map.effekt | 8 ++++---- examples/stdlib/map/minmax.effekt | 2 +- examples/stdlib/map/setops.effekt | 10 +++++----- examples/stdlib/set/unique.effekt | 6 +++--- examples/stdlib/stream/map.effekt | 2 +- libraries/common/effekt.effekt | 5 +++++ libraries/common/map.effekt | 6 +++--- libraries/common/set.effekt | 2 +- libraries/common/stream.effekt | 16 ++++++++-------- 11 files changed, 35 insertions(+), 30 deletions(-) diff --git a/examples/stdlib/map/cache.effekt b/examples/stdlib/map/cache.effekt index dcdfff816..b34d75ef7 100644 --- a/examples/stdlib/map/cache.effekt +++ b/examples/stdlib/map/cache.effekt @@ -7,7 +7,7 @@ effect time(): Int def main() = { var currentTime = 0 try { - var cache: Map[String, CacheEntry[String]] = map::empty() + var cache: Map[String, CacheEntry[String]] = map::empty(compareString) val maxAge = 8 def cachePut(key: String, value: String): Unit = @@ -21,8 +21,8 @@ def main() = { def cleanExpiredEntries(): Unit = { val currentTime = do time() - cache = cache.filter { (_, entry) => - currentTime - entry.timestamp < maxAge + cache = cache.filter { (_, entry) => + currentTime - entry.timestamp < maxAge } } diff --git a/examples/stdlib/map/counter.effekt b/examples/stdlib/map/counter.effekt index e108e5983..a70c5f30b 100644 --- a/examples/stdlib/map/counter.effekt +++ b/examples/stdlib/map/counter.effekt @@ -1,7 +1,7 @@ import map def counter(words: List[String]): Map[String, Int] = { - var m: Map[String, Int] = map::empty(); + var m: Map[String, Int] = map::empty(compareString); list::foreach(words) { word => m = m.putWithKey(word, 1) { (_, n1, n2) => n1 + n2 } diff --git a/examples/stdlib/map/map.effekt b/examples/stdlib/map/map.effekt index 7762d30c8..afcbb1f6a 100644 --- a/examples/stdlib/map/map.effekt +++ b/examples/stdlib/map/map.effekt @@ -3,7 +3,7 @@ import map def main() = { val l = [(0, "Hello"), (1, "World"), (2, "Woo!")] - val m = map::fromList(l) + val m = map::fromList(l, compareInt) println(map::internal::prettyPairs(m.toList)) val m2 = m.put(-1, "Hullo") @@ -17,12 +17,12 @@ def main() = { 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)) - val m5 = map::fromList(Cons((1, "Foo"), Cons((-1, "Huh?!"), m4.toList.reverse))) + val m5 = map::fromList(Cons((1, "Foo"), Cons((-1, "Huh?!"), m4.toList.reverse)), compareInt) println(map::internal::prettyPairs(m5.toList)) - val m6: Map[Int, String] = m5.toList.fromList + val m6: Map[Int, String] = m5.toList.fromList(compareInt) println(map::internal::prettyPairs(m6.toList)) - val nuMap = map::fromList(l.reverse) + val nuMap = map::fromList(l.reverse, compareInt) println(map::internal::prettyPairs(nuMap.toList)) } diff --git a/examples/stdlib/map/minmax.effekt b/examples/stdlib/map/minmax.effekt index ede299a41..627c5ace9 100644 --- a/examples/stdlib/map/minmax.effekt +++ b/examples/stdlib/map/minmax.effekt @@ -6,7 +6,7 @@ def show(opt: Option[(Int, String)]): String = opt match { } def main() = { - val m = map::fromList([(10, "Ten"), (20, "Twenty"), (5, "Five"), (30, "Thirty")]) + 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 diff --git a/examples/stdlib/map/setops.effekt b/examples/stdlib/map/setops.effekt index a8658b3d5..c68f08bed 100644 --- a/examples/stdlib/map/setops.effekt +++ b/examples/stdlib/map/setops.effekt @@ -2,19 +2,19 @@ import map def main() = { // Create multiple maps - val map1 = map::fromList([(1, "apple"), (2, "banana"), (3, "cherry")]) - val map2 = map::fromList([(2, "berry"), (3, "date"), (4, "elderberry")]) + 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)) + println(map::internal::prettyPairs(map1.union(map2, compareInt).toList)) println("\nUnion (combining values):") - val combinedMap = map1.union(map2) { (k, v1, v2) => v1 ++ "/" ++ v2 } + val combinedMap = map1.union(map2, compareInt) { (k, v1, v2) => v1 ++ "/" ++ v2 } println(map::internal::prettyPairs(combinedMap.toList)) // Test intersection println("\nIntersection (combining keys):") - val intersectedMap = map1.intersection(map2) { (k, v1, v2) => v1 ++ "-" ++ v2 } + val intersectedMap = map1.intersection(map2, compareInt) { (k, v1, v2) => v1 ++ "-" ++ v2 } println(map::internal::prettyPairs(intersectedMap.toList)) } diff --git a/examples/stdlib/set/unique.effekt b/examples/stdlib/set/unique.effekt index a7b000b40..d8ccc13a1 100644 --- a/examples/stdlib/set/unique.effekt +++ b/examples/stdlib/set/unique.effekt @@ -1,7 +1,7 @@ import set def unique(words: List[String]): Set[String] = { - var s: Set[String] = set::empty(); + var s: Set[String] = set::empty(compareString); list::foreach(words) { word => s = s.insert(word) @@ -50,8 +50,8 @@ def main() = { println(uniqueSpeech.toList) testSorted(uniqueSpeech) - println(set::fromList(speech).toList) - testSorted(set::fromList(speech)) + println(set::fromList(speech, compareString).toList) + testSorted(set::fromList(speech, compareString)) // --- println("") diff --git a/examples/stdlib/stream/map.effekt b/examples/stdlib/stream/map.effekt index 74a3cb119..138f85951 100644 --- a/examples/stdlib/stream/map.effekt +++ b/examples/stdlib/stream/map.effekt @@ -2,7 +2,7 @@ import stream import map def main() = { - val m = map::fromList([(1, 'H'), (2, 'e'), (3, 'l'), (4, 'l'), (5, 'o')]) + 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) ++ ")") diff --git a/libraries/common/effekt.effekt b/libraries/common/effekt.effekt index 4b9c0f19f..e9368477e 100644 --- a/libraries/common/effekt.effekt +++ b/libraries/common/effekt.effekt @@ -161,6 +161,11 @@ def compareInt(n: Int, m: Int) = else if (n < m) Less() else Greater() +def compareString(left: String, right: String) = + if (left == right) Equal() + else if (left < right) Less() + else Greater() + extern pure def genericCompareImpl[R](x: R, y: R): Int = js "$effekt.compare(${x}, ${y})" diff --git a/libraries/common/map.effekt b/libraries/common/map.effekt index f9f2349b6..bb10e2c89 100644 --- a/libraries/common/map.effekt +++ b/libraries/common/map.effekt @@ -186,7 +186,7 @@ def values[K, V](m: Map[K, V]): List[V] = { acc.reverse } -/// Create a map from a list of (key, value) pairs and a pure, first-class comparison function +/// Create a map from a list of (key, value) pairs and a pure, first-class comparison function. /// If the list contains more than one value for the same key, /// only the last value is used in the map. /// @@ -195,14 +195,14 @@ def values[K, V](m: Map[K, V]): List[V] = { def fromList[K, V](pairs: List[(K, V)], compare: (K, K) => Ordering at {}): Map[K, V] = Map(internal::fromList(pairs, compare), compare) -/// Create a map from a list of (key, value) pairs and a generic comparison. +/// Create a map from a list of (key, value) pairs and a generic comparison function. /// If the list contains more than one value for the same key, /// only the last value is used in the map. /// Works only on JavaScript backends! /// /// O(N) if the list is sorted by key, /// O(N log N) otherwise -def fromList[K, V](pairs: List[(K, V)]): Map[K, V] = { +def fromListGeneric[K, V](pairs: List[(K, V)]): Map[K, V] = { val compare: (K, K) => Ordering at {} = box { (left, right) => genericCompare(left, right) } Map(internal::fromList(pairs, compare), compare) } diff --git a/libraries/common/set.effekt b/libraries/common/set.effekt index c2dcb2a1c..99182b990 100644 --- a/libraries/common/set.effekt +++ b/libraries/common/set.effekt @@ -90,7 +90,7 @@ def fromList[A](list: List[A], compare: (A, A) => Ordering at {}): Set[A] = { /// /// O(N log N) /// O(N) if the list is sorted -def fromList[A](list: List[A]): Set[A] = { +def fromListGeneric[A](list: List[A]): Set[A] = { val compare: (A, A) => Ordering at {} = box { (left, right) => genericCompare(left, right) } val tree: internal::Tree[A, Unit] = list.map { k => (k, ()) }.fromList(compare) Set(tree, compare) diff --git a/libraries/common/stream.effekt b/libraries/common/stream.effekt index e501d03a8..2efb44357 100644 --- a/libraries/common/stream.effekt +++ b/libraries/common/stream.effekt @@ -261,27 +261,27 @@ def feed[T, R](list: List[T]) { reader: () => R / read[T] }: R = { } } -def collectMap[K, V, R] { stream: () => R / emit[(K, V)] }: (R, Map[K, V]) = +def collectMap[K, V, R](compare: (K, K) => Ordering at {}) { stream: () => R / emit[(K, V)] }: (R, Map[K, V]) = try { - (stream(), map::empty()) + (stream(), map::empty(compare)) } with emit[(K, V)] { case (k, v) => val (r, map) = resume(()); (r, map.put(k, v)) } -def collectMap[K, V] { stream: () => Any / emit[(K, V)] }: Map[K, V] = - collectMap[K, V, Any]{stream}.second +def collectMap[K, V](compare: (K, K) => Ordering at {}) { stream: () => Any / emit[(K, V)] }: Map[K, V] = + collectMap[K, V, Any](compare){stream}.second -def collectSet[A, R] { stream: () => R / emit[A] }: (R, Set[A]) = +def collectSet[A, R](compare: (A, A) => Ordering at {}) { stream: () => R / emit[A] }: (R, Set[A]) = try { - (stream(), set::empty()) + (stream(), set::empty(compare)) } with emit[A] { (v) => val (r, set) = resume(()); (r, set.insert(v)) } -def collectSet[A] { stream: () => Any / emit[A] }: Set[A] = - collectSet[A, Any]{stream}.second +def collectSet[A](compare: (A, A) => Ordering at {}) { stream: () => Any / emit[A] }: Set[A] = + collectSet[A, Any](compare){stream}.second def feed[T, R](array: Array[T]) { reader: () => R / read[T] }: R = { var i = 0 From e488a74001ae29f4ba5a4232e0e3a22659090e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Fri, 13 Dec 2024 20:33:31 +0100 Subject: [PATCH 27/29] More bug fixing --- examples/stdlib/map/setops.effekt | 2 +- examples/stdlib/stream/map.effekt | 2 +- libraries/common/map.effekt | 3 ++- libraries/common/set.effekt | 16 ++++++++-------- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/examples/stdlib/map/setops.effekt b/examples/stdlib/map/setops.effekt index c68f08bed..9f9c53dbe 100644 --- a/examples/stdlib/map/setops.effekt +++ b/examples/stdlib/map/setops.effekt @@ -7,7 +7,7 @@ def main() = { // Test union with different combine strategies println("Union (keeping first value):") - println(map::internal::prettyPairs(map1.union(map2, compareInt).toList)) + println(map::internal::prettyPairs(map1.union(map2).toList)) println("\nUnion (combining values):") val combinedMap = map1.union(map2, compareInt) { (k, v1, v2) => v1 ++ "/" ++ v2 } diff --git a/examples/stdlib/stream/map.effekt b/examples/stdlib/stream/map.effekt index 138f85951..7ee2da473 100644 --- a/examples/stdlib/stream/map.effekt +++ b/examples/stdlib/stream/map.effekt @@ -8,7 +8,7 @@ def main() = { println(show(k) ++ ": " ++ show(v) ++ " (" ++ show(v.toInt) ++ ")") } - val newMap = collectMap[Int, Char] { each(m) } + val newMap = collectMap[Int, Char](compareInt) { each(m) } println(map::internal::prettyPairs(newMap.toList)) val hello: String = collectString { eachValue(m) } diff --git a/libraries/common/map.effekt b/libraries/common/map.effekt index bb10e2c89..2c1ac0025 100644 --- a/libraries/common/map.effekt +++ b/libraries/common/map.effekt @@ -151,7 +151,8 @@ def filter[K, V](m: Map[K, V]) { shouldKeep: V => Bool }: Map[K, V] = /// O(N) /// /// TODO: Support {Control} for early exits. -def foreach[K, V](m: Map[K, V]) { action: (K, V) => Unit }: Unit = internal::foreach(m.tree) {action} +def foreach[K, V](m: Map[K, V]) { action: (K, V) => Unit }: Unit = + internal::foreach(m.tree) {action} /// Convert a map `m` into a list of (key, value) pairs. /// diff --git a/libraries/common/set.effekt b/libraries/common/set.effekt index 99182b990..c7a158126 100644 --- a/libraries/common/set.effekt +++ b/libraries/common/set.effekt @@ -72,8 +72,8 @@ def fromMapKeys[K, V](map: Map[K, V]): Set[K] = /// /// O(N) def toMap[K, V](keys: Set[K]) { valueOf: K => V }: Map[K, V] = { - val newTree = keys.tree.map { (k, _v) => valueOf(k) } - Set(newTree, keys.compare) + val newTree: internal::Tree[K, V] = internal::map(keys.tree) { (k, _v) => valueOf(k) } + Map(newTree, keys.compare) } /// Create a set from a given list and a pure, first-class comparison function. @@ -81,7 +81,7 @@ def toMap[K, V](keys: Set[K]) { valueOf: K => V }: Map[K, V] = { /// O(N log N) /// O(N) if the list is sorted def fromList[A](list: List[A], compare: (A, A) => Ordering at {}): Set[A] = { - val tree: internal::Tree[A, Unit] = list.map { k => (k, ()) }.fromList(compare) + val tree: internal::Tree[A, Unit] = internal::fromList(list.map { k => (k, ()) }, compare) Set(tree, compare) } @@ -92,7 +92,7 @@ def fromList[A](list: List[A], compare: (A, A) => Ordering at {}): Set[A] = { /// O(N) if the list is sorted def fromListGeneric[A](list: List[A]): Set[A] = { val compare: (A, A) => Ordering at {} = box { (left, right) => genericCompare(left, right) } - val tree: internal::Tree[A, Unit] = list.map { k => (k, ()) }.fromList(compare) + val tree: internal::Tree[A, Unit] = internal::fromList(list.map { k => (k, ()) }, compare) Set(tree, compare) } @@ -102,7 +102,7 @@ def fromListGeneric[A](list: List[A]): Set[A] = { /// /// O(N) def foreach[A](s: Set[A]) { action: A => Unit }: Unit = - s.tree.foreach {action} + internal::foreach(s.tree) { (k, _v) => action(k) } /// Create a list from a given set. /// @@ -206,7 +206,7 @@ def difference[A](s1: Set[A], s2: Set[A]): Set[A] = /// /// O(???) def union[A](s1: Set[A], s2: Set[A], compare: (A, A) => Ordering at {}): Set[A] = { - val newTree = internal::union(s1.tree, s2.tree, compare) + val newTree = internal::union(s1.tree, s2.tree, compare) { (_k, _v1, _v2) => () } Set(newTree, compare) } @@ -221,8 +221,8 @@ def union[A](s1: Set[A], s2: Set[A]): Set[A] = /// Uses an explicit comparison function. /// /// O(???) -def intersection[A](s1: Set[A], s2: Set[A]): Set[A] = { - val newTree = internal::intersection(s1.tree, s2.tree, compare) +def intersection[A](s1: Set[A], s2: Set[A], compare: (A, A) => Ordering at {}): Set[A] = { + val newTree = internal::intersection(s1.tree, s2.tree, compare) { (_k, _v1, _v2) => () } Set(newTree, compare) } From 12f7061fc855956ecdfa05a077e834753ed32a2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Fri, 13 Dec 2024 20:34:15 +0100 Subject: [PATCH 28/29] Try running 'map' and 'set' tests on Chez and LLVM --- effekt/jvm/src/test/scala/effekt/StdlibTests.scala | 6 ------ 1 file changed, 6 deletions(-) diff --git a/effekt/jvm/src/test/scala/effekt/StdlibTests.scala b/effekt/jvm/src/test/scala/effekt/StdlibTests.scala index f0d12dbdb..87965254f 100644 --- a/effekt/jvm/src/test/scala/effekt/StdlibTests.scala +++ b/effekt/jvm/src/test/scala/effekt/StdlibTests.scala @@ -27,8 +27,6 @@ abstract class StdlibChezTests extends StdlibTests { examplesDir / "stdlib" / "stream" / "characters.effekt", examplesDir / "stdlib" / "stream" / "fuse_newlines.effekt", examplesDir / "stdlib" / "stream" / "map.effekt", - examplesDir / "stdlib" / "map", - examplesDir / "stdlib" / "set", ) } class StdlibChezSchemeMonadicTests extends StdlibChezTests { @@ -69,9 +67,5 @@ class StdlibLLVMTests extends StdlibTests { examplesDir / "stdlib" / "list" / "build.effekt", examplesDir / "stdlib" / "string" / "strings.effekt", examplesDir / "stdlib" / "string" / "unicode.effekt", - - // Not implemented yet - examplesDir / "stdlib" / "map", - examplesDir / "stdlib" / "set", ) } From fd5b9b34f1d4f44f01b48ba097b0705fdb20ec61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Fri, 13 Dec 2024 20:51:26 +0100 Subject: [PATCH 29/29] Change 'internal::prettyPairs' to take show instances --- examples/stdlib/map/counter.effekt | 2 +- examples/stdlib/map/map.effekt | 14 +++++++------- examples/stdlib/map/setops.effekt | 6 +++--- examples/stdlib/stream/map.effekt | 2 +- libraries/common/map.effekt | 5 +++-- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/examples/stdlib/map/counter.effekt b/examples/stdlib/map/counter.effekt index a70c5f30b..fa3c7ef0b 100644 --- a/examples/stdlib/map/counter.effekt +++ b/examples/stdlib/map/counter.effekt @@ -35,5 +35,5 @@ def main() = { test("do") test("Effekt") - println(map::internal::prettyPairs(ctr.toList)) + println(map::internal::prettyPairs(ctr.toList) { s => show(s) } { n => show(n) }) } diff --git a/examples/stdlib/map/map.effekt b/examples/stdlib/map/map.effekt index afcbb1f6a..7033b8519 100644 --- a/examples/stdlib/map/map.effekt +++ b/examples/stdlib/map/map.effekt @@ -4,25 +4,25 @@ def main() = { val l = [(0, "Hello"), (1, "World"), (2, "Woo!")] val m = map::fromList(l, compareInt) - println(map::internal::prettyPairs(m.toList)) + println(map::internal::prettyPairs(m.toList) { n => show(n) } { s => show(s) }) val m2 = m.put(-1, "Hullo") - println(map::internal::prettyPairs(m2.toList)) + println(map::internal::prettyPairs(m2.toList) { n => show(n) } { s => show(s) }) val m3 = m2.put(-10, "EY") - println(map::internal::prettyPairs(m3.toList)) + 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)) + 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)) + 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)) + 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)) + println(map::internal::prettyPairs(nuMap.toList) { n => show(n) } { s => show(s) }) } diff --git a/examples/stdlib/map/setops.effekt b/examples/stdlib/map/setops.effekt index 9f9c53dbe..c8ebc6295 100644 --- a/examples/stdlib/map/setops.effekt +++ b/examples/stdlib/map/setops.effekt @@ -7,14 +7,14 @@ def main() = { // Test union with different combine strategies println("Union (keeping first value):") - println(map::internal::prettyPairs(map1.union(map2).toList)) + 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)) + 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)) + println(map::internal::prettyPairs(intersectedMap.toList) { n => show(n) } { s => show(s) }) } diff --git a/examples/stdlib/stream/map.effekt b/examples/stdlib/stream/map.effekt index 7ee2da473..4f1a7efb5 100644 --- a/examples/stdlib/stream/map.effekt +++ b/examples/stdlib/stream/map.effekt @@ -9,7 +9,7 @@ def main() = { } val newMap = collectMap[Int, Char](compareInt) { each(m) } - println(map::internal::prettyPairs(newMap.toList)) + println(map::internal::prettyPairs(newMap.toList) { n => show(n) } { c => show(c) }) val hello: String = collectString { eachValue(m) } println(hello) diff --git a/libraries/common/map.effekt b/libraries/common/map.effekt index 2c1ac0025..b528115d8 100644 --- a/libraries/common/map.effekt +++ b/libraries/common/map.effekt @@ -960,6 +960,7 @@ namespace internal { // Section: prettyprinting for trees: + /// Works only on the backends that support `genericShow` (JavaScript)! def prettyTree[K, V](m: Tree[K, V]): String = { // Helper function to recursively build the string representation of the tree def go(t: Tree[K, V], prefix: String, isTail: Bool): String = { @@ -981,9 +982,9 @@ namespace internal { go(m, "", true) } - def prettyPairs[K, V](list: List[(K, V)]): String = { + def prettyPairs[K, V](list: List[(K, V)]) { showLeft: K => String } { showRight: V => String }: String = { val res: String = - list.map { case (k, v) => k.genericShow ++ " → " ++ v.genericShow } + list.map { case (k, v) => showLeft(k) ++ " → " ++ showRight(v) } .join(", ") "[" ++ res ++ "]"