5

I could do this in brute force, but I was hoping there was clever coding, or perhaps an existing function, or something I am not realising...

So some examples of numbers I want:

00000000001111110000
11111100000000000000
01010101010100000000
10101010101000000000
00100100100100100100

The full permutation. Except with results that have ONLY six 1's. Not more. Not less. 64 or 32 bits would be ideal. 16 bits if that provides an answer.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
bliswell
  • 91
  • 7

4 Answers4

4

I think what you need here is using the itertools module.

BAD SOLUTION

But you need to be careful, for instance, using something like permutations would just work for very small inputs. ie:

Something like the below would give you a binary representation:

>>> ["".join(v) for v in set(itertools.permutations(["1"]*2+["0"]*3))]
['11000', '01001', '00101', '00011', '10010', '01100', '01010', '10001', '00110', '10100']

then just getting decimal representation of those number:

>>> [int("".join(v), 16) for v in set(itertools.permutations(["1"]*2+["0"]*3))]
[69632, 4097, 257, 17, 65552, 4352, 4112, 65537, 272, 65792]

if you wanted 32bits with 6 ones and 26 zeroes, you'd use:

>>> [int("".join(v), 16) for v in set(itertools.permutations(["1"]*6+["0"]*26))]

but this computation would take a supercomputer to deal with (32! = 263130836933693530167218012160000000 )

DECENT SOLUTION

So a more clever way to do it is using combinations, maybe something like this:

import itertools

num_bits = 32
num_ones = 6
lst = [
    f"{sum([2**vv for vv in v]):b}".zfill(num_bits)
    for v in list(itertools.combinations(range(num_bits), num_ones))
]
print(len(lst))

this would tell us there is 906192 numbers with 6 ones in the whole spectrum of 32bits numbers.

CREDITS:

Credits for this answer go to @Mark Dickinson who pointed out using permutations was unfeasible and suggested the usage of combinations

BPL
  • 9,632
  • 9
  • 59
  • 117
  • 1
    This won't work, at least not within any reasonable amount of time. In your last example, the `itertools.permutations` call will run through 32! = 263130836933693530167218012160000000 permutations. Making the optimistic assumption that we can deal with one permutation per nanosecond, it'll still take something over 8 million million million years to complete. – Mark Dickinson Apr 14 '19 at 14:40
  • @Mark Dickinson Oh, you're right! I'd considered the question so trivial that I'd even bothered to think about the performance implications at all, thanks to point it out, I'll edit my answer ;) – BPL Apr 14 '19 at 14:53
  • 1
    I think `itertools.combinations` could work, though. Use `itertools.combinations(range(32), 6)`, and interpret each returned tuple as giving the positions of the 1s. – Mark Dickinson Apr 14 '19 at 14:55
  • @MarkDickinson Edited my answer, on my laptop took 3.6s to compute... guess there is maybe faster solutions than this but ~4s much better than 8trillion years :) – BPL Apr 14 '19 at 15:23
  • @BPL yes there are faster ways even naive nested for loops like in my answer (one set bit) are much faster ... ~4ms on my setup but the most of time difference would be in handling the memory allocation of the solution list ... as I do not store the results anywhere ... – Spektre Apr 16 '19 at 16:27
1

Well I am not a Python coder so I can not post a valid code for you. Instead I can do a C++ one...

If you look at your problem you set 6 bits and many zeros ... so I would approach this by 6 nested for loops computing all the possible 1s position and set the bits...

Something like:

 for (i0=   0;i0<32-5;i0++)
  for (i1=i0+1;i1<32-4;i1++)
   for (i2=i1+1;i2<32-3;i2++)
    for (i3=i2+1;i3<32-2;i3++)
     for (i4=i3+1;i4<32-1;i4++)
      for (i5=i4+1;i5<32-0;i5++)
       // here i0,...,i5 marks the set bits positions

So the O(2^32) become to less than `~O(26.25.24.23.22.21/16) and you can not go faster than that as that would mean you miss valid solutions...

I assume you want to print the number so for speed up you can compute the number as a binary number string from the start to avoid slow conversion between string and number...

The nested for loops can be encoded as increment operation of an array (similar to bignum arithmetics)

When I put all together I got this C++ code:

int generate()
    {
    const int n1=6;     // number of set bits
    const int n=32;     // number of bits
    char x[n+2]; // output number string
    int i[n1],j,cnt; // nested for loops iterator variables and found solutions count
    for (j=0;j<n;j++) x[j]='0'; x[j]='b'; j++; x[j]=0;  // x = 0
    for (j=0;j<n1;j++){ i[j]=j; x[i[j]]='1'; } // first solution
    for (cnt=0;;)
        {
//      Form1->mm_log->Lines->Add(x);   // here x is the valid answer to print
        cnt++;
        for (j=n1-1;j>=0;j--) // this emulates n1 nested for loops
            {
            x[i[j]]='0'; i[j]++;
            if (i[j]<n-n1+j+1){ x[i[j]]='1'; break; }
            }
        if (j<0) break;
        for (j++;j<n1;j++){ i[j]=i[j-1]+1; x[i[j]]='1'; }
        }
    return cnt;         // found valid answers
    };

When I use this with n1=6,n=32 I got this output (without printing the numbers):

cnt = 906192

and it was finished in 4.246 ms on AMD A8-5500 3.2GHz (win7 x64 32bit app no threads) which is fast enough for me...

Beware once you start outputing the numbers somewhere the speed will drop drastically. Especially if you output to console or what ever ... it might be better to buffer the output somehow like outputting 1024 string numbers at once etc... But as I mentioned before I am no Python coder so it might be already handled by the environment...

On top of all this once you will play with variable n1,n you can do the same for zeros instead of ones and use faster approach (if there is less zeros then ones use nested for loops to mark zeros instead of ones)

If the wanted solution numbers are wanted as a number (not a string) then its possible to rewrite this so the i[] or i0,..i5 holds the bitmask instead of bit positions ... instead of inc/dec you just shift left/right ... and no need for x array anymore as the number would be x = i0|...|i5 ...

Spektre
  • 49,595
  • 11
  • 110
  • 380
1

You could create a counter array for positions of 1s in the number and assemble it by shifting the bits in their respective positions. I created an example below. It runs pretty fast (less than a second for 32 bits on my laptop):

bitCount = 32
oneCount = 6
maxBit   = 1<<(bitCount-1)
ones     = [1<<b for b in reversed(range(oneCount)) ] # start with bits on low end
ones[0] >>= 1  # shift back 1st one because it will be incremented at start of loop
index    = 0
result   = []
while index < len(ones):
    ones[index] <<= 1                # shift one at current position
    if index == 0:
        number = sum(ones)           # build output number
        result.append(number)
    if ones[index] == maxBit:    
        index += 1                   # go to next position when bit reaches max
    elif index > 0:               
        index -= 1                   # return to previous position
        ones[index] = ones[index+1]  # and prepare it to move up (relative to next)

64 bits takes about a minute, roughly proportional to the number of values that are output. O(n)

The same approach can be expressed more concisely in a recursive generator function which will allow more efficient use of the bit patterns:

def genOneBits(bitcount=32,onecount=6):
    for bitPos in range(onecount-1,bitcount):
        value = 1<<bitPos
        if onecount == 1: yield value; continue
        for otherBits in genOneBits(bitPos,onecount-1):
            yield value + otherBits

result = [ n for n in genOneBits(32,6) ] 

This is not faster when you get all the numbers but it allows partial access to the list without going through all values.

If you need direct access to the Nth bit pattern (e.g. to get a random one-bits pattern), you can use the following function. It works like indexing a list but without having to generate the list of patterns.

def numOneBits(bitcount=32,onecount=6):
    def factorial(X): return 1 if X < 2 else X * factorial(X-1)
    return factorial(bitcount)//factorial(onecount)//factorial(bitcount-onecount)

def nthOneBits(N,bitcount=32,onecount=6):
    if onecount == 1: return 1<<N
    bitPos = 0
    while bitPos<=bitcount-onecount:
        group = numOneBits(bitcount-bitPos-1,onecount-1)
        if N < group: break
        N -= group
        bitPos += 1
    if bitPos>bitcount-onecount: return None
    result  = 1<<bitPos
    result |= nthOneBits(N,bitcount-bitPos-1,onecount-1)<<(bitPos+1)
    return result


 # bit pattern at position 1000:
 nthOneBit(1000) # --> 10485799 (00000000101000000000000000100111)

This allows you to get the bit patterns on very large integers that would be impossible to generate completely:

 nthOneBits(10000, bitcount=256, onecount=9) 

 # 77371252457588066994880639
 # 100000000000000000000000000000000001000000000000000000000000000000000000000000001111111

It is worth noting that the pattern order does not follow the numerical order of the corresponding numbers

Although nthOneBits() can produce any pattern instantly, it is much slower than the other functions when mass producing patterns. If you need to manipulate them sequentially, you should go for the generator function instead of looping on nthOneBits().

Also, it should be fairly easy to tweak the generator to have it start at a specific pattern so you could get the best of both approaches.

Finally, it may be useful to obtain then next bit pattern given a known pattern. This is what the following function does:

def nextOneBits(N=0,bitcount=32,onecount=6):
     if N == 0: return (1<<onecount)-1
     bitPositions = []
     for pos in range(bitcount):
         bit = N%2
         N //= 2
         if bit==1: bitPositions.insert(0,pos)         
     index = 0
     result = None
     while index < onecount:
         bitPositions[index] += 1
         if bitPositions[index] == bitcount:
             index += 1
             continue
         if index == 0:
             result = sum( 1<<bp for bp in bitPositions )
             break
         if index > 0:
             index -= 1
             bitPositions[index] = bitPositions[index+1]
     return result    

nthOneBits(12)      #--> 131103 00000000000000100000000000011111 
nextOneBits(131103) #--> 262175 00000000000001000000000000011111  5.7ns
nthOneBits(13)      #--> 262175 00000000000001000000000000011111 49.2ns

Like nthOneBits(), this one does not need any setup time. It could be used in combination with nthOneBits() to get subsequent patterns after getting an initial one at a given position. nextOneBits() is much faster than nthOneBits(i+1) but is still slower than the generator function.

For very large integers, using nthOneBits() and nextOneBits() may be the only practical options.

gmds
  • 19,325
  • 4
  • 32
  • 58
Alain T.
  • 40,517
  • 4
  • 31
  • 51
0

You are dealing with permutations of multisets. There are many ways to achieve this and as @BPL points out, doing this efficiently is non-trivial. There are many great methods mentioned here: permutations with unique values. The cleanest (not sure if it's the most efficient), is to use the multiset_permutations from the sympy module.

import time
from sympy.utilities.iterables import multiset_permutations

t = time.process_time()

## Credit to @BPL for the general setup
multiPerms = ["".join(v) for v in multiset_permutations(["1"]*6+["0"]*26)]  

elapsed_time = time.process_time() - t

print(elapsed_time)

On my machine, the above computes in just over 8 seconds. It generates just under a million results as well:

len(multiPerms)
906192
Joseph Wood
  • 7,077
  • 2
  • 30
  • 65