Conta il numero di sequenze della distanza di Hamming


9

La distanza di Hamming tra due stringhe di uguale lunghezza è il numero di posizioni in cui i simboli corrispondenti sono diversi.

Lascia che Psia una stringa binaria di lunghezza ne che Tsia una stringa binaria di lunghezza 2n-1. Possiamo calcolare le ndistanze di Hamming tra Pe tutte le nsottostringhe di Tin ordine da sinistra a destra e inserirle in un array (o elenco).

Esempio di sequenza della distanza di Hamming

Let P = 101and T = 01100. La sequenza delle distanze di Hamming che ottieni da questa coppia è 2,2,1.

Compito

Per aumentare a npartire da n=1, considera tutte le possibili coppie di stringhe binarie Pdi lunghezza ne Tlunghezza 2n-1. Esistono 2**(n+2n-1)coppie del genere e quindi molte sequenze di distanze di Hamming. Tuttavia, molte di queste sequenze saranno identiche. Il compito è quello di trovare quanti sono distinti per ciascuno n.

Il tuo codice dovrebbe generare un numero per valore di n.

Punto

Il tuo punteggio è il massimo che il ntuo codice raggiunge sulla mia macchina in 5 minuti. Il tempismo è per il tempo di esecuzione totale, non solo per quello n.

Chi vince

Vince la persona con il punteggio più alto. Se due o più persone finiscono con lo stesso punteggio, è la prima risposta a vincere.

Risposte di esempio

Per ndal 1al 8ottimale risposte sono 2, 9, 48, 297, 2040, 15425, 125232, 1070553.

Lingue e biblioteche

È possibile utilizzare qualsiasi lingua e libreria disponibili. Laddove possibile, sarebbe opportuno poter eseguire il codice, quindi includere una spiegazione completa su come eseguire / compilare il codice in Linux, se possibile.

La mia macchina I tempi verranno eseguiti sulla mia macchina a 64 bit. Questa è un'installazione Ubuntu standard con 8 GB di RAM, processore AMD FX-8350 Eight-Core e Radeon HD 4250. Ciò significa anche che devo essere in grado di eseguire il codice.

Risposte principali

  • 11 in C ++ per feersum. 25 secondi.
  • 11 in C ++ di Andrew Epstein. 176 secondi.
  • 10 in Javascript di Neil. 54 secondi.
  • 9 in Haskell di nimi. 4 minuti e 59 secondi.
  • 8 in Javascript da fəˈnɛtɪk. 10 secondi.

.. eventuali lingue * gratuite disponibili ?
Stewie Griffin,

codice più veloce ? algoritmo non più veloce ? Sai, le persone potrebbero andare con la lingua con un interprete maledettamente veloce e fare una differenza significativa nel tempo, ma la complessità del tempo è sempre la stessa, quindi in qualche modo la rende giusta.
Matthew Roh,


4
@SIGSEGV fastest-codelascia più spazio per le ottimizzazioni attraverso ottimizzazioni a livello di codice e un buon algoritmo. Quindi penso che faster-codesia meglio di faster-algorithm.
Dada,

Risposte:


3

C ++ 11 (dovrebbe arrivare a 11 o 12)

Al momento questo è a thread singolo.

Compilare:

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

Ottieni 11 in meno di 30 secondi!

Nel caso sia interessante: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, punteggio 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]]

Compila con -O3. Sono necessari 6 minuti e 35 minuti sull'hardware del mio laptop di 6 anni n=9, quindi forse meno di 5 minuti sull'hardware di riferimento.

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

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

1
Laptop di 6 anni? Accidenti, questa è una tecnologia obsoleta!
Matthew Roh,

@SIGSEGV: forse è obsoleto, ma oltre a contare il numero di sequenze di distanze di Hamming fa abbastanza bene il suo lavoro.
nimi,

4

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

Spiegazione: Il calcolo n=10è difficile perché ci sono oltre due miliardi di coppie e oltre 26 miliardi di sequenze potenziali. Per velocizzare le cose ho diviso il calcolo in 121 scomparti. Poiché le sequenze sono invarianti sotto il complemento bit a bit, posso assumere senza perdita di generalità che il bit centrale di Tè zero. Ciò significa che posso determinare il primo e l'ultimo elemento della sequenza indipendentemente dai bit superiore n-1e inferiore n-1diT. Ogni bin corrisponde a una diversa coppia di primo e ultimo elemento; Quindi eseguo il ciclo attraverso tutti i possibili set di bit superiore e inferiore che corrispondono a ciascun bin e calcolo gli elementi rimanenti della sequenza, contando infine le sequenze uniche per ciascun bin. Resta quindi da totalizzare tutti i 121 scomparti. Inizialmente impiegando 45 ore, questo ora è stato completato in poco meno di tre minuti e mezzo sul mio AMD FX-8120. Modifica: grazie a @ChristianSievers per uno speedup del 50%. Uscita completa:

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

Il tuo codice non fornisce attualmente alcun output.
Felipa,

@felipa Non sei sicuro di cosa intendi. È una funzione anonima, quindi la chiami (magari assegnandola prima a una variabile e poi chiamando la variabile come se fosse una funzione) e la passi ncome parametro. (Ci scusiamo per la scelta sbagliata del nome del parametro lì.)
Neil

La domanda richiede il codice che stampa la risposta per n fino al valore più alto che può raggiungere in 5 minuti. "Il tuo codice dovrebbe generare un numero per valore di n."
Felipa,

Sarebbe bello se il tuo codice funzionasse da n = 1 e producesse i tempi in ogni fase. Dalla domanda "Il tempismo è per il tempo totale di esecuzione, non il tempo solo per quel n."

1
@Lembik Aggiunto il codice di temporizzazione e anche risolto il problema con i bug n=1(non so a mano perché si blocca).
Neil,

4

C ++, punteggio 10 11

Questa è una traduzione della risposta di @ Neil in C ++, con una semplice parallelizzazione. n=9si completa in 0,4 secondi, n=10in 4,5 secondi e n=11in circa 1 minuto sul mio Macbook Pro 2015. Inoltre, grazie a @ChristianSievers. A causa dei suoi commenti sulla risposta di @ Neil, ho notato alcune simmetrie aggiuntive. Dai 121 secchi originali (per n=10), ai 66 secchi quando si considera l'inversione, sono sceso a soli 21 secchi.

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

Utilizzare il seguente script per eseguire il codice:

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

L'output era il seguente: (Il formato è 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 ha impiegato 42 minuti per calcolare su un singolo thread e ha dato un risultato di 7368225813.


Come lo compileresti in Ubuntu usando clang?
Felipa,

@felipa Penso che la risposta sia sudo apt-get install libiomp-dev.

Sarebbe bello se il tuo codice funzionasse da n = 1 e producesse i tempi in ogni fase. Dalla domanda "Il tempismo è per il tempo totale di esecuzione, non il tempo solo per quel n."

Invece di reimplementarlo, probabilmente potresti semplicemente usarlo __builtin_popcount.
Neil,

@Lembik: apporterò le modifiche più tardi oggi. @Neil: la funzione popcnt viene valutata solo in fase di compilazione e non so come usarla __builtin_popcountin un contesto constexpr. Potrei andare con l'implementazione ingenua e non influirebbe sul tempo di esecuzione.
Andrew Epstein,

2

JavaScript 2,9,48.297,2040,15425,125232,1070553,9530752

Esegui in console:

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

Provalo online!

O come frammento di stack:

Il codice preinizializza l'array per rendere più veloce l'aggiunta di 1 s all'array

Il codice trova tutte le sequenze della distanza di percussione e le tratta come base numerica (input + 1), le usa per posizionare 1s in un array. Di conseguenza, questo genera un array con n 1s dove n è il numero di sequenze di distanza di hamming uniche. Infine, il numero di 1 viene contato utilizzando array.reduce () per sommare tutti i valori nell'array.

Questo codice non sarà in grado di funzionare per input di 10 poiché raggiunge limiti di memoria

Questo codice viene eseguito in tempo O (2 ^ 2n) perché è il numero di elementi che genera.


1
Non sorprende che il tentativo di creare un array di elementi 26 * 10 ^ 9 non funzioni
f Marnɛtɪk

n = 9richiede 5 minuti e 30 secondi per me usando node.js, quindi è troppo lento.

@Lembik n = 8inizialmente impiegava 24 secondi sul mio PC, ma sono stato in grado di ottimizzare il codice in modo da n = 8richiedere 6 secondi. Ho quindi provato n = 9e ci sono voluti 100 secondi.
Neil

@Neil Dovresti inviare una risposta!

Sarebbe bello se il tuo codice funzionasse da n = 1 e producesse i tempi in ogni fase. Dalla domanda "Il tempismo è per il tempo totale di esecuzione, non il tempo solo per quel n."
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.