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?
Since there are three booleans and they can be true or false, I have 8 possible combinations (2^3).
Boolean variables are stored as 16-bit (2-byte) numbers, but they can only be True or False.
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.
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());
}
If it has to be Stream
s:
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());
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With