Подсчитайте количество последовательностей расстояний Хэмминга


9

Расстояние Хэмминга между двумя строками одинаковой длины - это число позиций, в которых соответствующие символы различны.

Позвольте Pбыть двоичной строкой длины nи Tдвоичной строкой длины 2n-1. Мы можем вычислить nрасстояния Хэмминга между подстрокой Pкаждой nдлины Tв порядке слева направо и поместить их в массив (или список).

Пример последовательности расстояний Хэмминга

Пусть P = 101и T = 01100. Последовательность расстояний Хэмминга, которую вы получаете от этой пары, такова 2,2,1.

задача

Для увеличения, nначиная с n=1, рассмотрим все возможные пары двоичных строк Pдлины nи Tдлины 2n-1. Есть 2**(n+2n-1)такие пары и, следовательно, много последовательностей расстояний Хэмминга. Однако многие из этих последовательностей будут идентичны. Задача состоит в том, чтобы найти, сколько разных для каждого n.

Ваш код должен выводить одно число на значение n.

Гол

Ваша оценка - самая высокая, которую nваш код достигает на моей машине за 5 минут. Время для общего времени работы, а не время только для этого n.

Кто выигрывает

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

Пример ответов

Для nот 1до 8оптимальных ответов на этот вопрос 2, 9, 48, 297, 2040, 15425, 125232, 1070553.

Языки и библиотеки

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

Моя машина Время будет работать на моей 64-битной машине. Это стандартная установка Ubuntu с 8 ГБ ОЗУ, восьмиъядерным процессором AMD FX-8350 и Radeon HD 4250. Это также означает, что мне нужно иметь возможность запускать ваш код.

Ведущие ответы

  • 11 в C ++ по feersum. 25 секунд
  • 11 в C ++ Эндрю Эпштейн. 176 секунд
  • 10 в Javascript от Нила. 54 секунды
  • 9 в Хаскеле Ними. 4 минуты и 59 секунд.
  • 8 в Javascript от fəˈnɛtɪk. 10 секунд.

.. какие-либо свободные * языки?
Стьюи Гриффин

самый быстрый код ? не самый быстрый алгоритм ? Знаете, люди могли бы говорить с языком с чертовски быстрым переводчиком и иметь значительную разницу во времени, но сложность времени всегда одинакова, так что это в некоторой степени делает это справедливым.
Мэтью Ро


4
@SIGSEGV fastest-codeоставляет больше места для оптимизаций благодаря оптимизации на уровне кода и хорошему алгоритму. Так что я думаю, что faster-codeэто лучше, чем faster-algorithm.
Дада

Ответы:


3

C ++ 11 (должно доходить до 11 или 12)

На данный момент это однопоточный.

Компилировать:

g++ -std=c++11 -O2 -march=native feersum.cpp
#include <iostream>
#include <unordered_set>
#include <vector>
#include <unordered_map>
#include <string.h>

using seq = uint64_t;
using bitvec = uint32_t;
using seqset = std::unordered_set<seq>;
using std::vector;

#define popcount __builtin_popcount
#define MAX_N_BITS 4

bitvec leading(bitvec v, int n) {
    return v & ((1U << n) - 1);
}
bitvec trailing(bitvec v, int n, int total) {
    return v >> (total - n);
}

bitvec maxP(int n) {
    return 1 << (n - 1);  // ~P, ~T symmetry
}

void prefixes(int n, int pre, int P, seqset& p) {
    p.clear();
    for (bitvec pref = 0; pref < (1U << pre); pref++) {
        seq s = 0;
        for (int i = 0; i < pre; i++) {
            seq ham = popcount(trailing(pref, pre - i, pre) ^ leading(P, pre - i));
            s |= ham << i * MAX_N_BITS;
        }
        p.insert(s);
    }
}



vector<seqset> suffixes(int n, int suf, int off) {
    vector<seqset> S(maxP(n));
    for (bitvec P = 0; P < maxP(n); P++) {
        for (bitvec suff = 0; suff < (1U << suf); suff++) {
            seq s = 0;
            for (int i = 0; i < suf; i++) {
                seq ham = popcount(leading(suff, i + 1) ^ trailing(P, i + 1, n));
                s |= ham << (off + i) * MAX_N_BITS;
            }
            S[P].insert(s);
        }
    }
    return S;
}



template<typename T> 
void mids(int n, int npre, int nsuf, int mid, bitvec P, T& S, const seqset& pre) {
    seq mask = (1ULL << (npre + 1) * MAX_N_BITS) - 1;
    for(bitvec m = 0; m < 1U << mid; m++) {
        int pc = popcount(P ^ m);
        if(pc * 2 > n) continue; // symmetry of T -> ~T : replaces x with n - x
        seq s = (seq)pc << npre * MAX_N_BITS;
        for(int i = 0; i < npre; i++)
            s |= (seq)popcount(trailing(P, n - npre + i, n) ^ leading(m, n - npre + i)) << i * MAX_N_BITS;
        for(int i = 0; i < nsuf; i++)
            s |= (seq)popcount(leading(P, mid - 1 - i) ^ trailing(m, mid - 1 - i, mid)) << (npre + 1 + i) * MAX_N_BITS;
        for(seq a: pre)
            S[(s + a) & mask].insert(P | (s + a) << n);
    }
}

uint64_t f(int n) {
    if (n >= 1 << MAX_N_BITS) {
        std::cerr << "n too big";
        exit(1);
    }
    int tlen = 2*n - 1;
    int mid = n;
    int npre = (tlen - mid) / 2;
    if(n>6) --npre;
    int nsuf = tlen - npre - mid;
    seqset preset;
    auto sufs = suffixes(n, nsuf, npre + 1);
    std::unordered_map<seq, std::unordered_set<uint64_t>> premid;
    for(bitvec P = 0; P < maxP(n); P++) {
        prefixes(n, npre, P, preset);
        mids(n, npre, nsuf, mid, P, premid, preset);
    }
    uint64_t ans = 0;
    using counter = uint8_t;
    vector<counter> found((size_t)1 << nsuf * MAX_N_BITS);
    counter iz = 0;
    for(auto& z: premid) {
        ++iz;
        if(!iz) {
            memset(&found[0], 0, found.size() * sizeof(counter));
            ++iz;
        }
        for(auto& pair: z.second) {
            seq s = pair >> n;
            uint64_t count = 0;
            bitvec P = pair & (maxP(n) - 1);
            for(seq t: sufs[P]) {
                seq suff = (s + t) >> (npre + 1) * MAX_N_BITS;
                if (found[suff] != iz) {
                    ++count;
                    found[suff] = iz;
                }
            }
            int middle = int(s >> npre * MAX_N_BITS) & ~(~0U << MAX_N_BITS);
            ans += count << (middle * 2 != n);
        }
    }

    return ans;
}

int main() {
    for (int i = 1; ; i++)
        std::cout << i << ' ' << f(i) << std::endl;
}

Получить до 11 менее чем за 30 секунд!

В случае, если это интересно:feersum.cpp:111:61: warning: shifting a negative signed value is undefined [-Wshift-negative-value] int middle = int(s >> npre * MAX_N_BITS) & ~(~0 << MAX_N_BITS);

5

Хаскелл, оценка 9

import Data.Bits
import Data.List
import qualified Data.IntSet as S

main = mapM_ (print . S.size . S.fromList . hd) [1..9]

hd :: Int -> [Int]
hd n = [foldl' (\s e->s*m+e) 0 [popCount $ xor p (shiftR t i .&. m)|i<-[(0::Int)..n-1]] | let m=2^n-1,p<-[(0::Int)..2^n-1],t<-[(0::Int)..2^(2*n-1)-1]]

Компилировать с -O3. На моем 6-летнем ноутбуке требуется 6 минут 35 секунд n=9, поэтому на эталонном оборудовании это может быть меньше 5 минут.

> time ./113785
2
9
48
297
2040
15425
125232
1070553
9530752

real  6m35.763s
user  6m27.690s
sys   0m5.025s

1
6 лет ноутбук? Черт, это устаревшая технология!
Мэтью Ро

@SIGSEGV: возможно, он устарел, но помимо подсчета количества последовательностей расстояний Хэмминга, он делает свою работу довольно хорошо.
Ними

4

JavaScript, оценка 10

findHamming = m => { 
    if (m < 2) return 2;
    let popcnt = x => x && (x & 1) + popcnt(x >> 1);
    let a = [...Array(1 << m)].map((_,i) => popcnt(i));
    let t = 0;
    let n = (1 << m) - 1;
    for (let c = 0; c <= m; c++) {
        for (let g = 0; g <= c; g++) {
            let s = new Set;
            for (let i = 1 << m; i--; ) {
                for (let j = 1 << (m - 1); j--; ) {
                    if (a[i ^ j + j] != c) continue;
                    for (let k = 1 << m - 1; k--; ) {
                        if (a[i ^ k] != g) continue;
                        let f = j << m | k;
                        let h = 0;
                        for (l = m - 1; --l; ) h = h * (m + 1) + a[i ^ f >> l & n];
                        s.add(h);
                    }
                }
            }
            t += s.size * (g < c ? 2 : 1);
        }
    }
    return t;
};
let d = Date.now(); for (let m = 1; m < 11; m++) console.log(m, findHamming(m), Date.now() - d);

Объяснение: Расчет n=10затруднен, поскольку существует более двух миллиардов пар и более 26 миллиардов потенциальных последовательностей. Чтобы ускорить процесс, я разделил расчет на 121 лот. Поскольку последовательности инвариантны относительно побитового дополнения, я могу предположить без ограничения общности, что средний бит Tравен нулю. Это означает, что я могу определить первый и последний элементы последовательности независимо от верхних n-1и нижних n-1битовT, Каждый контейнер соответствует разной паре первого и последнего элементов; Затем я перебираю все возможные наборы верхних и нижних битов, которые соответствуют каждому бину, и вычисляю оставшиеся элементы последовательности, наконец, подсчитывая уникальные последовательности для каждого бина. Затем остается всего 121 корзина. Первоначально это заняло 45 часов, теперь это заняло чуть меньше трех с половиной минут на моем AMD FX-8120. Редактировать: Спасибо @ChristianSievers за 50% ускорение. Полный вывод:

1 2 0
2 9 1
3 48 1
4 297 2
5 2040 7
6 15425 45
7 125232 391
8 1070553 1844
9 9530752 15364
10 86526701 153699

Ваш код не выводит в данный момент.
Фелипа

@felipa Не уверен, что ты имеешь в виду. Это анонимная функция, поэтому вы вызываете ее (возможно, сначала назначаете ее переменной, а затем вызываете переменную, как если бы она была функцией) и передаете ее nв качестве параметра. (Извините за неправильный выбор имени параметра там.)
Нил

Вопрос запрашивает код, который выводит ответ для n до максимального значения, которое он может получить за 5 минут. «Ваш код должен выводить одно число на значение n».
Фелипа

Было бы здорово, если бы ваш код работал с n = 1 и выводил синхронизацию на каждом этапе. Из вопроса «Время рассчитано на общее время работы, а не на время только для этого n».

1
@Lembik Добавлен временный код, а также исправлена ​​ошибка n=1(не знаю, почему она зависает).
Нил

4

С ++, оценка 10 11

Это перевод ответа @ Neil на C ++ с некоторым простым распараллеливанием. n=9завершается за 0,4 секунды, n=10за 4,5 секунды и n=11примерно за 1 минуту на моем Macbook Pro 2015 года. Также спасибо @ChristianSievers. Из-за его комментариев к ответу @ Neil, я заметил некоторые дополнительные симметрии. От первоначальных 121 сегмента (для n=10) до 66 сегментов при учете аннулирования я получил всего 21 сегмент.

#include <iostream>
#include <cstdint>
#include <unordered_set>
#include <thread>
#include <future>
#include <vector>

using namespace std;

constexpr uint32_t popcnt( uint32_t v ) {
    uint32_t c = v - ( ( v >> 1 ) & 0x55555555 );
    c = ( ( c >> 2 ) & 0x33333333 ) + ( c & 0x33333333 );
    c = ( ( c >> 4 ) + c ) & 0x0F0F0F0F;
    c = ( ( c >> 8 ) + c ) & 0x00FF00FF;
    c = ( ( c >> 16 ) + c ) & 0x0000FFFF;
    return c;
}

template<uint32_t N>
struct A {
    constexpr A() : arr() {
        for( auto i = 0; i != N; ++i ) {
            arr[i] = popcnt( i );
        }
    }
    uint8_t arr[N];
};

uint32_t n = ( 1 << M ) - 1;
constexpr auto a = A < 1 << M > ();

uint32_t work( uint32_t c, uint32_t g, uint32_t mult ) {
    unordered_set<uint64_t> s;
    // Empirically derived "optimal" value
    s.reserve( static_cast<uint32_t>( pow( 5, M ) ) );

    for( int i = ( 1 << M ) - 1; i >= 0; i-- ) {
        for( uint32_t j = 1 << ( M - 1 ); j--; ) {
            if( a.arr[i ^ j + j] != c ) {
                continue;
            }

            for( uint32_t k = 1 << ( M - 1 ); k--; ) {
                if( a.arr[i ^ k] != g ) {
                    continue;
                }

                uint64_t f = j << M | k;
                uint64_t h = 0;

                for( uint32_t l = M - 1; --l; ) {
                    h = h * ( M + 1 ) + a.arr[i ^ ( f >> l & n )];
                }

                s.insert( h );
            }
        }
    }

    return s.size() * mult;

}

int main() {
    auto t1 = std::chrono::high_resolution_clock::now();

    if( M == 1 ) {
        auto t2 = std::chrono::high_resolution_clock::now();
        auto seconds = chrono::duration_cast<chrono::milliseconds>( t2 - t1 ).count() / 1000.0;
        cout << M << ": " << 2 << ", " << seconds << endl;
        return 0;
    }

    uint64_t t = 0;
    vector<future<uint32_t>> my_vector;

    if( ( M & 1 ) == 0 ) {
        for( uint32_t c = 0; c <= M / 2; ++c ) {
            for( uint32_t g = c; g <= M / 2; ++g ) {
                uint32_t mult = 8;

                if( c == M / 2 && g == M / 2 ) {
                    mult = 1;
                } else if( g == c || g == M / 2 ) {
                    mult = 4;
                }

                my_vector.push_back( async( work, c, g, mult ) );
            }

        }

        for( auto && f : my_vector ) {
            t += f.get();
        }

    } else {
        for( uint32_t c = 0; c <= ( M - 1 ) / 2; ++c ) {
            for( uint32_t g = c; g <= M - c; ++g ) {
                uint32_t mult = 4;

                if( g == c || g + c == M ) {
                    mult = 2;
                }

                my_vector.push_back( async( work, c, g, mult ) );
            }

        }

        for( auto && f : my_vector ) {
            t += f.get();
        }

    }

    auto t2 = std::chrono::high_resolution_clock::now();
    auto seconds = chrono::duration_cast<chrono::milliseconds>( t2 - t1 ).count() / 1000.0;
    cout << M << ": " << t << ", " << seconds << endl;
    return 0;
}

Используйте следующий скрипт для выполнения кода:

#!/usr/bin/env bash

for i in {1..10}
do
    clang++ -std=c++14 -march=native -mtune=native -Ofast -fno-exceptions -DM=$i hamming3.cpp -o hamming
    ./hamming
done

Вывод был следующим: (формат M: result, seconds)

1: 2, 0
2: 9, 0
3: 48, 0
4: 297, 0
5: 2040, 0
6: 15425, 0.001
7: 125232, 0.004
8: 1070553, 0.029
9: 9530752, 0.419
10: 86526701, 4.459
11: 800164636, 58.865

n=12 потребовалось 42 минуты, чтобы рассчитать на один поток, и дал результат 7368225813.


Как бы вы скомпилировали это в Ubuntu, используя Clang?
Фелипа

@felipa Я думаю, что ответ sudo apt-get install libiomp-dev.

Было бы здорово, если бы ваш код работал с n = 1 и выводил синхронизацию на каждом этапе. Из вопроса «Время рассчитано на общее время работы, а не на время только для этого n».

Вместо того, чтобы переопределить это, вы могли бы просто использовать __builtin_popcount.
Нил

@Lembik: я внесу изменения позже сегодня. @Neil: функция popcnt оценивается только во время компиляции, и я не знаю, как ее использовать __builtin_popcountв контексте constexpr. Я мог бы пойти с наивной реализацией, и это не повлияло бы на время выполнения.
Эндрю Эпштейн

2

JavaScript 2,9,48,297,2040,15425,125232,1070553,9530752

Запустите в консоли:

console.time("test");
h=(w,x,y,z)=>{retVal=0;while(x||y){if(x%2!=y%2)retVal++;x>>=1;y>>=1}return retVal*Math.pow(w+1,z)};
sum=(x,y)=>x+y;
for(input=1;input<10;input++){
  hammings=new Array(Math.pow(input+1,input));
  for(i=1<<(input-1);i<1<<input;i++){
    for(j=0;j<1<<(2*input);j++){
      hamming=0;
      for(k=0;k<input;k++){
        hamming+=(h(input,(j>>k)%(1<<input),i,k));
      }
      hammings[hamming]=1;
    }
  }
  console.log(hammings.reduce(sum));
}
console.timeEnd("test");

Попробуйте онлайн!

Или как фрагмент стека:

Код инициализирует массив для ускорения добавления единиц в массив.

Код находит все последовательности расстояний Хэмминга и обрабатывает их как основание чисел (input + 1), использует их для размещения единиц в массиве. В результате создается массив с n 1, где n - количество уникальных последовательностей расстояний Хэмминга. Наконец, число 1 с подсчитывается с помощью array.reduce () для суммирования всех значений в массиве.

Этот код не сможет работать для ввода 10, так как он достигает пределов памяти

Этот код выполняется за O (2 ^ 2n) времени, потому что именно столько элементов он генерирует.


1
Неудивительно, что попытка создать массив элементов размером 26 * 10 ^ 9 не работает
fəˈnɛtɪk

n = 9Мне нужно 5 минут и 30 секунд, используя node.js, так что это слишком медленно.

n = 8Изначально @Lembik на моем ПК заняла 24 секунды, но я смог оптимизировать код, так что это n = 8заняло 6 секунд. Затем я попытался, n = 9и это заняло 100 секунд.
Нил

@Neil Вы должны отправить ответ!

Было бы здорово, если бы ваш код работал с n = 1 и выводил синхронизацию на каждом этапе. Из вопроса «Время рассчитано на общее время работы, а не на время только для этого n».
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.