Формула для N-го количества битов (или количества населения) размера M

Мне интересно найти простую формулу для определения n-го раза, когда определенный бит_счет встречается в последовательности натуральных чисел. В частности, какова связь между K и N в таблице ниже. Так, например, что такое N из K=123456789123456789123456789, я могу вам сказать, что M есть 50, но что такое N?

length = 5
for K in range(2**length):
    bits = bin(K)[2:].zfill(length)
    M    = K.bit_count() # numbers of "1"s in the binary sequence
    N    = sum(1 for i in range(K) if M==i.bit_count())
    print(f'{K: <2}',bits,M,N)

'''
K  bits  M N
0  00000 0 0
1  00001 1 0
2  00010 1 1
3  00011 2 0
4  00100 1 2
5  00101 2 1
6  00110 2 2
7  00111 3 0
8  01000 1 3
9  01001 2 3
10 01010 2 4
11 01011 3 1
12 01100 2 5
13 01101 3 2
14 01110 3 3
15 01111 4 0
16 10000 1 4
17 10001 2 6
18 10010 2 7
19 10011 3 4
20 10100 2 8
21 10101 3 5
22 10110 3 6
23 10111 4 1
24 11000 2 9
25 11001 3 7
26 11010 3 8
27 11011 4 2
28 11100 3 9
29 11101 4 3
30 11110 4 4
31 11111 5 0
...
'''

Итак, я, кажется, решил половину проблемы. Кажется, что

N = (K-sum(2**i for i in range(M)).bit_count()

когда угодно N<=M. Это, по-видимому, потому, что

K = sum(2**i for i in range(M)) + sum(2**(M-1-i) for i in range(N))

опять же, когда угодно N<=M. Похоже, что N<=M встречается примерно в половине случаев.

length = 5
for K in range(2**length):
    bits = bin(K)[2:].zfill(length)
    M    = K.bit_count() # numbers of "1"s in the binary sequence
    N    = sum(1 for i in range(K) if M==i.bit_count())
    if N <= M:
        A = sum(2**i for i in range(M)) + sum(2**(M-1-i) for i in range(N))
        B = (K - sum(2**i for i in range(M))).bit_count()
    else:
        A = '-'
        B = '-'
    print(f'{K: <2}',bits,M,N,A,B)

Это больше похоже на вопрос по Математике, а не на вопрос по программированию.

Barmar 12.04.2024 00:36

Если я скажу «pop_count» на math.stackexchange, они немедленно удалят вопрос. Несколько недель назад я попытался использовать знак «%» для символа мода, но мне сразу отказали.

Bobby Ocean 12.04.2024 00:41

Попробуйте использовать терминологию теории чисел, а не компьютерного программирования. Вы не используете компьютерное программирование для разработки формулы, вы используете компьютерное программирование для реализации формулы после ее математического определения.

Barmar 12.04.2024 00:43

Поскольку для любого K N является суммой всех целых чисел < K, которые разделяют число битов M с K, вы можете либо перебирать ряд (что является простой, хотя и очень вычислительно интенсивной проблемой программирования), либо придумать некоторая закономерность в этой серии (которая кажется нетривиальной математической задачей). На пути программирования методом грубой силы вместо этого вы могли бы сгенерировать все числа < K с определенным количеством битов и суммировать их.

Grismar 12.04.2024 00:50

@Grismar Да, это определенно нетривиально. Просто кажется, что это кто-то видел раньше. Я попробовал построить график пар (M,N) в системе координат, и кажется, что есть закономерность, но опять же нетривиальная. pop_count — настолько распространенная функция в компьютерном программировании и обработке сигналов, что я надеюсь, что кто-то знает больше меня.

Bobby Ocean 12.04.2024 00:53

Кстати, я почему-то сказал «сумма», но это, конечно, счет.

Grismar 12.04.2024 02:47

@barmar Я не совсем согласен с вами в том, что это скорее вопрос по математике, но я также чувствую, что на самом деле он просто требует алгоритмического подхода к вопросу по математике. Справедливо назвать это вопросом информатики, и если бы он был сформулирован в терминах «как более эффективно решить эту проблему» с данным решением, это был бы скорее вопрос программирования. Но вы правы, это довольно маргинально. Однако я не согласен с мнением, что подобные вещи — это «математика заранее» — многие математические задачи решаются с помощью алгоритмической игры. Если сформулировать правильно, это подходит для SO.

Grismar 12.04.2024 03:06

@EricPostpischil, ты должен опубликовать это как ответ

Nick 13.04.2024 06:37

Ваше значение N начинается с 0. Наверняка оно должно начинаться с 1?

Nick 13.04.2024 10:44

@Ник: Хорошо, готово.

Eric Postpischil 15.04.2024 03:44
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
10
151
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Тривиальное решение:

def count_same0(k: int) -> int:
    s = 0
    m = k.bit_count()
    for n in range(k):
        if n.bit_count() == m:
            s += n
    return s

Однако вместо этого вы можете попытаться сгенерировать все числа < k только с определенным количеством битов и посчитать их.

Вот простая попытка сделать это:

def count_same1(k: int) -> int:
    from itertools import permutations
    from math import log2, floor

    m = k.bit_count()
    positions = floor(log2(k)) + 1  # the total number of bit positions 

    s = 0
    # start generating unique permutations of positions #bits
    ps = set()
    for p in permutations([0] * (positions-m) + [1] * m):
        if p not in ps:
            ps.add(p)
            n = 0
            for b in p:
                n = (n << 1) | b
            # stop when we first exceed k
            if n >= k:
                break
            s += n

    return s 

Это работает, но очень неэффективно, поскольку все равно приходится генерировать все неуникальные перестановки. И, в конце концов, создание перестановки происходит намного медленнее, чем просто подсчет битов.

Вместо этого, возможно, было бы лучше сгенерировать уникальные комбинации позиций нулей для чисел, которые имеют определенное количество единиц, а затем вычислить эти числа:

def gen_int_n_ones(n: int) -> Generator[int, None, None]:
    from itertools import combinations

    z = 0
    while True:
        bits_cs = list(combinations(range(1, n), z))
        yield from (sum(0 if i in bits else 2**(n-(i+1)) for i in range(n)) for bits in bits_cs)
        z += 1
        n += 1


def count_same2(k: int) -> int:
    s = 0
    for n in gen_int_n_ones(k.bit_count()):
        if n >= k:
            break
        s += n

    return s

Однако наивное решение использует операции, которые настолько эффективны, что эффективнее просто проверить все числа, а не решать, каких из них следует избегать.

Попробуйте запустить это:

def main():
    from timeit import timeit

    for x in range(1, 100):
        print(x, count_same0(x), count_same1(x), count_same2(x))
        print(
            timeit(lambda: count_same0(x), number=10000), 
            timeit(lambda: count_same1(x), number=10000), 
            timeit(lambda: count_same2(x), number=10000)
        )

Вы обнаружите, что count_same0 — самый быстрый (на самом деле, просто быстрый) и что count_same2 лучше, чем count_same1, но не приближается к count_same0.

Поэтому я думаю, что простой ответ на самом деле:

def n_for_k(k: int) -> int:
    b = k.bit_count()
    return sum(1 for i in range(k) if i.bit_count() == b)

Однако для вашего примера значения 123456789123456789123456789 это займет много времени. Однако я сомневаюсь, что существует известное более простое решение, учитывая, что (после решения, как описано выше) я также нашел последовательность в OEIS: https://oeis.org/A079071 и она по сути определяет то же самое. (и глядя на график, лучше не становится: https://oeis.org/A079071/graph)

Кто или что поручил вам решить эту задачу?

Это выглядит почти идентично предоставленному мной коду, и, как мы оба отмечаем, это решение не решает данное значение K. Значение K было выбрано большим. В общем, учитывая 100-значные двоичные числа, мне хотелось бы знать их индекс в сгенерированном списке, поэтому я выбрал K, который отражал бы эту трудность. Я согласен, что наивного подхода недостаточно. Однако обратите внимание: если K = 549755811839, то решением будет сразу M = 38 и N = (K - sum(2**i for i in range(38))).bit_count() = 27. Это потому что N<=M. Итак, для некоторых K существуют быстрые формулы.

Bobby Ocean 12.04.2024 03:08

Я не спорю, просто хотел поделиться в комментариях результатом того, что предложил - считаю это бесплодным направлением. Однако, если посмотреть на данные OEIS, это выглядит как проблема, решение которой потребует написания математической работы. То, что вы предлагаете относительно «некоторых K», — это просто еще один способ сказать, что границы могут быть установлены для N, что ясно из графика и, вероятно, приведет к аналогичной формулировке для крайних случаев. Но это не ведет к общему решению для любого N, о чем вы, кажется, спрашиваете.

Grismar 12.04.2024 03:12

Я бы предложил задать более широкий вопрос на доске по математике (как предложил пользователь @barmar), сослаться на последовательность OEIS и посмотреть, знают ли какие-нибудь математики о атаке на эту проблему, которая имеет решение для любого N.

Grismar 12.04.2024 03:15

Возможно, в этом вы правы. В своем редактировании я неправильно заявил, что решил «половину», на самом деле это гораздо меньший процент, например log(n!) или что-то в этом роде, и поэтому, вероятно, соответствует какому-то краю или границе на графике. Хотя кажется, что K <-> (M,N) — это спаривание 1-1, так что в этом смысле моя формула действительно решает «половину» пар (M,N).

Bobby Ocean 12.04.2024 03:18
floor(log2(k)) + 1 крайне неэффективно. log2 = b.bit_length() было бы намного лучше
phuclv 12.04.2024 04:34

@phuclv конечно, купите, очевидно, что это не имеет значения, поскольку оно вычисляется только один раз и полностью уничтожается остальной частью алгоритма.

Grismar 12.04.2024 04:39

@phuclv Это тоже неверно, например для K= 562949953421311.

no comment 12.04.2024 04:42

@nocomment Я просто пропускаю + 1, K.bit_length() + 1 всегда работают правильно, потому что bit_length всегда является нижней частью log2

phuclv 12.04.2024 08:36

@phuclv Хм? Нет, не добавляйте 1. Я говорил о логовом способе. Всегда в какой-то момент ошибаюсь, я не знаю, почему люди продолжают этим пользоваться, особенно опытные люди, которые наверняка знают о проблемах с поплавками.

no comment 12.04.2024 09:40

@nocomment, почему бы и нет? результат равен полу, поэтому без добавления 1 он эквивалентен полу (log2 (k)). Длина в битах должна быть равна дну log2.

phuclv 12.04.2024 14:32

@phuclv «без добавления 1 это эквивалентно полу(log2(k))» — Нет, это не так.

no comment 12.04.2024 14:51

@nocomment в документации говорится. Аналогично, когда abs(x) достаточно мал, чтобы иметь правильно округленный логарифм, тогда k = 1 + int(log(abs(x), 2)). И это также легко увидеть из базовой математики. На самом деле bit_length всегда корректно для сколь угодно больших значений, тогда как log2 нет, поскольку он использует математические вычисления с плавающей запятой ограниченной точности. Просто докажите любое положительное число, в котором bit_length неверно и не эквивалентно floor(log2(k)). Если ты этого не понимаешь, то хватит спорить

phuclv 12.04.2024 16:40

@phuclv Как ты не видишь 1 +, который ты туда скопировал? Вы привели доказательство моей правоты.

no comment 12.04.2024 17:13
oeis.org/A079071 последовательность не та; он учитывает только общую длину каждого значения в битах. Таким образом, двоичный код 10 не считается вторым экземпляром одного установленного бита (первым является 1), поскольку в нем есть 0, а 1 нет. Однако в этом вопросе OP считает, что все числа имеют то же количество бит, что и значение K.
Nick 13.04.2024 11:09
Ответ принят как подходящий

Я не знаю Python, так что это Ruby. Алгоритм должен легко транслироваться. Идея состоит в том, чтобы для каждого установленного бита (от меньшего к большему) подсчитать все числа, которые одинаковы слева от этого бита, имеют одинаковое количество установленных битов в этом бите и справа от него, но не имеют одинакового количества установленных битов в этом бите и справа от него. этот бит установлен.

  • Перемещайте биты вашего ввода от меньшего к большему.
  • Следите за установленными просмотренными битами и общим количеством просмотренных битов.
  • Подсчитайте все числа с одинаковым количеством видимых установленных битов, но с меньшим максимальным установленным битом (чем текущий индекс) каждый раз, когда вы встречаете установленный бит.
  • Например, при достижении меньшего 1 из 10100 мы добавляем 2 к счету, потому что есть 2 числа с 1 установленным битом и меньшим максимальным установленным битом: 10 и 01.

Код:

def g(k)
  set_bits_seen = 0
  all_bits_seen = 0
  
  total = 0
  
  while k > 0
    all_bits_seen += 1
    
    if k % 2 == 1 # cur bit is set
      set_bits_seen += 1
      if all_bits_seen > set_bits_seen
        total += n_choose_k(all_bits_seen - 1, set_bits_seen)
      end
    end
    k /= 2
  end
  return total
end

def n_choose_k(n, k)
  return 1 if k == 0 or k == n
  (k + 1 .. n).reduce(:*) / (1 .. n - k).reduce(:*)
end

Полученные результаты:

1.upto(20) do |i|
  puts "#{i.to_s(2)}  --  #{g(i)}"
end

1  --  0
10  --  1
11  --  0
100  --  2
101  --  1
110  --  2
111  --  0
1000  --  3
1001  --  3
1010  --  4
1011  --  1
1100  --  5
1101  --  2
1110  --  3
1111  --  0
10000  --  4
10001  --  6
10010  --  7
10011  --  4
10100  --  8

И для вашего большого примера:

> g(123456789123456789123456789)
=> 3594960708495168399327022

Это действительно делает то, что, по моему мнению, должно делать. А как насчет задом наперед? Если я дал тебе (N,M)=(50,3594960708495168399327022), как ты найдешь K?

Bobby Ocean 12.04.2024 03:47

@BobbyOcean Я думаю, что есть простой подход, но у меня нет времени подтвердить это или написать; вероятно, завтра. Не могли бы вы опубликовать это как отдельный связанный вопрос? Кто-то другой может опередить меня, и это нормально.

Dave 12.04.2024 03:52

Думаю, я мог бы увидеть, как сделать это задом наперед. Большое спасибо, я знал, что это должно быть что-то вроде суммы биномиальных коэффициентов для каждого из установленных битов.

Bobby Ocean 12.04.2024 03:55

Выражение числа в виде комбинации битов и вычисление индекса комбинации:

from more_itertools import combination_index
from math import comb

K = 123456789123456789123456789

M = K.bit_length()
c = [i for i, b in enumerate(f'{K:b}') if b == '1']
N = comb(M, len(c)) - 1 - combination_index(c, range(M))

print(N)

Вывод (тот же, что и у Дэйва):

3594960708495168399327022

Черт, я только что понял, что тебе нужна «простая формула». Ну что ж. Возможно, это все еще полезно для людей, которые приходят сюда в поисках простого кода.

Результат

Для вычислений в любом направлении (от битовой строки k до количества бит m и индекса n или от количества битов и индекса до битовой строки) требуется всего несколько арифметических операций на битовую позицию. Реализации Python и C приведены ниже.

Чтобы проиллюстрировать, что алгоритмы состоят из нескольких элементарных арифметических операций на битовую позицию, версия C показана здесь без комментариев. Полностью прокомментированный код приведен ниже:

static void BitStringToIndex(unsigned Bits, unsigned *NBits, unsigned *Index)
{
    unsigned b = 0;
    while (Bits>>b & 1)
        ++b;
    unsigned m = b, n = 0, c = 1;
    while (Bits>>++b)
        if (Bits>>b & 1)
            n += (c = c * b / ++m);
        else
            c = c * b / (b-m);
    *NBits = m;
    *Index = n;
}


static unsigned IndexToBitString(unsigned m, unsigned n)
{
    unsigned b = m, c = 1;
    for (unsigned t; (t = c * (b+1) / ((b+1)-m)) <= n; ++b)
        c = t;
    unsigned k = 0;
    for (; n; --b)
    {
        if (c <= n)
        {
            n -= c;
            k |= 1;
            c = c * m-- / b;
        }
        else
            c = c * (b-m) / b;
        k <<= 1;
    }
    k <<= b;
    k |= (1u << m) - 1;
    return k;
}

Алгоритмы

Число способов расположить m 1 бит в b позициях с 0 битами в остальных m−b позициях равно bCm = b! / м! / (б-м)!.1

В этом ответе позиции битов нумеруются 0 для наименее значимого, затем 1 для следующего и так далее. Двоичное число 10000 имеет 1 в битовой позиции 4 и 4 нуля «справа» от него или «ниже».

Рассмотрим список двоичных чисел в порядке возрастания с фиксированным количеством 1 бит. Прежде чем бит в позиции b сможет стать 1 (если он не начинается с 1), либо с начала списка, либо с момента последнего изменения с 1 на 0, биты b справа от него должны пройти через все bC m расположение битов m 1 в битах b, где m — количество битов 1 в позиции b и ниже. Число с 1 битом в позициях b0, b1, b2,…, каждое из которых соответствует соответствующему m0, m1, m2,… , появляется после шагов b0Cm0 в списке, чтобы получить 1 в бите b0 плюс b1 Cm1 выполняет шаги в списке, чтобы получить 1 в бите b1, и так далее. Итак, все, что нам нужно сделать, чтобы найти позицию этого числа в списке, — это добавить bCm к каждому биту, кроме тех, что вместе справа.2 Это показывает процедура BitStringToIndex ниже.

И наоборот, учитывая количество битов m и индекс n, мы находим наибольшее значение b до того, как bCm превысит n, и это говорит нам, где находится первый бит в битовой строке. Тогда строка, которую мы ищем, представляет собой n − bCm шагов в списке после того места, где впервые появляется этот ведущий 1 бит, поэтому мы можем обновить n, а затем определить позицию следующего 1 бита. Об этом свидетельствует рутина IndexToBitString.

В этом описании алгоритмов bCm неоднократно упоминается. Однако нам не нужно каждый раз рассчитывать его с нуля. По мере перехода от bCm к b+1Cm или b+1Cm+1 значение C изменяется на один множитель в числителе и на один множитель в знаменателе. Это показано в комментариях в коде ниже. Таким образом, одного умножения и одного деления на позицию бита достаточно для поддержания постоянного значения этой функции.

Реализация Python

# At several points, comments mention maintaining c = C(b, m).  This means
# that, as the values of b and/or m change, the value of c is modified so
# that it remains equal to C(b, m) for the new values of b or m.


# Convert bit string Bits (a binary numeral) to the number of 1 bits it
# contains and the index of that string in ascending lexicographic order of the
# strings with that number of 1 bits.
def BitStringToIndex(Bits):

    # Set b to the position number of the lowest 0 bit in Bits.
    b = 0
    while Bits>>b & 1:
        b += 1

    # Start with number of bits b and index 0.  Maintain c = C(b, m) =
    # b! / m! / (b-m)!, the number of ways to arrange m 1 bits and b-m 0 bits
    # in b positions.
    m = b
    n = 0
    c = 1

    # Increment b and continue while there are further 1 bits at or above b.
    b += 1
    while Bits>>b:

        # If bit b is 1, count another 1 bit (++m) and add to the index n
        # the number of arrangements we have passed -- all the arrangements
        # of m 1 bits in the b positions below position b, C(b, m).
        #
        # Update c to transition to the new C(b, m) from the prior
        # C(b-1, m-1): C(b, m) / C(b-1, m-1) =
        # b! / m! / (b-m)! / ((b-1)! / (m-1)! / ((b-1) - (m-1))!) = b / m.
        if Bits>>b & 1:
            m += 1
            c = c * b // m
            n += c

        # If bit is 0, do not update the index.  Merely update c:
        # C(b, m) / C(b-1, m) =
        # b! / m! / (b-m)! / ((b-1)! / m! / ((b-1)-m)!) = b / (b-m).
        else:
            c = c * b // (b-m)

        b += 1

    # Return the bit count and the index.
    return [m, n]


# Convert a number of 1 bits, m, and an index, n, of a bit string in ascending
# lexicographic order of the strings with that number of 1 bits to the bit
# string (a binary numeral).
def IndexToBitString(m, n):

    # Find highest bit and maintain c = C(b, m) = b! / m! / (b-m)!,
    # the number of ways to arrange m 1 bits and b-m 0 bits in b positions.
    b = m
    c = 1
    while 1:
        t = c * (b+1) // ((b+1)-m)
        if n < t:
            break
        c = t
        b += 1

    # Start the index k at zero.  For each bit position b, in descending
    # order, test whether the index n indicates we have passed C(b, m) prior
    # arrangements.  If so, there is a 1 at the current bit position, and we
    # decrement m by 1 to account for the 1 bit here and n by C(b, m) to
    # account for the number of prior arrangements.
    k = 0

    # Continue until base case, index n is zero, at which time there are m 1
    # bits in the m low positions.
    while 0 < n:
    
        # If n equals or exceeds C(b, m), we passed all prior arrangements
        # of m bits in the b lower positions, so there must be a 1 bit here.
        if c <= n:
            # Decrement n for the number of prior arrangements.
            # 
            # Put a 1 bit at this position.  (We shift k later.)
            # 
            # Update c to C(b-1, m-1) from C(b, m):  C(b-1, m-1) / C(b, m) =
            # (b-1)! / (m-1)! / ((b-1)-(m-1))! / (b! / m! / (b-m)!) =
            # m / b.
            # 
            # Decrement m by 1 for this 1 bit.
            n -= c
            k |= 1
            c = c * m // b
            m -= 1

        # Otherwise, merely update c to C(b-1, m) from C(b, m):
        # C(b-1, m) / C(b, m) =
        # (b-1)! / m! / ((b-1)-m)! / (b! / m! / (b-m)!) = (b-m) / b.
        else:
            c = c * (b-m) // b

        # Shift k to work on next bit position.
        k <<= 1;

        b -= 1

    # The loop above can be made sufficient to construct all of k by running
    # it down to a final iteration in which b is zero (which requires some
    # care in the loop limit and avoiding division by zero in updating c).
    # That is mathematically aesthetic, but, once n reaches zero, we can
    # handle the final bits directly with the code below.

    # The bit at position 0 in the value under construction in k is destined
    # to be bit b in the final value, so shift k.
    k <<= b

    # Here n is zero which is the index for all m 1 bits in the low
    # positions. Add this to k, and we are done.
    k |= (1 << m) - 1

    return k;


for k in range (32):
    m, n = BitStringToIndex(k)
    print(k, m, n)
    t = IndexToBitString(m, n)
    if t != k:
        print("Error, expected ({}, {}) -> {} but got {}.".format(m, n, t, k))
        exit(1)

C Реализация

#include <stdio.h>
#include <stdlib.h>


/*  At several points, comments mention maintaining c = C(b, m).  This means
    that, as the values of b and/or m change, the value of c is modified so
    that it remains equal to C(b, m) for the new values of b or m.
*/


/*  Convert bit string Bits (a binary numeral) to the number of 1 bits it
    contains, *NBits, and the index, *Index, of that string in ascending
    lexicographic order of the strings with that number of 1 bits.
*/
static void BitStringToIndex(unsigned Bits, unsigned *NBits, unsigned *Index)
{
    //  Set b to the position number of the lowest 0 bit in Bits.
    unsigned b = 0;
    while (Bits>>b & 1)
        ++b;

    /*  Start with number of bits b and index 0.  Maintain c = C(b, m) =
        b! / m! / (b-m)!, the number of ways to arrange m 1 bits and b-m 0 bits
        in b positions.
    */
    unsigned m = b, n = 0, c = 1;

    //  Increment b and continue while there are further 1 bits at or above b.
    while (Bits>>++b)

        /*  If bit b is 1, count another 1 bit (++m) and add to the index n
            the number of arrangements we have passed -- all the arrangements
            of m 1 bits in the b positions below position b, C(b, m).

            Update c to transition to the new C(b, m) from the prior
            C(b-1, m-1): C(b, m) / C(b-1, m-1) =
            b! / m! / (b-m)! / ((b-1)! / (m-1)! / ((b-1) - (m-1))!) = b / m.
        */
        if (Bits>>b & 1)
            n += (c = c * b / ++m);

        /*  If bit is 0, do not update the index.  Merely update c:
            C(b, m) / C(b-1, m) =
            b! / m! / (b-m)! / ((b-1)! / m! / ((b-1)-m)!) = b / (b-m).
        */
        else
            c = c * b / (b-m);

    //  Return the bit count and the index.
    *NBits = m;
    *Index = n;
}


/*  Convert a number of 1 bits, m, and an index, n, of a bit string in
    ascending lexicographic order of the strings with that number of 1 bits to
    the bit string (a binary numeral).
*/
static unsigned IndexToBitString(unsigned m, unsigned n)
{
    /*  Find highest bit and maintain c = C(b, m) = b! / m! / (b-m)!,
        the number of ways to arrange m 1 bits and b-m 0 bits in b positions.

        The unusual loop construction is due to the fact we want to test
        whether increasing b would yield n < C(b, m), in which case we want to
        use b before increasing it.  So a temporary value t is assigned the
        value of what C(b, m) would be if b were increased.  If it does not
        exceed n, c is updated and b is increased, and the loop continues.
    */
    unsigned b = m, c = 1;
    for (unsigned t; (t = c * (b+1) / ((b+1)-m)) <= n; ++b)
        c = t;

    /*  Start the index k at zero.  For each bit position b, in descending
        order, test whether the index n indicates we have passed C(b, m) prior
        arrangements.  If so, there is a 1 at the current bit position, and we
        decrement m by 1 to account for the 1 bit here and n by C(b, m) to
        account for the number of prior arrangements.
    */
    unsigned k = 0;

    /*  Continue until base case, index n is zero, at which time there are m 1
        bits in the m low positions.
    */
    for (; n; --b)
    {
        /*  If n equals or exceeds C(b, m), we passed all prior arrangements
            of m bits in the b lower positions, so there must be a 1 bit here.
        */
        if (c <= n)
        {
            /*  Decrement n for the number of prior arrangements.

                Put a 1 bit at this position.  (We shift k later.)

                Update c to C(b-1, m-1) from C(b, m):  C(b-1, m-1) / C(b, m) =
                (b-1)! / (m-1)! / ((b-1)-(m-1))! / (b! / m! / (b-m)!) =
                m / b.

                Decrement m by 1 for this 1 bit.
            */
            n -= c;
            k |= 1;
            c = c * m-- / b;
        }

        /*  Otherwise, merely update c to C(b-1, m) from C(b, m):
            C(b-1, m) / C(b, m) =
            (b-1)! / m! / ((b-1)-m)! / (b! / m! / (b-m)!) = (b-m) / b.
        */
        else
            c = c * (b-m) / b;

        //  Shift k to work on next bit position.
        k <<= 1;
    }
    /*  The loop above can be made sufficient to construct all of k by running
        it down to a final iteration in which b is zero (which requires some
        care in the loop limit and avoiding division by zero in updating c).
        That is mathematically aesthetic, but, once n reaches zero, we can
        handle the final bits directly with the code below.
    */

    /*  The bit at position 0 in the value under construction in k is destined
        to be bit b in the final value, so shift k.
    */
    k <<= b;

    /*  Here n is zero which is the index for all m 1 bits in the low
        positions. Add this to k, and we are done.
    */
    k |= (1u << m) - 1;

    return k;
}


int main(void)
{
    for (unsigned k = 0; k < 32; ++k)
    {
        unsigned m, n;
        BitStringToIndex(k, &m, &n);
        printf("%u %u %u\n", k, m, n);
        unsigned t = IndexToBitString(m, n);
        if (t != k)
        {
            fprintf(stderr, "Error, expected (%u, %u) -> %u but got %u.\n",
                m, n, k, t);
            exit(EXIT_FAILURE);
        }
    }
}

Сноски

1 Это известный элементарный результат комбинаторики. Иногда ее называют функцией «n выбирают k», потому что это также количество способов выбора k объектов из n и обычно пишется nCk. Фактически размещение m1 бит в b позициях означает выбор m позиций из b позиций. Однако, поскольку в вопросе n и k используются для других целей, я не смог использовать их для этого.

2 Крайние правые биты изначально содержат 1, поэтому для установки их в 1 не требуется никаких шагов по списку, поэтому они не вносят вклад в индекс.

Ваш IndexToBitString похож на алгоритм Баклза; это правильно?

Nick 15.04.2024 03:57

@BobbyOcean эти реализации должны быть быстрее, чем ваш текущий ответ.

Nick 15.04.2024 03:58

@Ник: Может быть. Я просто написал это с нуля.

Eric Postpischil 15.04.2024 04:03
dl.acm.org/doi/10.1145/355732.355739
Nick 15.04.2024 04:04

@Ник: В статье Баклза и Либанона все описывается по-разному, но я думаю, что основные идеи по сути одинаковы. Биномиальная функция обладает большой гибкостью. Например, сумма функции по диагональному отрезку равна разнице значений функции в начальной и конечной точках, и я думаю, что это объяснило бы некоторую разницу между моими выражениями и их выражениями. Обратите внимание, что их код неоднократно вызывает биномиальную функцию, чего я избегаю.

Eric Postpischil 17.04.2024 03:14

Согласованный; ваше упрощение за счет использования соотношений между различными значениями, а не вычисления их с нуля, определенно является хорошим.

Nick 17.04.2024 05:02

Другие вопросы по теме