Đếm số lượng khoảng cách Hamming


9

Các khoảng cách Hamming giữa hai chuỗi chiều dài bằng nhau là số vị trí mà tại đó những biểu tượng tương ứng là khác nhau.

Hãy Plà một chuỗi nhị phân có độ dài nTlà một chuỗi nhị phân có độ dài 2n-1. Chúng ta có thể tính nkhoảng cách Hamming giữa Pvà mọi nchuỗi con Tthứ cấp theo thứ tự từ trái sang phải và đặt chúng vào một mảng (hoặc danh sách).

Ví dụ trình tự khoảng cách Hamming

Hãy để P = 101T = 01100. Trình tự khoảng cách Hamming bạn nhận được từ cặp này là 2,2,1.

Bài tập

Để tăng nbắt đầu từ n=1, hãy xem xét tất cả các cặp chuỗi nhị phân có thể có Pđộ dài nTđộ dài 2n-1. Có 2**(n+2n-1)những cặp như vậy và do đó có nhiều chuỗi khoảng cách Hamming. Tuy nhiên, nhiều trong số các chuỗi đó sẽ giống hệt nhau. Nhiệm vụ là tìm ra bao nhiêu là khác biệt cho mỗi n.

Mã của bạn sẽ xuất ra một số cho mỗi giá trị n.

Ghi bàn

Điểm của bạn là cao nhất nmà mã của bạn đạt được trên máy của tôi trong 5 phút. Thời gian là cho tổng thời gian chạy, không phải thời gian cho việc đó n.

Ai thắng

Người có số điểm cao nhất sẽ chiến thắng. Nếu hai hoặc nhiều người kết thúc với cùng số điểm thì đó là câu trả lời đầu tiên chiến thắng.

Ví dụ câu trả lời

Cho ntừ 1đến 8câu trả lời tối ưu là 2, 9, 48, 297, 2040, 15425, 125232, 1070553.

Ngôn ngữ và thư viện

Bạn có thể sử dụng bất kỳ ngôn ngữ và thư viện có sẵn mà bạn thích. Nếu khả thi, sẽ rất tốt để có thể chạy mã của bạn, vì vậy vui lòng bao gồm một lời giải thích đầy đủ về cách chạy / biên dịch mã của bạn trong Linux nếu có thể.

Máy của tôi Thời gian sẽ được chạy trên máy 64 bit của tôi. Đây là bản cài đặt Ubuntu tiêu chuẩn với RAM 8GB, Bộ xử lý tám nhân AMD FX-8350 và Radeon HD 4250. Điều này cũng có nghĩa là tôi cần có khả năng chạy mã của bạn.

Câu trả lời hàng đầu

  • 11 trong C ++ bởi frageum. 25 giây.
  • 11 trong C ++ của Andrew Epstein. 176 giây.
  • 10 trong Javascript của Neil. 54 giây.
  • 9 trong Haskell bởi nimi. 4 phút và 59 giây.
  • 8 trong Javascript bởi fəˈnɛtɪk. 10 giây.

.. bất kỳ ngôn ngữ * miễn phí có sẵn ?
Stewie Griffin

mã nhanh nhất ? thuật toán không nhanh nhất ? Bạn biết đấy, mọi người có thể đi với ngôn ngữ với một thông dịch viên nhanh chóng và tạo ra sự khác biệt đáng kể về thời gian, nhưng sự phức tạp về thời gian luôn giống nhau, vì vậy nó phần nào làm cho nó công bằng.
Matthew Roh


4
@SIGSEGV fastest-codeđể lại nhiều không gian hơn cho việc tối ưu hóa thông qua cả tối ưu hóa mức mã thuật toán tốt. Vì vậy, tôi nghĩ rằng đó faster-codelà tốt hơn faster-algorithm.
Dada

Câu trả lời:


3

C ++ 11 (nên đến 11 hoặc 12)

Tại thời điểm này là đơn luồng.

Để biên dịch:

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

Nhận được đến 11 trong chưa đầy 30 giây!

Trong trường hợp đó là điều đáng quan tâm: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, điểm 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]]

Biên dịch với -O3. Phải mất 6 phút35 trên phần cứng máy tính xách tay 6 tuổi của tôi để chạy n=9, vì vậy có thể dưới 5 phút trên phần cứng tham chiếu.

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

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

1
Laptop 6 năm? Chết tiệt, đó là một số công nghệ lỗi thời!
Matthew Roh

@SIGSEGV: có thể nó đã lỗi thời, nhưng bên cạnh việc đếm số lượng khoảng cách Hamming, nó hoạt động khá tốt.
nimi

4

JavaScript, điểm 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);

Giải thích: Tính toán n=10rất khó vì có hơn hai tỷ cặp và hơn 26 tỷ chuỗi tiềm năng. Để tăng tốc mọi thứ, tôi chia phép tính thành 121 thùng. Bởi vì các chuỗi là bất biến dưới sự bổ sung bitwise, tôi có thể giả sử mà không mất tính tổng quát mà bit giữa Tlà 0. Điều này có nghĩa là tôi có thể xác định các phần tử đầu tiên và cuối cùng của chuỗi một cách độc lập với các bit trên cùng n-1và dưới cùng n-1củaT. Mỗi thùng tương ứng với một cặp yếu tố đầu tiên và cuối cùng khác nhau; Sau đó tôi lặp qua tất cả các bộ bit trên và dưới có thể tương ứng với mỗi thùng và tính toán các phần tử còn lại của chuỗi, cuối cùng đếm các chuỗi duy nhất cho mỗi thùng. Sau đó, nó vẫn còn tổng cộng tất cả 121 thùng. Ban đầu mất 45 giờ, giờ đây nó đã hoàn thành trong vòng chưa đầy ba phút rưỡi trên AMD FX-8120 của tôi. Chỉnh sửa: Cảm ơn @ChristianSievers để tăng tốc 50%. Đầu ra đầy đủ:

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

Mã của bạn hiện không có đầu ra.
felipa

@felipa Không chắc ý bạn là gì. Đó là một hàm ẩn danh, vì vậy bạn gọi nó (có lẽ bằng cách gán nó cho một biến trước rồi gọi biến đó như thể nó là một hàm) và truyền nón dưới dạng tham số. (Xin lỗi vì sự lựa chọn không tốt của tên tham số ở đó.)
Neil

Câu hỏi yêu cầu mã in ra câu trả lời cho n lên đến giá trị cao nhất mà nó có thể nhận được trong 5 phút. "Mã của bạn sẽ xuất một số cho mỗi giá trị của n."
felipa

Sẽ thật tuyệt nếu mã của bạn hoạt động từ n = 1 và xuất ra thời gian ở mỗi giai đoạn. Từ câu hỏi "Thời gian dành cho tổng thời gian chạy, không phải thời gian chỉ dành cho n đó."

1
@Lembik Đã thêm mã thời gian và cũng đã khắc phục lỗi cho n=1(không biết tại sao nó bị treo).
Neil

4

C ++, điểm 10 11

Đây là bản dịch câu trả lời của @ Neil sang C ++, với một số cách song song đơn giản. n=9hoàn thành trong 0,4 giây, n=10trong 4,5 giây và n=11trong khoảng 1 phút trên Macbook Pro 2015 của tôi. Ngoài ra, cảm ơn @ChristianSievers. Do nhận xét của anh ấy về câu trả lời của @ Neil, tôi nhận thấy một số đối xứng bổ sung. Từ 121 thùng ban đầu (cho n=10), đến 66 thùng khi tính toán đảo ngược, tôi đã giảm xuống chỉ còn 21 thùng.

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

Sử dụng tập lệnh sau để thực thi mã:

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

Đầu ra như sau: (Định dạng là 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 mất 42 phút để tính toán trên một chuỗi, và cho kết quả là 7368225813.


Làm thế nào bạn có thể biên dịch cái này trong ubfox bằng clang?
felipa

@felipa Tôi nghĩ câu trả lời là sudo apt-get install libiomp-dev.

Sẽ thật tuyệt nếu mã của bạn hoạt động từ n = 1 và xuất ra thời gian ở mỗi giai đoạn. Từ câu hỏi "Thời gian dành cho tổng thời gian chạy, không phải thời gian chỉ dành cho n đó."

Thay vì thực hiện lại nó, bạn có thể chỉ cần sử dụng __builtin_popcount.
Neil

@Lembik: Tôi sẽ thực hiện các thay đổi sau ngày hôm nay. @Neil: Hàm popcnt chỉ được đánh giá tại thời điểm biên dịch và tôi không biết cách sử dụng __builtin_popcounttrong ngữ cảnh constexpr. Tôi có thể đi với việc thực hiện ngây thơ và nó sẽ không ảnh hưởng đến thời gian chạy.
Andrew Epstein

2

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

Chạy trong bảng điều khiển:

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

Hãy thử trực tuyến!

Hoặc như một đoạn trích Stack:

Mã xác định trước mảng để làm cho việc thêm 1s vào mảng nhanh hơn nhiều

Mã tìm thấy tất cả các chuỗi khoảng cách hamming và coi chúng là cơ sở số (đầu vào + 1), sử dụng chúng để đặt 1s trong một mảng. Kết quả là, điều này tạo ra một mảng với n 1s trong đó n là số chuỗi khoảng cách hamming duy nhất. Cuối cùng, số lượng 1 được tính bằng mảng.reduce () để tổng hợp tất cả các giá trị trong mảng.

Mã này sẽ không thể chạy cho đầu vào 10 vì nó đạt giới hạn bộ nhớ

Mã này chạy trong thời gian O (2 ^ 2n) vì đó là bao nhiêu phần tử mà nó tạo ra.


1
Không có gì đáng ngạc nhiên, cố gắng tạo một mảng phần tử 26 * 10 ^ 9 không hoạt động
fnɛtɪk

n = 9tôi mất 5 phút và 30 giây để sử dụng node.js nên quá chậm.

@Lembik n = 8ban đầu mất 24 giây trên PC của tôi, nhưng tôi đã có thể tối ưu hóa mã để n = 8mất 6 giây. Sau đó tôi đã thử n = 9và mất 100 giây.
Neil

@Neil Bạn nên gửi câu trả lời!

Sẽ thật tuyệt nếu mã của bạn hoạt động từ n = 1 và xuất ra thời gian ở mỗi giai đoạn. Từ câu hỏi "Thời gian dành cho tổng thời gian chạy, không phải thời gian chỉ dành cho n đó."
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.