ハミング距離シーケンスの数を数える


9

等しい長さの2つの文字列間のハミング距離は、対応するシンボルが異なる位置の数です。

してみましょうP長さのバイナリ文字列にするnT長さのバイナリ文字列です2n-1。の左から右へのすべての-lengthサブストリングn間のハミング距離を計算し、それらを配列(またはリスト)に入れることができます。PnT

ハミング距離シーケンスの例

ましょうP = 101T = 01100。このペアから取得するハミング距離のシーケンスは2,2,1です。

仕事

増加のためnで始まるn=1、バイナリ文字列のすべての可能なペアを検討P長さのnT長さのを2n-1。その2**(n+2n-1)ようなペアが存在するため、ハミング距離のシーケンスが多くなります。ただし、これらのシーケンスの多くは同一です。タスクは、どれがどれだけ異なるかを見つけることnです。

コードは、の値ごとに1つの数値を出力する必要がありますn

スコア

あなたのスコアは、nあなたのコードが私のマシンで5分間に達成した最高点です。タイミングは、そのためだけの時間ではなく、合計実行時間に対するものnです。

誰が勝ちますか

最もスコアの高い人が勝ちます。2人以上の人が同じスコアで終わる場合、それが勝つ最初の答えです。

回答例

ためにnから18最適な答えがあります2, 9, 48, 297, 2040, 15425, 125232, 1070553

言語とライブラリ

利用可能な任意の言語とライブラリを使用できます。可能であれば、コードを実行できるようにするとよいでしょう。そのため、可能であれば、Linuxでコードを実行/コンパイルする方法の完全な説明を含めてください。

マイマシンタイミングは64ビットマシンで実行されます。これは、8GB RAM、AMD FX-8350 Eight-Core Processor、Radeon HD 4250を備えた標準のUbuntuインストールです。これは、コードを実行できる必要があることも意味します。

主要な回答

  • 11におけるC ++ feersumによる。25秒。
  • 11C ++アンドリュー・エプスタインによります。176秒。
  • Neil によるJavascriptの10。54秒。
  • 9Haskellの nimiによります。4分59秒。
  • 8Javascriptを fənɛtɪkによります。10秒。

..利用可能な無料 *言語はありますか?
Stewie Griffin

最速コード最速アルゴリズムではありませんか?ご存知のように、人々は非常に高速なインタープリターを使用して言語を使い、時間に大きな違いをもたらすことができますが、時間の複雑さは常に同じであるため、ある程度公平になります。
Matthew Roh


4
@SIGSEGV fastest-codeは、コードレベルの最適化優れたアルゴリズムの両方により、最適化のためのより多くのスペースを残します。だから私はそれfaster-codeがより良いと思いますfaster-algorithm
ダダ

回答:


3

C ++ 11(11または12になるはずです)

現時点では、これはシングルスレッドです。

コンパイルする:

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秒未満で11になろう!

興味がある場合: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、スコア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]]

でコンパイルし-O3ます。私の6年前のラップトップハードウェアで最大6分35秒かかるn=9ため、参照ハードウェアでは5分未満になる可能性があります。

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

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

1
6年ラップトップ?くそー、それはいくつかの時代遅れの技術です!
Matthew Roh

@SIGSEGV:古くなっているかもしれませんが、ハミング距離シーケンスの数を数えるだけでなく、うまく機能します。
nimi

4

JavaScript、スコア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);

説明:n=1020億を超えるペアと260億を超える潜在的なシーケンスがあるため、計算は困難です。処理速度を上げるために、計算を121個のビンに分割しました。シーケンスはビット単位の補数の下で不変なので、一般性を失うことなく、中間ビットTがゼロであると仮定できます。私は独立して上からシーケンスの最初と最後の要素を決定することができることは、この手段n-1と底n-1のビットT。各ビンは、最初と最後の要素の異なるペアに対応しています。次に、各ビンに対応するすべての可能なトップビットとボトムビットのセットをループ処理して、シーケンスの残りの要素を計算し、最後に各ビンの一意のシーケンスをカウントします。その後、121ビンすべてを合計します。元々は45時間かかっていましたが、AMD FX-8120では3分半弱で完了しました。編集:50%高速化してくれた@ChristianSieversに感謝します。完全な出力:

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

現在、コードは出力を提供していません。
felipa

@felipaどういう意味かわかりません。これは無名関数なので、呼び出して(おそらく、まず変数に割り当ててから、関数のように変数を呼び出して)、それnをパラメーターとして渡します。(そこではパラメータ名の選択が悪いので申し訳ありません。)
Neil

この質問では、nの回答を5分間で到達できる最高値まで出力するコードを求めています。「コードはnの値ごとに1つの数値を出力する必要があります。」
felipa

コードがn = 1から作成され、各ステージでタイミングを出力すると、すばらしいでしょう。質問から「タイミングは、そのnだけの時間ではなく、総実行時間に対するものです。」

1
@Lembikタイミングコードを追加し、バグを回避しましたn=1(ハングする理由がわかりません)。
Neil

4

C ++、スコア10 11

これは、@ Neilの回答をC ++に翻訳したもので、いくつかの単純な並列化が含まれています。私の2015 Macbook Pro n=9では、0.4秒、n=104.5秒、n=11約1分で完了します。また、@ ChristianSieversに感謝します。@Neilの回答に対する彼のコメントのため、追加の対称性に気づきました。元の121バケット(の場合n=10)から反転を考慮した場合の66バケットまで、たったの21バケットになりました。

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

次のスクリプトを使用してコードを実行します。

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

出力は次のとおりです:(形式は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 シングルスレッドでの計算に42分かかり、結果は7368225813でした。


clangを使用してubuntuでこれをどのようにコンパイルしますか?
felipa 2017年

@felipa答えはだと思いますsudo apt-get install libiomp-dev

コードがn = 1から作成され、各ステージでタイミングを出力すると、すばらしいでしょう。質問から「タイミングは、そのnだけの時間ではなく、総実行時間に対するものです。」

それを再実装するのではなく、おそらく単に使用することができます__builtin_popcount
Neil

@Lembik:今日は後で変更します。@ニール:popcnt関数はコンパイル時にのみ評価され__builtin_popcount、constexprコンテキストでの使用方法がわかりません。私はナイーブな実装で行くことができ、それはランタイムに影響を与えません。
アンドリューエプスタイン

2

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

コンソールで実行:

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

オンラインでお試しください!

またはスタックスニペットとして:

コードは配列を事前初期化して、配列に1をより速く追加します

コードは、すべてのハミング距離シーケンスを検出し、それらを基数(input + 1)として扱い、それらを使用して配列に1を配置します。結果として、これはnが1の配列を生成します。ここで、nは一意のハミング距離シーケンスの数です。最後に、1の数は、array.reduce()を使用してカウントされ、配列内のすべての値が合計されます。

このコードはメモリ制限に達したため、10の入力に対して実行できません。

このコードはO(2 ^ 2n)時間で実行されます。これは、それが生成する要素の数だからです。


1
当然のことながら、26 * 10 ^ 9の要素配列を作成しようとしても機能しません
fəˈnɛtɪk

n = 9node.jsを使用して5分30秒かかるので、遅すぎます。

@Lembikはn = 8もともと私のPCで24秒かかっていましたが、コードを最適化してn = 86秒かかるようにできました。それから私は試しましたn = 9、そしてそれは100秒かかりました。
Neil

@ニールあなたは答えを提出する必要があります!

コードがn = 1から作成され、各ステージでタイミングを出力すると、すばらしいでしょう。質問から「タイミングは、そのnだけの時間ではなく、総実行時間に対するものです。」
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.