Policz liczbę sekwencji odległości Hamminga


9

Odległość Hamminga pomiędzy dwa ciągi o równej długości jest numer pozycji, w którym odpowiednie symbole są różne.

Niech Pbędzie dwójkowym ciągiem długości ni Tdwójkowym ciągiem długości 2n-1. Możemy obliczyć nodległości Hamminga między podciągami Pkażdej ndługości Tw kolejności od lewej do prawej i umieścić je w tablicy (lub liście).

Przykład sekwencji odległości Hamminga

Niech P = 101i T = 01100. Sekwencja odległości Hamminga uzyskana z tej pary to 2,2,1.

Zadanie

Aby zwiększyć, nzaczynając od n=1, rozważ wszystkie możliwe pary ciągów binarnych Po długości ni Tdługości 2n-1. Istnieją 2**(n+2n-1)takie pary, a zatem wiele sekwencji odległości Hamminga. Jednak wiele z tych sekwencji będzie identycznych. Zadanie polega na ustaleniu, ile jest odrębnych dla każdego z nich n.

Twój kod powinien wypisywać jedną liczbę na wartość n.

Wynik

Twój wynik jest najwyższy, jaki nTwój kod osiągnie na moim komputerze w ciągu 5 minut. Czas dotyczy całkowitego czasu działania, a nie tylko tego czasu n.

Kto wygrywa

Osoba z najwyższym wynikiem wygrywa. Jeśli dwie lub więcej osób uzyska ten sam wynik, wygrywa pierwsza odpowiedź.

Przykładowe odpowiedzi

Ponieważ nod 1do 8optymalnych odpowiedzi są 2, 9, 48, 297, 2040, 15425, 125232, 1070553.

Języki i biblioteki

Możesz użyć dowolnego dostępnego języka i bibliotek, które ci się podobają. Tam, gdzie jest to wykonalne, dobrze byłoby móc uruchomić kod, więc proszę podać pełne wyjaśnienie, jak uruchomić / skompilować kod w systemie Linux, jeśli to w ogóle możliwe.

Moja maszyna Czasy zostaną uruchomione na moim komputerze 64-bitowym. Jest to standardowa instalacja ubuntu z 8 GB pamięci RAM, ośmiordzeniowym procesorem AMD FX-8350 i Radeon HD 4250. Oznacza to również, że muszę mieć możliwość uruchomienia kodu.

Wiodące odpowiedzi

  • 11 w C ++ przez feersum. 25 sekund.
  • 11 w C ++ autorstwa Andrew Epsteina. 176 sekund.
  • 10 w JavaScript autorstwa Neila. 54 sekundy.
  • 9 w Haskell autorstwa nich. 4 minuty i 59 sekund.
  • 8 w JavaScript autorstwa fəˈnɛtɪk. 10 sekund.

.. jakieś dostępne darmowe * języki?
Stewie Griffin

najszybszy kod ? nie najszybszy algorytm ? Wiesz, ludzie mogą chodzić z językiem z cholernie szybkim tłumaczem i robić znaczącą różnicę w czasie, ale złożoność czasu jest zawsze taka sama, więc w pewnym sensie sprawia, że ​​jest to sprawiedliwe.
Matthew Roh


4
@SIGSEGV fastest-codepozostawia więcej miejsca na optymalizacje dzięki optymalizacji zarówno poziomu kodu, jak i dobrego algorytmu. Myślę więc, że faster-codeto lepsze niż faster-algorithm.
Dada

Odpowiedzi:


3

C ++ 11 (powinien dostać się do 11 lub 12)

W tej chwili jest to wątek jednowątkowy.

Kompilować:

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;
}

Dotrzyj do 11 w mniej niż 30 sekund!

W razie zainteresowania: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

Haskell, ocena 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]]

Kompiluj z -O3. Uruchomienie 6-letniego sprzętu na moim 6-letnim laptopie zajmuje 6 n=9minut, więc może na urządzeniu referencyjnym jest to mniej niż 5 minut.

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

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

1
6-letni laptop? Cholera, to trochę przestarzała technologia!
Matthew Roh,

@SIGSEGV: może jest przestarzały, ale oprócz zliczania sekwencji odległości Hamminga całkiem dobrze sobie radzi.
nimi

4

JavaScript, wynik 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);

Wyjaśnienie: Obliczanie n=10jest trudne, ponieważ istnieje ponad dwa miliardy par i ponad 26 miliardów potencjalnych sekwencji. Aby przyspieszyć, podzieliłem obliczenia na 121 przedziałów. Ponieważ sekwencje są niezmienne w bitowym uzupełnieniu, mogę założyć bez utraty ogólności, że środkowy bit Tto zero. Oznacza to, że mogę określić pierwszy i ostatni element sekwencji niezależnie od górnych n-1i dolnych n-1bitówT. Każdy pojemnik odpowiada innej parze pierwszego i ostatniego elementu; Następnie przeglądam wszystkie możliwe zestawy górnych i dolnych bitów odpowiadających każdemu przedziałowi i obliczam pozostałe elementy sekwencji, w końcu licząc unikalne sekwencje dla każdego przedziału. Pozostaje wtedy suma wszystkich 121 pojemników. Początkowo trwało to 45 godzin, teraz zakończyłem to w niespełna trzy i pół minuty na moim AMD FX-8120. Edycja: Dzięki @ChristianSievers za 50% przyspieszenia. Pełna wydajność:

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

Twój kod nie daje obecnie danych wyjściowych.
felipa

@felipa Nie jestem pewien, co masz na myśli. Jest to funkcja anonimowa, więc wywołujesz ją (być może najpierw przypisując ją do zmiennej, a następnie wywołując zmienną tak, jakby była funkcją) i przekazujesz ją njako parametr. (Przepraszamy za zły wybór nazwy parametru.)
Neil

Pytanie dotyczy kodu, który wypisuje odpowiedź od n do najwyższej wartości, jaką może uzyskać w ciągu 5 minut. „Twój kod powinien wypisywać jedną liczbę na wartość n.”
felipa

Byłoby wspaniale, gdyby Twój kod działał od n = 1 i wyświetlał czas na każdym etapie. Z pytania „Czas dotyczy całkowitego czasu działania, a nie samego czasu n”.

1
@Lembik Dodano kod czasowy, a także pracował nad błędem n=1(nie wiem od razu, dlaczego się zawiesza).
Neil

4

C ++, wynik 10 11

To jest tłumaczenie odpowiedzi @ Neila na C ++, z pewną prostą równoległością. n=9kończy się za 0,4 sekundy, n=10za 4,5 sekundy i n=11za około 1 minutę na moim MacBooku Pro 2015. Również dzięki @ChristianSievers. Dzięki jego komentarzom do odpowiedzi @ Neila zauważyłem dodatkowe symetrie. Od oryginalnych 121 wiader (dla n=10) do 66 wiader przy rozliczaniu odwrócenia, zjechałem do zaledwie 21 wiader.

#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;
}

Użyj następującego skryptu, aby wykonać kod:

#!/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

Dane wyjściowe były następujące: (Format jest 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 obliczenie pojedynczego wątku zajęło 42 minuty, co dało wynik 7368225813.


Jak skompilowałbyś to w ubuntu przy użyciu clang?
felipa

@felipa Myślę, że odpowiedź brzmi sudo apt-get install libiomp-dev.

Byłoby wspaniale, gdyby Twój kod działał od n = 1 i wyświetlał czas na każdym etapie. Z pytania „Czas dotyczy całkowitego czasu działania, a nie samego czasu n”.

Zamiast go ponownie wdrożyć, prawdopodobnie możesz po prostu użyć __builtin_popcount.
Neil

@Lembik: Dokonam zmian później dzisiaj. @Neil: Funkcja popcnt jest oceniana tylko w czasie kompilacji i nie wiem, jak jej używać __builtin_popcountw kontekście constexpr. Mógłbym przejść do naiwnego wdrożenia i nie wpłynęłoby to na czas działania.
Andrew Epstein

2

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

Uruchom w konsoli:

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

Wypróbuj online!

Lub jako fragment kodu stosu:

Kod wstępnie inicjuje tablicę, aby znacznie przyspieszyć dodawanie 1s do tablicy

Kod znajduje wszystkie sekwencje odległości Hamminga i traktuje je jako bazę liczb (wejście + 1), używa ich do umieszczenia 1 w tablicy. W rezultacie generuje to tablicę z n 1s, gdzie n jest liczbą unikalnych sekwencji odległości hamowania. Na koniec liczba 1 jest liczona za pomocą array.reduce (), aby zsumować wszystkie wartości w tablicy.

Ten kod nie będzie mógł zostać uruchomiony dla wprowadzenia wartości 10, ponieważ osiągnie limit pamięci

Ten kod działa w czasie O (2 ^ 2n), ponieważ tyle elementów generuje.


1
Nic dziwnego, że próba utworzenia tablicy 26 * 10 ^ 9 elementów nie działa
fəˈnɛtɪk

n = 9przy użyciu node.js zajmuje mi 5 minut i 30 sekund, więc jest po prostu zbyt wolny.

@Lembik n = 8początkowo zajmował 24 sekundy na moim komputerze, ale udało mi się zoptymalizować kod, więc n = 8zajęło mi to 6 sekund. Potem spróbowałem n = 9i zajęło to 100 sekund.
Neil

@ Nee Powinieneś przesłać odpowiedź!

Byłoby wspaniale, gdyby Twój kod działał od n = 1 i wyświetlał czas na każdym etapie. Z pytania „Czas dotyczy całkowitego czasu działania, a nie samego czasu n”.
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.