Thanks to the awesomeness of peg.js, it's not too hard to provide a check function that returns true (and consumes the input) if a list of elements s
is a combination of some set of elements S
(no repetitions allowed). The basic idea is to compute the powerset of S
and map each element of s
to a prime. Each element of S
is mapped to the product of primes of its corresponding elements, i.e. each element of the powerset of S
is mapped to a unique number. A set s
is a combination of the elements in S
if and only if the product of the corresponding primes in s
is among the products of primes computed from S
. (I guess, there is more than one way to perform this check :-) ). Below is a solution for peg.js with 5 elements which I consider pretty efficient. (A little gotcha when using & { predicate }
: the javascript inside is called with all named expressions in the arguments object, so (a / b /c /d /e)+
has to have a name such as el:(a / b /c /d /e)+
).
{
// array of elements (expressions)
var data = ['a','b','c', 'd', 'e'];
// map elements to primes
var primemap = {
a: 2,
b: 3,
c: 5,
d: 7,
e: 11
};
// powerset of an array
function powerset(arr) {
var ps = [ [] ];
for (var i=0; i < arr.length; i++) {
for (var j = 0, len = ps.length; j < len; j++) {
ps.push(ps[j].concat(arr[i]));
}
}
return ps;
}
// compute the product of primes corresponding to each element of an array arr
function primeprod(arr) {
return arr.reduce( function(p,c) { return p * primemap[c] }, 1 );
}
// compute powerset and remove empty set at index 0 of the powerset
var ps = powerset(data);
ps.splice(0,1);
// map elements of powerset to products of primes
var prods = ps.map( function(el) { return primeprod(el); });
// returns true if an arr is a combination of the elements
function isCombination(arr) {
return prods.indexOf(primeprod(arr)) !== -1
}
}
expr = exp / blankline;
exp = (el:(a / b / c / d / e)+ &{ return isCombination(Array.prototype.slice.call(arguments)[0]); } {return el; } ) rest*
a = _ a:'a' {return a; }
b = _ b:'b' {return b; }
c = _ c:'c' {return c; }
d = _ d:'d' {return d; }
e = _ e:'e' {return e; }
rest = [^abcde]
blankline =
[ \t]* ("\n" / eof) { return []; }
_ = [ \t]*
eof = !.