Finding all combinations of longs with certain bits set

无人久伴 提交于 2019-12-06 06:24:35

问题


This is such an obscure problem that I suspect I'll have to do it at a different level in my code...but hopefully the hive mind that is Stack Overflow can help...

I have a long, which if expressed as a binary string will have exactly five bits set. For example,

long l = 341; // as a bit string, "101010101"

I'm seeking an array containing all ten possible longs which exactly three of those bits set. To continue the example,

long[] results = {
  101010000,
  101000100,
  101000001,
  100010100,
  100010001,
  100000101,
    1010100,
    1010001,
    1000101,
      10101
}

Here's what the appropriate method signature might look like:

public long[] toThreeBitCombinations(long l) {
    // what goes here?
}

(The problem domain is poker; enumerating all the possible board card combinations in a Omaha Poker hand. Yep, there are other ways to approach this, but I am testing out this approach, as dealing with bits is so much quicker than most other alternatives.)


回答1:


Well, I got it. I think. I constructed a version of Gosper's Hack for fragmented fields that I'm not entirely sure about, but it worked for this case.

static long next(long v, long m)
{
    long t = v | (v - 1 & m);
    long t1 = (((t | ~m) + 1) & m);
    int c = Long.numberOfTrailingZeros(v) + 2; // *
    long w = t1 | (((~t & t1) - 1 & m) >>> c);
    return w;
}

I'm not sure why the 2 in the line marked with an asterisk is a 2 instead of a 1.

Anyway, if you do x = next(x, 0x155) in a loop (start with x = 0x15 of course) you get those ten things you listed.




回答2:


I also tried to adapt the standard algorithm for enumerating combinations of a complete set of bits. That algorithm finds the lowest group of 1-bits, moves the highest bit one to the left, and shifts the other ones to the bottom. So for our case, we need to find the k lowest set bits. I didn't have any idea how to do that without a loop, which assumes a fast "popcount" instruction is available (count the number of 1-bits):

unsigned next_combination(unsigned comb, unsigned set) {
    unsigned h = (-comb & (comb ^ set)) - 1;
    unsigned l = set;
    for (int i = 0; i < popcount(h & comb) - 1; ++i)
        l &= l - 1;
    comb = (set & h) ^ l;
    return comb;
}

Edit: I found a different approach that does without the popcount at the chess programming wiki: Traversing Subsets of a Set. It can be slightly simplified as follows:

unsigned next_combination(unsigned comb, unsigned set) {
    unsigned tmp = comb - 1;
    unsigned rip = set & ((tmp | comb) - set);
    for (comb = (tmp ^ rip) & comb; comb; rip ^= tmp, set ^= tmp) {
        tmp = set & -set;
        comb &= comb - 1;
    }
    return rip;
}

Since the loop is only executed once on average, this seems to be actually slightly faster on my machine, probably also because of the bad latency of popcount.




回答3:


Here are a few fast solutions.

Constructing the array

public static final long[] toThreeBitCombinations(long e) {
    //   get lowest 1 bit; turn off that bit;
    final long a = e & -e; e ^= a;
    final long b = e & -e; e ^= b;
    final long c = e & -e; e ^= c;
    final long d = e & -e; e ^= d;

    final long ab = a | b;
    final long ae = a | e;
    final long be = b | e;
    final long cd = c | d;

    return new long[] { cd | e, be | d, ae | d, be | c, ae | c,
                        ab | e, b | cd, a | cd, ab | d, ab | c
                      };
}

This method produces the same output as you expect for your example input. If you want the array in ascending order instead:

public static final long[] toThreeBitCombinations(long e) {
    //   get lowest 1 bit; turn off that bit;
    final long a = e & -e; e ^= a;
    final long b = e & -e; e ^= b;
    final long c = e & -e; e ^= c;
    final long d = e & -e; e ^= d;

    final long ab = a | b;
    final long ae = a | e;
    final long be = b | e;
    final long cd = c | d;

    return new long[] { ab | c, ab | d, a | cd, b | cd, ab | e,
                        ae | c, be | c, ae | d, be | d, cd | e
                      };
}

As can be seen, the order is reversed.

We have, for the construction of the entire array:

ALU usage
  • 4 &
  • 14 |
  • 4 ^
  • 4 unary -
Variable memory usage
  • Maximum of 10 64-bit longs live simultaneously
Method calls
  • 1/10 of a call to this method per element

26 ALU instructions, 80 bytes used, and 1 method call for the entire array compare favorably with other solutions presented here. It also doesn't require the extra work of figuring out what combination-of-three-bits value to start those loops with.

Telling if an element is in the array without needing to construct the array

This is usually slower than constructing a ten-element array and using a linear search on it unless you're trashing arrays pretty quickly after constructing them.

public static final boolean inThreeBitCombinations(final long three, final long five) {
    return ((three & ~five) == 0) && (Long.bitCount(three) == 3);
}


来源:https://stackoverflow.com/questions/19069488/finding-all-combinations-of-longs-with-certain-bits-set

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!