continuations
Here's a solution involving delimited continuations – delimited continuations are sometimes called composable continuations because they have a return value, and thus can be composed with any other ordinary functions – additionally, they can be called multiple times which can produce extraordinary effects
// identity :: a -> a
const identity = x =>
x
// concatMap :: (a -> [b]) -> [a] -> [b]
const concatMap = f => ([x,...xs]) =>
x === undefined
? []
: f (x) .concat (concatMap (f) (xs))
// cont :: a -> cont a
const cont = x =>
k => k (x)
// reset :: cont a -> (a -> b) -> b
const reset = m =>
k => m (k)
// shift :: ((a -> b) -> cont a) -> cont b
const shift = f =>
k => f (x => k (x) (identity))
// amb :: [a] -> cont [a]
const amb = xs =>
shift (k => cont (concatMap (k) (xs)))
// demo
reset (amb (['J', 'Q', 'K', 'A']) (x =>
amb (['♡', '♢', '♤', '♧']) (y =>
cont ([[x, y]]))))
(console.log)
// [ ['J','♡'], ['J','♢'], ['J','♤'], ['J','♧'], ['Q','♡'], ['Q','♢'], ['Q','♤'], ['Q','♧'], ['K','♡'], ['K','♢'], ['K','♤'], ['K','♧'], ['A','♡'], ['A','♢'], ['A','♤'], ['A','♧'] ]
Of course this works for any variety of inputs and any nesting limit (that doesn't blow the stack ^_^)
const choices =
[0,1]
reset (amb (choices) (x =>
amb (choices) (y =>
amb (choices) (z =>
cont ([[x, y, z]])))))
(console.log)
// [ [0,0,0], [0,0,1], [0,1,0], [0,1,1], [1,0,0], [1,0,1], [1,1,0], [1,1,1] ]
But you must be wondering how we can abstract the nesting of amb
itself – for example, in the code above, we have 3 levels of nesting to generate permutations of length 3 – what if we wanted to permute our choices 4, 5, or N times ?
const permute = (n, choices) =>
{
const loop = (acc, n) =>
n === 0
? cont ([acc])
: amb (choices) (x =>
loop (acc.concat ([x]), n - 1))
return loop ([], n)
}
permute (4, [true,false]) (console.log)
// [ [ true , true , true , true ],
// [ true , true , true , false ],
// [ true , true , false, true ],
// [ true , true , false, false ],
// [ true , false, true , true ],
// [ true , false, true , false ],
// [ true , false, false, true ],
// [ true , false, false, false ],
// [ false, true , true , true ],
// [ false, true , true , false ],
// [ false, true , false, true ],
// [ false, true , false, false ],
// [ false, false, true , true ],
// [ false, false, true , false ],
// [ false, false, false, true ],
// [ false, false, false, false ] ]
sounds german, or something
If I'm understanding your comment correctly, you want something that zips the input and permutes each pair – shall we call it, zippermute
?
const zippermute = (xs, ys) =>
{
const loop = (acc, [x,...xs], [y,...ys]) =>
x === undefined || y === undefined
? cont ([acc])
: amb ([x,y]) (choice =>
loop (acc.concat ([choice]), xs, ys))
return loop ([], xs, ys)
}
zippermute (['a', 'b', 'c'], ['x', 'y', 'z']) (console.log)
// [ [ 'a', 'b', 'c' ],
// [ 'a', 'b', 'z' ],
// [ 'a', 'y', 'c' ],
// [ 'a', 'y', 'z' ],
// [ 'x', 'b', 'c' ],
// [ 'x', 'b', 'z' ],
// [ 'x', 'y', 'c' ],
// [ 'x', 'y', 'z' ] ]