Hamming mesafe dizilerinin sayısını sayın


9

Hamming mesafesi eşit uzunlukta iki sırası arasındaki karşılık gelen simgeler farklı olan pozisyonların sayısıdır.

Izin Pvermek bir ikili dize uzunluğu nve Tbir ikili dize uzunluğu 2n-1. Soldan sağa sırayla nHamming mesafelerini Pve her nuzunluktaki alt dizelerini hesaplayabilir Tve bunları bir diziye (veya listeye) koyabiliriz.

Örnek Hamming mesafe sırası

Bırakın P = 101ve T = 01100. Bu çiftten aldığınız Hamming mesafelerinin sırası 2,2,1.

Görev

Artan İçin nbaşlayan n=1ikili dizeleri tüm olası çiftleri dikkate Puzunluğu nve Tuzunluk 2n-1. Öyle 2**(n+2n-1)çiftler var ve bu nedenle Hamming mesafelerinin birçok dizisi var. Bununla birlikte, bu dizilerin çoğu aynı olacaktır. Görev, her biri için kaç tanesinin farklı olduğunu bulmaktır n.

Kodunuzun değeri başına bir sayı çıkmalıdır n.

Puan

Puanınız nkodunuzun 5 dakika içinde makinemde ulaştığı en yüksek puan . Zamanlama, sadece bunun için değil toplam çalışma süresi içindir n.

Kim kazanır

En yüksek puanı alan kişi kazanır. İki veya daha fazla kişi aynı puanı alırsa, kazanan ilk cevaptır.

Örnek cevaplar

İçin ngelen 1için 8cevaplar optimum 2, 9, 48, 297, 2040, 15425, 125232, 1070553.

Diller ve kütüphaneler

İstediğiniz dil ve kitaplıkları kullanabilirsiniz. Mümkün olduğunda, kodunuzu çalıştırabilmeniz iyi olur, bu nedenle lütfen mümkünse kodunuzu Linux'ta nasıl çalıştıracağınız / derleyeceğinize ilişkin tam bir açıklama ekleyin.

Benim Makine zamanlamaları benim 64 bit makinede işletilecek. Bu, 8GB RAM, AMD FX-8350 Sekiz Çekirdekli İşlemci ve Radeon HD 4250 ile standart bir ubuntu yüklemesidir. Bu aynı zamanda kodunuzu çalıştırabilmem gerektiği anlamına gelir.

Öncü cevaplar

  • 11 de C ++ feersum ile. 25 saniye.
  • 11 yılında C ++ Andrew Epstein tarafından. 176 saniye.
  • 10 yılında Javascript Neil tarafından. 54 saniye.
  • 9 içinde Haskell'e niml ile. 4 dakika 59 saniye.
  • 8 yılında Javascript fənɛtɪk tarafından. 10 saniye.

.. herhangi bir ücretsiz * dil?
Stewie Griffin

en hızlı kod ? en hızlı algoritma değil mi? Biliyorsunuz, insanlar hızlı bir tercümanla dil ile gidip zaman içinde önemli bir fark yaratabilirler, ancak zaman karmaşıklığı her zaman aynıdır, bu yüzden biraz adil kılar.
Matthew Roh


4
@SIGSEGV fastest-codeyaprakları kod seviyesi optimizasyonlar hem yoluyla optimizasyonlar için daha fazla alan ve iyi bir algoritma. Bence bunun faster-codedaha iyi olduğunu düşünüyorum faster-algorithm.
Dada

Yanıtlar:


3

C ++ 11 (11 veya 12'ye ulaşmalıdır)

Şu anda bu tek iş parçacıklı.

Derlemek için:

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

30 saniyeden daha az sürede 11'e çıkın!

İlgilenmesi halinde: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, skor 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]]

İle derleyin -O3. 6 yaşındaki dizüstü bilgisayar donanımımın çalışması için 6dk35s alır n=9, bu yüzden referans donanımda 5 dakikanın altında olabilir.

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

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

1
6 yıllık dizüstü bilgisayar? Kahretsin, bu modası geçmiş bir teknoloji!
Matthew Roh

@SIGSEGV: belki modası geçmiş, ancak Hamming mesafe dizilerinin sayısını saymanın yanı sıra işini de oldukça iyi yapıyor.
nimi

4

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

Açıklama: n=10İki milyardan fazla çift ve 26 milyardan fazla potansiyel dizi olduğu için hesaplamak zordur. İşleri hızlandırmak için hesaplamayı 121 kutuya ayırdım. Diziler bitsel tamamlayıcı altında değişmez olduğu için, genel bitim olmadan orta bitin Tsıfır olduğunu varsayabilirim . Bu, dizinin ilk ve son elemanlarını, üst n-1ve alt n-1bitlerinden bağımsız olarak belirleyebileceğim anlamına gelir .T. Her bölme, farklı bir birinci ve son eleman çiftine karşılık gelir; Daha sonra, her bir bölmeye karşılık gelen tüm olası üst ve alt bit kümeleri arasında döngü yapıp, dizinin kalan öğelerini hesaplayarak, sonunda her bölme için benzersiz dizileri sayarım. Daha sonra toplam 121 kutunun tamamı kalır. Başlangıçta 45 saat süren bu işlem, AMD FX-8120'mde üç buçuk dakikadan kısa bir sürede tamamlandı. Düzenleme:% 50 hızlanma için @ChristianSievers'a teşekkürler. Tam çıktı:

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

Kodunuz şu anda çıktı vermiyor.
felipa

@felipa Ne demek istediğinizden emin değilim. Anonim bir işlevdir, bu yüzden (belki de önce bir değişkene atayarak ve sonra değişkeni bir işlevmiş gibi çağırarak) çağırır ve parametre olarak iletirsiniz n. (Parametre adının kötü seçimi için özür dileriz.)
Neil

Soru, n için 5 dakika içinde alabileceği en yüksek değere kadar cevap yazdıran kod ister. "Kodunuz her n değeri için bir sayı vermelidir."
felipa

Kodunuzun n = 1'den çalışıp her aşamada zamanlaması vermesi harika olurdu. "Zamanlama, toplam çalışma süresi içindir, sadece o zaman için değil."

1
@Lembik Zamanlama kodu eklendi ve ayrıca hata için çalıştı n=1(neden asılı olduğunu bilmiyorum).
Neil

4

C ++, puan 10 11

Bu, @ Neil'in cevabının basit bir paralelleştirmeyle C ++ 'a çevirisi. 2015 Macbook Pro'mda n=90,4 saniye, n=104,5 saniye ve n=11yaklaşık 1 dakika içinde tamamlanıyor . Ayrıca, @ChristianSievers sayesinde. @ Neil'in cevabı hakkındaki yorumları nedeniyle, bazı ek simetriler fark ettim. Orijinal 121 kovadan (for n=10), tersine çevirmeyi hesaplarken 66 kovaya kadar, sadece 21 kovaya düştüm.

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

Kodu yürütmek için aşağıdaki komut dosyasını kullanın:

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

Çıktı aşağıdaki gibidir: (Format 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 tek bir iş parçacığında hesaplanması 42 dakika sürdü ve 7368225813 sonucunu verdi.


Ubuntu'da clang kullanarak bunu nasıl derlersiniz?
felipa

@felipa Bence cevap sudo apt-get install libiomp-dev.

Kodunuzun n = 1'den çalışıp her aşamada zamanlaması vermesi harika olurdu. "Zamanlama, toplam çalışma süresi içindir, sadece o zaman için değil."

Yeniden uygulamak yerine, muhtemelen sadece kullanabilirsiniz __builtin_popcount.
Neil

@Lembik: Değişiklikleri bugün yapacağım. @Neil: Popcnt işlevi yalnızca derleme zamanında değerlendirilir ve __builtin_popcountbir bağlam bağlamında nasıl kullanılacağını bilmiyorum . Saf bir uygulama ile gidebilir ve çalışma süresini etkilemez.
Andrew Epstein

2

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

Konsolda çalıştır:

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

Çevrimiçi deneyin!

Veya Yığın Snippet'i olarak:

Kod, diziye 1'ler eklemeyi çok daha hızlı hale getirmek için diziyi önceden başlatır

Kod, tüm çarpma mesafesi dizilerini bulur ve sayı tabanı (giriş + 1) olarak ele alır, bunları bir diziye 1'ler yerleştirmek için kullanır. Sonuç olarak, bu, n 'ler ile bir dizi oluşturur, burada n, benzersiz çekiçleme mesafesi dizilerinin sayısıdır. Son olarak, dizideki tüm değerleri toplamak için 1s sayısı array.reduce () kullanılarak sayılır.

Bu kod, bellek sınırlarına ulaştığı için 10 girişi için çalıştırılamayacak

Bu kod O (2 ^ 2n) zamanında çalışır, çünkü kaç eleman üretir.


1
Şaşırtıcı olmayan bir şekilde, 26 * 10 ^ 9 öğe dizisi oluşturmaya çalışmak işe yaramıyor
fəˈnɛtɪk

n = 9node.js kullanarak benim için 5 dakika 30 saniye sürer, bu yüzden çok yavaş.

@Lembik n = 8başlangıçta bilgisayarımda 24 saniye sürdü, ancak n = 86 saniye süren kodu optimize edebildim . Sonra denedim n = 9ve bu 100 saniye sürdü.
Neil

@Neil Bir cevap göndermelisin!

Kodunuzun n = 1'den çalışıp her aşamada zamanlaması vermesi harika olurdu. "Zamanlama, toplam çalışma süresi içindir, sadece o zaman için değil."
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.