滚动你自己的数据结构
另一种选择是编写自己的map 模块,该模块不依赖于 JavaScript 的原生 Map。这完全将我们从其可变行为中解放出来,并防止每次我们希望 set、update 或 del 时都制作完整副本。该解决方案让您可以完全控制并有效地演示如何实现您想象中的任何数据结构 -
// main.js
import { fromEntries, set, del, toString } from "./map.js"
const a =
[["d",3],["e",4],["g",6],["j",9],["b",1],["a",0],["i",8],["c",2],["h",7],["f",5]]
const m =
fromEntries(a)
console.log(1, toString(m))
console.log(2, toString(del(m, "b")))
console.log(3, toString(set(m, "c", "#")))
console.log(4, toString(m))
我们希望得到预期的输出 -
- 映射
m,fromEntries(a)的结果
- 映射的导数
m 与键 "b" 已删除
- 映射
m 的导数与键"c" 更新为"#"
- 映射
m,未经上述操作修改
1 (a, 0)->(b, 1)->(c, 2)->(d, 3)->(e, 4)->(f, 5)->(g, 6)->(h, 7)->(i, 8)->(j, 9)
2 (a, 0)->(c, 2)->(d, 3)->(e, 4)->(f, 5)->(g, 6)->(h, 7)->(i, 8)->(j, 9)
3 (a, 0)->(b, 1)->(c, #)->(d, 3)->(e, 4)->(f, 5)->(g, 6)->(h, 7)->(i, 8)->(j, 9)
4 (a, 0)->(b, 1)->(c, 2)->(d, 3)->(e, 4)->(f, 5)->(g, 6)->(h, 7)->(i, 8)->(j, 9)
是时候实现我们的愿望并实施我们的map 模块了。我们将从定义 empty 映射的含义开始 -
// map.js
const empty =
Symbol("map.empty")
const isEmpty = t =>
t === empty
接下来我们需要一种方法将我们的条目插入map。这调用存在,fromEntries、set、update 和 node -
// map.js (continued)
const fromEntries = a =>
a.reduce((t, [k, v]) => set(t, k, v), empty)
const set = (t, k, v) =>
update(t, k, _ => v)
const update = (t, k, f) =>
isEmpty(t)
? node(k, f())
: k < t.key
? node(t.key, t.value, update(t.left, k, f), t.right)
: k > t.key
? node(t.key, t.value, t.left, update(t.right, k, f))
: node(k, f(t.value), t.left, t.right)
const node = (key, value, left = empty, right = empty) =>
({ key, value, left, right })
接下来,我们将定义一种方法来get 映射中的值 -
// main.js (continued)
const get = (t, k) =>
isEmpty(t)
? undefined
: k < t.key
? get(t.left, k)
: k > t.key
? get(t.right, k)
: t.value
现在我们将定义一种方法来delete 从我们的地图中获取一个条目,这也调用存在 concat -
// map.js (continued)
const del = (t, k) =>
isEmpty(t)
? t
: k < t.key
? node(t.key, t.value, del(t.left, k), t.right)
: k > t.key
? node(t.key, t.value, t.left, del(t.right, k))
: concat(t.left, t.right)
const concat = (l, r) =>
isEmpty(l)
? r
: isEmpty(r)
? l
: r.key < l.key
? node(l.key, l.value, concat(l.left, r), l.right)
: r.key > l.key
? node(l.key, l.value, l.left, concat(l.right, r))
: r
最后,我们提供了一种使用toString 可视化地图的方法,它调用了inorder。作为奖励,我们将提供toArray -
const toString = (t) =>
Array.from(inorder(t), ([ k, v ]) => `(${k}, ${v})`).join("->")
function* inorder(t)
{ if (isEmpty(t)) return
yield* inorder(t.left)
yield [ t.key, t.value ]
yield* inorder(t.right)
}
const toArray = (t) =>
Array.from(inorder(t))
导出模块的功能 -
// map.js (continued)
export { empty, isEmpty, fromEntries, get, set, update, del, append, inorder, toArray, toString }
唾手可得的果实
您的地图模块已完成,但我们可以添加一些有价值的功能,而无需付出太多努力。下面我们实现preorder 和postorder 映射遍历。此外,我们向toString 和toArray 添加了第二个参数,允许您选择要使用的遍历。默认使用inorder -
// map.js (continued)
function* preorder(t)
{ if (isEmpty(t)) return
yield [ t.key, t.value ]
yield* preorder(t.left)
yield* preorder(t.right)
}
function* postorder(t)
{ if (isEmpty(t)) return
yield* postorder(t.left)
yield* postorder(t.right)
yield [ t.key, t.value ]
}
const toArray = (t, f = inorder) =>
Array.from(f(t))
const toString = (t, f = inorder) =>
Array.from(f(t), ([ k, v ]) => `(${k}, ${v})`).join("->")
export { ..., preorder, postorder }
我们可以扩展fromEntries 以接受任何可迭代的,而不仅仅是数组。这与Object.fromEntries 和Array.from 的功能相匹配-
// map.js (continued)
function fromEntries(it)
{ let r = empty
for (const [k, v] of it)
r = set(r, k, v)
return r
}
就像我们上面做的那样,我们可以添加第二个参数,它允许我们指定如何将条目添加到地图中。现在它就像Array.from 一样工作。为什么Object.fromEntries 没有这种行为让我很困惑。 Array.from 很聪明。就像Array.from -
// map.js (continued)
function fromEntries(it, f = v => v)
{ let r = empty
let k, v
for (const e of it)
( [k, v] = f(e)
, r = set(r, k, v)
)
return r
}
// main.js
import { fromEntries, toString } from "./map.js"
const a =
[["d",3],["e",4],["g",6],["j",9],["b",1],["a",0],["i",8],["c",2],["h",7],["f",5]]
const z =
fromEntries(a, ([ k, v ]) => [ k.toUpperCase(), v * v ])
console.log(toString(z))
(A, 0)->(B, 1)->(C, 4)->(D, 9)->(E, 16)->(F, 25)->(G, 36)->(H, 49)->(I, 64)->(J, 81)
演示
展开下面的sn-p,在你自己的浏览器中验证我们的Map模块的结果-
// map.js
const empty =
Symbol("map.empty")
const isEmpty = t =>
t === empty
const node = (key, value, left = empty, right = empty) =>
({ key, value, left, right })
const fromEntries = a =>
a.reduce((t, [k, v]) => set(t, k, v), empty)
const get = (t, k) =>
isEmpty(t)
? undefined
: k < t.key
? get(t.left, k)
: k > t.key
? get(t.right, k)
: t.value
const set = (t, k, v) =>
update(t, k, _ => v)
const update = (t, k, f) =>
isEmpty(t)
? node(k, f())
: k < t.key
? node(t.key, t.value, update(t.left, k, f), t.right)
: k > t.key
? node(t.key, t.value, t.left, update(t.right, k, f))
: node(k, f(t.value), t.left, t.right)
const del = (t, k) =>
isEmpty(t)
? t
: k < t.key
? node(t.key, t.value, del(t.left, k), t.right)
: k > t.key
? node(t.key, t.value, t.left, del(t.right, k))
: concat(t.left, t.right)
const concat = (l, r) =>
isEmpty(l)
? r
: isEmpty(r)
? l
: r.key < l.key
? node(l.key, l.value, concat(l.left, r), l.right)
: r.key > l.key
? node(l.key, l.value, l.left, concat(l.right, r))
: r
function* inorder(t)
{ if (isEmpty(t)) return
yield* inorder(t.left)
yield [ t.key, t.value ]
yield* inorder(t.right)
}
const toArray = (t) =>
Array.from(inorder(t))
const toString = (t) =>
Array.from(inorder(t), ([ k, v ]) => `(${k}, ${v})`).join("->")
// main.js
const a =
[["d",3],["e",4],["g",6],["j",9],["b",1],["a",0],["i",8],["c",2],["h",7],["f",5]]
const m =
fromEntries(a)
console.log(1, toString(m))
console.log(2, toString(del(m, "b")))
console.log(3, toString(set(m, "c", "#")))
console.log(4, toString(m))
console.log(5, get(set(m, "z", "!"), "z"))