Підрахуйте кількість послідовностей відстаней Хеммінга


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-річному апаратному ПК для ноутбука потрібно 6min35s 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

@felipa Не впевнений, що ти маєш на увазі. Це анонімна функція, тому ви викликаєте її (можливо, спершу призначивши її змінній, а потім викликавши змінну так, ніби це функція) і передаєте її nяк параметр. (Вибачте за поганий вибір назви параметра.)
Ніл

Питання запитує код, який виводить відповідь на n до максимального значення, яке воно може отримати за 5 хвилин. "Ваш код повинен виводити одне число на значення n."
felipa

Було б чудово, якби ваш код працював з n = 1 і виводив терміни на кожному етапі. З питання "Час часу - це загальний час роботи, а не час тільки для цього n".

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

4

C ++, оцінка 10 11

Це переклад відповіді @ Ніла на C ++ з деякою простою паралелізацією. n=9завершується за 0,4 секунди, n=10за 4,5 секунди та n=11приблизно за 1 хвилину на моєму Macbook Pro 2015 року. Також дякую @ChristianSievers. Завдяки його коментарям до відповіді @ Ніла я помітив деякі додаткові симетрії. Від оригінальних 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

@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");

Спробуйте в Інтернеті!

Або як фрагмент стека:

Код визначає масив, щоб зробити додавання 1s до масиву набагато швидше

Код знаходить усі послідовності дистанції злучення і розглядає їх як основу чисел (вхід + 1), використовує їх для розміщення значень 1s у масиві. В результаті цього створюється масив з n 1s, де n - кількість унікальних послідовностей дистанцій. Нарешті, число 1s підраховується за допомогою array.reduce () для підсумовування всіх значень у масиві.

Цей код не вдасться запустити для введення 10, оскільки він досягає меж пам'яті

Цей код працює в O (2 ^ 2n) час, оскільки саме стільки елементів генерується.


1
Не дивно, що спроба створити масив елементів 26 * 10 ^ 9 не працює
fəˈnɛtɪk

n = 9мені потрібно 5 хвилин і 30 секунд за допомогою node.js, тому це занадто повільно.

@Lembik n = 8спочатку займав 24 секунди на моєму ПК, але я зміг оптимізувати код так, що це n = 8зайняло 6 секунд. Потім я спробував, n = 9і це зайняло 100 секунд.
Ніл

@Neil Ви повинні надіслати відповідь!

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