A bidirectional map (bijection) with reverse lookup, insertion order, index access, and a sorted BiBTreeMap variant - MoonBit port of Rust's bimap
let m = @aurasuisui/bimap.new()
m.insert("alice", "admin") |> ignore
m.insert("bob", "user") |> ignore
// Forward and reverse lookup:
println(m.get_by_left("alice")) // Some("admin")
println(m.get_by_right("user")) // Some("bob")
// Index access (insertion order preserved):
println(m.get_index(0)) // Some(("alice", "admin"))| Feature | built-in Map | BiMap | indexmap |
|---|---|---|---|
| key → value | ✅ | ✅ | ✅ |
| value → key (reverse) | ❌ | ✅ | ❌ |
| index access get_index(i) | ❌ | ✅ | ✅ |
| keys unique | ✅ | ✅ | ✅ |
| values also unique (bijection) | ❌ | ✅ | ❌ |
| preserves insertion order | impl-defined | ✅ | ✅ |
| Eq/Hash semantics | order-independent | order-independent | order-sensitive |
import {
"aurasuisui/bimap@0.3.0",
}import {
"aurasuisui/bimap",
}| Case | Condition | insert returns | len change |
|---|---|---|---|
| C0 | neither l nor r present | Neither | +1 |
| C1 | the exact pair (l, r) already present | Pair(l, r) | 0 |
| C2 | l was bound to r'≠r; r free | Left(l, r') | 0 |
| C3 | r was bound to l'≠l; l free | Right(l', r) | 0 |
| C4 | l→r' and l'→r both exist | Both((l,r'), (l',r)) | −1 |
C4 collapses two pairs into one — insert can reduce the map's size! This mirrors Rust bimap's Overwritten::Both exactly.
let m = @aurasuisui/bimap.new()
m.insert("a", 1) |> ignore // Neither {a↔1}
m.insert("b", 2) |> ignore // Neither {a↔1, b↔2}
m.insert("a", 4) |> ignore // Left(a, 1) {a↔4, b↔2}
m.insert("c", 2) |> ignore // Right(b, 2) {a↔4, c↔2}
let r = m.insert("a", 2) // Both((a,4),(c,2)) {a↔2} — len 2→1!| Category | Methods |
|---|---|
| Construct | new(), with_capacity(n), from_array(pairs), default(), copy(), from_json(json), from_json_with(json, parse_key) |
| Query | len(), is_empty(), capacity() |
| Insert | insert(l, r) -> Overwritten, insert_no_overwrite(l, r) -> Result[Unit,(L,R)] |
| Forward | get_by_left(l), contains_left(l), remove_by_left(l) -> R? |
| Reverse | get_by_right(r), contains_right(r), remove_by_right(r) -> L? |
| Index | get_index(i), get_index_of_left(l), get_index_of_right(r), first(), last() |
| Iterate | iter(), lefts(), rights(), into_array() |
| Views | contains_pair(l, r), left_keys(), right_values() |
| Entry | get_or_insert_left(l, r) -> R, get_or_insert_right(r, l) -> L |
| Bulk | retain(pred) |
| Convert | to_inverse() -> BiMap[R, L] |
| Traits | Debug, Default, Show, Hash, Eq, ToJson, Arbitrary |
let json = @json.parse("{\"bob\":2,\"alice\":1}") // text -> Json
// BiMap: free function; insertion order = JSON text key order
let m : @aurasuisui/bimap.BiMap[String, Int] = @aurasuisui/bimap.from_json(json)
// BiBTreeMap: method call; sorted by left key (input key order irrelevant)
let s : @aurasuisui/bimap.BiBTreeMap[String, Int] =
@aurasuisui/bimap.BiBTreeMap::from_json(json)
// Int-keyed text: from_json_with + a TOTAL parse_key (P1-2: no raise channel —
// absorb parse failures inside, e.g. fall back to a default):
let ints = @json.parse("{\"10\":10,\"20\":20}")
let n : @aurasuisui/bimap.BiMap[Int, Int] = @aurasuisui/bimap.from_json_with(
ints,
fn(k) { @string.parse_int(k) catch { _ => 0 } },
)let m = @aurasuisui/bimap.BiBTreeMap::from_array([("b", 2), ("a", 1), ("c", 3)])
println(m.into_array()) // [("a", 1), ("b", 2), ("c", 3)] — ascending by left
println(m.first()) // Some(("a", 1)) — smallest left key
println(m.range("a", "b").to_array()) // [("a", 1), ("b", 2)] — [lo, hi] inclusive| Category | Methods |
|---|---|
| Construct | new(), from_array(pairs), default(), copy(), from_json(json), from_json_with(json, parse_key) |
| Query | len(), is_empty(), first(), last(), range(lo, hi) -> Iter |
| Insert | insert(l, r) -> Overwritten, insert_no_overwrite(l, r) -> Result[Unit,(L,R)] |
| Forward | get_by_left(l), contains_left(l), remove_by_left(l) -> R? |
| Reverse | get_by_right(r), contains_right(r), remove_by_right(r) -> L? |
| Views | contains_pair(l, r), left_keys(), right_values() |
| Entry | get_or_insert_left(l, r) -> R, get_or_insert_right(r, l) -> L |
| Bulk | retain(pred) |
| Iterate | iter(), into_array() |
| Convert | to_inverse() -> BiBTreeMap[R, L] |
| Traits | Debug, Default, Show, Hash, Eq, ToJson, Arbitrary |
Note: the cmd/* example packages are standalone modules (they import the published aurasuisui/bimap and are not part of the root package — the repo ships no workspace manifest). To run one, make the package resolvable (e.g. after moon publish) and run moon run cmd/<name>.
| operation | BiMap | built-in Map |
|---|---|---|
| insert (fresh pair) | ≈ 2 980 ns | ≈ 410 ns |
| get_by_left (hit) | ≈ 260 ns | ≈ 130 ns (get) |
| get_by_right (hit) | ≈ 330 ns | — |
| insert_no_overwrite (conflict) | ≈ 300 ns | — |
| remove_by_left (tail-first drain)¹ | ≈ 1 510 ns | ≈ 396 ns² |
| into_array (full traversal) | ≈ 495 ns/pair | — |
moon check # type check
moon test # run all 368 tests
moon fmt # format
moon build # buildpub(all) suberror BiMapDecodeError {
Decode(JsonDecodeError)
DuplicateRightValue(String, String, String)
} derive(Eq, Debug)impl Show for BiMapDecodeErrortype BiBTreeMap[L, R]impl Default for BiBTreeMap[L, R]impl Eq for BiBTreeMap[L, R]impl Hash for BiBTreeMap[L, R]impl Show for BiBTreeMap[L, R]impl ToJson for BiBTreeMap[L, R]impl Debug for BiBTreeMap[L, R]impl Arbitrary for BiBTreeMap[L, R]fn[L : Arbitrary + Compare + Eq, R : Arbitrary + Compare + Eq] arbitrary(size : Int, r0 : RandomState) -> BiBTreeMap[L, R]fn[L : Compare + Eq, R : Eq] BiBTreeMap::contains_pair(self : BiBTreeMap[L, R], l : L, r : R) -> Boolfn[L : Compare + Eq, R : Compare + Eq] BiBTreeMap::from_array(pairs : Array[(L, R)]) -> BiBTreeMap[L, R]fn[R : FromJson + Compare + Eq] BiBTreeMap::from_json(json : Json) -> BiBTreeMap[String, R] raise BiMapDecodeErrorfn[K : Compare + Eq, R : FromJson + Compare + Eq] BiBTreeMap::from_json_with(json : Json, parse_key : (String) -> K) -> BiBTreeMap[K, R] raise BiMapDecodeErrorfn[L : Compare + Eq, R : Compare + Eq] BiBTreeMap::get_or_insert_left(self : BiBTreeMap[L, R], l : L, r : R) -> Rfn[L : Compare + Eq, R : Compare + Eq] BiBTreeMap::get_or_insert_right(self : BiBTreeMap[L, R], r : R, l : L) -> Lfn[L : Compare + Eq, R : Compare + Eq] BiBTreeMap::insert(self : BiBTreeMap[L, R], l : L, r : R) -> Overwritten[L, R]fn[L : Compare + Eq, R : Compare + Eq] BiBTreeMap::insert_no_overwrite(self : BiBTreeMap[L, R], l : L, r : R) -> Result[Unit, (L, R)]fn[L : Compare + Eq, R : Compare + Eq] BiBTreeMap::remove_by_left(self : BiBTreeMap[L, R], l : L) -> R?fn[L : Compare + Eq, R : Compare + Eq] BiBTreeMap::remove_by_right(self : BiBTreeMap[L, R], r : R) -> L?fn[L : Compare + Eq, R : Compare + Eq] BiBTreeMap::retain(self : BiBTreeMap[L, R], f : (L, R) -> Bool) -> Unittype BiMap[L, R]Install
Download zipA bidirectional map (bijection) with reverse lookup, insertion order, index access, and a sorted BiBTreeMap variant - MoonBit port of Rust's bimap