使用会聚生成列表的所有旋转时的意外结果

时间:2019-05-23 00:11:14

标签: javascript functional-programming ramda.js

我正在尝试获取列表v的所有轮换。因此,在rotations的定义中,我将rotateLeft的翻转版本用作第一个分支函数(以便首先接受列表),然后使用返回列表[0, 1, 2, ..., v.length-1]的函数,其中map为收敛函数。

const {curry,mathMod,pipe,splitAt,reverse,unnest,converge,map,flip,length,times,identity} = require("ramda");

const v = [1,2,3,4];

const rotateLeft = curry((n,vet) => {
    const i = mathMod(n,vet.length);
    return pipe(
        splitAt(i),
        reverse,
        unnest
    )(vet);
});

const rotations = converge(map,[
    flip(rotateLeft),
    pipe(length,times(identity))
]);

rotations(v);

但是,这并没有返回我期望的结果。相反,如果我按如下所示重写它,则可以正常工作:

map(flip(rotateLeft)(v),
    pipe(length,times(identity))(v));

// gives [[1,2,3,4],[2,3,4,1],[3,4,1,2],[4,1,2,3]]

据我了解,converge将两个分支函数应用于v,然后将结果作为参数提供给map。那正确吗? 那么,rotations(v)为什么不返回相同的东西?

Code

使用reduce

的更简洁的版本

受您的 vanilla JS 版本启发,我提出了以下reduceRotations函数,该函数不使用map的index参数或一种明显的方式。然后,当然,我以完全没有意义的方式将其翻译为 vanilla Ramda 。 :)

const {converge,reduce,always,append,pipe,last,head,tail,identity,unapply} = require("ramda");

const reduceRotations = (v) => {
    const rotate = (v) => append(head(v),tail(v));
    return reduce(
        (acc,_) => append(rotate(last(acc)),acc),
        [v],
        tail(v)
    );
};

const pointFreeRotations = 
    converge(reduce,[
        always(converge(append,[
            pipe(last,converge(append,[head,tail])),
            identity
        ])),
        unapply(identity),
        tail
    ]);

Code

又一个人

以下等效函数使用scan代替reduce

const {converge,scan,always,append,head,tail,identity} = require("ramda");

const scanRotations = (v) => {
    const rotate = (v) => append(head(v),tail(v));
    return scan(rotate,v,tail(v));
};

const scanPointFreeRotations =
    converge(scan,[
        always(converge(append,[head,tail])),
        identity,
        tail
    ]);

Code

2 个答案:

答案 0 :(得分:4)

这是因为top_block_cls将提供给它的最长函数的arity作为其arity。

因此,由于convergeflip(rotateLeft).length //=> 2,旋转的长度为2。但是您显然想要一元函数。解决此问题的最简单方法是将pipe(length,times(identity)) //=> 1包裹在flip(rotateLeft)内:

unary
const rotateLeft = curry((n,vet) => {
    const i = mathMod(n,vet.length);
    return pipe(
        splitAt(i),
        reverse,
        unnest
    )(vet);
});

const rotations = converge (map, [
  unary ( flip (rotateLeft) ),
  pipe ( length, times(identity) )
])

const v = [1,2,3,4];

console .log (
  rotations (v)
)

还请注意,这实际上并不需要Ramda的所有机器。在香草JS中很容易做到这一点:

<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script><script>
const {curry, mathMod, pipe, splitAt, reverse, unnest, converge, map, unary, flip, length, times, identity} = R   </script>

答案 1 :(得分:3)

通过rotations的这种简单的递归实现来证明,有时由大量微型函数组成的无点代码根本不值得增加麻烦-

const rotations = ([ x, ...xs ], count = 0) =>
  count > xs.length
    ? []
    : [ [ x, ...xs ], ...rotations ([ ...xs, x ], count + 1) ]

console.log(rotations([1,2,3]))
// [ [ 1, 2, 3 ], [ 2, 3, 1 ], [ 3, 1, 2 ] ]

console.log(rotations([1,2,3,4]))
// [ [ 1, 2, 3, 4 ], [ 2, 3, 4, 1 ], [ 3, 4, 1, 2 ], [ 4, 1, 2, 3 ] ]

上面,销毁分配会创建许多中间值,但是我们可以使用稍微不同的算法来解决此问题-

const rotations = (xs = [], i = 0) =>
  i >= xs.length
    ? []
    : [ xs.slice(i).concat(xs.slice(0, i)) ].concat(rotations(xs, i + 1))

console.log(rotations([1,2,3]))
// [ [ 1, 2, 3 ], [ 2, 3, 1 ], [ 3, 1, 2 ] ]

console.log(rotations([1,2,3,4]))
// [ [ 1, 2, 3, 4 ], [ 2, 3, 4, 1 ], [ 3, 4, 1, 2 ], [ 4, 1, 2, 3 ] ]

像您一样定义辅助功能是要保持良好的卫生习惯。这也使我们的其他功能更具可读性-

const rotate = (xs = []) =>
  xs.slice(1).concat(xs.slice(0, 1))

const rotations = (xs = [], i = 0) =>
  i >= xs.length
    ? []
    : [ xs ].concat(rotations(rotate(xs), i + 1))

console.log(rotations([1,2,3]))
// [ [ 1, 2, 3 ], [ 2, 3, 1 ], [ 3, 1, 2 ] ]

console.log(rotations([1,2,3,4]))
// [ [ 1, 2, 3, 4 ], [ 2, 3, 4, 1 ], [ 3, 4, 1, 2 ], [ 4, 1, 2, 3 ] ]