Создание всех возможных значений массива фиксированного размера

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

На самом деле это делается на ассемблере, но я объясню это на C.

В принципе, скажем, у нас есть массив uint8_t *data=malloc(10);

Я хочу создать алгоритм, который будет печатать все возможные комбинации байтов в массиве data.

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

(обратите внимание, я говорю перестановка, потому что [0,1,2] не следует считать так же, как [2,1,0])

редактировать: Кроме того, постарайтесь не использовать слишком много функций libc, потому что я буду конвертировать это в автономный загрузчик всего с 512 байтами.

Я знаю, что знаю, как это сделать, но хоть убей, я просто не могу заставить алгоритм работать в моей голове!


person Earlz    schedule 05.01.2010    source источник
comment
Существует 1208925819614629174706176 (~1e24) возможных комбинаций для вашего примера с 10 значениями uint8_t. Вам это действительно нужно?   -  person Alok Singhal    schedule 05.01.2010
comment
Да? Я знаю, как его вычислить и насколько это огромное число. Может быть, мой компьютер справится с ним за несколько месяцев.   -  person Earlz    schedule 05.01.2010
comment
Нет. Даже если под вашим компьютером понимают самый быстрый компьютер в мире на сегодняшний день (Jaguar), вы не сможете пройти его за несколько месяцев. Jaguar имеет четверть миллиона ядер, каждое из которых работает на частоте 2,6 ГГц. Даже если бы вы могли печатать по одному битовому шаблону каждый цикл на каждом отдельном ядре, что явно абсурдно, потребовалось бы 58 лет, чтобы пройти через все пространство данных. Поскольку вычислительная мощность вашего компьютера в лучшем случае составляет одну десятитысячную вычислительной мощности Jaguar, вам потребуется полмиллиона лет.   -  person Stephen Canon    schedule 05.01.2010
comment
@earlz, как сказал Стивен, рассчитывая, что в ближайшее время не произойдет много перестановок. Нам нужны реалистичные пределы :-)   -  person Alok Singhal    schedule 05.01.2010
comment
Все, что вычисляется за 0,5 млн лет, имеет один и тот же ответ: 42.   -  person Hans Passant    schedule 05.01.2010
comment
Это безнадежное дело. Если вы пытаетесь взломать какое-то шифрование (мое лучшее предположение о конечной цели этой программы), вам будет гораздо лучше искать и использовать математическую слабость в алгоритме.   -  person user168715    schedule 05.01.2010
comment
Нет, на самом деле это для создания контента... ничего общего с шифрованием.   -  person Earlz    schedule 05.01.2010
comment
Вы не возражаете, если я спрошу, для чего?   -  person GManNickG    schedule 05.01.2010
comment
Ну... У меня была эта сумасшедшая идея... но это невозможно, так что теперь зря... Я не осознавал, что современные компьютеры такие медленные :)   -  person Earlz    schedule 05.01.2010
comment
Что-то вроде генерации каждой перестановки квадратного изображения? :P Растет сверхэкспоненциально.   -  person GManNickG    schedule 05.01.2010


Ответы (6)


Ваш вопрос страдает от странной терминологической путаницы. Из того, что вы описываете, кажется, что вы хотите сгенерировать все возможные 10 кортежей беззнаковых 8-битных значений. Это не "перестановки" и все это не имеет ничего общего с порождением перестановок.

Код, который генерирует все возможные 10 кортежей значений uint8_t, легко придумать. Например, следующий простой код сделает это

#define N 10u

uint8_t data[N] = { 0 };
unsigned i;

do {

  /* Process the current 10-typle in `data` array */
  /* in any way you want do */

  /* Generate next tuple */
  for (i = 0; i < N && ++data[i] == 0; ++i);

} while (i < N);

Это не что иное, как просто циклическое приращение 80-битного числа с прямым порядком байтов.

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

person AnT    schedule 05.01.2010
comment
Вот это да. Ну, это бесполезно (например, в ближайшее время я не смогу что-то вычислить), но оно делает именно то, что я хочу, и работает :) - person Earlz; 05.01.2010
comment
@Стивен: О. Да, это 80, а не 800 :) - person AnT; 05.01.2010
comment
@Alok: Да, он должен повторяться снова и снова. И это так. Так в чем именно вы видите проблему? - person AnT; 05.01.2010
comment
@AndreyT: Извини, я не подумал. - person Alok Singhal; 05.01.2010
comment
У него есть небольшая ошибка, из-за которой самое первое число не повторяется, но все в порядке, я понял это... преобразование его в сборку дает мне некоторые припадки... - person Earlz; 05.01.2010
comment
@earlz: О каком самом первом номере ты говоришь? - person AnT; 05.01.2010
comment
@earlz: я не вижу проблем с 0-м элементом массива data. Он действительно циклически повторяет data[0]. - person AnT; 05.01.2010
comment
Что мешает вам конвертировать в сборку? Это должно быть совершенно просто (не говоря уже о том, что в моем ответе уже есть реализация сборки) - person Stephen Canon; 05.01.2010

Что ж, все это бесполезное занятие (см. Мой комментарий, прикрепленный к вопросу), но в любом случае вы идете (сборка в стиле x86_64 AT&T, предполагает соглашения о вызовах AMD system V). Я просто пишу это здесь без тестирования, поэтому вполне возможно, что в нем есть ошибки. Тем не менее, основная работа кода должна быть полностью понятна.

Вместо того, чтобы работать с 80-битным буфером в памяти, я просто просматриваю все возможности 80-битного поля, разделенного на два 64-битных регистра. Ваша подпрограмма, которая проверяет ваши условия, может сохранять их в памяти и обращаться к этой памяти как uint8_t, если вы действительно этого хотите.

    push r12
    push r13
    xor  r12, r12 // zero out low 64 bits of our "buffer" in register
    xor  r13, r13 // zero out high 16 bits of our "buffer"

loop:
    // Copy the current array value into rsi:rdi and call whatever routine you're
    // using to check for magic conditions.  This routine's copy (in r13:r12)
    // should be unaffected if you're obeying the System V calling conventions.
    mov  r12, rdi
    mov  r13, rsi
    call _doSomethingWithValue

    // Increment r13:r12 to get the next value.  We only need to worry about r13
    // if the increment of r12 wraps around to zero.
    inc  r12
    jnz  loop
    inc  r13

    // Check for the termination condition, though you'll never hit it =)
    cmp  $0x10000, r13
    jne  loop

    // We don't actually need to clean up; the apocalypse will come and there
    // won't be electricity to run the computer before it reaches this point of
    // the program.  Nonetheless, let's be exhaustively correct.
    pop  r13 
    pop  r12
person Stephen Canon    schedule 05.01.2010
comment
Справедливости ради, вполне вероятно, что компьютер просто перестанет работать задолго до того, как апокалипсис лишит его электричества =) - person Stephen Canon; 05.01.2010
comment
Удивительно, на какие глубины идут некоторые люди ради очков репутации :-) :-) - person Stephen C; 05.01.2010
comment
Нет, если бы я действительно занимался репутацией, я бы написал это на ассемблере ARM и переименовал вопрос в iPhone. знак равно - person Stephen Canon; 05.01.2010
comment
Я хочу генерировать изображения на Iphone? Как бы вы это сделали? ‹вставьте награду в 500 повторений› ‹/сарказм› - person Earlz; 05.01.2010

Я бы посоветовал вам прочитать,

Дональд Кнут. Искусство компьютерного программирования, том 4, выпуск 2: создание всех кортежей и перестановок.

person grokus    schedule 05.01.2010

Существует классический рекурсивный подход к проблеме, аналогичный следующему:

#include <stdio.h>


void print(const uint8_t *v, const int size)
{
  if (v != 0) {
    for (int i = 0; i < size; i++) {
      printf("%4d", v[i] );
    }
    printf("\n");
  }
} // print


void visit(uint8_t *Value, int N, int k)
{
  static level = -1;
  level = level+1; Value[k] = level;

  if (level == N)
    print(Value, N);
  else
    for (int i = 0; i < N; i++)
      if (Value[i] == 0)
        visit(Value, N, i);

  level = level-1; Value[k] = 0;
}


main()
{
  const int N = 4;
  uint8_t Value[N];
  for (int i = 0; i < N; i++) {
    Value[i] = 0;
  }
  visit(Value, N, 0);
}

пример взят из ссылки, в которой есть другие подходы. Теория, лежащая в основе этого, довольно проста. Если вам нужно, я могу объяснить алгоритм, но он не требует пояснений.

person Jack    schedule 05.01.2010
comment
Здесь я имею дело с довольно большим числом N .. поэтому я не думаю, что рекурсия будет работать даже с большим стеком в 64 КБ (и все алгоритмы в этой ссылке рекурсивны) - person Earlz; 05.01.2010
comment
раскрутить рекурсию и использовать собственный стек! :П - person Charles Ma; 05.01.2010
comment
если N довольно велико, вы все равно идете вверх по ручью без весла. - person Keith Randall; 05.01.2010

Взгляните на этот алгоритм для создания комбинаций N из M элементов. Для комбинаций N выберите N, просто используйте inittwiddle(N, N, p);

int twiddle(x, y, z, p)
int *x, *y, *z, *p;
  {
  register int i, j, k;
  j = 1;
  while(p[j] <= 0)
    j++;
  if(p[j-1] == 0)
    {
    for(i = j-1; i != 1; i--)
      p[i] = -1;
    p[j] = 0;
    *x = *z = 0;
    p[1] = 1;
    *y = j-1;
    }
  else
    {
    if(j > 1)
      p[j-1] = 0;
    do
      j++;
    while(p[j] > 0);
    k = j-1;
    i = j;
    while(p[i] == 0)
      p[i++] = -1;
    if(p[i] == -1)
      {
      p[i] = p[k];
      *z = p[k]-1;
      *x = i-1;
      *y = k-1;
      p[k] = -1;
      }
    else
      {
      if(i == p[0])
    return(1);
      else
    {
    p[j] = p[i];
    *z = p[i]-1;
    p[i] = 0;
    *x = j-1;
    *y = i-1;
    }
      }
    }
  return(0);
  }

void inittwiddle(m, n, p)
int m, n, *p;
  {
  int i;
  p[0] = n+1;
  for(i = 1; i != n-m+1; i++)
    p[i] = 0;
  while(i != n+1)
    {
    p[i] = i+m-n;
    i++;
    }
  p[n+1] = -2;
  if(m == 0)
    p[1] = 1;
  }

/************************
  Here is a sample use of twiddle() and inittwiddle():
#define N 5
#define M 2
#include <stdio.h>
void main()
  {
  int i, x, y, z, p[N+2], b[N];
  inittwiddle(M, N, p);
  for(i = 0; i != N-M; i++)
    {
    b[i] = 0;
    putchar('0');
    }
  while(i != N)
    {
    b[i++] = 1;
    putchar('1');
    }
  putchar('\n');
  while(!twiddle(&x, &y, &z, p))
    {
    b[x] = 1;
    b[y] = 0;
    for(i = 0; i != N; i++)
      putchar(b[i]? '1': '0');
    putchar('\n');
    }
  }
************************/

Ответ на этот пост также может помочь вам алгоритм возврата все комбинации k элементов из n

person Charles Ma    schedule 05.01.2010

Если бы вы работали на C++,

#include <algorithm>
#include <iterator>
#include <iostream>
#include <numeric>

int main() {
    int N;
    std::cin >> N;
    std::vector<int> data(N);
    std::fill(data.begin(), data.end(), 1);
    std::partial_sum(data.begin(), data.end(), data.begin());

    do {
        std::copy(data.begin(), data.end(),
                std::ostream_iterator<int>(std::cout, " "));
        std::cout << std::endl;
    } while (std::next_permutation(data.begin(), data.end()));

    return 0;
}

Если вы введете 3, он выводит

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

См. Следующая перестановка: когда C++ делает все правильно, чтобы узнать, как работает std::next_permutation.


Переводя это на простой C,

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

int main() {
    int i, N, *data;

    scanf("%d", &N);
    data = malloc(N);
    for (i = 0; i < N; i++) data[i] = i + 1;

    while (1) {
        int j, temp;

        for (i = 0; i < N; i++) printf("%d ", data[i]);
        printf("\n");

        for (i = N - 1; i > 0 && data[i] < data[i - 1]; i--);
        if (i <= 0) break;
        for (j = N; data[i - 1] >= data[--j];);
        temp = data[i - 1], data[i - 1] = data[j], data[j] = temp;
        for (j = N - 1; i < j; i++, j--)
            temp = data[i], data[i] = data[j], data[j] = temp;
    }

    return 0;
}

Если вопрос заключается не в перестановке существующего массива, а в создании всего возможного содержимого массива, это намного проще. (Также есть намного больше комбинаций.)

memset(data, 0, N);
do {
    for (i = 0; i < N; i++) printf("%d ", data[i]);
    printf("\n");
    for (i = 0; i < N && !++data[i++];);
} while (i < N);
person ephemient    schedule 05.01.2010