Каков наилучший алгоритм для этой проблемы сравнения массива?

Что наиболее эффективно для алгоритма скорости для решения следующей проблемы?

Для 6 массивов D1, D2, D3, D4, D5 и D6, каждый из которых содержит 6 чисел, таких как:

D1[0] = number              D2[0] = number      ......       D6[0] = number
D1[1] = another number      D2[1] = another number           ....
.....                       ....                ......       ....
D1[5] = yet another number  ....                ......       ....

Учитывая второй массив ST1, содержащий 1 номер:

ST1[0] = 6

Учитывая третий массив ans, содержащий 6 чисел:

ans[0] = 3, ans[1] = 4, ans[2] = 5, ......ans[5] = 8

Используя в качестве индекса для массивов D1, D2, D3, D4, D5 и D6 число, которое идет от 0, к числу, хранящемуся в ST1 [0] минус один, в этом примере 6, поэтому от 0 до 6 -1, сравните массив ans с каждым массивом D. Результат должен быть 0, если одно или несколько чисел ans не найдены ни в одном D в одном и том же индексе и должны быть равны 1, если все числа ans найдены в некотором D в одном и том же индексе. То есть, возвращаем 0, если некоторые ans [i] не равны ни одному D N [i] и возвращают 1, если каждый ans [i] равен некоторому D N [i ].

Мой алгоритм до сих пор:
Я старался держать все как можно больше.

EML  := ST1[0]   //number contained in ST1[0]   
EML1 := 0        //start index for the arrays D 

While EML1 < EML
   if D1[ELM1] = ans[0] 
     goto two
   if D2[ELM1] = ans[0] 
     goto two
   if D3[ELM1] = ans[0] 
     goto two
   if D4[ELM1] = ans[0] 
     goto two
   if D5[ELM1] = ans[0] 
     goto two
   if D6[ELM1] = ans[0] 
     goto two

   ELM1 = ELM1 + 1

return 0     //If the ans[0] number is not found in either D1[0-6], D2[0-6].... D6[0-6] return 0 which will then exclude ans[0-6] numbers


two:

EML1 := 0      start index for arrays Ds 
While EML1 < EML
   if D1[ELM1] = ans[1] 
     goto three
   if D2[ELM1] = ans[1] 
     goto three
   if D3[ELM1] = ans[1] 
     goto three
   if D4[ELM1] = ans[1] 
     goto three
   if D5[ELM1] = ans[1] 
     goto three
   if D6[ELM1] = ans[1] 
     goto three
   ELM1 = ELM1 + 1

return 0    //If the ans[1] number is not found in either D1[0-6], D2[0-6]....  D6[0-6]  return 0 which will then exclude ans[0-6] numbers

three:

EML1 := 0      start index for arrays Ds 

While EML1 < EML
   if D1[ELM1] = ans[2] 
     goto four
   if D2[ELM1] = ans[2] 
     goto four
   if D3[ELM1] = ans[2] 
     goto four
   if D4[ELM1] = ans[2] 
     goto four
   if D5[ELM1] = ans[2] 
     goto four
   if D6[ELM1] = ans[2] 
     goto four
   ELM1 = ELM1 + 1

return 0   //If the ans[2] number is not found in either D1[0-6], D2[0-6]....  D6[0-6]  return 0 which will then exclude ans[0-6] numbers

four:

EML1 := 0      start index for arrays Ds 

While EML1 < EML
   if D1[ELM1] = ans[3] 
     goto five
   if D2[ELM1] = ans[3] 
     goto five
   if D3[ELM1] = ans[3] 
     goto five
   if D4[ELM1] = ans[3] 
     goto five
   if D5[ELM1] = ans[3] 
     goto five
   if D6[ELM1] = ans[3] 
     goto five
   ELM1 = ELM1 + 1

return 0 //If the ans[3] number is not found in either D1[0-6], D2[0-6]....  D6[0-6]  return 0 which will then exclude ans[0-6] numbers


five:

EML1 := 0      start index for arrays Ds 

While EML1 < EML
   if D1[ELM1] = ans[4] 
     goto six
   if D2[ELM1] = ans[4] 
     goto six
   if D3[ELM1] = ans[4] 
     goto six
   if D4[ELM1] = ans[4] 
     goto six
   if D5[ELM1] = ans[4] 
     goto six
   if D6[ELM1] = ans[4] 
     goto six
   ELM1 = ELM1 + 1

return 0  //If the ans[4] number is not found in either D1[0-6], D2[0-6]....  D6[0-6]  return 0 which will then exclude ans[0-6] numbers

six:

EML1 := 0      start index for arrays Ds 

While EML1 < EML
   if D1[ELM1] = ans[5] 
     return 1            ////If the ans[1] number is not found in either D1[0-6].....  
   if D2[ELM1] = ans[5]      return 1 which will then include ans[0-6] numbers
     return 1
   if D3[ELM1] = ans[5] 
     return 1
   if D4[ELM1] = ans[5] 
     return 1
   if D5[ELM1] = ans[5] 
     return 1
   if D6[ELM1] = ans[5] 
     return 1
   ELM1 = ELM1 + 1

return 0 

Как язык выбора, он был бы чистым c

Ответ 1

Я сделал прямую тривиальную реализацию C алгоритма, предоставленного оригинальным плакатом. Это здесь

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

EDIT: я несколько раз изменил код C, потому что даже так просто, как кажется, возникают проблемы, когда JIT компилирует или выполняет его с помощью CUDA (и CUDA, кажется, не очень многословно об ошибках). Вот почему часть кода ниже использует глобальные переменные... и это просто тривиальная реализация. Мы еще не стремимся к скорости. В нем много говорится о преждевременной оптимизации. Зачем делать это быстро, если мы не можем заставить его работать? Я думаю, есть еще проблемы, поскольку CUDA, похоже, накладывает множество ограничений на код, который вы можете сделать, если я верю в статью в Википедии. Также, возможно, мы должны использовать float вместо int?

#include <stdio.h>

int D1[6] = {3, 4, 5, 6, 7, 8};
int D2[6] = {3, 4, 5, 6, 7, 8};
int D3[6] = {3, 4, 5, 6, 7, 8};
int D4[6] = {3, 4, 5, 6, 7, 8};
int D5[6] = {3, 4, 5, 6, 7, 8};
int D6[6] = {3, 4, 5, 6, 7, 9};
int ST1[1] = {6};
int ans[6] = {1, 4, 5, 6, 7, 9};
int * D[6] = { D1, D2, D3, D4, D5, D6 };

/* beware D is passed through globals */
int algo(int * ans, int ELM){
    int a, e, p;

    for (a = 0 ; a < 6 ; a++){ 
        for (e = 0 ; e < ELM ; e++){
            for (p = 0 ; p < 6 ; p++){
                if (D[p][e] == ans[a]){
                    goto cont;
                }
            }
        }
        return 0; //bad row of numbers found
    cont:;
    }
    return 1;
}

int main(){
    int res;
    res = algo(ans, ST1[0]);
    printf("algo returned %d\n", res);
}

Теперь это интересно, потому что мы можем понять, что делает код. Кстати, выполняя эту работу по упаковке, я исправил несколько странностей в исходном вопросе. Я считаю, что это опечатки, поскольку это было нелогично вообще в глобальном контексте. - перейти всегда к двум (он должен был прогрессировать) - последний тест проверял ans [0] вместо ans [5]

Пожалуйста, отметьте, исправьте меня, если я ошибаюсь в приведенных выше предположениях о том, что должен сделать исходный код, и ваш исходный алгоритм является бесплатным.

Что делает код для каждого значения в проверке as, он присутствует в двухмерном массиве. Если любое число пропущено, оно возвращает 0. Если все числа найдены, он возвращает 1.

Что бы я сделал, чтобы получить реальный быстрый код, это не реализовать его в C, а на другом языке, таком как python (или С++), где set - это базовая структура данных, предоставляемая стандартными библиотеками. Тогда я бы построил набор со всеми значениями массивов (то есть O (n)) и проверить, имеются ли найденные числа в наборе или нет (это O (1)). Окончательная реализация должна быть быстрее, чем существующий код, по крайней мере, с алгоритмической точки зрения.

Пример Python ниже, поскольку он действительно тривиальный (напечатайте true/false вместо 1/0, но вы получите идею):

ans_set = set(ans)
print len(set(D1+D2+D3+D4+D5+D6).intersection(ans_set)) == len(ans_set)

Вот возможная реализация С++ с использованием наборов:

#include <iostream>
#include <set>

int algo(int * D1, int * D2, int * D3, int * D4, int * D5, int * D6, int * ans, int ELM){
    int e, p;
    int * D[6] = { D1, D2, D3, D4, D5, D6 };
    std::set<int> ans_set(ans, ans+6);

    int lg = ans_set.size();

    for (e = 0 ; e < ELM ; e++){
        for (p = 0 ; p < 6 ; p++){
            if (0 == (lg -= ans_set.erase(D[p][e]))){
                // we found all elements of ans_set
                return 1;
            }
        }
    }
    return 0; // some items in ans are missing
}

int main(){
    int D1[6] = {3, 4, 5, 6, 7, 8};
    int D2[6] = {3, 4, 5, 6, 7, 8};
    int D3[6] = {3, 4, 5, 6, 7, 8};
    int D4[6] = {3, 4, 5, 6, 7, 8};
    int D5[6] = {3, 4, 5, 6, 7, 8};
    int D6[6] = {3, 4, 5, 6, 7, 1};

    int ST1[1] = {6};

    int ans[] = {1, 4, 5, 6, 7, 8};

    int res = algo(D1, D2, D3, D4, D5, D6, ans, ST1[0]);
    std::cout << "algo returned " << res << "\n";
}

Мы делаем гипотезу о производительности: содержимое ans должно быть отсортировано или мы должны построить его иначе, мы полагаем, что содержимое D1..D6 будет меняться между вызовами в algo. Следовательно, мы не собираемся строить для него набор (поскольку построенная конструкция O (n) в любом случае ничего не выиграет, если D1..D6 меняются). Но если мы вызываем несколько раз algo с тем же D1..D6 и это ans, то это изменение, мы должны сделать обратное и преобразовать D1..D6 в один более большой набор, который мы сохраняем.

Если я придерживаюсь C, я мог бы сделать это следующим образом:

  • скопировать все числа в D1.. D6 в один уникальный массив (используя memcpy для каждой строки)
  • сортировать содержимое этого массива
  • используйте дихотомический поиск, чтобы проверить, доступен ли номер

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

EDIT2: существуют жесткие ограничения для подмножества C, поддерживаемого CUDA. Наиболее ограничительным является то, что мы не должны использовать указатели на основную память. Это нужно учитывать. Это объясняет, почему текущий код не работает. Простейшее изменение, вероятно, можно назвать его для каждого массива D1..D6 по очереди. Чтобы сохранить его коротким и избежать стоимости вызова функции, мы можем использовать макрос или встроенную функцию. Я отправлю предложение.

Ответ 2

Я был немного смущен вашим вопросом, но я думаю, что у меня его достаточно, чтобы помочь вам начать.

#define ROW 6
#define COL 6

int D[ROW][COL]; // This is all of your D arrays in one 2 dimensional array.

Затем вы, вероятно, должны использовать вложенные для циклов. Каждый цикл будет соответствовать размеру D. Помните, что порядок ваших индексов имеет значение. Самый простой способ сохранить его прямо в C - это помнить, что D[i] является допустимым выражением даже тогда, когда D имеет более одного измерения (и будет оценивать указатель на строку: вспомогательный массив).

Если вы не можете изменить независимые массивы D на один многомерный массив, вы можете легко создать массив указателей, члены которого указывают на головки каждого из этих массивов и достигнут того же эффекта.

Затем вы можете использовать оператор break для выхода из внутреннего цикла после того, как вы определили, что текущий D[i] не соответствует ans.

Ответ 3

Если сравнивать только 36 значений, самым эффективным подходом было бы не использовать CUDA вообще.

Просто используйте процессорный цикл.

Если вы измените свои входы, я изменю свой ответ.

Ответ 4

Если диапазон чисел ограничен, было бы, вероятно, проще создать бит-массив, например:

int IsPresent(int arrays[][6], int ans[6], int ST1)
{
    uint32_t bit_mask = 0;
    for(int i = 0; i < 6; ++ i) {
        for(int j = 0; j < ST1; ++ j) {
            assert(arrays[i][j] >= 0 && arrays[i][j] < 32); // range is limited
            bit_mask |= 1 << arrays[i][j];
        }
    }
    // make a "list" of numbers that we have

    for(int i = 0; i < 6; ++ i) {
        if(((bit_mask >> ans[i]) & 1) == 0)
            return 0; // in ans, there is a number that is not present in arrays
    }
    return 1; // all of the numbers were found
}

Это всегда будет работать в O (6 * ST1 + 6). Теперь у этого есть недостаток: сначала пройти до 36 массивов, а затем проверить на шесть значений. Если есть сильное предположение о том, что цифры будут в основном присутствовать, можно отменить тест и обеспечить ранний выход:

int IsPresent(int arrays[][6], int ans[6], int ST1)
{
    uint32_t bit_mask = 0;
    for(int i = 0; i < 6; ++ i) {
        assert(ans[i][j] >= 0 && ans[i][j] < 32); // range is limited
        bit_mask |= 1 << ans[i];
    }
    // make a "list" of numbers that we need to find

    for(int i = 0; i < 6; ++ i) {
        for(int j = 0; j < ST1; ++ j)
            bit_mask &= ~(1 << arrays[i][j]); // clear bits of the mask

        if(!bit_mask) // check if we have them all
            return 1; // all of the numbers were found
    }

    assert(bit_mask != 0);
    return 0; // there are some numbers remaining yet to be found
}

Это будет работать максимум в O (6 * ST1 + 6), в лучшем случае в O (6 + 1), если первое число в первом массиве охватывает все ansans в шесть раз больше такое же количество). Обратите внимание, что тест для бит-маски, равный нулю, может быть либо после каждого массива (как сейчас), либо после каждого элемента (этот способ включает в себя большую проверку, а также более раннее обрезание, когда все числа найдены). В контексте CUDA первая версия алгоритма, скорее всего, будет быстрее, поскольку она включает в себя меньше ветвей, и большинство циклов (кроме одного для ST1) могут быть автоматически развернуты.

Однако, если диапазон чисел не ограничен, мы могли бы сделать что-то еще. Поскольку в ans и во всех массивах есть только до 7 * 6 = 42 разных чисел, можно было бы сопоставить их с 42 различными номерами и использовать 64-битное целое для битовой маски. Но, возможно, это сопоставление чисел с целыми числами уже было бы достаточно для теста, и было бы возможно вообще пропустить этот тест.

Другой способ сделать это - сортировать массивы и просто подсчитывать количество отдельных номеров:

int IsPresent(int arrays[][6], int ans[6], int ST1)
{
    int all_numbers[36], n = ST1 * 6;
    for(int i = 0; i < 6; ++ i)
        memcpy(&all_numbers[i * ST1], &arrays[i], ST1 * sizeof(int));
    // copy all of the numbers into a contiguous array

    std::sort(all_numbers, all_numbers + n);
    // or use "C" standard library qsort() or a bitonic sorting network on GPU
    // alternatively, sort each array of 6 separately and then merge the sorted
    // arrays (can also be done in parallel, to some level)

    n = std::unique(all_numbers, all_numbers + n) - all_numbers;
    // this way, we can also remove duplicate numbers, if they are
    // expect to occur frequently and make the next test faster.
    // std::unique() actually moves the duplicates to the end of the list
    // and returns an iterator (a pointer in this case) to one past
    // the last unique element of the list - that gives us number of
    // unique items.

    for(int i = 0; i < 6; ++ i) {
        int *p = std::lower_bound(all_numbers, all_numbers + n, ans[i]);
        // use binary search to find the number in question
        // or use "C" standard library bfind()
        // or implement binary search yourself on GPU

        if(p == all_numbers + n)
            return 0; // not found
        // alternately, make all_numbers array of 37 and write
        // all_numbers[n] = -1; before this loop. that will act
        // as a sentinel and will save this one comparison (assuming
        // that there is a value that is guaranteed not to occur in ans)

        if(*p != ans[i])
            return 0; // another number found, not ans[i]
        // std::lower_bound looks for the given number, or for one that
        // is greater than it, so if the number was to be inserted there
        // (before the bigger one), the sequence would remain ordered.
    }

    return 1; // all the numbers were found
}

Это будет выполняться в O (n) для копирования O (36 log 36) для сортировки, опционально O (n) для unique (где n - 6 * ST1) и O (n log n) для поиска ( где n может быть меньше 6 * ST1, если используется unique). Таким образом, весь алгоритм работает в линеарифмическом времени. Обратите внимание, что это не связано с распределением динамической памяти и, как таковая, подходит даже для платформ GPU (нужно было бы выполнить сортировку и порт std::unique() и std::lower_bound(), но все они - простые функции).