Найдите все способы вставить нули в битовый паттерн - PullRequest
7 голосов
/ 29 мая 2010

Почему-то я изо всех сил пытался обернуть голову вокруг этого. У меня есть 15 бит, которые представляют собой число. Биты должны соответствовать шаблону. Образец определяется по тому, как начинаются биты: они находятся в наиболее правостороннем представлении этого образца. Скажем, шаблон 1 1 1. Биты будут:

000000010111101

Таким образом, общее правило состоит в том, чтобы взять каждое число в шаблоне, создать столько битов (в данном случае 1, 4 или 1), а затем разделить их хотя бы одним пробелом. Так что, если это 1 2 6 1 (это будет случайным):

001011011111101

Начиная с версии справа-вниз, я хочу сгенерировать каждое возможное число, которое соответствует этому шаблону. Количество битов будет храниться в переменной. Итак, для простого случая предположим, что это 5 битов, а начальный битовый шаблон: 00101. Я хочу сгенерировать:

00101 01001 01010 10001 10010 10100

Я пытаюсь сделать это в Objective-C, но все, что напоминает C, будет в порядке. Я просто не могу придумать хороший рекурсивный алгоритм для этого. Это имеет смысл в приведенном выше примере, но когда я начинаю входить в 12431 и вынужден отслеживать все, он ломается.

Ответы [ 6 ]

3 голосов
/ 29 мая 2010

Надеюсь, это поможет вам обернуть голову (пожалуйста, прочитайте это с ручкой и бумагой в руке).

Скажите, что число нулей (начиная справа) равно x 1 , x 2 , ..., x n . Например: если битовая комбинация 00001110001001, то x 1 = 0, x 2 = 2, x 3 = 3, x 4 = 4. n на единицу больше, чем количество блоков единиц. Заметьте, что зная x 1 , x 2 , ..., x n достаточно, чтобы выяснить битовую комбинацию.

Теперь, если общее число единиц, которые у вас есть, равно S, а общее количество доступных битов равно M, тогда мы должны иметь это

x 1 + x 2 + ... + x n = M - S

и x 1 & ge; 0, x n & ge; 0, х 2 & ge; 1, x 3 & ge; 1, ...

Пусть z 1 = x 1 + 1 и z n = x n + 1

Таким образом, мы имеем

z 1 + x 2 + ... x n-1 + z n = M - S + 2

Где z 1 & ge; 1, х 2 & ge; 1, x 3 & ge; 1, ..., z n & ge; 1.

Теперь рассмотрим раздел из M-S + 2 предмета, где в каждом разделе есть хотя бы один предмет. Любое разбиение соответствует решению вышеприведенного уравнения, а решение соответствует разбиению 1-1.

Поместите M-S + 2 предмета вдоль линии. Чтобы получить раздел, рассмотрите возможность размещения n-1 палочек в доступных местах M-S + 2-1 = M-S + 1 между предметами.

Таким образом, решение (и, в конечном итоге, требуемый битовый шаблон) однозначно соответствует способу выбора n-1 точек среди M-S + 1.

В случае 5 битов, причем 1 бит равен 1 и 1.

У вас n = 3, M = 5 и S = ​​2.

Таким образом, у вас есть M-S + 1, выберите n-1 = 4, выберите 2 = 6 возможностей.

Перечисление n выберите r комбинаций является стандартной проблемой, и вы должны найти большое разнообразие решений (некоторые из них очень умные!) Для этого в Интернете.

Пример см. Здесь: http://compprog.files.wordpress.com/2007/10/comb1.c, который, кажется, поддерживает «ленивое» перечисление: next_combination и не требует огромных объемов памяти.

2 голосов
/ 29 мая 2010

Я не собираюсь давать вам код Objective-C в основном потому, что:

  • Я знаю Objective-C только на очень поверхностном уровне.
  • У меня нет желания писать весь код управления памятью, необходимый для того, чтобы это работало на языке, подобном C, и в любом случае это только ухудшило бы читаемость.

Вместо этого я дам вам несколько идей и код, показывающий, как я мог бы реализовать это на более высоком языке с генераторами и сборкой мусора (в данном случае с Python), а также подсказку, как это сделать без генераторов. Надеюсь, кто-то еще сможет перенести код для вас, если вы не можете сделать это самостоятельно.

Я бы подумала о вашей проблеме немного иначе:

  • Сколько лидирующих нулей в исходной схеме "справа-вниз".
  • Сколько существует способов разбить это количество нулей на n разбиений.

В последнем примере у вас есть два ведущих нуля и три раздела с разделителями '10' и '1':

2 0 0: 00101  
1 1 0: 01001   
1 0 1: 01010   
0 2 0: 10001   
0 1 1: 10010   
0 0 2: 10100

Разделители всегда имеют форму 111..10, за исключением последнего, равного 111..1 без конечного нуля.

Для перечисления вышеупомянутых разделов используйте функцию, подобную следующей в Python:

def partitions(n, x):
    if n == 1:
        yield [x]
    else:
        for i in range(x + 1):
            for p in partitions(n - 1, x - i):
                yield [i] + p

for p in partitions(3, 2):
    print p

Результат:

[0, 0, 2]
[0, 1, 1]
[0, 2, 0]
[1, 0, 1]
[1, 1, 0]
[2, 0, 0]

Если у вас есть эти разделы, то легко создавать шаблоны.

Одна из проблем заключается в том, что Objective-C не имеет встроенной поддержки конструкции yield. Следующее переписывание вышеуказанной функции может быть легче преобразовать в Objective-C:

def partitions(n, x):
    if n == 1:
        return [[x]]
    else:
        result = []
        for i in range(x + 1):
            for p in partitions(n - 1, x - i):
                result.append([i] + p)
        return result

Надеюсь, это вам пригодится.

1 голос
/ 31 мая 2010

Опираясь на @ Марк Байерс и Морон ответы на вашу задачу можно переформулировать следующим образом:

Перечислите все способы размещения K нулей в N местах (см. комбинаций с повторениями и Звезды и столбцы ).

Пример: для 15 битов и шаблона 1 2 6 1 есть N = 5 мест (до / после числа и между 1 с), чтобы поставить K = 2 нуля (число ведущих нулей для правильного числа сброса) ). Число путей равно биномиальному (N + K - 1, K), то есть биномиальному (5 + 2-1, 2) = 15.

Ключевыми функциями в коде ниже являются next_combination_counts() и comb2number().

Полная программа на C

#include <assert.h>
#include <stdbool.h>
#include <stdio.h>

#define SIZE(arr) (sizeof(arr)/sizeof(*(arr)))

#define PRInumber "u"
typedef unsigned number_t;

// swap values pointed to by the pointer
static void
iter_swap(int* ia, int* ib) {
  int t = *ia;
  *ia = *ib;
  *ib = t;
}

// see boost::next_combinations_counts()
// http://photon.poly.edu/~hbr/boost/combinations.html
// http://photon.poly.edu/~hbr/boost/combination.hpp
static bool 
next_combination_counts(int* first, int* last) {
  /*
0 0 2 
0 1 1 
0 2 0 
1 0 1 
1 1 0 
2 0 0 
   */
    int* current = last;
    while (current != first && *(--current) == 0) {
    }
    if (current == first) {
        if (first != last && *first != 0)
            iter_swap(--last, first);
        return false;
    }
    --(*current);
    iter_swap(--last, current);
    ++(*(--current));
    return true;
}

// convert combination and pattern to corresponding number
// example: comb=[2, 0, 0] pattern=[1,1] => num=5 (101 binary)
static number_t 
comb2number(int comb[], int comb_size, int pattern[], int pattern_size) {
  if (pattern_size == 0)
    return 0;
  assert(pattern_size > 0);
  assert(comb_size > pattern_size);

  // 111 -> 1000 - 1 -> 2**3 - 1 -> (1 << 3) - 1
  // 111 << 2 -> 11100
  number_t num = ((1 << pattern[pattern_size-1]) - 1) << comb[pattern_size];  
  int len = pattern[pattern_size-1] + comb[pattern_size];
  for (int i = pattern_size - 1; i--> 0; ) {
    num += ((1 << pattern[i]) - 1) << (comb[i+1] + 1 + len);
    len += pattern[i] + comb[i+1] + 1;
  }  

  return num;
}

// print binary representation of number
static void 
print_binary(number_t number) {
  if (number > 0) {
    print_binary(number >> 1);
    printf("%d", number & 1);
  }
}

// print array
static void
printa(int arr[], int size, const char* suffix) {
  printf("%s", "{");
  for (int i = 0; i < (size - 1); ++i)
    printf("%d, ", arr[i]);
  if (size > 0)
    printf("%d", arr[size - 1]);
  printf("}%s", suffix);
}

static void 
fill0(int* first, int* last) {
  for ( ; first != last; ++first)
    *first = 0;
}

// generate {0,0,...,0,nzeros} combination
static void
init_comb(int comb[], int comb_size, int nzeros) {
  fill0(comb, comb + comb_size);
  comb[comb_size-1] = nzeros;
}

static int
sum(int* first, int* last) {
  int s = 0;
  for ( ; first != last; ++first)
    s += *first;
  return s;
}

// calculated max width required to print number (in PRInumber format)
static int 
maxwidth(int comb[], int comb_size, int pattern[], int pattern_size) {
  int initial_comb[comb_size];

  int nzeros = sum(comb, comb + comb_size);
  init_comb(initial_comb, comb_size, nzeros);
  return snprintf(NULL, 0, "%" PRInumber, 
                  comb2number(initial_comb, comb_size, pattern, pattern_size)); 
}

static void 
process(int comb[], int comb_size, int pattern[], int pattern_size) {
  // print combination and pattern
  printa(comb, comb_size, " ");
  printa(pattern, pattern_size, " ");
  // print corresponding number
  for (int i = 0; i < comb[0]; ++i)
    printf("%s", "0");
  number_t number = comb2number(comb, comb_size, pattern, pattern_size);
  print_binary(number);
  const int width = maxwidth(comb, comb_size, pattern, pattern_size);
  printf(" %*" PRInumber "\n", width, number);
}

// reverse the array
static void 
reverse(int a[], int n) {
  for (int i = 0, j = n - 1; i < j; ++i, --j) 
    iter_swap(a + i, a + j);  
}

// convert number to pattern
// 101101111110100 -> 1, 2, 6, 1
static int 
number2pattern(number_t num, int pattern[], int nbits, int comb[]) {
  // SIZE(pattern) >= nbits
  // SIZE(comb) >= nbits + 1
  fill0(pattern, pattern + nbits);
  fill0(comb, comb + nbits + 1);

  int i = 0;
  int pos = 0;
  for (; i < nbits && num; ++i) {
    // skip trailing zeros
    for ( ; num && !(num & 1); num >>= 1, ++pos)
      ++comb[i];
    // count number of 1s
    for ( ; num & 1; num >>=1, ++pos) 
      ++pattern[i];
  }
  assert(i == nbits || pattern[i] == 0);  
  const int pattern_size = i;  

  // skip comb[0]
  for (int j = 1; j < pattern_size; ++j) --comb[j];
  comb[pattern_size] = nbits - pos;

  reverse(pattern, pattern_size);
  reverse(comb, pattern_size+1);
  return pattern_size;
}

int 
main(void) {
  number_t num = 11769; 
  const int nbits = 15;

  // clear hi bits (required for `comb2number() != num` relation)
  if (nbits < 8*sizeof(number_t))
    num &=  ((number_t)1 << nbits) - 1;
  else
    assert(nbits == 8*sizeof(number_t));

  // `pattern` defines how 1s are distributed in the number
  int pattern[nbits];
  // `comb` defines how zeros are distributed 
  int comb[nbits+1];
  const int pattern_size = number2pattern(num, pattern, nbits, comb);
  const int comb_size = pattern_size + 1;

  // check consistency
  // . find number of leading zeros in a flush-right version
  int nzeros = nbits;
  for (int i = 0; i < (pattern_size - 1); ++i)
    nzeros -= pattern[i] + 1;
  assert(pattern_size > 0);
  nzeros -= pattern[pattern_size - 1];
  assert(nzeros>=0);

  // . the same but using combination
  int nzeros_comb = sum(comb, comb + comb_size);
  assert(nzeros_comb == nzeros);

  // enumerate all combinations 
  printf("Combination Pattern Binary Decimal\n");
  assert(comb2number(comb, comb_size, pattern, pattern_size) == num);
  process(comb, comb_size, pattern, pattern_size); // process `num`

  // . until flush-left number 
  while(next_combination_counts(comb, comb + comb_size))
    process(comb, comb_size, pattern, pattern_size);

  // . until `num` number is encounterd  
  while (comb2number(comb, comb_size, pattern, pattern_size) != num) {
    process(comb, comb_size, pattern, pattern_size);
    (void)next_combination_counts(comb, comb + comb_size);
  }

  return 0;
}

Выход:

Combination Pattern Binary Decimal
{1, 0, 0, 1, 0} {1, 2, 6, 1} 010110111111001 11769
{1, 0, 1, 0, 0} {1, 2, 6, 1} 010110011111101 11517
{1, 1, 0, 0, 0} {1, 2, 6, 1} 010011011111101  9981
{2, 0, 0, 0, 0} {1, 2, 6, 1} 001011011111101  5885
{0, 0, 0, 0, 2} {1, 2, 6, 1} 101101111110100 23540
{0, 0, 0, 1, 1} {1, 2, 6, 1} 101101111110010 23538
{0, 0, 0, 2, 0} {1, 2, 6, 1} 101101111110001 23537
{0, 0, 1, 0, 1} {1, 2, 6, 1} 101100111111010 23034
{0, 0, 1, 1, 0} {1, 2, 6, 1} 101100111111001 23033
{0, 0, 2, 0, 0} {1, 2, 6, 1} 101100011111101 22781
{0, 1, 0, 0, 1} {1, 2, 6, 1} 100110111111010 19962
{0, 1, 0, 1, 0} {1, 2, 6, 1} 100110111111001 19961
{0, 1, 1, 0, 0} {1, 2, 6, 1} 100110011111101 19709
{0, 2, 0, 0, 0} {1, 2, 6, 1} 100011011111101 18173
{1, 0, 0, 0, 1} {1, 2, 6, 1} 010110111111010 11770
0 голосов
/ 31 мая 2010

Если ваш шаблон 00101, как в примере, то для генерации шести шаблонов вы можете рассмотреть один из следующих способов:

Посмотрите на шаблон 0000, затем выберите два из нулей, чтобы изменить их на единицы. Теперь у вас будет что-то вроде 0011. Теперь просто вставьте 0 после каждого 1 (кроме последнего). Теперь у вас будет 00101.

Обратите внимание, что вы выбираете 2 из 4 мест, и есть 6 различных возможных способов сделать это (в соответствии с тем, что вы написали). Теперь вам просто нужен способ выбрать два бита для переворачивания. Вы можете использовать что-то вроде алгоритма, указанного по этой ссылке:

http://compprog.wordpress.com/2007/10/17/generating-combinations-1/

0 голосов
/ 31 мая 2010

Это теоретическая или практическая проблема? Вам нужно оптимальное O (N) или достаточно хорошее время выполнения? Если это практическая проблема, и если она не находится во внутреннем цикле чего-либо, просто проверка каждого 15-битного числа должна быть достаточно быстрой. Это только 32 тыс. Номеров.

Просто получите разделы для своего номера, примерно так:

void get_groups(ushort x, int* groups) {
  int change_group = 0;
  while(x) {
    if (x & 1) {
      ++(*groups);
      change_group = 1;
    } else {
      if (change_group) {
        ++groups;
        change_group = 0;
      }
    }
    x >>= 1;
  }
}

А затем, для каждого 15-битного числа, проверьте, производит ли он тот же массив групп, что и ваш начальный номер.

Примечание: Массив groups должен быть способен вместить максимальное количество групп (например, должен иметь размер 8 для 15-битных чисел).

0 голосов
/ 30 мая 2010

Предположим, у нас есть:

000101101

Сначала посчитайте 0 (5) и посчитайте группы 0, окруженные 1 (1). Мы можем забыть об 1с сейчас. Число нулей на группу в любой комбинации может быть списком, описываемым как (где + означает «или более»):

[0+, 1+, 1+, 0+] where the sum is 6

Это всего лишь разновидность аналогичной проблемы: найдите все наборы из N неотрицательных целых чисел, которые суммируются с K. Например:

[0+, 0+, 0+, 0+] where the sum is 6

Теперь, чтобы решить эту проблему, начните с N = 1. Решение, очевидно, просто [6]. При N = 2 решение:

[0,6] [1,5] [2,4] [3,3] [4,2] [5,1] [6,0]

Здесь важно отметить основную тему: когда левая сторона становится богаче, правая сторона становится беднее. Я буду использовать Haskell для описания алгоритма, поскольку он оказывается довольно элегантным для такого типа вещей:

sumsTo k n
    | n == 1 = [[k]]
    | n > 1  = do
        i <- [0..k]
        s <- sumsTo (k-i) (n-1)
        return (i : s)

Случай n == 1 довольно прост для понимания: он просто возвращает одну комбинацию: [k]. В случае n > 1 здесь на самом деле происходит вложенный цикл. Это в основном говорит:

for each number i from 0 to k
    for each s in sumsTo (k-i) (n-1)
        prepend i to s

Хотя этот алгоритм не совсем решает вашу проблему, в целом полезно знать.

Теперь мы хотим, чтобы алгоритм работал по-другому, чтобы обрабатывать то, как элементы среднего списка не могут быть равны нулю. Для этих случаев мы хотим использовать i <- [1..k] вместо i <- [0..k]. Для конечного числа это не имеет значения, так как у него нет свободной воли (оно зависит исключительно от суммы предыдущих пунктов). Подойдя ближе, можно сказать:

sumsTo k n
    | n == 1 = [[k]]
    | n > 1  = do
        i <- [1..k]
        s <- sumsTo (k-i) (n-1)
        return (i : s)

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

sumsTo k n first_start
    | n == 1 = [[k]]
    | n > 1  = do
        i <- [first_start..k]
        s <- sumsTo (k-i) (n-1) 1
             -- make subsequent sumsTo calls start from 1 instead of 0
        return (i : s)

Это дает нам последовательности, которые нам нужны: K (количество нулей) и N (количество внутренних групп от 0 сек плюс 2). Все, что остается, - это упорядочить последовательности (например, превратить [1,1,0] в «01»). Мы могли бы пойти так далеко, что встроили строковое преобразование непосредственно в наш рекурсивный алгоритм.

Подводя итог, вот решение на Haskell:

import Data.List

sumsTo k n first_start
    | n == 1 = [[k]]
    | n > 1  = do
        i <- [first_start..k]
        s <- sumsTo (k-i) (n-1) 1
        return (i : s)

-- remove any xes found at the beginning or end of list
trim x list
    = dropWhile (== x)
    $ reverse
    $ dropWhile (== x)
    $ reverse
    $ list

-- interleave [1,3,5] [2,4] = [1,2,3,4,5]
interleave xs ys = concat $ transpose [xs,ys]

solve input = solutions where
    -- pull out groups of 1s and put them in a list
    groupsOfOnes = filter ((== '1') . head) $ group input

    -- count 0s
    k = length $ filter (== '0') input

    -- trim outer 0s
    trimmed = trim '0' input

    -- count inner groups of 0s
    innerGroups = length $ filter ((== '0') . head) $ group trimmed

    -- n is number of outer groups (which is innerGroups + 2)
    n = innerGroups + 2

    -- compute our solution sequences
    -- reverse them so our answer will be in lexicographic order
    sequences = reverse $ sumsTo k n 0

    -- to transform a sequence into a group of zeros,
    -- simply make strings of the indicated number of zeros
    groupsOfZeros seq = [replicate n '0' | n <- seq]

    -- a solution for a sequence is just the zeros interleaved with the ones
    solution seq = concat $ interleave (groupsOfZeros seq) groupsOfOnes

    solutions = map solution sequences

main = do
    input <- getLine
    putStr $ unlines $ solve input

В заключение я рекомендую изучить Haskell, чтобы вы могли быстрее создавать прототипы алгоритмов: -)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...