我想知道是否有办法使用换能器来展平列表并过滤唯一值?
通过链接,非常简单:
import {uniq, flattenDeep} from 'lodash';|
const arr = [1, 2, [2, 3], [1, [4, 5]]];
uniq(flattendDeep(arr)); // -> [1, 2, 3, 4, 5]
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.core.min.js"></script>
但是在这里我们在列表中循环两次(+ n 按深度层(。不理想。
我试图实现的是在这种情况下使用换能器。 我已经阅读了有关它的Ramda文档 https://ramdajs.com/docs/#transduce,但我仍然找不到正确编写它的方法。
目前,我使用一个reduce函数,里面有一个递归函数:
import {isArray} from 'lodash';
const arr = [1, 2, [2, 3], [1, [4, 5]]];
const flattenDeepUniq = (p, c) => {
if (isArray(c)) {
c.forEach(o => p = flattenDeepUniq(p, o));
}
else {
p = !p.includes(c) ? [...p, c] : p;
}
return p;
};
arr.reduce(flattenDeepUniq, []) // -> [1, 2, 3, 4, 5]
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.core.min.js"></script>
我们在元素上有一个循环(+n个具有深层深度层的循环(,这似乎更好,更优化。
在这种情况下,甚至可以使用换能器和迭代器吗? 有关Ramda转换功能的更多信息:https://gist.github.com/craigdallimore/8b5b9d9e445bfa1e383c569e458c3e26
换能器在这里没有多大意义。 数据结构是递归的。 处理递归结构的最佳代码通常需要递归算法。
换能器的工作原理
(罗曼·柳蒂科夫(Roman Liutikov(写了一篇很好的换能器介绍。
传感器就是用单个数据替换通过相同数据的多次行程,将步骤的原子操作组合成单个操作。
换能器非常适合转换此代码:
xs.map(x => x * 7).map(x => x + 3).filter(isOdd(x)).take(5)
// ^ ^ ^ ^
// `------ Iteration 4
// `--------------------- Iteration 3
// `-------------------------------------- Iteration 2
// `----------------------------------------------------- Iteration 1
变成这样的东西:
xs.reduce((r, x) => r.length >= 5 ? res : isOdd(x * 7 + 3) ? res.concat(x * 7 - 3) : res, [])
// ^
// `------------------------------------------------------- Just one iteration
在Ramda中,由于map
、filter
和take
都启用了换能器,因此我们可以转换
const foo = pipe(
map(multiply(7)),
map(add(3)),
filter(isOdd),
take(3)
)
foo([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) //=> [17, 31, 45]
(遍历数据四次(到
const bar = compose(
map(multiply(7)),
map(add(3)),
filter(isOdd),
take(3)
)
into([], bar, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) //=> [17, 31, 45]
它只迭代一次。 (请注意从pipe
到compose
的切换。 转导器的组成顺序与普通函数的顺序相反。
请注意,这种传感器的关键点是它们都以类似的方式工作。map
将一个列表转换为另一个列表,filter
和take
也是如此。 虽然您可以使用在不同类型上运行的换能器,并且map
和filter
也可以多态地处理此类换能器,但只有当您组合在同一类型上运行的功能时,它们才会协同工作。
Flatten
是传感器的弱拟合度
您的结构更复杂。 虽然我们当然可以创建一个函数,以某种方式(预序,后序(爬行它,因此可能会用它开始换能器管道,但处理递归结构的逻辑方法是使用递归算法。
展平这种嵌套结构的一种简单方法是这样的:
const flatten = xs => xs.reduce(
(a, x) => concat(a, isArray(x) ? flatten(x) : [x]),
[]
);
(由于各种技术原因,Ramda的代码要复杂得多。
但是,这种递归版本不太适合与换能器一起使用,换能器基本上必须逐步工作。
Uniq
不太适合传感器
另一方面,uniq
对于这种换能器来说意义不大。 问题是uniq
使用的容器,如果要从传感器中获得任何好处,则必须是具有快速插入和快速查找的容器,最有可能是Set
或Object
。 假设我们使用Set
. 然后我们有一个问题,因为我们的flatten
在列表上运行。
不同的方法
由于我们不能轻松地将现有函数折叠成一个可以执行您正在寻找的功能的函数,因此我们可能需要编写一次性函数。
早期解决方案的结构使得添加唯一性约束变得相当容易。 同样,那是:
const flatten = xs => xs.reduce(
(a, x) => concat(a, isArray(x) ? flatten(x) : [x]),
[]
);
使用将所有元素添加到Set
的辅助函数:
const addAll = (set, xs) => xs.reduce((s, x) => s.add(x), set)
我们可以编写一个扁平化的函数,只保留唯一值:
const flattenUniq = xs => xs.reduce(
(s, x) => addAll(s, isArray(x) ? flattenUniq(x) : [x]),
new Set()
)
请注意,这具有上述结构,仅切换为使用Set
,因此从concat
切换到我们的addAll
。
当然,您可能希望在最后使用一个数组。 我们可以通过使用Set -> Array
函数包装它来做到这一点,如下所示:
const flattenUniq = xs => Array.from(xs.reduce(
(s, x) => addAll(s, isArray(x) ? flattenUniq(x) : [x]),
new Set()
))
您也可以考虑将此结果保留为Set
。 如果您确实想要唯一值的集合,Set
是合乎逻辑的选择。
这样的函数没有无点转导函数的优雅,但它是有效的,并且暴露的管道使与原始数据结构和普通flatten
函数的关系更加清晰。
我想你可以把这整个长答案看作是一种冗长的方式,指出user633183在评论中所说的:"扁平化和Uniq都不是换能器的好用例。
Uniq现在是Ramda中的一个换能器,所以你可以直接使用它。至于展平,你可以遍历前面的树来产生一堆平面值
const arr = [1, 2, [2, 3], [1, [4, 5]]];
const deepIterate = function*(list) {
for (const it of list) {
yield* Array.isArray(it) ? deepIterate(it) : [it];
}
}
R.into([], R.uniq(), deepIterate(arr)) // -> [1, 2, 3, 4, 5]
这使您可以组成其他换能器
R.into([], R.compose(R.uniq(), R.filter(isOdd), R.take(5)), deepIterate(arr))