2

Is there a way to easily calculate the sequence of all numbers with a specific amount of set bits?

E.g. I want to have all numbers with 2 bits set with a maximum of 4 bits:

3: has 2 bits set to true
5: ,,
6: ,,
9: ,,
10: ,,
12: ,,

Is there a way to determine these numbers without manually counting the bits?

Edit: The order of numbers isn't really relevant. I do want to know the fastest way to obtain all of them though for a specific number of bits set and a maximum number of bits. (I don't need a method that could determine the nth number in this sequence)

Edit2: The reason I want is to be able to get combinations of elements in a list like done here: All Possible Combinations of a list of Values . This solution will give all combinations where I only want the combinations with exactly 8 unique values.

Devedse
  • 1,801
  • 1
  • 19
  • 33
  • Are you thinking of a specific "the sequence"? Or is the order irrelevant? – Yunnosch May 18 '21 at 15:00
  • Order would be irrelevant. I could theoretically sort it after :). – Devedse May 18 '21 at 15:02
  • 1
    I've added the C# language since that's the language I'd want to write it in, but I think I'd be able to read it in most languages. – Devedse May 18 '21 at 15:04
  • Are you open to lateral solutions, aka "cheating"? I am thinking of non-solutions like lookup-tables. I assume that you want scalability or have other reasons for algorhitmic solutions. But you might want to specify. – Yunnosch May 18 '21 at 15:09
  • The main reason I want this is to be able to improve upon this: https://stackoverflow.com/questions/7802822/all-possible-combinations-of-a-list-of-values I do want to find all combinations of 8 unique elements in a list with 12 items. (This function will give back all possible combinations). So regarding the cheating, the easiest way I've now found is simply loop over the numbers and take all the ones with 8 bits. This number will get big really quick though so I think cheating will quicklky end up with numbers getting too large. – Devedse May 18 '21 at 15:10
  • I only can think of bit-number-neutral operations, two of which I came up with, shift (rotating kind) and XOR with even number of set bits values; but not how to put them into an algo. – Yunnosch May 18 '21 at 15:18
  • See https://stackoverflow.com/questions/12171584/what-is-the-fastest-way-to-count-set-bits-in-uint32 – Dmitry May 18 '21 at 15:19
  • Yep I already saw that comment, but that would still require me to loop over all numbers. – Devedse May 18 '21 at 15:24
  • Finding these numbers is mathematically equivalent to selecting 8 items from the set 1, 2, 4, 8, ..., 2048 so if there was an algorithm to find the numbers directly you could simply apply it to your original problem and skip the numbers. Maybe there is such an algorithm, I don't know ;-) – Christopher Hamkins May 18 '21 at 15:45

2 Answers2

5

Yes, there is bit trick to generate all combinations of k set bits in n places.

Described here.

using System;

public class Test
{
    public static void Main()
    {
        int k = 2;
        int n = 4;
        int v = (1 << k) - 1;
        int finish = v << (n - k);
        Console.WriteLine(v);
        while (v != finish) {
            int t = (v | (v - 1)) + 1;  
            v = t | ((((t & -t) / (v & -v)) >> 1) - 1); 
            Console.WriteLine(v);
        }
    }
}
MBo
  • 77,366
  • 5
  • 53
  • 86
1

MBo's answer is an elegant solution if your total set has less than 64 elements. If you have more elements, here's an iterator that will generate all the combination of k elements taken from a set of n elements to solve your root problem.

using System;
using System.Collections;
using System.Collections.Generic;

namespace CPHUtils
{
    public class CombinationIterator : IEnumerable<int[]>
    {
        private int setSize;
        private int combinationSize;
        private int[] indices;
        private int indexToIncrement;

        /// <summary>
        /// Public constructor takes total set size and number of elements in the combinations to be returned
        /// </summary>
        /// <param name="combinationSize_"></param>
        public CombinationIterator(int combinationSize_, int setSize_)
        {
            if (combinationSize_ <= 0) throw new ArgumentException(string.Format("{0} ({1}) is <= 0", nameof(combinationSize_), combinationSize_));
            if (setSize_ <= 0) throw new ArgumentException(string.Format("{0} ({1}) is <= 0", nameof(setSize_), setSize_));
            if (combinationSize_ > setSize_) throw new ArgumentException(string.Format("{0} ({1}) is <= {2} ({3})",
                nameof(combinationSize_), combinationSize_, nameof(setSize_), setSize_));
            this.combinationSize = combinationSize_;
            this.setSize = setSize_;
            // Create internal list of indices with one additional element to make comparison easier during iteration
            indices = new int[combinationSize + 1];
            for (int i = 0; i < combinationSize; i++)
            {
                indices[i] = i;
            }
            indices[combinationSize] = setSize;
        }

        /// <summary>
        /// Returns subsequent arrays of <see cref="combinationSize"/> indices to enumerate all unique
        /// combinations of elements taken from a set of size <see cref="setSize"/>.<br/>
        /// Based on https://en.wikipedia.org/wiki/Combination "simpler faster way"<br/>
        /// There are many ways to enumerate k combinations. One way is to visit all the binary numbers less than 2n. 
        /// Choose those numbers having k nonzero bits, although this is very inefficient even for small n 
        /// (e.g. n = 20 would require visiting about one million numbers while the maximum number of allowed k combinations 
        /// is about 186 thousand for k = 10). The positions of these 1 bits in such a number is a specific k-combination of 
        /// the set { 1, …, n }.[8] Another simple, faster way is to track k index numbers of the elements selected, 
        /// starting with {0 .. k−1} (zero-based) or {1 .. k} (one-based) as the first allowed k-combination and then 
        /// repeatedly moving to the next allowed k-combination by incrementing the last index number if it is lower 
        /// than n-1 (zero-based) or n (one-based) or the last index number x that is less than the index number 
        /// following it minus one if such an index exists and resetting the index numbers after x to {x+1, x+2, …}.
        /// </summary>
        /// <returns>Subsequently returns an array of <see cref="combinationSize"/> int's such that each element
        /// is the index of an element in the original set, until all such unique combinations have been enumerated.</returns>

        public IEnumerator<int[]> GetEnumerator()
        {
            // On first call the array is already set up, can return it directly
            int[] result = new int[combinationSize];
            Array.Copy(indices, result, combinationSize);
            yield return result;

            indexToIncrement = combinationSize - 1;

            while (indexToIncrement >= 0)
            {
                // Increment at this index until done there
                while (indices[indexToIncrement] < (indices[indexToIncrement + 1] - 1))
                {
                    indices[indexToIncrement]++;
                    result = new int[combinationSize];
                    Array.Copy(indices, result, combinationSize);
                    yield return result;
                }
                // Now start at next lower index
                indexToIncrement--;

                // Are we done?
                if (indexToIncrement < 0) yield break;

                // Room for further iterations here?
                if (indices[indexToIncrement] < (setSize - combinationSize + indexToIncrement))
                {
                    indices[indexToIncrement]++;
                    for (int i = indexToIncrement + 1; i < combinationSize; i++)
                    {
                        indices[i] = indices[i - 1] + 1;
                    }
                    result = new int[combinationSize];
                    Array.Copy(indices, result, combinationSize);
                    yield return result;

                    // If there is more room at the top, we need to start there again
                    if (indices[combinationSize - 1] < (setSize - 1))
                    {
                        indexToIncrement = combinationSize - 1;
                    }
                }
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}

This is how you use it:


        [Theory]
        [InlineData(1, 1, "A")]
        [InlineData(2, 2, "AB")]
        [InlineData(1, 4, "ABCD")]
        [InlineData(2, 4, "ABACADBCBDCD")]
        [InlineData(3, 4, "ABCABDACDBCD")]
        [InlineData(3, 3, "ABC")]
        public void genericCombinationIteratorUnitTest(int combinationSize, int setSize, string expectedValue)
        {
            string[] data = new string[setSize];
            for (int i = 0; i < setSize; i++)
            {
                data[i] = ((char)(((int)'A') + i)).ToString();
            }
            CombinationIterator enumerator = new CombinationIterator(combinationSize, setSize);
            StringBuilder sb = new StringBuilder();
            foreach (int[] combination in enumerator)
            {
                foreach (int ix in combination)
                {
                    sb.Append(data[ix]);
                }
            }
            string result = sb.ToString();
            Assert.Equal(expectedValue, result);
        }

Christopher Hamkins
  • 1,442
  • 9
  • 18