Conte o número de seqüências de distância de Hamming

9

A distância de Hamming entre duas cordas de igual comprimento é o número de posições nas quais os símbolos correspondentes são diferentes.

Let PSer uma seqüência de comprimento binária ne Tser uma seqüência de comprimento binária 2n-1. Podemos calcular as ndistâncias de Hamming entre Ptodas as nsubcadeias de comprimento da Tordem da esquerda para a direita e colocá-las em uma matriz (ou lista).

Exemplo de seqüência de distância de Hamming

Let P = 101e T = 01100. A sequência de distâncias de Hamming que você obtém desse par é 2,2,1.

Tarefa

Para aumentar a npartir de n=1, considere todos os pares possíveis de cadeias binárias Pde comprimento ne Tcomprimento 2n-1. Existem 2**(n+2n-1)tais pares e, portanto, muitas seqüências de distâncias de Hamming. No entanto, muitas dessas sequências serão idênticas. A tarefa é descobrir quantas são distintas para cada uma n.

Seu código deve gerar um número por valor de n.

Ponto

Sua pontuação é a mais alta que nseu código alcança na minha máquina em 5 minutos. O tempo é para o tempo total de execução, não o tempo apenas para isso n.

Quem ganha

A pessoa com a pontuação mais alta ganha. Se duas ou mais pessoas terminam com a mesma pontuação, então é a primeira resposta que ganha.

Respostas de exemplo

Para nde 1para 8as respostas ideais são 2, 9, 48, 297, 2040, 15425, 125232, 1070553.

Línguas e bibliotecas

Você pode usar qualquer idioma e bibliotecas disponíveis que desejar. Sempre que possível, seria bom poder executar seu código; portanto, inclua uma explicação completa de como executar / compilar seu código no Linux, se possível.

Minha máquina Os tempos serão executados na minha máquina de 64 bits. Esta é uma instalação padrão do ubuntu com 8GB de RAM, processador de oito núcleos AMD FX-8350 e Radeon HD 4250. Isso também significa que eu preciso executar seu código.

Respostas principais

  • 11 em C ++ por feersum. 25 segundos.
  • 11 em C ++ por Andrew Epstein. 176 segundos.
  • 10 em Javascript por Neil. 54 segundos.
  • 9 em Haskell por nimi. 4 minutos e 59 segundos.
  • 8 em Javascript por fəˈnɛtɪk. 10 segundos.

fonte
.. algum idioma grátis * disponível ?
Stewie Griffin
código mais rápido ? não é o algoritmo mais rápido ? Você sabe, as pessoas podem usar a linguagem com um intérprete rápido e fazer uma diferença significativa no tempo, mas a complexidade do tempo é sempre a mesma, por isso, de certa forma, é justo.
Matthew Roh 24/03
Relacionado.
Martin Ender
4
O @SIGSEGV fastest-codedeixa mais espaço para otimizações por meio de otimizações em nível de código e de um bom algoritmo. Então eu acho que isso faster-codeé melhor do que faster-algorithm.
Dada

Respostas:

3

C ++ 11 (deve chegar a 11 ou 12)

No momento, isso é de thread único.

Compilar:

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;
}
feersum
fonte
Chegue ao 11 em menos de 30 segundos!
Caso isso seja de interesse: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, pontuação 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]]

Compile com -O3. São necessários 6min35s no meu laptop de 6 anos para rodar n=9, então talvez ele esteja abaixo de 5min no hardware de referência.

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

real  6m35.763s
user  6m27.690s
sys   0m5.025s
nimi
fonte
11
Laptop de 6 anos? Porra, isso é alguma tecnologia ultrapassada!
Matthew Roh
@SIGSEGV: talvez esteja desatualizado, mas além de contar o número de sequências de distâncias de Hamming, ele faz seu trabalho muito bem.
N
4

JavaScript, pontuação 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);

Explicação: O cálculo n=10é difícil porque existem mais de dois bilhões de pares e mais de 26 bilhões de seqüências em potencial. Para acelerar as coisas, divido o cálculo em 121 posições. Como as seqüências são invariantes no complemento bit a bit, posso assumir, sem perda de generalidade, que o bit do meio Té zero. Isso significa que eu posso determinar o primeiro e o último elemento da sequência independentemente dos bits superior n-1e inferior n-1daT. Cada compartimento corresponde a um par diferente de primeiro e último elementos; Depois, percorro todos os conjuntos possíveis de bits superior e inferior que correspondem a cada compartimento e calculo os elementos restantes da sequência, finalmente contando as seqüências exclusivas de cada compartimento. Resta então totalizar todos os 121 compartimentos. Originalmente levando 45 horas, isso agora foi concluído em pouco menos de três minutos e meio na minha AMD FX-8120. Edit: Obrigado a @ChristianSievers por uma aceleração de 50%. Saída total:

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
Neil
fonte
Seu código não fornece saída atualmente.
felipa 27/03
@felipa Não sei ao certo o que você quer dizer. É uma função anônima, então você a chama (talvez atribuindo-a a uma variável primeiro e depois chamando a variável como se fosse uma função) e a passa ncomo parâmetro. (Desculpem a má escolha de nome de parâmetro lá.)
Neil
A pergunta solicita um código que imprima a resposta para n até o valor mais alto possível em 5 minutos. "Seu código deve gerar um número por valor de n."
felipa 27/03
Seria ótimo se seu código funcionasse com n = 1 e produzisse o tempo em cada estágio. Da pergunta "O tempo é para o tempo total de execução, não o tempo apenas para esse n."
11
@Lembik Adicionado código de tempo e também contornou o bug n=1(não sei de antemão por que ele trava).
315 Neil
4

C ++, pontuação 10 11

Esta é uma tradução da resposta de @ Neil em C ++, com alguma paralelização simples. n=9termina em 0,4 segundos, n=10em 4,5 segundos e n=11em aproximadamente 1 minuto no meu Macbook Pro de 2015. Além disso, obrigado a @ChristianSievers. Devido a seus comentários sobre a resposta de @ Neil, notei algumas simetrias adicionais. Dos 121 baldes originais (para n=10), aos 66 baldes ao contabilizar a reversão, reduzi para apenas 21 baldes.

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

Use o seguinte script para executar o código:

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

A saída foi a seguinte: (O 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 demorou 42 minutos para calcular em um único encadeamento e deu um resultado de 7368225813.

Andrew Epstein
fonte
Como você compilaria isso no ubuntu usando o clang?
felipa 27/03
@ Felipa Acho que a resposta é sudo apt-get install libiomp-dev.
Seria ótimo se seu código funcionasse com n = 1 e produzisse o tempo em cada estágio. Da pergunta "O tempo é para o tempo total de execução, não o tempo apenas para esse n."
Em vez de reimplementá-lo, você provavelmente poderia apenas usá-lo __builtin_popcount.
315 Neil
@Lembik: Eu vou fazer as alterações hoje mais tarde. @ Neil: A função popcnt só é avaliada em tempo de compilação, e eu não sei como usar __builtin_popcountem um contexto constexpr. Eu poderia ir com a implementação ingênua e isso não afetaria o tempo de execução.
Andrew Epstein
2

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

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

Experimente online!

Ou como um snippet de pilha:

O código pré-inicializa a matriz para tornar a adição de 1s na matriz muito mais rápida

O código localiza todas as seqüências de distância de hamming e as trata como base de números (entrada + 1), usa-as para colocar 1s em uma matriz. Como resultado, isso gera uma matriz com os n 1s em que n é o número de seqüências únicas de distâncias de hamming. Finalmente, o número de 1s é contado usando array.reduce () para somar todos os valores na matriz.

Este código não poderá executar a entrada 10, pois atinge os limites de memória

Esse código é executado no tempo O (2 ^ 2n) porque é quantos elementos ele gera.

fəˈnɛtɪk
fonte
11
Sem surpresa, tentando criar uma matriz elemento 26 * 10 ^ 9 não trabalho
fənɛtɪk
n = 9leva 5 minutos e 30 segundos para eu usar o node.js, então é muito lento.
O @Lembik n = 8originalmente levou 24 segundos no meu PC, mas eu pude otimizar o código para n = 8levar 6 segundos. Eu tentei n = 9e isso levou 100 segundos.
Neil
@ Neil Você deve enviar uma resposta!
Seria ótimo se seu código funcionasse com n = 1 e produzisse o tempo em cada estágio. Da pergunta "O tempo é para o tempo total de execução, não o tempo apenas para esse n."