Blind binary adder
Python 3, 5 calls, 92 pairs, 922 bytes
Python 3, 5 calls, 134 pairs, 3120 bytes
Python 3, 6 calls, 106 pairs, 2405 bytes
[JavaScript (Node.js)], 9 calls, 91 pairs, 1405 bytes
JavaScript (Node.js), 16 calls, 31 pairs, 378 bytes
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
Try it online!
FIRST VERSION Okay that's not golfed. It's just an adaptation of @ngn's code.
The only idea here is that you don't need to compute the last carry since you discard overflow. Also, the calls of F
are grouped by two. Maybe they may be grouped in another way, but I doubt you can reduce significantly the number of pairs, due to the nature of the basic addition algorithm.
EDIT: Still not golfed. The number of pairs could certainly be reduced, and probably the number of calls too. See https://gist.github.com/jferard/864f4be6e4b63979da176bff380e6c62 for a "proof" with sympy.
EDIT 2 Switched to Python because it's more readable for me. Now I have got the general formula, I think I may reach the limit of 5 (maybe 4) calls.
EDIT 3 Here are the basic bricks:
alpha[i] = a[i] ^ b[i]
beta[i] = a[i] * b[i]
c[0] = beta[0]
r[0] = alpha[0]
The general formula is:
c[i] = alpha[i]*c[i-1] ^ beta[i]
r[i] = a[i] ^ b[i] ^ c[i-1]
The expanded version is:
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 calls seems the limit for me. Now I have a little work to remove pairs and golf it!
EDIT 4 I golfed this one.
Ungolfed version:
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
Try it online!
Haskell, 1 call (cheating???), 32 pairs (could be improved), 283 bytes (same)
Please don't be angry with me, I don't want to win with this, but I was encouraged in the remarks to the challenge to explain what I was talking about.
I tried to use the state monad to handle adding boxes and counting calls and pairs, and that worked, but I didn't manage to make my solution working in that setting. So I did what was also suggested in the comments: just hide the data behind a data constructor and don't peek. (The clean way would be to use a seperate module and not export the constructor.) This version has the advantage of being much simpler.
Since we are talking about boxes of bits, I put Bool
values into them.
I define zero
as the given box with the zero bit - a one
is not needed.
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
We're using the debugging function trace
to see how often f
was called, and with how many pairs. &&&
looks into the boxes by pattern matching, the inequality /=
used on Bool
values is xor
.
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)
The test
function takes a blind binary adder as first argument, and then two numbers for which addition is tested. It returns a Bool
indicating whether the test was successful. First the input boxes are created, then the adder is called, the result unboxed (with unB
) and compared with the expected result.
I implemented two adders, the sample solution simple
, so that we can see that the debug output works correctly, and my solution using 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]) ] ]
See how I'm defining res
in terms of itself? That is also known as tying the knot.
Now we can see how f
is only called once:
*Main> test valrec 123 456
f was called with 32 pairs
True
Or replace valrec
by simple
to see f
being called 32 times.
Try it online! (the tracing output appears under "Debug")