Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected result when using converge to generate all rotations of a list

I'm trying to get all the rotations of the list v. So, in the definition of rotations, I use the flipped version of rotateLeft as the first branching function (in order to accept the list first) and then a function that returns the list [0, 1, 2, ..., v.length-1], with map as the converging function.

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);

However, this doesn't return what I expected. Instead, it works fine if I rewrite that as follows:

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]]

As I understand, converge applies the two branching functions to v and then feeds the results as arguments to map. Is that right? So why doesn't rotations(v) return the same thing?

Code

More concise version using reduce

Inspired by your versions in vanilla JS, I've come up with the following reduceRotations function, which doesn't use the index parameter of map or recursion in an obvious way. Then, of course, I translated that into vanilla Ramda, in a totally point-free fashion. :)

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

Yet another one

The following equivalent functions employ scan instead of 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

like image 768
Louis Sakh Avatar asked Mar 04 '23 23:03

Louis Sakh


1 Answers

This is because converge takes as its arity the arity of the longest function supplied to it.

So, since flip(rotateLeft).length //=> 2 and pipe(length,times(identity)) //=> 1, rotations will have length 2. But you clearly want a unary function. The easiest way to fix it is to simply wrap flip(rotateLeft) inside 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)
)
<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>

Also note that this doesn't really require all of Ramda's machinery. It's pretty easy to do this in vanilla JS:

const rotations = v => v .map ( (_, i) => v .slice (i) .concat ( v .slice(0, i) ) )
like image 71
Scott Sauyet Avatar answered Apr 06 '23 01:04

Scott Sauyet