Я изо всех сил пытался перенести голову вокруг этого по некоторым причинам. У меня есть 15 битов, которые представляют число. Биты должны соответствовать шаблону. Шаблон определяется в способе, которым начинаются биты: они находятся в самом выровненном по правой границе представлении того шаблона. Поэтому скажите, что шаблон равняется 1 4 1. Биты будут:
000000010111101
Таким образом, общее правило, возьмите каждое число в шаблоне, создайте это много битов (1, 4 или 1 в этом случае) и затем имейте по крайней мере одно пространство, разделяющее их. Таким образом, если это будет 1 2 6 1 (то это будет случайно):
001011011111101
Начиная с выровненной по правой границе версии, я хочу генерировать каждое возможное число, которое встречает тот шаблон. # битов будет сохранен в переменной. Таким образом для простого случая, предположите, что это - 5 битов, и начальная комбинация двоичных разрядов: 00101. Я хочу генерировать:
00101 01001 01010 10001 10010 10100
Я пытаюсь сделать это в Objective C, но что-либо напоминающее C было бы прекрасно. Я просто, может казаться, не придумываю хороший рекурсивный алгоритм для этого. Это имеет смысл в вышеупомянутом примере, но когда я начинаю входить 12431 и иметь для отслеживания все, что это ломает.
Опираясь на ответы @Mark Byers и Moron, вашу задачу можно сформулировать следующим образом:
Перечислите все способы размещения K
нулей на N
местах (см. комбинации с повторением и звезды и решетки).
Пример: Для 15 бит и шаблона 1 2 6 1 существует N=5 мест (до/после числа и между 1
s) для размещения K=2 нулей (количество ведущих нулей для числа заподлицо). Количество способов - биномиальное(N + K - 1, K), т.е. биномиальное(5+2-1, 2) = 15.
Ключевыми функциями в приведенном ниже коде являются next_combination_counts()
и comb2number()
.
#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
Я не собираюсь давать вам код Objective-C в основном потому, что:
Вместо этого я дам вам несколько идей и код, показывающий, как я могу реализовать это на более высоком языке с генераторами и сборкой мусора (в данном случае Python), а также подскажу, как это сделать без генераторов. Надеюсь, кто-то другой сможет перенести код за вас, если вы не можете сделать это самостоятельно.
Я бы подумал о вашей проблеме немного по-другому:
В вашем последнем примере у вас есть два ведущих нуля и три раздела с разделителями «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
Я надеюсь, что это будет полезно для вас.
Надеюсь, это облегчит вам задачу (пожалуйста, читайте это с ручкой и бумагой в руках).
Скажем, количество нулей (начиная справа) равно x1, x2, ..., xn. Например: если битовая схема 00001110001001, то x1 = 0, x2 = 2, x3 = 3, x4 = 4. n на единицу больше числа блоков единиц. Заметим, что знания x1, x2, ..., xn достаточно, чтобы вычислить битовый шаблон.
Теперь, если общее количество единиц равно S, а общее количество доступных битов равно M, то мы должны иметь, что
x1 + x2 + ... + xn = M - S
и x1 ≥ 0, xn ≥ 0, x2 ≥ 1, x3 ≥ 1, ...
Пусть z1 = x1 + 1 и zn = xn + 1
Таким образом, имеем
z1 + x2 + ... xn-1 + zn = M - S + 2
Где z1 ≥ 1, x2 ≥ 1, x3 ≥ 1, ..., zn ≥ 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 choose r является стандартной задачей, и в Интернете можно найти множество решений (некоторые из них очень умные!) для этого.
Пример см. здесь: http://compprog.files.wordpress.com/2007/10/comb1.c, который, похоже, поддерживает "ленивое" перечисление: next_combination и не требует огромного количества памяти.
Это теоретическая или практическая проблема? Вам нужно оптимальное O(N) или достаточно хорошее время выполнения? Если это практическая задача, и если только это не во внутреннем цикле чего-либо, то просто проверка каждого 15-битного числа должна быть достаточно быстрой. Это всего 32k чисел.
Просто получите разделы для вашего числа, примерно так:
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-битных чисел).
Предположим, у нас есть:
000101101
Сначала посчитаем нули ( 5
) и посчитаем группы нулей, окруженные единицами ( 1
) . Мы можем пока забыть о единицах.Количество нулей в группе в любой комбинации может быть списком, описанным как (где +
означает «или больше»):
[0+, 1+, 1+, 0+] where the sum is 6
Это всего лишь вариант аналогичной задачи: найти все наборы из N non -отрицательные целые числа, которые в сумме равны 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
(количество внутренних групп нулей плюс 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, чтобы вы могли быстрее создавать прототипы алгоритмов: -)
Если ваш шаблон 00101
, как в примере, то вы можете рассмотреть один из способов создания шести шаблонов:
Посмотрите на шаблон 0000
, затем выберите два нулей заменить на единицы. Теперь у вас будет что-то вроде 0011
. Теперь просто вставьте 0
после каждого 1
(кроме последнего). Теперь у вас будет 00101
.
Обратите внимание, что вы выбираете два места из четырех, и есть шесть различных возможных способов сделать это (в соответствии с тем, что вы написали). Теперь вам просто нужно выбрать два бита, которые нужно перевернуть. Вы можете использовать что-то вроде алгоритма, указанного по этой ссылке: Генерация комбинаций