Comptez le nombre de séquences de distance de Hamming


9

La distance de Hamming entre deux chaînes de longueur égale est le nombre de positions auxquelles les symboles correspondants sont différents.

Soit Pune chaîne binaire de longueur net Tune chaîne binaire de longueur 2n-1. Nous pouvons calculer les ndistances de Hamming entre Pet chaque nsous-chaîne de longueur Tdans l'ordre de gauche à droite et les mettre dans un tableau (ou une liste).

Exemple de séquence de distance de Hamming

Soit P = 101et T = 01100. La séquence des distances de Hamming que vous obtenez de cette paire est 2,2,1.

Tâche

Pour augmenter à npartir de n=1, considérez toutes les paires possibles de chaînes binaires Pde longueur net Tde longueur 2n-1. Il existe de 2**(n+2n-1)telles paires et donc de nombreuses séquences de distances de Hamming. Cependant, bon nombre de ces séquences seront identiques. La tâche consiste à trouver combien sont distincts pour chacun n.

Votre code doit afficher un nombre par valeur de n.

But

Votre score est le plus élevé natteint par votre code sur ma machine en 5 minutes. Le timing est pour le temps de fonctionnement total, pas le temps juste pour cela n.

Qui gagne

La personne avec le score le plus élevé gagne. Si deux personnes ou plus se retrouvent avec le même score, c'est la première réponse qui l'emporte.

Exemples de réponses

Pour nde 1aux 8réponses optimales sont 2, 9, 48, 297, 2040, 15425, 125232, 1070553.

Langues et bibliothèques

Vous pouvez utiliser toutes les langues et bibliothèques disponibles que vous aimez. Dans la mesure du possible, il serait bon de pouvoir exécuter votre code, veuillez donc inclure une explication complète sur la façon d'exécuter / compiler votre code sous Linux si possible.

Ma machine Les horaires seront exécutés sur ma machine 64 bits. Il s'agit d'une installation Ubuntu standard avec 8 Go de RAM, processeur AMD FX-8350 à huit cœurs et Radeon HD 4250. Cela signifie également que je dois pouvoir exécuter votre code.

Réponses principales

  • 11 en C ++ par feersum. 25 secondes.
  • 11 en C ++ par Andrew Epstein. 176 secondes.
  • 10 en Javascript par Neil. 54 secondes.
  • 9 à Haskell par nimi. 4 minutes et 59 secondes.
  • 8 en Javascript par fəˈnɛtɪk. 10 secondes.

.. toutes les langues gratuites * disponibles ?
Stewie Griffin

code le plus rapide ? pas l' algorithme le plus rapide ? Vous savez, les gens pourraient utiliser la langue avec un interprète extrêmement rapide et faire une différence significative dans le temps, mais la complexité du temps est toujours la même, donc cela rend les choses assez justes.
Matthew Roh


4
@SIGSEGV fastest-codelaisse plus d'espace pour les optimisations grâce aux optimisations au niveau du code et à un bon algorithme. Je pense donc que faster-codec'est mieux que faster-algorithm.
Dada

Réponses:


3

C ++ 11 (devrait atteindre 11 ou 12)

Pour le moment, il s'agit d'un seul thread.

Compiler:

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

Obtenez à 11 en moins de 30 secondes!

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, score 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]]

Compilez avec -O3. Il faut 6min35s sur mon matériel d'ordinateur portable de 6 ans pour fonctionner n=9, donc c'est peut-être moins de 5min sur le matériel de référence.

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

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

1
Ordinateur portable de 6 ans? Merde, c'est une technologie obsolète!
Matthew Roh

@SIGSEGV: c'est peut-être dépassé, mais en plus de compter le nombre de séquences de distance de Hamming, il fait très bien son travail.
nimi

4

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

Explication: le calcul n=10est difficile car il y a plus de deux milliards de paires et plus de 26 milliards de séquences potentielles. Afin d'accélérer les choses, j'ai divisé le calcul en 121 cases. Parce que les séquences sont invariantes sous complément binaire, je peux supposer sans perte de généralité que le bit central de Test nul. Cela signifie que je peux déterminer les premier et dernier éléments de la séquence indépendamment des bits supérieur n-1et inférieur n-1deT. Chaque bac correspond à une paire différente de premier et dernier éléments; Je passe ensuite en revue tous les ensembles possibles de bits supérieurs et inférieurs qui correspondent à chaque groupe et calcule les éléments restants de la séquence, en comptant enfin les séquences uniques pour chaque groupe. Il reste alors à totaliser l'ensemble des 121 bacs. Prenant à l'origine 45 heures, cela s'est terminé en un peu moins de trois minutes et demie sur mon AMD FX-8120. Edit: Merci à @ChristianSievers pour une accélération de 50%. Sortie complète:

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

Votre code ne donne aucune sortie actuellement.
felipa

@felipa Je ne sais pas ce que tu veux dire. C'est une fonction anonyme, vous l'appelez donc (peut-être en l'assignant d'abord à une variable, puis en appelant la variable comme s'il s'agissait d'une fonction) et en la passant ncomme paramètre. (Désolé pour le mauvais choix de nom de paramètre là-bas.)
Neil

La question demande un code qui imprime la réponse pour n jusqu'à la valeur la plus élevée à laquelle il peut arriver en 5 minutes. "Votre code doit afficher un nombre par valeur de n."
felipa

Ce serait formidable si votre code fonctionnait à partir de n = 1 et produisait le timing à chaque étape. De la question "Le timing est pour le temps de fonctionnement total, pas le temps juste pour ce n."

1
@Lembik Ajout d'un code de synchronisation et également contourné le bogue de n=1(je ne sais pas pourquoi il se bloque).
Neil

4

C ++, score 10 11

Il s'agit d'une traduction de la réponse de @ Neil en C ++, avec une simple parallélisation. n=9se termine en 0,4 seconde, n=10en 4,5 secondes et n=11en environ 1 minute sur mon Macbook Pro 2015. Merci aussi à @ChristianSievers. En raison de ses commentaires sur la réponse de @ Neil, j'ai remarqué quelques symétries supplémentaires. Des 121 seaux d'origine (pour n=10) à 66 seaux en tenant compte de l'inversion, je suis descendu à seulement 21 seaux.

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

Utilisez le script suivant pour exécuter le code:

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

Le résultat était le suivant: (Le format est 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 a pris 42 minutes pour calculer sur un seul thread et a donné un résultat de 7368225813.


Comment compileriez-vous cela dans Ubuntu en utilisant Clang?
felipa

@felipa Je pense que la réponse est sudo apt-get install libiomp-dev.

Ce serait formidable si votre code fonctionnait à partir de n = 1 et produisait le timing à chaque étape. De la question "Le timing est pour le temps de fonctionnement total, pas le temps juste pour ce n."

Plutôt que de le réimplémenter, vous pourriez probablement simplement l'utiliser __builtin_popcount.
Neil

@Lembik: Je ferai les changements plus tard dans la journée. @Neil: La fonction popcnt n'est évaluée qu'au moment de la compilation, et je ne sais pas comment l'utiliser __builtin_popcountdans un contexte constexpr. Je pourrais aller avec l'implémentation naïve et cela n'affecterait pas le temps d'exécution.
Andrew Epstein

2

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

Exécuter dans la 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");

Essayez-le en ligne!

Ou en tant qu'extrait de pile:

Le code préinitialise le tableau pour accélérer l'ajout de 1 au tableau

Le code trouve toutes les séquences de distance de hamming et les traite comme une base numérique (entrée + 1), les utilise pour placer des 1 dans un tableau. En conséquence, cela génère un tableau avec les n 1s où n est le nombre de séquences uniques de distance de brouillage. Enfin, le nombre de 1 est compté à l'aide de array.reduce () pour additionner toutes les valeurs du tableau.

Ce code ne pourra pas s'exécuter pour une entrée de 10 car il atteint les limites de la mémoire

Ce code s'exécute en temps O (2 ^ 2n) car c'est le nombre d'éléments qu'il génère.


1
Sans surprise, essayer de créer un tableau d'éléments 26 * 10 ^ 9 ne fonctionne pas
fəˈnɛtɪk

n = 9prend 5 minutes et 30 secondes pour moi en utilisant node.js est donc trop lent.

@Lembik a n = 8initialement pris 24 secondes sur mon PC, mais j'ai pu optimiser le code, ce qui a n = 8pris 6 secondes. J'ai ensuite essayé n = 9et cela a pris 100 secondes.
Neil

@Neil Vous devez soumettre une réponse!

Ce serait formidable si votre code fonctionnait à partir de n = 1 et produisait le timing à chaque étape. De la question "Le timing est pour le temps de fonctionnement total, pas le temps juste pour ce n."
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.