Gui*_*sco 3 javascript recursion transducer ramda.js
我想知道是否有一种方法可以使用换能器来展平列表并过滤唯一值?
通过链接,这很容易:
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 写了一篇很好的关于换能器的介绍。)
Transducers 都是用一个单一的数据替换多次遍历相同的数据,将步骤的原子操作组合成一个单一的操作。
转换器非常适合转换此代码:
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将一个列表转换为另一个列表,就像 dofilter和 一样take。虽然您可以拥有对不同类型进行操作的转换器,并且map也filter可能以多态方式对此类类型进行操作,但只有在组合对相同类型进行操作的函数时,它们才能协同工作。
Flatten 不适合换能器你的结构更复杂。虽然我们当然可以创建一个以某种方式(前序、后序)抓取它的函数,因此可能会用它开始一个转换器管道,但处理递归结构的逻辑方法是使用递归算法。
扁平化这种嵌套结构的一种简单方法是这样的:
const flatten = xs => xs.reduce(
  (a, x) => concat(a, isArray(x) ? flatten(x) : [x]), 
  []
);
(由于各种技术原因,Ramda 的代码要复杂得多。)
然而,这种递归版本不太适合与换能器一起工作,换能器基本上必须逐步工作。
Uniq 不适合换能器uniq,另一方面,对于这样的换能器来说意义不大。问题是 使用的容器uniq,如果您要从换能器中获得任何好处,则必须是具有快速插入和快速查找功能的容器,aSet或 aObject最有可能。假设我们使用Set. 然后我们有一个问题,因为我们flatten对列表进行操作。
由于我们无法轻松地将现有函数合并为一个可以满足您要求的函数,因此我们可能需要编写一个一次性函数。
早期解决方案的结构使得添加唯一性约束相当容易。再次,那是:
const flatten = xs => xs.reduce(
  (a, x) => concat(a, isArray(x) ? flatten(x) : [x]), 
  []
);
使用辅助函数将所有元素添加到 a 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()
)
请注意,这与上述结构有很多相似之处,仅切换到使用 a Set,因此从 切换concat到我们的addAll.
当然,最后你可能想要一个数组。我们可以通过用一个Set -> Array函数包装它来做到这一点,如下所示:
const flattenUniq = xs => Array.from(xs.reduce(
  (s, x) => addAll(s, isArray(x) ? flattenUniq(x) : [x]), 
  new Set()
))
您也可以考虑将此结果保留为Set. 如果您真的想要一组唯一值,那么 aSet是合乎逻辑的选择。
这样的函数没有无点转换函数的优雅,但它可以工作,并且暴露的管道使与原始数据结构和普通flatten函数的关系更加清晰。
我想您可以将整个冗长的答案视为一种冗长的方式来指出 user633183 在评论中所说的话:“flatten 和 uniq 都不是传感器的好用例。”
| 归档时间: | 
 | 
| 查看次数: | 421 次 | 
| 最近记录: |