นับจำนวนลำดับระยะทาง Hamming


9

ระยะ Hammingระหว่างสองสายความยาวเท่ากันคือจำนวนของตำแหน่งที่สัญลักษณ์ที่สอดคล้องกันจะแตกต่างกัน

ให้Pเป็นสตริงไบนารีของความยาวnและเป็นสตริงไบนารีของความยาวT 2n-1เราสามารถคำนวณnระยะทาง Hamming ระหว่างPและnสตริงย่อย -length ของTตามลำดับจากซ้ายไปขวาและใส่ลงในอาร์เรย์ (หรือรายการ)

ตัวอย่างลำดับระยะทาง Hamming

ขอและP = 101 T = 01100ลำดับของ Hamming 2,2,1ระยะทางที่คุณได้รับจากคู่นี้คือ

งาน

สำหรับการnเริ่มต้นที่เพิ่มขึ้นn=1ให้พิจารณาคู่สายอักขระไบนารีที่มีPความยาวnและTความยาวที่เป็น2n-1ไปได้ทั้งหมด มี2**(n+2n-1)คู่ดังกล่าวและด้วยเหตุนี้จึงมีหลายลำดับของระยะทาง Hamming อย่างไรก็ตามลำดับเหล่านั้นจำนวนมากจะเหมือนกัน nงานคือการหาวิธีการจำนวนมากที่แตกต่างกันสำหรับแต่ละ

nรหัสของคุณควรส่งออกจำนวนหนึ่งต่อมูลค่าของ

คะแนน

คะแนนของคุณคือnรหัสสูงสุดของคุณถึงบนเครื่องของฉันใน 5 นาที nระยะเวลาเป็นเวลาการทำงานรวมเวลาไม่เพียงแค่นั้น

ใครชนะ

ผู้ที่มีคะแนนสูงสุดจะเป็นผู้ชนะ หากคนสองคนหรือมากกว่านั้นจบลงด้วยคะแนนเดียวกันมันเป็นคำตอบแรกที่ชนะ

ตัวอย่างคำตอบ

สำหรับการnจาก1ไปคำตอบที่ดีที่สุดที่มี82, 9, 48, 297, 2040, 15425, 125232, 1070553

ภาษาและห้องสมุด

คุณสามารถใช้ภาษาและไลบรารี่ที่คุณต้องการได้ หากเป็นไปได้จะเป็นการดีที่จะสามารถเรียกใช้รหัสของคุณได้ดังนั้นโปรดระบุคำอธิบายโดยละเอียดเกี่ยวกับวิธีเรียกใช้ / รวบรวมรหัสของคุณใน Linux หากเป็นไปได้

My Machineการจับเวลาจะทำงานบนเครื่อง 64 บิตของฉัน นี่คือการติดตั้ง Ubuntu แบบมาตรฐานที่มี 8GB RAM, AMD FX-8350 Eight-Core Processor และ Radeon HD 4250 นี่หมายความว่าฉันต้องสามารถเรียกใช้รหัสของคุณได้

คำตอบชั้นนำ

  • 11ในC ++โดย feersum 25 วินาที
  • 11ในC ++โดย Andrew Epstein 176 วินาที
  • 10ในJavascriptโดย Neil 54 วินาที
  • 9ในHaskellโดย nimi 4 นาทีและ 59 วินาที
  • 8ในJavascriptโดย fəˈnɛtɪk 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;
}

รับเป็น 11 ในเวลาน้อยกว่า 30 วินาที!

ในกรณีที่เป็นที่สนใจ: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 นาที 35 วินาทีบนฮาร์ดแวร์แล็ปท็อปอายุ 6 ปีของฉันเพื่อให้ทำงานได้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: อาจจะล้าสมัยไปแล้ว แต่นอกจากการนับจำนวน Hamming distance sequences แล้วมันยังทำงานได้ค่อนข้างดี
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=10เป็นเรื่องยากเพราะมีมากกว่าสองพันล้านคู่และมากกว่า 26 พันล้านลำดับที่อาจเกิดขึ้น เพื่อเร่งสิ่งต่าง ๆ ฉันแบ่งการคำนวณออกเป็น 121 ถังขยะ เนื่องจากซีเควนซ์นั้นไม่แปรเปลี่ยนภายใต้ส่วนประกอบ bitwise ฉันสามารถสรุปได้โดยไม่สูญเสียความสามารถทั่วไปที่บิตกลางTเป็นศูนย์ ซึ่งหมายความว่าฉันสามารถกำหนดองค์ประกอบแรกและสุดท้ายของลำดับได้อย่างอิสระจากบิตบนn-1และล่างn-1ของT. แต่ละ bin สอดคล้องกับคู่ที่หนึ่งขององค์ประกอบแรกและสุดท้าย จากนั้นฉันวนลูปผ่านชุดของบิตบนและล่างที่เป็นไปได้ทั้งหมดที่สอดคล้องกับแต่ละถังขยะและคำนวณองค์ประกอบที่เหลือของลำดับในที่สุดก็นับลำดับที่ไม่ซ้ำกันสำหรับแต่ละถัง จากนั้นยังคงมีจำนวนทั้งหมด 121 ถังขยะ เดิมใช้เวลา 45 ชั่วโมงตอนนี้เสร็จในเวลาไม่ถึงสามนาทีครึ่งใน AMD FX-8120 ของฉัน แก้ไข: ขอบคุณ @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

คำถามถามหารหัสที่พิมพ์คำตอบสำหรับ n สูงสุดถึงค่าสูงสุดที่จะได้รับใน 5 นาที รหัสของคุณควรส่งออกหนึ่งหมายเลขต่อค่า n
felipa

มันจะดีถ้ารหัสของคุณทำงานจาก n = 1 และแสดงเวลาในแต่ละขั้นตอน จากคำถามที่ว่า "เวลาเป็นเวลารวมของการวิ่งไม่ใช่เวลาสำหรับการวิ่ง n"

1
@Lembik เพิ่มรหัสเวลาและแก้ไขข้อผิดพลาดด้วยn=1(ไม่รู้ว่าทำไมแฮงค์)
Neil

4

C ++, คะแนน10 11

นี่คือการแปลคำตอบของ @ Neil เป็น C ++ พร้อมการต่อขนานอย่างง่าย n=9เสร็จใน 0.4 วินาทีn=10ใน 4.5 วินาทีและn=11ในประมาณ 1 นาทีสำหรับ Macbook Pro ของฉันในปี 2015 นอกจากนี้ขอขอบคุณ @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


คุณจะรวบรวมสิ่งนี้ในอูบุนตูโดยใช้เสียงดังกราวได้อย่างไร?
felipa

@felipa sudo apt-get install libiomp-devผมคิดว่าคำตอบคือ

มันจะดีถ้ารหัสของคุณทำงานจาก n = 1 และแสดงเวลาในแต่ละขั้นตอน จากคำถามที่ว่า "เวลาเป็นเวลารวมของการวิ่งไม่ใช่เวลาสำหรับการวิ่ง n"

แทนที่จะนำมาใช้ซ้ำคุณอาจจะ__builtin_popcountใช้ได้
Neil

@Lembik: ฉันจะทำการเปลี่ยนแปลงในวันนี้ @Neil: ฟังก์ชั่น popcnt จะได้รับการประเมินในเวลารวบรวมเท่านั้นและฉันไม่รู้วิธีใช้__builtin_popcountในบริบทของ constexpr ฉันสามารถไปกับการใช้งานไร้เดียงสาและมันจะไม่ส่งผลกระทบต่อเวลาทำงาน
Andrew Epstein

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

ลองออนไลน์!

หรือเป็นตัวอย่างสแต็ก:

รหัสจะกำหนดค่าเริ่มต้นให้อาร์เรย์เพื่อเพิ่ม 1s ลงในอาร์เรย์เร็วขึ้นมาก

รหัสจะค้นหาลำดับการเรียงตัวระยะห่างทั้งหมดและถือเป็นตัวเลขฐาน (อินพุต + 1) ใช้เพื่อวาง 1s ในอาร์เรย์ ด้วยเหตุนี้สิ่งนี้จึงสร้างอาร์เรย์ที่มี n 1s โดยที่ n คือจำนวนของลำดับระยะทางที่เป็นเอกลักษณ์ ในที่สุดจำนวน 1s จะถูกนับโดยใช้ array.reduce () เพื่อรวมค่าทั้งหมดในอาร์เรย์

รหัสนี้จะไม่สามารถเรียกใช้งานได้สำหรับการป้อนข้อมูล 10 เนื่องจากเป็นขีด จำกัด หน่วยความจำ

รหัสนี้ทำงานในเวลา O (2 ^ 2n) เพราะนั่นคือองค์ประกอบที่มันสร้างขึ้น


1
แปลกใจพยายามที่จะสร้างองค์ประกอบอาร์เรย์ 26 * 10 ^ 9 ไม่ทำงาน
fənɛtɪk

n = 9ใช้เวลา 5 นาที 30 วินาทีสำหรับฉันที่ใช้ node.js ช้าเกินไป

@ Lembik n = 8เดิมใช้เวลา 24 วินาทีบนพีซีของฉัน แต่ฉันสามารถปรับรหัสให้เหมาะสมเพื่อที่n = 8ใช้เวลา 6 วินาที ฉันลองแล้วn = 9และใช้เวลา 100 วินาที
Neil

@Neil คุณควรส่งคำตอบ!

มันจะดีถ้ารหัสของคุณทำงานจาก n = 1 และแสดงเวลาในแต่ละขั้นตอน จากคำถามที่ว่า "เวลาเป็นเวลารวมของการวิ่งไม่ใช่เวลาสำหรับการวิ่ง n"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.