换能器变平和唯一

时间:2018-08-24 16:20:50

标签: javascript recursion ramda.js transducer

我想知道是否有一种方法可以使用换能器来展平列表并过滤唯一值?

通过链接,非常简单:

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)。不理想。

我要达到的目的是在这种情况下使用换能器。 我已经阅读了https://ramdajs.com/docs/#transduce的Ramda文档,但仍然找不到正确编写方法。

当前,我在内部使用带有递归函数的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

1 个答案:

答案 0 :(得分:2)

传感器在这里没有多大意义。您的数据结构是递归的。处理递归结构的最佳代码通常需要递归算法。

换能器如何工作

(罗马·柳蒂科夫(Roman Liutikov)向传感器写了nice introduction。)

换能器的目的是用单个数据替换同一数据中的多个行程,将步骤的原子操作组合为一个操作。

换能器很适合转换此代码:

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中,由于mapfiltertake已启用换能器,因此我们可以进行转换

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]

只会迭代一次。 (请注意,从pipecompose的切换。换能器的编写顺序与普通函数相反。)

请注意,此类换能器的要点是它们的操作均类似。 mapfiltertake一样,将一个列表转换为另一个列表。尽管您可以使用不同类型的换能器,并且mapfilter也可以多态地工作于此类类型,但是只有在您组合了相同类型的功能时,它们才能一起工作。 / p>

Flatten对于换能器不适合

您的结构更加复杂。虽然我们当然可以创建一个将以某种方式(预购,后购)对其进行爬网的函数,从而可能以此来启动换能器管道,但是处理递归结构的逻辑方法是使用递归算法。

扁平化这种嵌套结构的简单方法如下:

const flatten = xs => xs.reduce(
  (a, x) => concat(a, isArray(x) ? flatten(x) : [x]), 
  []
);

(由于各种技术原因,Ramda的代码要复杂得多。)

但是,此递归版本并不适合与换能器一起使用,换能器实际上必须逐步进行。

Uniq很不适合换能器

另一方面,

uniq对于此类换能器来说意义不大。问题是,uniq使用的容器(如果要从换能器中获得任何好处)必须是具有快速插入和快速查找功能的容器,Set或{{1} } 最有可能的。假设我们使用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函数的关系更加清晰。 / p>


我想您可以将整个长答案看作是指出user633183在评论中所说的漫长方式:“扁平化或uniq都不是换能器的好用例。”