해밍 거리 시퀀스 수 계산


9

길이가 같은 두 줄 사이 의 해밍 거리는 해당 기호가 다른 위치 수입니다.

하자 P길이의 이진 문자열 nT길이의 이진 문자열 2n-1. 왼쪽에서 오른쪽으로 순서대로 모든 길이의 하위 문자열 n사이 의 해밍 거리를 계산하여 배열 (또는 목록)에 넣을 수 있습니다.PnT

해밍 거리 시퀀스 예

하자 P = 101T = 01100. 이 쌍에서 얻는 해밍 거리의 순서는 2,2,1입니다.

직무

증가 n에서 시작 n=1, 이진 문자열의 모든 가능한 쌍을 고려 P길이 nT길이를 2n-1. 있다 2**(n+2n-1)이러한 쌍 따라서 해밍 거리의 많은 시퀀스. 그러나 이러한 많은 시퀀스는 동일합니다. 과제는 각각에 대해 몇 개가 다른지를 찾는 것입니다 n.

코드는의 값당 하나의 숫자를 출력해야합니다 n.

점수

귀하의 점수는 n귀하의 코드가 5 분 안에 내 컴퓨터에서 가장 높은 점수입니다 . 타이밍은 총 실행 시간이 아닌 시간입니다 n.

누가 이겼어

가장 높은 점수를받은 사람이 승리합니다. 두 명 이상의 사람들이 같은 점수를 얻는다면 가장 먼저 정답입니다.

답변 예

위해 n에서 18대한 답변이 최적 2, 9, 48, 297, 2040, 15425, 125232, 1070553.

언어와 라이브러리

원하는 언어와 라이브러리를 사용할 수 있습니다. 가능한 경우 코드를 실행할 수 있으면 좋을 것이므로 가능한 경우 Linux에서 코드를 실행 / 컴파일하는 방법에 대한 자세한 설명을 포함하십시오.

내 컴퓨터 타이밍이 64 비트 컴퓨터에서 실행됩니다. 이것은 8GB RAM, AMD FX-8350 8 코어 프로세서 및 Radeon HD 4250이 포함 된 표준 우분투 설치입니다. 또한 코드를 실행할 수 있어야합니다.

선행 답변

  • feersum에 의한 C ++ 에서 11 . 25 초
  • Andrew Epstein의 C ++ 에서 11 개 176 초
  • Neil의 Javascript 에서 10 . 54 초
  • 하스켈 에서 9 번 . 4 분 59 초
  • fəˈnɛtɪk에 의해 Javascript 에서 8 . 10 초.

.. 사용 가능한 무료 언어가 있습니까?
Stewie Griffin

가장 빠른 코드 ? 없는 빠른 알고리즘 ? 사람들은 대담한 빠른 통역사로 언어를 사용하여 시간을 크게 변화시킬 수 있지만 시간 복잡성은 항상 동일하므로 다소 공정합니다.
Matthew Roh


4
@SIGSEGV fastest-code는 코드 수준 최적화 우수한 알고리즘을 통해 최적화를위한 더 많은 공간을 제공 합니다. 그래서 나는 그것이 faster-code보다 낫다고 생각합니다 faster-algorithm.
Dada

답변:


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

하스켈, 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

자바 스크립트, 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가 0 이라고 가정 할 수 있습니다 . 이것은 시퀀스의 첫 번째 요소와 마지막 요소를 최상위 비트 n-1와 하위 n-1비트 와 독립적으로 결정할 수 있음을 의미합니다.T. 각 구간은 서로 다른 첫 번째 및 마지막 요소 쌍에 해당합니다. 그런 다음 각 빈에 해당하는 가능한 모든 상위 및 하위 비트 세트를 반복하고 시퀀스의 나머지 요소를 계산하여 마지막으로 각 빈의 고유 시퀀스를 계산합니다. 그런 다음 총 121 개의 빈을 모두 유지합니다. 원래 45 시간이 걸렸는데, 이제 AMD FX-8120에서 3 분 30 초 안에 완료되었습니다. 편집 : @ChristianSievers 덕분에 50 % 속도가 향상되었습니다. 전체 출력 :

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

이 질문은 5 분 안에 얻을 수있는 가장 높은 값까지 n에 대한 답변을 인쇄하는 코드를 묻습니다. "코드는 n 값당 하나의 숫자를 출력해야합니다."
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의 답변에 대한 그의 의견으로 인해 추가 대칭이 있음을 알았습니다. n=10반전을 고려할 때 원래 121 개의 버킷 (의 경우 )에서 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을 사용하여 우분투에서 어떻게 컴파일합니까?
felipa

@ felipa 나는 대답이 있다고 생각합니다 sudo apt-get install libiomp-dev.

코드가 n = 1에서 작동하여 각 단계에서 타이밍을 출력하면 좋을 것입니다. "타이밍은 총 n 시간이 아니라 총 실행 시간에 대한 것입니다."

다시 구현하는 대신 아마도을 사용할 수 있습니다 __builtin_popcount.
Neil

@Lembik : 오늘 나중에 변경하겠습니다. @ Neil : popcnt 함수는 컴파일 타임에만 평가되며 __builtin_popcountconstexpr 컨텍스트에서 사용하는 방법을 모르겠습니다 . 순진한 구현으로 갈 수 있으며 런타임에는 영향을 미치지 않습니다.
앤드류 엡스타인

2

자바 스크립트 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을 더 빠르게 추가하기 위해 배열을 사전 초기화합니다.

이 코드는 모든 해밍 거리 시퀀스를 찾고이를 숫자 기준 (입력 +1)으로 취급하고이를 사용하여 배열에 1을 배치합니다. 결과적으로 이것은 n 1을 갖는 배열을 생성합니다. 여기서 n은 고유 해밍 거리 시퀀스의 수입니다. 마지막으로 array.reduce ()를 사용하여 1의 수를 계산하여 배열의 모든 값을 합합니다.

이 코드는 메모리 한계에 도달하므로 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

@Neil 답변을 제출해야합니다!

코드가 n = 1에서 작동하여 각 단계에서 타이밍을 출력하면 좋을 것입니다. "타이밍은 총 n 시간이 아니라 총 실행 시간에 대한 것입니다."
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.