盲二进制加法器


10

假设您有两个盒子B(x)B(y),每个盒子都包含一个未知的位-0或1,以及一台F可以对其进行X射线检查并产生第三个盒子B(x^y)xor)的机器。F也可以计算B(x*y))。实际上,这些只是机器可以执行的单个操作的特殊情况- 每个内部产品F()如下所示。

对于两个相同长度的数组

[B(x[0]), B(x[1]), ..., B(x[n-1])]
[B(y[0]), B(y[1]), ..., B(y[n-1])]

内积定义为

B(x[0]*y[0] ^ x[1]*y[1] ^ ... ^ x[n-1]*y[n-1])

每个 ”是指F()可以处理多对x[]y[]一气呵成。一对中的x[]y[]必须具有相同的长度;来自不同对的x[]-s和y[]-s不一定需要。

框由唯一的整数ID表示。

每个 JavaScript 内部产品的实现可能看起来像

var H=[0,1];          // hidden values, indexed by boxId
function B(x) {       // seal x in a new box and return the box id
  return H.push(x)-1;
}
function F(pairs) {   // "inner product each"
  return pairs.map(function (pair) {
    var r = 0, x = pair[0], y = pair[1];
    for (var i = 0; i < x.length; i++) r ^= H[x[i]] * H[y[i]];
    return B(r);
  })
}

(请将以上内容翻译成您选择的语言。)

允许访问F()适合您的语言的实现(但不能访问HB()),并给两个框ID数组构成两个整数a和的16位二进制表示形式b,您的任务是为16位二进制表示产生框ID的通话a+b次数最少(丢弃溢出)F()

调用F()次数最少的解决方案将获胜。通过计算调用x[],y[]对的总数将打破关系F()-越少越好。如果仍然束手无策,则代码的大小(不包括的实现F()及其助手)将以传统的代码高尔夫方式确定赢家。请使用“ MyLang,123个呼叫,456个对,789个字节”之类的标题作为答案。

编写函数或完整程序。输入/输出/参数/结果是任何合理格式的int数组。二进制表示形式可以是小端或大端的-选择一种。


附录1:为使挑战稍微容易些,您可以假定ID为0和1的框包含值0和1。这将为您提供常量,例如对求反(x^1“否”)很有用。当然,有很多方法可以克服缺少常量的问题,但是其余的挑战无论如何还是很难的,所以让我们消除这种干扰。


附录2:要获得赏金,您必须执行以下一项操作:

  • 在截止日期之前发布您的得分(通话,配对,字节)和代码

  • 在截止日期之前发布分数和代码的sha256哈希;然后在截止日期后的23小时内发布实际代码


如果将其翻译成我选择的语言(Haskell),则可以使用值递归并且F仅调用一次。那肯定是作弊,但是我不确定这是好作弊还是作弊。
Christian Sievers,2017年

我知道在Haskell中不欢迎使用全局状态,但是让我作为一个思想实验来问一下:如果我在F的实现中增加一个全局计数器,到最后它会增长多少?-这就是我对“通话次数”的理解。
ngn

我可以完全做到这一点,它会说1。但是无法使用您的代码将其翻译回JavaScript。基本上我会说y=f(x),让我们x依靠y
Christian Sievers,2017年

恐怕我不知道该如何工作。您能显示示例代码吗?我的Haskell很差,但是我敢肯定,如果我可以使用代码,我可以弄清楚。
ngn

也许我们可以使用以下类型对这个问题进行建模?data Box = B Int deriving (Show); f :: [[[Box]]] -> [Box]我将需要更多时间来弄清楚如何实现f(在此,Haskell强制使用小写字母)-明天再尝试。
ngn

Answers:


6

Python 3,5个调用,92对,922字节

Python 3,5个调用,134对,3120字节

Python 3,6个调用,106对,2405字节

[JavaScript(Node.js)],9个调用,91对,1405字节

JavaScript(Node.js),16个调用,31个对,378个字节

def add(F,a,b):r=[];p=lambda x:(x,x);q=lambda u,v,t:([u,v]+t[0],[u,v]+t[1]);s=lambda c,k,n:([e[j][n]for j in range(k,-1,-1)]+[f[n]],[c]+f[n-k:n+1]);t=lambda c,k,n:q(a[n],b[n],s(c,k,n-1));z=F([p([a[i],b[i]])for i in range(16)]+[([a[i]],[b[i]])for i in range(16)]);e=[z[0:16]];f=z[16:32];r+=[e[0][0]];c=f[0];z=F([p([a[1],b[1],c]),([e[0][1],f[1]],[c,f[1]])]+[([e[0][i]],[e[0][i-1]])for i in range(3,16)]);r+=[z[0]];c=z[1];e+=[[0]*3+z[2:15]];z=F([p([a[2],b[2],c]),t(c,0,3),s(c,1,3)]+[([e[j][i]],[e[1][i-j-1]])for j in range(2)for i in range(6+j,16)]);r+=z[0:2];c=z[2];e+=u(2,4,z[3:]);z=F([p([a[4],b[4],c])]+[t(c,i,i+5)for i in range(0,3)]+[s(c,3,7)]+[([e[j][i]],[e[3][i-j-1]])for j in range(4)for i in range(12+j,16)]);r+=z[0:4];c=z[4];e+=u(4,8,z[5:]);z=F([p([a[8],b[8],c])]+[t(c,i,i+9) for i in range(0,7)]);return r+z
def u(b,e,z):
	j=0;w=[0]*(e-b)
	for i in range(b,e):w[i-b]=[0]*(i+e)+z[j:j+16-(i+e)];j+=16-(i+e)
	return w

在线尝试!

第一个版本 好了,这不是golfed。这只是@ngn代码的改编。

这里唯一的想法是,由于丢弃了溢出,因此您无需计算最后的进位。同样,的呼叫F被两个分组。也许可以用另一种方式对它们进行分组,但是我怀疑由于基本加法算法的本质,您是否可以大大减少对的数量。

编辑:仍然不打高尔夫球。对的数量当然可以减少,并且呼叫的数量也可以减少。请参阅https://gist.github.com/jferard/864f4be6e4b63979da176bff380e6c62,以获取关于sympy的“证明”。

编辑2切换到Python,因为它对我来说更具可读性。现在我有了通用公式,我想我可以达到5个(也许4个)调用的限制。

编辑3 这是基本的积木:

alpha[i] = a[i] ^ b[i]
beta[i] = a[i] * b[i]
c[0] = beta[0]
r[0] = alpha[0]

通用公式为:

c[i] = alpha[i]*c[i-1] ^ beta[i]
r[i] = a[i] ^ b[i] ^ c[i-1]

扩展版本为:

c[0] = beta[0]
c[1] = alpha[1]*beta[0] ^ beta[1]
c[2] = alpha[2]*alpha[1]*beta[0] ^ alpha[2]*beta[1] ^ beta[2]
c[3] = alpha[3]*alpha[2]*alpha[1]*beta[0] ^ alpha[3]*alpha[2]*beta[1] ^ alpha[3]*beta[2] ^ beta[3]
...
c[i] = alpha[i]*...*alpha[1]*beta[0] ^ alpha[i]*...*alpha[2]*beta[1] ^ .... ^ alpha[i]*beta[i-1] ^ beta[i]

5个电话似乎对我来说是个限制。现在,我需要进行一些工作以移除双打并打高尔夫球!

编辑4我打了这个。

非高尔夫版本:

def add(F, a, b):
    r=[]
    # p is a convenient way to express x1^x2^...x^n
    p = lambda x:(x,x)
    # q is a convenient way to express a[i]^b[i]^carry[i-1]
    q = lambda u,v,t:([u,v]+t[0],[u,v]+t[1])

    # step1: the basic bricks
    z=F([p([a[i],b[i]]) for i in range(16)]+[([a[i]],[b[i]]) for i in range(16)])
    alpha=z[0:16];beta=z[16:32]
    r.append(alpha[0])
    c = beta[0]

    # step 2
    z=F([
        p([a[1],b[1],c]),
        ([alpha[1],beta[1]],[c,beta[1]])
        ]+[([alpha[i]],[alpha[i-1]]) for i in range(3,16)])
    r.append(z[0])
    c = z[1] # c[1]
    alpha2=[0]*3+z[2:15]
    assert len(z)==15, len(z)

    # step 3
    t0=([alpha[2],beta[2]],[c,beta[2]])
    t1=([alpha2[3],alpha[3],beta[3]],[c,beta[2],beta[3]])
    z=F([
        p([a[2],b[2],c]),
        q(a[3],b[3],t0),
        t1]+
        [([alpha[i]],[alpha2[i-1]]) for i in range(6,16)]+
        [([alpha2[i]],[alpha2[i-2]]) for i in range(7,16)])
    r.extend(z[0:2])
    c = z[2] # c[3]
    alpha3=[0]*6+z[3:13]
    alpha4=[0]*7+z[13:22]
    assert len(z)==22, len(z)

    # step 4
    t0=([alpha[4],beta[4]],[c,beta[4]])
    t1=([alpha2[5],alpha[5],beta[5]],[c,beta[4],beta[5]])
    t2=([alpha3[6],alpha2[6],alpha[6],beta[6]],[c,beta[4],beta[5],beta[6]])
    t3=([alpha4[7],alpha3[7],alpha2[7],alpha[7],beta[7]],[c,beta[4],beta[5],beta[6],beta[7]])
    z=F([
        p([a[4],b[4],c]),
        q(a[5],b[5],t0),
        q(a[6],b[6],t1),
        q(a[7],b[7],t2),
        t3]+
        [([alpha[i]],[alpha4[i-1]]) for i in range(12,16)]+
        [([alpha2[i]],[alpha4[i-2]]) for i in range(13,16)]+
        [([alpha3[i]],[alpha4[i-3]]) for i in range(14,16)]+
        [([alpha4[i]],[alpha4[i-4]]) for i in range(15,16)])
    r.extend(z[0:4])
    c = z[4] # c[7]
    alpha5 = [0]*12+z[5:9]
    alpha6 = [0]*13+z[9:12]
    alpha7 = [0]*14+z[12:14]
    alpha8 = [0]*15+z[14:15]
    assert len(z) == 15, len(z)

    # step 5
    t0=([alpha[8],beta[8]],[c,beta[8]])
    t1=([alpha2[9],alpha[9],beta[9]],[c,beta[8],beta[9]])
    t2=([alpha3[10],alpha2[10],alpha[10],beta[10]],[c,beta[8],beta[9],beta[10]])
    t3=([alpha4[11],alpha3[11],alpha2[11],alpha[11],beta[11]],[c,beta[8],beta[9],beta[10],beta[11]])
    t4=([alpha5[12],alpha4[12],alpha3[12],alpha2[12],alpha[12],beta[12]],[c,beta[8],beta[9],beta[10],beta[11],beta[12]])
    t5=([alpha6[13],alpha5[13],alpha4[13],alpha3[13],alpha2[13],alpha[13],beta[13]],[c,beta[8],beta[9],beta[10],beta[11],beta[12],beta[13]])
    t6=([alpha7[14],alpha6[14],alpha5[14],alpha4[14],alpha3[14],alpha2[14],alpha[14],beta[14]],[c,beta[8],beta[9],beta[10],beta[11],beta[12],beta[13],beta[14]])
    t7=([alpha8[15],alpha7[15],alpha6[15],alpha5[15],alpha4[15],alpha3[15],alpha2[15],alpha[15],beta[15]],[c,beta[8],beta[9],beta[10],beta[11],beta[12],beta[13],beta[14],beta[15]])

    z=F([
        p([a[8],b[8],c]),
        q(a[9],b[9],t0),
        q(a[10],b[10],t1),
        q(a[11],b[11],t2),
        q(a[12],b[12],t3),
        q(a[13],b[13],t4),
        q(a[14],b[14],t5),
        q(a[15],b[15],t6)
    ])
    r.extend(z)
    return r

在线尝试!


很好:)您发现我故意遗漏了两个简单的优化方法。“我怀疑您是否可以大大减少对的数量”-请注意,获胜的第一个条件是致电的数量F()。我保证有一种方法可以大大减少这些(这是这个挑战中最难的部分),然后将有空间优化对数,最后当然可以打码(但这是最不重要的标准)。
ngn

好,我知道了!迟早您会收到类似的信息:... + x * y * z + ...。我们无法使用F它来进行评估,但是如果我们x * y使用上一个F调用进行了计算,则只需要执行以下操作... + (x * y) * z + ...:(它与的格式匹配F)。在使用sympy的情况下,我设法保留了一个调用(步骤1:计算r0,c0,r1;步骤2:计算c1和一些aux值;步骤3:计算r2,c2,r3,c3),现在我正在寻找通用的解。
jferard '17年

是的,换句话说:输出位是输入位中高于2的次数的多项式。内积最多可以将m次和n次多项式组合为(m + n)次多项式。别着急-几个小时后我就可以设立赏金计划:)
ngn

您可能要考虑利用上面的附录2。否则:如果有人复制了您的代码,删除了一个空格,然后重新发布,从技术上讲,我将不得不向他们奖励。
ngn

2
为了记录在案,不可能使用少于五个的调用,因为该解决方案需要32度多项式。(与输入位的任何函数相对应的多项式都是唯一的。)
Nitrodon '17

2

Haskell,1个呼叫(作弊???),32对(可以改进),283个字节(相同)

请不要生我的气,我不想赢,但是我对挑战的评论感到鼓舞,以解释我在说什么。

我试图使用状态monad来处理添加框以及计算呼叫和对的数量,这确实有效,但是我没有设法使解决方案在该设置下正常工作。因此,我做了注释中也建议的操作:将数据隐藏在数据构造函数后面,不要窥视。(干净的方法是使用单独的模块而不导出构造函数。)此版本的优点是简单得多。

由于我们谈论的是位盒,因此我将Bool值放入其中。我zero用零位定义为给定的框- one不需要a。

import Debug.Trace

data B = B { unB :: Bool }

zero :: B
zero = B False

f :: [([B],[B])] -> [B]
f pairs =  trace ("f was called with " ++ show (length pairs) ++ " pairs") $
           let (B i) &&& (B j) = i && j
           in map (\(x,y) ->  B ( foldl1 (/=) (zipWith (&&&) x y))) pairs

我们正在使用调试功能trace来查看f被调用的频率以及有多少对。&&&通过模式匹配查看框,值/= 使用的不等式Boolxor

bits :: Int -> [Bool]
bits n = bitsh n 16
  where bitsh _ 0 = []
        bitsh n k = odd n : bitsh (n `div` 2) (k-1)

test :: ( [B] -> [B] -> [B] ) -> Int -> Int -> Bool
test bba n m = let x = map B (bits n)
                   y = map B (bits m)
                   r = bba x y
                   res = map unB r
               in res==bits(n+m)

test函数将盲二进制加法器作为第一个参数,然后将对两个数字进行加法测试。它返回一个Bool指示测试是否成功。首先创建输入框,然后调用加法器,将结果拆箱(带有unB),然后与预期结果进行比较。

我实现了两个加法器,即示例解决方案simple,以便我们可以看到调试输出正常工作,而我的解决方案使用了value recursion valrec

simple a b = let [r0] = f [([a!!0,b!!0],[a!!0,b!!0])]
                 [c]  = f [([a!!0],[b!!0])]
             in loop 1 [r0] c
             where loop 16 rs _ = rs
                   loop i  rs c = let [ri] = f [([a!!i,b!!i,c],[a!!i,b!!i,c])]
                                      [c'] = f [([a!!i,b!!i,c],[b!!i,c,a!!i])]
                                  in loop (i+1) (rs++[ri]) c'

valrec a b =
    let res = f (pairs res a b)
    in [ res!!i | i<-[0,2..30] ]
  where pairs res a b =
           let ts = zipWith3 (\x y z -> [x,y,z])
                             a b (zero : [ res!!i | i<-[1,3..29] ]) in
           [ p | t@(h:r) <- ts, p <- [ (t,t), (t,r++[h]) ] ]

看看我是如何定义res自己的?这也称为打结

现在我们可以看到如何f只被调用一次:

*Main> test valrec 123 456
f was called with 32 pairs
True

或更换valrecsimplef被称为32倍。

在线尝试!(跟踪输出显示在“调试”下)


在这里没有生气:)因此,如果我理解正确,则f当您遍历时,to的参数是一个懒惰的,可能无限的列表,这些列表会变成现实吗?恐怕这与挑战的精神i+1背道而驰-它使您在获得与-th 相对应的结果之后,推迟关于将-st参数传递为til 的决定if用完全物化的,不可变的参数找出需要多少个调用是非常有趣的:)
ngn

我同意。@jferard做的很棒的工作不应该被这种技巧所破坏。虽然f可以接受无限输入(添加无限位流,是的!),但这不是重点。哦,实际上,该trace消息确保长度是有限的,并且一开始就知道。另外,我不会说有一个延迟的决定:一切都是提前计划的,按照要求,我只是盲目地改组。请注意,这与参数顺序无关:我可以更改它,以便res首先包含结果,然后包含进位。
Christian Sievers

“我只是盲目地拖着箱子走”-假设您通过打电话获得了一个箱子f;您是否在同一调用中将该框作为另一个参数反馈f
ngn

是的,我愿意。这就是价值递归的全部意义。您拥有这样的权利:它使用的是惰性,而事实是我可以使用未完全实现的参数(我喜欢这种描述)。鉴于挑战的明显精神,正如所宣布的那样,这显然是作弊。如果有人认为这是发明性或值得注意的,则可能会认为这是很好的作弊方式。
Christian Sievers

这当然是好方法-显然您无意在这里欺骗。函数式编程中的惰性是一个漂亮的概念,它有其有效的用途。几年前,当我试图学习一些Haskell时,我记得给“斐波那契”数字“打结”的单线印象非常深刻。
ngn

0

JavaScript,32个调用,32对,388字节

Dyalog APL,32个呼叫,32对,270个字节

这是一个简单的示例解决方案,可以用作模板。

请注意,字节数必须仅包括用“ BEGIN / END SOLUTION”包围的部分。

说明:

我选择了小尾数位顺序(x[0]最低有效位)。

请注意,单位加法mod 2可以实现为F([[[x,y],[x,y]]])(即:x*x ^ y*y-乘法mod 2是幂等的),而二进制乘法可以实现为F([[[x],[y]]])

我们从最低有效位到最高有效位遍历所有位,并在每一步计算结果位和进位。

#!/usr/bin/env node
'use strict'
let H=[0,1]
,B=x=>H.push(x)-1
,nCalls=0
,nPairs=0
,F=pairs=>{
  nCalls++;nPairs+=pairs.length
  return pairs.map(([x,y])=>{let s=0;for(let i=0;i<x.length;i++)s^=H[x[i]]*H[y[i]];return B(s)})
}

// -----BEGIN SOLUTION-----
var f=(a,b)=>{
  var r=[], c // r:result bits (as box ids), c:carry (as a box id)
  r[0]=F([[[a[0],b[0]],[a[0],b[0]]]])          // r0 = a0 ^ b0
  c=F([[[a[0]],[b[0]]]])                       // c = a0*b0
  for(var i=1;i<16;i++){
    r.push(F([[[a[i],b[i],c],[a[i],b[i],c]]])) // ri = ai ^ bi ^ c
    c=F([[[a[i],b[i],c],[b[i],c,a[i]]]])       // c = ai*bi ^ bi*c ^ c*ai
  }
  return r
}
// -----END SOLUTION-----

// tests
let bits=x=>{let r=[];for(let i=0;i<16;i++){r.push(x&1);x>>=1}return r}
,test=(a,b)=>{
  console.info(bits(a))
  console.info(bits(b))
  nCalls=nPairs=0
  let r=f(bits(a).map(B),bits(b).map(B))
  console.info(r.map(x=>H[x]))
  console.info('calls:'+nCalls+',pairs:'+nPairs)
  console.assert(bits(a+b).every((x,i)=>x===H[r[i]]))
}

test(12345,6789)
test(12,3)
test(35342,36789)

在Dyalog APL中相同(但使用随机框ID):

⎕io←0⋄K←(V←⍳2),2+?⍨1e6⋄B←{(V,←⍵)⊢K[≢V]}⋄S←0⋄F←{S+←1,≢⍵⋄B¨2|+/×/V[K⍳↑⍉∘↑¨⍵]}
⍝ -----BEGIN SOLUTION-----
f←{
  r←F,⊂2⍴⊂⊃¨⍺⍵        ⍝ r0 = a0 ^ b0
  c←⊃F,⊂,¨⊃¨⍺⍵        ⍝ c = a0*b0
  r,⊃{
    ri←⊃F,⊂2⍴⊂⍺⍵c     ⍝ ri = ai ^ bi ^ c
    c⊢←⊃F,⊂(⍺⍵c)(⍵c⍺) ⍝ c = ai*bi ^ bi*c ^ c*ai
    ri
  }¨/1↓¨⍺⍵
}
⍝ -----END SOLUTION-----
bits←{⌽(16⍴2)⊤⍵}
test←{S⊢←0⋄r←⊃f/B¨¨bits¨⍺⍵
      ⎕←(↑bits¨⍺⍵)⍪V[K⍳r]⋄⎕←'calls:' 'pairs:',¨S
      (bits⍺+⍵)≢V[K⍳r]:⎕←'wrong!'}
test/¨(12345 6789)(12 3)(35342 36789)
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.