计算汉明距离序列的数量


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

谁赢

得分最高的人获胜。如果两个或两个以上的人最终获得相同的分数,那么这是第一个获胜的答案。

示例答案

对于n18最佳答案2, 9, 48, 297, 2040, 15425, 125232, 1070553

语言和图书馆

您可以使用任何喜欢的语言和库。在可行的情况下,能够运行您的代码将是一件好事,因此,请尽可能提供有关如何在Linux中运行/编译代码的完整说明。

我的机器计时将在我的64位机器上运行。这是带有8GB RAM,AMD FX-8350八核处理器和Radeon HD 4250的标准ubuntu安装。这也意味着我需要能够运行您的代码。

领先的答案

  • 11C ++通过feersum。25秒
  • C ++ 11,作者:Andrew Epstein。176秒。
  • 10的Javascript尼尔。54秒
  • nimi 在Haskell中9。4分59秒。
  • 8的Javascript通过fənɛtɪk。10秒

..任何可用的免费 *语言?
Stewie Griffin

最快的代码?不是最快的算法?您知道,人们可以使用快速的解释器来处理语言,并在时间上产生重大差异,但是时间复杂度始终相同,因此这在一定程度上是公平的。
马修·罗


4
@SIGSEGV fastest-code通过代码级优化良好的算法为优化留出了更多空间。所以我的确faster-code比这更好faster-algorithm
达达

Answers:


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年笔记本电脑?该死的,那是一些过时的技术!
马修·

@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=10很困难,因为有超过20亿对和超过260亿个潜在序列。为了加快处理速度,我将计算分为121个bin。因为在按位补码下序列是不变的,所以我可以不失一般性地假设的中间位T为零。这意味着,我可以确定独立地从顶部的序列的第一个和最后一个元素n-1和底部n-1的位T。每个bin对应于不同的第一对和最后一个元素对;然后,我遍历与每个bin对应的所有可能的最高位和最低位,并计算序列的其余元素,最后对每个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添加了计时代码,并且还解决了bug n=1(不知道为什么会挂起)。
尼尔

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

@felipa我认为答案是sudo apt-get install libiomp-dev

如果您的代码从n = 1开始工作并在每个阶段输出时序,那就太好了。从问题“计时是针对总运行时间,而不是仅针对该n的时间。”

与其重新实现它,不如直接使用它__builtin_popcount
尼尔

@Lembik:我将在今天晚些时候进行更改。@Neil: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的速度更快

该代码查找所有汉明距离序列并将它们视为数字基(输入+1),并使用它们将1放置在数组中。结果,这将生成一个具有n 1的数组,其中n是唯一的汉明距离序列的数量。最后,使用array.reduce()对1的数量进行计数,以求和数组中的所有值。

由于达到内存限制,此代码将无法在输入10时运行

该代码在O(2 ^ 2n)的时间内运行,因为这就是它生成的元素数量。


1
毫不奇怪,尝试创建26 * 10 ^ 9元素数组不起作用
fəˈnɛtɪk

n = 9使用node.js对我来说需要5分30秒,所以太慢了。

@Lembik n = 8最初在我的PC上花费了24秒,但是我能够优化代码,因此n = 8花费了6秒。然后我尝试了n = 9,这花了100秒。
尼尔

@Neil您应该提交答案!

如果您的代码从n = 1开始工作并在每个阶段输出时序,那就太好了。从问题“计时是针对总运行时间,而不是仅针对该n的时间。”
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.