Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Most elegant way to generate possible boolean combinations

What is the most elegant way to generate the possible boolean combinations given the max number of booleans you want?

Ex :

bool(1) -> [false], [true]
bool(2) -> [false, false], [false, true], [true, false], [true, true]
...

This is my current implementation:

public static List<Boolean[]> bool(int n) {
    return IntStream.range(0, (int) Math.pow(2, n))
                    .mapToObj(i -> StringUtils.leftPad(Integer.toBinaryString(i), n, '0').chars().mapToObj(c -> c != '0').toArray(Boolean[]::new))
                    .collect(Collectors.toList());
}

However I'm not satisfied with the fact that I use integers, then map to binary with StringUtils.leftPad and the map that returns a Boolean[] instead of a boolean[].

Is there a nicer way to do it in a one-liner using the Stream API?

like image 508
user2336315 Avatar asked Jan 16 '15 22:01

user2336315


People also ask

How many combinations of 3 Boolean are there?

Since there are three booleans and they can be true or false, I have 8 possible combinations (2^3).

What is the size of Boolean?

Boolean variables are stored as 16-bit (2-byte) numbers, but they can only be True or False.


3 Answers

Try this:

boolean[] bitSetToArray(BitSet bs, int width) {
    boolean[] result = new boolean[width]; // all false
    bs.stream().forEach(i -> result[i] = true);
    return result;
}

List<boolean[]> bool(int n) {
    return IntStream.range(0, (int)Math.pow(2, n))
        .mapToObj(i -> bitSetToArray(BitSet.valueOf(new long[] { i }), n))
        .collect(toList());
}

The key is that BitSet has a stream() method on it that returns a stream of indexes of the one-bits. This can be used to set true values into a boolean[]. Also note that (like Bubletan) it's possible to have a List<boolean[]> instead of List<Boolean[]>. That is, a list of array of primitive boolean values instead of a list of array of boxed Boolean values. (This is because arrays are of reference types and thus can be used as a type argument.)

Finally, thanks to Bubletan, whose solution I augmented by adding bitSetToArray().

UPDATE

srborlongan asked in a comment whether the following might be better:

List<boolean[]> bool(int n) {
    return IntStream.range(0, (int)Math.pow(2, n))
        .mapToObj(i -> new long[] { i })
        .map(BitSet::valueOf)
        .map(bs -> bitSetToArray(bs, n))
        .collect(toList());
}

It does have the advantage of being less dense. After all, this isn't code golf, APL, or Perl where the goal seems to be to write something the most terse way possible. Code that's less dense is often, but not always, easier to read and understand.

There are some nuances in this case though, I think. The "obj" being emitted by the mapToObj stage is long[], which is inferred to be the parameter type of BitSet::valueOf. This in turn affects overload resolution! Unless you're already intimately familar with the BitSet API, you have to do a bit of type inference yourself to figure out what this is doing. So in this case it might be better to have a direct method call to BitSet.valueOf(long[]).

In terms of performance -- which is not always top priority -- I think that direct method calls probably perform better than a chain of map operations. Passing a value through an additional stream operation involves perhaps two method calls, plus the overhead of the Lambda Metafactory calls for the additional lambda(s). In addition, direct method calls are likely more easily optimized by the JIT, and inlined, than passing values through the stream. But I haven't verified any of this.

like image 50
Stuart Marks Avatar answered Nov 17 '22 12:11

Stuart Marks


Tried something. Doesn't use the Stream API for everything.. Though I don't think it's possible to create a boolean[] with it. Haven't used it much so I might be wrong.

public static List<boolean[]> bool(int n) {
    return IntStream.range(0, 1 << n)
            .mapToObj(i -> BitSet.valueOf(new long[] {i}))
            .map(bs -> {
                boolean[] a = new boolean[n];
                for (int i = 0; i < n; i++) {
                    a[n - i - 1] = bs.get(i);
                }
                return a;
            })
            .collect(Collectors.toList());
}
like image 2
Bubletan Avatar answered Nov 17 '22 14:11

Bubletan


If it has to be Streams:

public static List<boolean[]> bool(int n) {
    return IntStream.range(0, 1<<n).mapToObj(
        i->IntStream.range(0, n)
            .collect(()->new boolean[n], (ba,j)->ba[j]=((i>>>j)&1)!=0,
            (x,y)->{throw new UnsupportedOperationException();})
      ).collect(Collectors.toList());
}

I left out the unused merge function for two boolean[] arrays, though it is possible to provide such function. But especially the inner Stream code is not a big win compared to a straight-forward loop:

public static List<boolean[]> bool(int n) {
    return IntStream.range(0, 1<<n).mapToObj(i-> {
        boolean[] ba=new boolean[n];
        for(int j=0; j<n; j++) ba[j]=((i>>>j)&1)!=0;
        return ba;
    }).collect(Collectors.toList());
}
like image 1
Holger Avatar answered Nov 17 '22 14:11

Holger