你能走多高?(编码+算法挑战)


34

现在,每个人都开发了自己的(通常是惊人的)低级编码专业知识,以了解Python到底有多慢?(或者您的语言有多快?)以及Python 到底有多慢(第二部分)?现在是挑战的时候了,这也将扩展您改进算法的能力。

以下代码计算长度为9的列表。列表中的位置i用于i计算计算F和之间的内积时找到至少连续零的次数S。为了精确地做到这一点,它遍历了所有可能F的length n列表S和length 列表n+m-1

#!/usr/bin/python
import itertools
import operator

n=8
m=n+1
leadingzerocounts = [0]*m
for S in itertools.product([-1,1], repeat = n+m-1):
    for F in itertools.product([-1,1], repeat = n):
        i = 0
        while (i<m and sum(map(operator.mul, F, S[i:i+n])) == 0):
            leadingzerocounts[i] +=1
            i+=1
print leadingzerocounts

输出是

[4587520、1254400、347648、95488、27264、9536、4512、2128、1064]

如果使用此代码将n增加到10、12、14、16、18、20,则它很快变得太慢。

规则

  • 挑战在于给出尽可能大的n的正确输出。仅n的偶数是相关的。
  • 如果平局,胜出将是我机器上最快的代码,即最大的n。
  • 我保留不测试超过10分钟的代码的权利。
  • 您可以按照自己喜欢的任何方式更改算法,只要它能提供正确的输出即可。实际上,您将必须更改算法才能在获胜方面取得不错的进展。
  • 提出问题后的一周内将授予获奖者。
  • 赏金将在到期时被授予,而获胜者将在不久后获得赏金。

我的机器时间将在我的机器上运行。这是在AMD FX-8350八核处理器上的标准ubuntu安装。这也意味着我需要能够运行您的代码。因此,请仅使用易于使用的免费软件,并请提供有关如何编译和运行代码的完整说明。

状态

  • Ç。@Fors在49秒内n = 12
  • Java。n = 16 in 3:07 @PeterTaylor
  • C ++。n = 16在2:21中@ilmale
  • Rpython的。n = 22在3:11中@primo
  • Java。n = 22在6:56由@PeterTaylor
  • 尼姆罗德。@ReimerBehrends在9:28秒内n = 24

冠军是Reimer Behrends,他在Nimrod参赛!

作为检查,n = 22的输出应为[12410090985684467712, 2087229562810269696, 351473149499408384, 59178309967151104, 9975110458933248, 1682628717576192, 284866824372224, 48558946385920, 8416739196928, 1518499004416, 301448822784, 71620493312, 22100246528, 8676573184, 3897278464, 1860960256, 911646720, 451520512, 224785920, 112198656, 56062720, 28031360, 14015680]


竞赛已经结束,但是...我将为每次提交的n 增加2(在计算机上10分钟内)提供200点,直到我用完为止。这项优惠是永远开放的。


1
“我保留不测试超过几分钟的代码的权利。” >您应在机器上指定准确的时间,否则该问题将缺乏客观的获胜标准。
pastebin.com斜线0mr8spkT

14
喜欢这些“提高速度”的挑战。如果您要用这些产品来构建商业产品,那么您将对快速产品感到不满,而且您也是一个邪恶的天才
Rainbolt 2014年

1
也许更多翔实的标题会引起人们的注意?
TheDoctor 2014年

8
如果我们继续进行这种挑战,我认为我们至少应该尝试解决一个不同的问题以使其有趣(而不是对具有额外规范的同一问题进行变形)。
grovesNL

2
@Claudiu,他的CPU有8个物理内核,但是取/解码和FPU单元在内核之间共享。因此,当瓶颈在这些区域之一上时,它的行为就更像是四核。滥用整数逻辑并避免使用大代码,它更像是8核。
Stefan 2014年

Answers:


20

猎人(N = 22)

import math, locks

const
  N = 20
  M = N + 1
  FSize = (1 shl N)
  FMax = FSize - 1
  SStep = 1 shl (N-1)
  numThreads = 16

type
  ZeroCounter = array[0..M-1, int]
  ComputeThread = TThread[int]

var
  leadingZeros: ZeroCounter
  lock: TLock
  innerProductTable: array[0..FMax, int8]

proc initInnerProductTable =
  for i in 0..FMax:
    innerProductTable[i] = int8(countBits32(int32(i)) - N div 2)

initInnerProductTable()

proc zeroInnerProduct(i: int): bool =
  innerProductTable[i] == 0

proc search2(lz: var ZeroCounter, s, f, i: int) =
  if zeroInnerProduct(s xor f) and i < M:
    lz[i] += 1 shl (M - i - 1)
    search2(lz, (s shr 1) + 0, f, i+1)
    search2(lz, (s shr 1) + SStep, f, i+1)

when defined(gcc):
  const
    unrollDepth = 1
else:
  const
    unrollDepth = 4

template search(lz: var ZeroCounter, s, f, i: int) =
  when i < unrollDepth:
    if zeroInnerProduct(s xor f) and i < M:
      lz[i] += 1 shl (M - i - 1)
      search(lz, (s shr 1) + 0, f, i+1)
      search(lz, (s shr 1) + SStep, f, i+1)
  else:
    search2(lz, s, f, i)

proc worker(base: int) {.thread.} =
  var lz: ZeroCounter
  for f in countup(base, FMax div 2, numThreads):
    for s in 0..FMax:
      search(lz, s, f, 0)
  acquire(lock)
  for i in 0..M-1:
    leadingZeros[i] += lz[i]*2
  release(lock)

proc main =
  var threads: array[numThreads, ComputeThread]
  for i in 0 .. numThreads-1:
    createThread(threads[i], worker, i)
  for i in 0 .. numThreads-1:
    joinThread(threads[i])

initLock(lock)
main()
echo(@leadingZeros)

编译

nimrod cc --threads:on -d:release count.nim

(Nimrod可以在此处下载。)

这在n = 20的分配时间内运行(如果仅使用一个线程,则为n = 18,在后一种情况下大约需要2分钟)。

该算法使用递归搜索,只要遇到非零内部乘积,就会修剪搜索树。我们还观察到对于任何一对向量,(F, -F)我们只需要考虑一个向量就将搜索空间减少了一半,因为另一个向量会产生完全相同的内积集(S也可以取负)。

该实现使用Nimrod的元编程工具来展开/内联递归搜索的前几个级别。当使用gcc 4.8和4.9作为Nimrod的后端时,这可以节省一些时间,而对于clang来说可以节省很多时间。

通过观察我们只需要考虑与我们选择的F的前N个偶数个偶数个数不同的S值,就可以进一步缩小搜索空间。但是,对于较大的值,S的复杂性或存储需求不会扩展N,因为在这些情况下,循环体被完全跳过了。

列出内部乘积为零的位置似乎比在循环中使用任何位计数功能要快。显然,访问该表具有很好的局部性。

考虑到递归搜索的工作原理,该问题似乎应该适合于动态编程,但是没有明显的方法来使用合理的内存量。

输出示例:

N = 16:

@[55276229099520, 10855179878400, 2137070108672, 420578918400, 83074121728, 16540581888, 3394347008, 739659776, 183838720, 57447424, 23398912, 10749184, 5223040, 2584896, 1291424, 645200, 322600]

N = 18:

@[3341140958904320, 619683355033600, 115151552380928, 21392898654208, 3982886961152, 744128512000, 141108051968, 27588886528, 5800263680, 1408761856, 438001664, 174358528, 78848000, 38050816, 18762752, 9346816, 4666496, 2333248, 1166624]

N = 20:

@[203141370301382656, 35792910586740736, 6316057966936064, 1114358247587840, 196906665902080, 34848574013440, 6211866460160, 1125329141760, 213330821120, 44175523840, 11014471680, 3520839680, 1431592960, 655872000, 317675520, 156820480, 78077440, 39005440, 19501440, 9750080, 4875040]

为了将算法与其他实现进行比较,使用单线程时,N = 16在我的计算机上花费约7.9秒,而使用四个内核时,则花费2.3秒。

在具有gcc 4.4.6作为Nimrod后端的64核计算机上,N = 22大约需要15分钟,并且会溢出64位整数leadingZeros[0](可能不是未签名的整数,没有看过)。


更新:我发现了一些进一步改进的空间。首先,对于给定的值F,我们可以S精确地枚举对应向量的前16个条目,因为它们的位置必须完全不同N/2。所以我们预先计算大小的位向量的名单NN/2位设置和使用这些推导的初始部分SF

其次,我们可以通过观察我们始终知道的值F[N](因为在位表示中MSB为零)来改进递归搜索。这使我们可以准确地预测从内部乘积递归到哪个分支。虽然这实际上使我们可以将整个搜索变成一个递归循环,但实际上恰好使分支预测搞砸了很多,因此我们将顶层保留为原始形式。我们仍然节省了一些时间,主要是通过减少正在执行的分支数量。

为了进行一些清理,代码现在使用无符号整数并将其修复为64位(以防万一有人希望在32位体系结构上运行此整数)。

总体提速介于x3和x4之间。N = 22在10分钟内仍需要8个以上的内核才能运行,但是在64核计算机上,现在仅需4分钟左右的时间(相应numThreads地增加了)。但是,如果没有其他算法,我认为没有更多的改进空间。

N = 22:

@[12410090985684467712, 2087229562810269696, 351473149499408384, 59178309967151104, 9975110458933248, 1682628717576192, 284866824372224, 48558946385920, 8416739196928, 1518499004416, 301448822784, 71620493312, 22100246528, 8676573184, 3897278464, 1860960256, 911646720, 451520512, 224785920, 112198656, 56062720, 28031360, 14015680]

再次更新,以进一步减少搜索空间。在我的四核计算机上以N = 22的速度运行约9:49分钟。

最终更新(我认为)。对于F的选择,更好的对等类将N = 22的运行时缩短到了3:19分 57秒(编辑:我不小心只用一个线程运行了它)。

这种改变利用了这样的事实:如果一对矢量可以通过旋转将其转换为另一矢量,则它们会产生相同的前导零。不幸的是,一个相当关键的低级优化要求位表示中的F的最高位始终是相同的,并且在使用这种等效方法时,搜索空间大大减少了,与使用不同的状态空间相比,运行时间减少了约四分之一。降低F,消除低级优化所产生的开销远不止于此。但是,事实证明,可以通过考虑彼此相反的F也相等这一事实来消除此问题。虽然这稍微增加了等效类的计算复杂度,但它也允许我保留上述低级优化,从而使速度提高了大约x3。

再进行一次更新以支持累积数据的128位整数。要使用128位整数进行编译,您需要longint.nim此处开始并使用进行编译-d:use128bit。N = 24仍然需要超过10分钟,但对于感兴趣的人,我在下面列出了结果。

N = 24:

@[761152247121980686336, 122682715414070296576, 19793870419291799552, 3193295704340561920, 515628872377565184, 83289931274780672, 13484616786640896, 2191103969198080, 359662314586112, 60521536552960, 10893677035520, 2293940617216, 631498735616, 230983794688, 102068682752, 48748969984, 23993655296, 11932487680, 5955725312, 2975736832, 1487591936, 743737600, 371864192, 185931328, 92965664]

import math, locks, unsigned

when defined(use128bit):
  import longint
else:
  type int128 = uint64 # Fallback on unsupported architectures
  template toInt128(x: expr): expr = uint64(x)

const
  N = 22
  M = N + 1
  FSize = (1 shl N)
  FMax = FSize - 1
  SStep = 1 shl (N-1)
  numThreads = 16

type
  ZeroCounter = array[0..M-1, uint64]
  ZeroCounterLong = array[0..M-1, int128]
  ComputeThread = TThread[int]
  Pair = tuple[value, weight: int32]

var
  leadingZeros: ZeroCounterLong
  lock: TLock
  innerProductTable: array[0..FMax, int8]
  zeroInnerProductList = newSeq[int32]()
  equiv: array[0..FMax, int32]
  fTable = newSeq[Pair]()

proc initInnerProductTables =
  for i in 0..FMax:
    innerProductTable[i] = int8(countBits32(int32(i)) - N div 2)
    if innerProductTable[i] == 0:
      if (i and 1) == 0:
        add(zeroInnerProductList, int32(i))

initInnerProductTables()

proc ror1(x: int): int {.inline.} =
  ((x shr 1) or (x shl (N-1))) and FMax

proc initEquivClasses =
  add(fTable, (0'i32, 1'i32))
  for i in 1..FMax:
    var r = i
    var found = false
    block loop:
      for j in 0..N-1:
        for m in [0, FMax]:
          if equiv[r xor m] != 0:
            fTable[equiv[r xor m]-1].weight += 1
            found = true
            break loop
        r = ror1(r)
    if not found:
      equiv[i] = int32(len(fTable)+1)
      add(fTable, (int32(i), 1'i32))

initEquivClasses()

when defined(gcc):
  const unrollDepth = 4
else:
  const unrollDepth = 4

proc search2(lz: var ZeroCounter, s0, f, w: int) =
  var s = s0
  for i in unrollDepth..M-1:
    lz[i] = lz[i] + uint64(w)
    s = s shr 1
    case innerProductTable[s xor f]
    of 0:
      # s = s + 0
    of -1:
      s = s + SStep
    else:
      return

template search(lz: var ZeroCounter, s, f, w, i: int) =
  when i < unrollDepth:
    lz[i] = lz[i] + uint64(w)
    if i < M-1:
      let s2 = s shr 1
      case innerProductTable[s2 xor f]
      of 0:
        search(lz, s2 + 0, f, w, i+1)
      of -1:
        search(lz, s2 + SStep, f, w, i+1)
      else:
        discard
  else:
    search2(lz, s, f, w)

proc worker(base: int) {.thread.} =
  var lz: ZeroCounter
  for fi in countup(base, len(fTable)-1, numThreads):
    let (fp, w) = fTable[fi]
    let f = if (fp and (FSize div 2)) == 0: fp else: fp xor FMax
    for sp in zeroInnerProductList:
      let s = f xor sp
      search(lz, s, f, w, 0)
  acquire(lock)
  for i in 0..M-1:
    let t = lz[i].toInt128 shl (M-i).toInt128
    leadingZeros[i] = leadingZeros[i] + t
  release(lock)

proc main =
  var threads: array[numThreads, ComputeThread]
  for i in 0 .. numThreads-1:
    createThread(threads[i], worker, i)
  for i in 0 .. numThreads-1:
    joinThread(threads[i])

initLock(lock)
main()
echo(@leadingZeros)

N = 22的结果是12410090985684467712,它占用63.42位,因此适合无符号64位。
Stefan

2
您的门槛肯定提高了。

1
很高兴看到有人使用Nimrod。:)
cjfaure

@Stefan也许您的编码向导可以在10分钟内将N = 22用作此方法?

我尝试了N = 22,但几个小时后便终止了。但是它给了我[-6036653088025083904,2087229562810269696,351473149499408384,59178309967151104,9975110458933248,1682628717576192,284866824372224,48558946385920,8416739196928,1518499004416,301448822784,71620493312,22100246528,82660760760360 360016 14015680],这似乎是溢出错误。我不知道nimrod,但是可以使用unsigned int解决此问题吗?

11

Java(n=22?)

我认为大多数答案比n=16使用类似方法要好得多,尽管它们在利用对称性和在线程之间划分任务的方式方面有所不同。

问题中定义的向量可以替换为位串,而内积可以通过对重叠窗口进行XOR运算并检查是否已正确n/2设置了位(从而n/2清除了位)来替换。有n! / ((n/2)!)(中心二项式系数)n位串n/2(已设置位串)(我称其为平衡串),因此对于任何给定的位F,都有许多窗口的S内积为零。此外,滑动的作用S沿一个和检查我们是否仍然可以找到的进入位,它给出了一个零个内积对应于在其节点是窗口和其边缘连接的节点的图寻找一个边缘u至一个节点v,其第一n-1比特是最后一个n-1的位u

例如,使用n=6F=001001我们得到以下图表:

F = 001001的图形

并且F=001011我们得到这张图:

F = 001011的图形

然后,我们需要为每个i0n多少个长度的路径进行计数,并对每个i的图求和F。我认为我们大多数人都在使用深度优先搜索。

请注意,这些图是稀疏的:很容易证明每个节点的入度最大为1,出度最大为1。这也意味着唯一可能的结构是简单的链和简单的循环。这稍微简化了DFS。

我利用了两种对称性:平衡的字符串在位反转(~ALGOL系列中的多种语言中的操作)和位旋转的情况下关闭,因此我们可以F将与这些操作相关的值分组在一起,而仅使用DFS一旦。

public class CodeGolf26459v8D implements Runnable {
    private static final int NUM_THREADS = 8;

    public static void main(String[] args) {
        v8D(22);
    }

    private static void v8D(int n) {
        int[] bk = new int[1 << n];
        int off = 0;
        for (int i = 0; i < bk.length; i++) {
            bk[i] = Integer.bitCount(i) == n/2 ? off++ : -1;
        }

        int[] fwd = new int[off];
        for (int i = 0; i < bk.length; i++) {
            if (bk[i] >= 0) fwd[bk[i]] = i;
        }

        CodeGolf26459v8D[] runners = new CodeGolf26459v8D[NUM_THREADS];
        Thread[] threads = new Thread[runners.length];
        for (int i = 0; i < runners.length; i++) {
            runners[i] = new CodeGolf26459v8D(n, i, runners.length, bk, fwd);
            threads[i] = new Thread(runners[i]);
            threads[i].start();
        }

        try {
            for (int i = 0; i < threads.length; i++) threads[i].join();
        }
        catch (InterruptedException ie) {
            throw new RuntimeException("This shouldn't be reachable", ie);
        }

        long surviving = ((long)fwd.length) << (n - 1);
        for (int i = 0; i <= n; i++) {
            for (CodeGolf26459v8D runner : runners) surviving -= runner.survival[i];
            System.out.print(i == 0 ? "[" : ", ");
            java.math.BigInteger result = new java.math.BigInteger(Long.toString(surviving));
            System.out.print(result.shiftLeft(n + 1 - i));
        }
        System.out.println("]");
    }

    public final int n;
    protected final int id;
    protected final int numRunners;
    private final int[] bk;
    private final int[] fwd;

    public long[] survival;

    public CodeGolf26459v8D(int n, int id, int numRunners, int[] bk, int[] fwd) {
        this.n = n;
        this.id = id;
        this.numRunners = numRunners;

        this.bk = bk;
        this.fwd = fwd;
    }

    private int dfs2(int[] graphShape, int flip, int i) {
        if (graphShape[i] != 0) return graphShape[i];

        int succ = flip ^ (fwd[i] << 1);
        if (succ >= bk.length) succ ^= bk.length + 1;

        int j = bk[succ];
        if (j == -1) return graphShape[i] = 1;

        graphShape[i] = n + 1; // To detect cycles
        return graphShape[i] = dfs2(graphShape, flip, j) + 1;
    }

    @Override
    public void run() {
        int n = this.n;
        int[] bk = this.bk;
        int[] fwd = this.fwd;

        // NB The initial count is approx 2^(2n - 1.33 - 0.5 lg n)
        // For n=18 we overflow 32-bit
        // 64-bit is good up to n=32.
        long[] survival = new long[n + 1];
        boolean[] visited = new boolean[1 << (n - 1)];
        int th = 0;
        for (int f = 0; f < visited.length; f++) {
            if (visited[f]) continue;

            int m = 1, g = f;
            while (true) {
                visited[g] = true;
                int ng = g << 1;
                if ((ng >> (n - 1)) != 0) ng ^= (1 << n) - 1;
                if (ng == f) break;
                m++;
                g = ng;
            }

            if (th++ % numRunners != id) continue;

            int[] graphShape = new int[fwd.length];
            int flip = (f << 1) ^ f;
            for (int i = 0; i < graphShape.length; i++) {
                int life = dfs2(graphShape, flip, i);
                if (life <= n) survival[life] += m;
            }
        }

        this.survival = survival;
    }
}

在我的2.5GHz Core 2上

# n=18
$ javac CodeGolf26459v8D.java && time java CodeGolf26459v8D
[3341140958904320, 619683355033600, 115151552380928, 21392898654208, 3982886961152, 744128512000, 141108051968, 27588886528, 5800263680, 1408761856, 438001664, 174358528, 78848000, 38050816, 18762752, 9346816, 4666496, 2333248, 1166624]

real    0m3.131s
user    0m10.133s
sys     0m0.380s

# n=20
$ javac CodeGolf26459v8D.java && time java CodeGolf26459v8D
[203141370301382656, 35792910586740736, 6316057966936064, 1114358247587840, 196906665902080, 34848574013440, 6211866460160, 1125329141760, 213330821120, 44175523840, 11014471680, 3520839680, 1431592960, 655872000, 317675520, 156820480, 78077440, 39005440, 19501440, 9750080, 4875040]

real    1m8.706s
user    4m20.980s
sys     0m0.564s

# n=22
$ javac CodeGolf26459v8D.java && time java CodeGolf26459v8D
[12410090985684467712, 2087229562810269696, 351473149499408384, 59178309967151104, 9975110458933248, 1682628717576192, 284866824372224, 48558946385920, 8416739196928, 1518499004416, 301448822784, 71620493312, 22100246528, 8676573184, 3897278464, 1860960256, 911646720, 451520512, 224785920, 112198656, 56062720, 28031360, 14015680]

real    20m10.654s
user    76m53.880s
sys     0m6.852s

由于Lembik的计算机具有8个内核,并且执行我之前的单线程程序的速度是我的两倍,因此我很乐观地认为它将n=22在8分钟内执行。


7:17!非常好。您介意进一步解释您的方法吗?

6

C

基本上,这只是该问题中算法的稍微优化的实现。它可以n=12在期限内进行管理。

#include <stdio.h>
#include <inttypes.h>

#define n 12
#define m (n + 1)

int main() {
    int i;
    uint64_t S, F, o[m] = {0};
    for (S = 0; S < (1LLU << (n + m - 1)); S += 2)
        for (F = 0; F < (1 << (n - 1)); F++)
            for (i = 0; i < m; i++)
                if (__builtin_popcount(((S >> i) & ((1 << n) - 1)) ^ F) == n >> 1)
                    o[i] += 4;
                else
                    break;
    for (i = 0; i < m; i++)
        printf("%" PRIu64 " ", o[i]);
    return 0;
}

测试运行n=12,包括编译:

$ clang -O3 -march=native -fstrict-aliasing -ftree-vectorize -Wall fast.c
$ time ./a.out 
15502147584 3497066496 792854528 179535872 41181184 9826304 2603008 883712 381952 177920 85504 42560 21280 
real    0m53.266s
user    0m53.042s
sys     0m0.068s
$

评论:我只是打开了脑袋,并使用了一些简单的组合方法来计算第一个值始终为n! / ((n / 2)!)^2 * 2^(n + m - 1)。在我看来,这个问题必须有一个完全代数的解决方案。


编译时会收到很多警告。尝试使用gcc -Wall -Wextra Fors.c -o Fors

在早期的迭代中有两个未使用的变量被遗忘了,但是我删除了它们,因此至少应该有一些警告消失了。我目前没有可用的GCC(仅Clang),并且Clang目前(在删除未使用的变量之后)没有任何警告。而且,由于Clang通常在警告方面更为严格,所以我必须对您收到任何警告感到有些惊讶。
Fors

它抱怨Fors.c:13:17:警告:在'&'[-Wparentheses](两次)的操作数中在'-'周围建议括号,并且还警告:格式'%llu'期望类型为'long long unsigned int的参数',但参数2的类型为'uint64_t'[-Wformat =]。实际上clang也为我抱怨printf语句,

对于最新更改,GCC不应抛出任何警告消息。
Fors

它仍然抱怨Fors.c:13:49:警告:在'^'[-Wparentheses]操作数周围的算术中建议使用括号,但是在更糟糕的消息中……在我的机器上花费的时间超过10分钟。

5

Java n=16

对于任何给定的值,F都有\binom{n}{n/2}向量,其内积为零。因此,我们可以构建一个图,其顶点是那些匹配的向量,并且其边沿对应于的移动S,然后我们只需要计算图中的最大路径长度即可n

我还没有尝试通过按位运算替换条件运算来对此进行微优化,但是每个双递增 n运行时间运行时间就会增加约16倍,因此除非我非常接近阈值,否则这不会产生太大的影响。在我的机器上,我不是。

public class CodeGolf26459 {

    public static void main(String[] args) {
        v3(16);
    }

    // Order of 2^(2n-1) * n ops
    private static void v3(int n) {
        long[] counts = new long[n+1];
        int mask = (1 << n) - 1;
        for (int f = 0; f < (1 << (n-1)); f++) {
            // Find adjacencies
            long[] subcounts = new long[1 << n];
            for (int g = 0; g < (1 << n); g++) {
                subcounts[g] = Integer.bitCount(f ^ g) == n/2 ? 2 : -1;
            }

            for (int round = 0; round <= n; round++) {
                long count = 0;
                // Extend one bit.
                long[] next = new long[1 << n];
                for (int i = 0; i < (1 << n); i++) {
                    long s = subcounts[i];
                    if (s == -1) next[i] = -1;
                    else {
                        count += s;
                        int j = (i << 1) & mask;
                        if (subcounts[j] >= 0) next[j] += s;
                        if (subcounts[j + 1] >= 0) next[j + 1] += s;
                    }
                }
                counts[round] += count << (n - round);
                subcounts = next;
            }
        }

        System.out.print("[");
        for (long count : counts) System.out.print(count+", ");
        System.out.println("]");
    }
}

在我的2.5GHz Core 2上

$ javac CodeGolf26459.java && time java -server CodeGolf26459 
[55276229099520, 10855179878400, 2137070108672, 420578918400, 83074121728, 16540581888, 3394347008, 739659776, 183838720, 57447424, 23398912, 10749184, 5223040, 2584896, 1291424, 645200, 322600, ]

real    6m2.663s
user    6m4.631s
sys     0m1.580s

ggy带,因为我现在不想实施自己的解决方案。每个顶点最多具有一个后继者,因此您实际上不需要数组。为了有效地f迭代顶点和顶点的组合,请f_xor_g使用完全n/2设置的位对所有顶点进行迭代。对于这些中的每一个,都要遍历全部f并接受g = f ^ f_xor_g
David Eisenstat 2014年

@David,我知道,我的版本7在Atom上网本上在一分钟内显示n = 18,但是直到我从假期回来之前我才能发布它。
彼得·泰勒

4

RPython,N = 22〜3:23

多线程,使用无堆栈递归下降。该程序接受两个命令行参数:N和辅助线程数。

from time import sleep

from rpython.rlib.rthread import start_new_thread, allocate_lock
from rpython.rlib.rarithmetic import r_int64, build_int, widen
from rpython.rlib.rbigint import rbigint

r_int8 = build_int('r_char', True, 8)

class ThreadEnv:
  __slots__ = ['n', 'counts', 'num_threads',
               'v_range', 'v_num', 'running', 'lock']

  def __init__(self):
    self.n = 0
    self.counts = [rbigint.fromint(0)]
    self.num_threads = 0
    self.v_range = [0]
    self.v_num = 0
    self.running = 0
    self.lock = None

env = ThreadEnv()

bt_bits = 12
bt_mask = (1<<bt_bits)-1
# computed compile time
bit_table = [r_int8(0)]
for i in xrange(1,1<<bt_bits):
  bit_table += [((i&1)<<1) + bit_table[i>>1]]

def main(argv):
  argc = len(argv)
  if argc < 2 or argc > 3:
    print 'Usage: %s N [NUM_THREADS=2]'%argv[0]
    return 1

  if argc == 3:
    env.num_threads = int(argv[2])
  else:
    env.num_threads = 2

  env.n = int(argv[1])
  env.counts = [rbigint.fromint(0)]*env.n
  env.lock = allocate_lock()

  v_range = []
  v_max = 1<<(env.n-1)
  v_num = 0
  v = (1<<(env.n>>1))-1
  while v < v_max:
    v_num += 1
    v_range += [v]
    if v&1:
      # special case odd v
      s = (v+1)&-v
      v ^= s|(s>>1)
    else:
      s = v&-v
      r = v+s
      # s is at least 2, skip two iterations
      i = 3
      s >>= 2
      while s:
        i += 1
        s >>= 1
      v = r|((v^r)>>i)
  env.v_range = v_range
  env.v_num = v_num

  for i in xrange(env.num_threads-1):
    start_new_thread(run,())

  # use the main process as a worker
  run()

  # wait for any laggers
  while env.running:
    sleep(0.05)

  result = []
  for i in range(env.n):
    result += [env.counts[i].lshift(env.n-i+3).str()]
  result += [env.counts[env.n-1].lshift(3).str()]
  print result
  return 0

def run():
  with env.lock:
    v_start = env.running
    env.running += 1

  n = env.n
  counts = [r_int64(0)]*n
  mask = (1<<n)-1
  v_range = env.v_range
  v_num = env.v_num
  z_count = 1<<(n-2)

  for i in xrange(v_start, v_num, env.num_threads):
    v = v_range[i]
    counts[0] += z_count
    counts[1] += v_num
    r = v^(v<<1)
    for w in v_range:
      # unroll counts[2] for speed
      # ideally, we could loop over x directly,
      # rather than over all v, only to throw the majority away
      # there's a 2x-3x speed improvement to be had here...
      x = w^r
      if widen(bit_table[x>>bt_bits]) + widen(bit_table[x&bt_mask]) == n:
        counts[2] += 1
        x, y = v, x
        o, k = 2, 3
        while k < n:
          # x = F ^ S
          # y = F ^ (S<<1)
          o = k
          z = (((x^y)<<1)^y)&mask
          # z is now F ^ (S<<2), possibly xor 1
          # what S and F actually are is of no consequence

          # the compiler hint `widen` let's the translator know
          # to store the result as a native int, rather than a signed char
          bt_high = widen(bit_table[z>>bt_bits])
          if bt_high + widen(bit_table[z&bt_mask]) == n:
            counts[k] += 1
            x, y = y, z
            k += 1
          elif bt_high + widen(bit_table[(z^1)&bt_mask]) == n:
            counts[k] += 1
            x, y = y, z^1
            k += 1
          else: k = n

  with env.lock:
    for i in xrange(n):
      env.counts[i] = env.counts[i].add(rbigint.fromrarith_int(counts[i]))
    env.running -= 1

def target(*args):
  return main, None

编译

使用mercurial,git或您喜欢的任何方式在PyPy存储库中进行本地克隆。输入以下咒语(假设上面的脚本名为convolution-high.py):

$ pypy %PYPY_REPO%/rpython/bin/rpython --thread convolution-high.py

其中%PYPY_REPO%代表一个环境变量,指向刚刚克隆的存储库。编译大约需要一分钟。


采样时间

N = 16,4个线程:

$ timeit convolution-high-c 16 4
[55276229099520, 10855179878400, 2137070108672, 420578918400, 83074121728, 16540581888, 3394347008, 739659776, 183838720, 57447424, 23398912, 10749184, 5223040, 2584896, 1291424, 645200, 322600]
Elapsed Time:     0:00:00.109
Process Time:     0:00:00.390

N = 18,4个线程:

$ timeit convolution-high-c 18 4
[3341140958904320, 619683355033600, 115151552380928, 21392898654208, 3982886961152, 744128512000, 141108051968, 27588886528, 5800263680, 1408761856, 438001664, 174358528, 78848000, 38050816, 18762752, 9346816, 4666496, 2333248, 1166624]
Elapsed Time:     0:00:01.250
Process Time:     0:00:04.937

N = 20,4个线程:

$ timeit convolution-high-c 20 4
[203141370301382656, 35792910586740736, 6316057966936064, 1114358247587840, 196906665902080, 34848574013440, 6211866460160, 1125329141760, 213330821120, 44175523840, 11014471680, 3520839680, 1431592960, 655872000, 317675520, 156820480, 78077440, 39005440, 19501440, 9750080, 4875040]
Elapsed Time:     0:00:15.531
Process Time:     0:01:01.328

N = 22,4个线程:

$ timeit convolution-high-c 22 4
[12410090985684467712, 2087229562810269696, 351473149499408384, 59178309967151104, 9975110458933248, 1682628717576192, 284866824372224, 48558946385920, 8416739196928, 1518499004416, 301448822784, 71620493312, 22100246528, 8676573184, 3897278464, 1860960256, 911646720, 451520512, 224785920, 112198656, 56062720, 28031360, 14015680]
Elapsed Time:     0:03:23.156
Process Time:     0:13:25.437

9:26 欢迎来到22名工作人员:)

我不确定为什么,但是您的新版本对我而言并不快。我还是在9:30左右。/primo-c22 8。

@Lembik,如果除法平均大约和3个右移一样快(3 = Sum {(n + 1)/(2 ^ n)},n = 1..infty)就会有意义。对于certian架构,我想可能是这种情况,但是在矿区划分上明显要慢一些。感谢您抽出
宝贵

3

Python 3.3,N = 20,3.5分钟

免责声明:我的意图是 不是将其发布为我自己的答案,因为我正在使用的算法只是primo的RPython解决方案的无耻移植。我在这里的目的仅仅是说明如果结合了NumpyNumba模块的魔力,可以在Python中做什么。

Numba简短地解释了:

Numba是即时专业化编译器,可将带注释的Python和NumPy代码编译为LLVM(通过装饰器)。http://numba.pydata.org/

更新1:扔掉周围的数字后,我注意到我们可以完全跳过一些数字。所以现在maxf变成(1 << n)// 2maxs变成maxf 2 **。这将大大加快该过程。n = 16现在仅需约48秒(从4.5分钟降低)。我也有另一个想法,我将尝试看看是否可以使它运行得更快。

更新2:更改了算法(primo的解决方案)。虽然我的端口尚不支持多线程,但添加起来却微不足道。甚至可以使用Numba和ctypes释放CPython GIL。但是,该解决方案也可以在单核上非常快速地运行!

import numpy as np
import numba as nb

bt_bits = 11
bt_mask = (1 << bt_bits) - 1
bit_table = np.zeros(1 << bt_bits, np.int32)

for i in range(0, 1 << bt_bits):
    bit_table[i] = ((i & 1) << 1) + bit_table[i >> 1]

@nb.njit("void(int32, int32, int32, int32, int64[:], int64[:])")
def run(n, m, start, re, counts, result):
    mask = (1 << n) - 1

    v_max = (1 << n) // 2
    rr = v_max // 2

    v = (1 << (n >> 1)) - 1
    while v < v_max:
        s = start

        while s < rr:
            f = v ^ s
            counts[0] += 8
            t = s << 1
            o, j = 0, 1

            while o < j and j < m:
                o = j
                w = (t ^ f) & mask
                bt_high = bit_table[w >> bt_bits]

                if bt_high + bit_table[w & bt_mask] == n:
                    counts[j] += 8
                    t <<= 1
                    j += 1
                elif bt_high + bit_table[(w ^ 1) & bt_mask] == n:
                    counts[j] += 8
                    t = (t | 1) << 1
                    j += 1
                    s += re

            s = v & -v
            r = v + s
            o = v ^ r
            o = (o >> 2) // s
            v = r | o

    for e in range(m):
        result[e] += counts[e] << (n - e)

最后:

if __name__ == "__main__":
    n = 20
    m = n + 1

    result = np.zeros(m, np.int64)
    counts = np.zeros(m, np.int64)

    s1 = time.time() * 1000
    run(n, m, 0, 1, counts, result)
    s2 = time.time() * 1000

    print(result)
    print("{0}ms".format(s2 - s1))

这可以在212688毫秒(约3.5分钟)内在我的计算机上运行。


谢谢。现在n = 18呢?:)

自从我使用n = 18启动程序以来,已经快20分钟了。我可以肯定地说,即使使用此特定算法,即使使用Numba,Python也无法解决此问题。
Anna Jokela 2014年

我乐观地认为存在更好的算法。

我尝试了pip install numba,但是它说找不到llvmpy。我尝试了sudo pip install llvmpy,但是它说找不到versioneer。我尝试了sudo pip install versioneer,但它说我已经拥有了。

尽管我还没有numba可以工作(我想最终我必须安装anaconda),但我对此印象深刻。问题是,是否可以使用类似于消旋器的方法来求解N = 22?

2

C ++ N = 16

我正在用原子在EEEPC上进行测试..我的时间没有多大意义。:D
原子在34秒内求解n = 14。在20分钟内n = 16。我想在OP pc上测试n = 16。我很乐观

这样的想法是,每次我们找到给定F的解时,我们都会找到2 ^ i解,因为我们可以更改S的下半部分以得到相同的结果。

#include <stdio.h>
#include <cinttypes>
#include <cstring>

int main()
{
   const int n = 16;
   const int m = n + 1;
   const uint64_t maxS = 1ULL << (2*n);
   const uint64_t maxF = 1ULL << n;
   const uint64_t mask = (1ULL << n)-1;
   uint64_t out[m]={0};
   uint64_t temp[m] = {0};
   for( uint64_t F = 0; F < maxF; ++F )
   {
      for( uint64_t S = 0; S < maxS; ++S )
      {
         int numSolution = 1;
         for( int i = n; i >= 0; --i )
         {
            const uint64_t window = S >> i;
            if( __builtin_popcount( mask & (window ^ F) ) == (n / 2) )
            {
               temp[i] += 1;
            } else {
               numSolution = 1 << i;
               S += numSolution - 1;
               break;
            }
         }
         for( int i = n; i >= 0; --i )
         {
            out[i] += temp[i]*numSolution;
            temp[i] = 0;
         }
      }
   }
   for( int i = n; i >= 0; --i )
   {
      uint64_t x = out[i];
      printf( "%lu ", x );
   }
   return 0;
}

编译:

gcc 26459.cpp -std = c ++ 11 -O3 -march = native -fstrict-aliasing -ftree-vectorize -Wall -pedantic -o 26459


1
这很棒。实际上,对于如何解决较大的n值,我有一些半熟的想法。您想听听他们的意见还是会破坏比赛?

2

JAVASCRIPT n:12

在我的计算机上,它花费了231.242秒。在演示中,我使用Webworkers防止冻结浏览器。并行工作人员可以进一步提高这一确定性。我知道JS在这次挑战中没有机会,但我这样做很有趣!

单击以运行在线演示

var n = 8;        
var m = n + 1;
var o = [];
var popCount = function(bits) {
  var SK5  = 0x55555555,
      SK3  = 0x33333333,
      SKF0 = 0x0f0f0f0f,
      SKFF = 0xff00ff;

  bits -= (bits >> 1) & SK5;
  bits  = (bits & SK3) + ((bits >> 2) & SK3);
  bits  = (bits & SKF0) + ((bits >> 4) & SKF0);
  bits += bits >> 8;

  return (bits + (bits >> 15)) & 63;
};
for(var S = 0; S < (1 << n + m - 1); S += 2){
  for(var F = 0; F < (1 << n - 1); F += 1){
    for (var i = 0; i < m; i++){
      var c = popCount(((S >> i) & ((1 << n) - 1)) ^ F);
      if(c == n >> 1){
        if(!o[i]) o[i] = 0;
        o[i] += 4;
      } else break;
    }
  }
}
return o;

那些新的(ish)快速JavaScript引擎之一怎么办?可以使用吗?

你是说飞镖吗?
rafaelcastrocouto

1
其实我错了。您也可以尝试同时使用Firefox和Chrome。除非您当然想在asm.js中编写它:)

1
接受挑战...要做!
rafaelcastrocouto

1
尝试了一下,花了我的电脑5.4秒钟来完成n=22 [235388928,86292480,19031048,5020640,1657928,783920,545408,481256,463832,460256,459744,459744,459744,459744,459744,459744,459744,459744,459744,459744,459744,459744] i.imgur.com/FIJa2Ch.png
Spedwards 2014年

1

Fortran:n = 12

我只是在Fortran中制作了一个快速版本,除OpenMP之外没有其他优化。在OPs机器上,n = 12时它应该挤在10分钟以下,而在我的机器上它需要10:39,这要慢得多。

64位整数确实会对性能产生负面影响;猜想我将不得不重新考虑整个算法,以使其更快。不知道我是否会打扰,我想我宁愿花一些时间思考一个更好的挑战,这更符合我的口味。如果有人想接受并运行它,请继续:)

program golf
use iso_fortran_env
implicit none
integer, parameter ::  n=12
integer :: F(n), S(2*n)
integer(int64) :: leadingzerocounts(n+1)
integer :: k
integer(int64) :: i,j,bindec,enc

leadingzerocounts=0

!$OMP parallel do private(i,enc,j,bindec,S,F,k) reduction(+:leadingzerocounts) schedule(dynamic)
do i=0,2**(2*n)-1
  enc=i
  ! Short loop to convert i into the array S with -1s and 1s
  do j=2*n,1,-1
    bindec=2**(j-1)
    if (enc-bindec .ge. 0) then
      S(j)=1
      enc=enc-bindec
    else
      S(j)=-1
    endif
  end do
  do j=0,2**(n)-1
    ! Convert j into the array F with -1s and 1s
    enc=j
    do k=n,1,-1
      bindec=2**(k-1)
      if (enc-bindec .ge. 0) then
        F(k)=1
        enc=enc-bindec
      else
        F(k)=-1
      endif
    end do
    ! Compute dot product   
    do k=1,n+1
      if (dot_product(F,S(k:k+n-1)) /= 0) exit
      leadingzerocounts(k)=leadingzerocounts(k)+1
    end do
  end do
end do
!$OMP end parallel do

print *, leadingzerocounts

end

1

Lua:n = 16

免责声明:我的意图不是将其发布为我自己的答案,因为我使用的算法是从Anna Jokela的聪明答案中偷偷偷走。这是无耻地被盗ilmale的巧妙回答

此外,它甚至是无效的-由于浮点数而导致不准确(如果Lua支持64位整数会更好)。但是,我仍在上传它,只是为了展示此解决方案的速度。它是一种动态编程语言,但是我可以在合理的时间内(在800MHz CPU上为1分钟)计算n = 16。

使用LuaJIT运行时,标准解释器太慢。

local bit = require "bit"
local band = bit.band
local bor = bit.bor
local bxor = bit.bxor
local lshift = bit.lshift
local rshift = bit.rshift

-- http://stackoverflow.com/a/11283689/736054
local function pop_count(w)
    local b1 = 1431655765
    local b2 = 858993459
    local b3 = 252645135
    local b7 = 63

    w = band(rshift(w, 1), b1) + band(w, b1)
    w = band(rshift(w, 2), b2) + band(w, b2)
    w = band(w + rshift(w, 4), b3)
    return band(rshift(w, 24) + rshift(w, 16) + rshift(w, 8) + w, b7)
end

local function gen_array(n, value)
    value = value or 0
    array = {}
    for i = 1, n do
        array[i] = value
    end
    return array
end

local n = 16
local u = math.floor(n / 2)
local m = n + 1
local maxf = math.floor(lshift(1, n) / 2)
local maxs = maxf ^ 2
local mask = lshift(1, n) - 1

local out = gen_array(m, 0)
local temp = gen_array(m, 0)


for f = 0, maxf do
    local s = 0
    while s <= maxs do
        local num_solution = 1

        for i = n, 0, -1 do
            if pop_count(band(mask, bxor(rshift(s, i), f))) == u then
                temp[i + 1] = temp[i + 1] + 8
            else
                num_solution = lshift(1, i)
                s = s + num_solution - 1
                break
            end
        end

        for i = 1, m do
            out[i] = out[i] + temp[i] * num_solution
            temp[i] = 0
        end

        s = s + 1
    end
end

for i = m, 1, -1 do
    print(out[i])
end

谢谢。我认为最新的lua版本使用long long int,在64位系统上应为64位。请参阅“lua_integer”在lua.org/work/doc/manual.html

@Lembik:有趣。无论哪种方式,它都是标准的Lua(已经支持long long而不是double使用编译设置),而不是LuaJIT。
Konrad Borowski

我想无论如何我都错了。一个将需要5.3,而这是不存在的。人们可以给lua最好的建议是“尝试5.3-workx”。
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.