Adicionador binário cego

10

Imagine que você tem duas caixas B(x)e B(y), cada uma contendo um bit desconhecido - 0 ou 1, e uma máquina Fque pode radiografá-las e produzir uma terceira caixa para B(x^y)( xor ). Ftambém pode calcular B(x*y)( e ). De fato, esses são apenas casos especiais da operação única que a máquina pode executar - produto interno cada , indicado F()abaixo.

Para duas matrizes do mesmo comprimento

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

produto interno é definido como

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

" Cada " meios F()pode processar vários pares de x[], y[]de uma só vez. O x[]e y[]de um par deve ter o mesmo comprimento; x[]-s e y[]-s de pares diferentes não precisam necessariamente.

As caixas são representadas por IDs inteiros exclusivos.

Uma implementação de produto interno, cada uma em JavaScript, pode parecer

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

(Traduza o texto acima para o seu idioma preferido.)

Dado o acesso a uma F()implementação conforme apropriado para o seu idioma (mas sem acesso a Hou B()) e duas matrizes de IDs de caixa que constituem as representações binárias de 16 bits de dois números inteiros ae b, sua tarefa é produzir IDs de caixa para a representação binária de 16 bits de a+b(descartando estouro) com o número mínimo de F()chamadas.

A solução que chama F()menos vezes vence. Os laços serão quebrados contando o número total de x[],y[]pares que F()foram chamados - menos é melhor. Se ainda estiver empatado, o tamanho do seu código (excluindo a implementação F()e seus auxiliares) determina o vencedor da maneira tradicional do código de golfe. Por favor, use um título como "MyLang, 123 chamadas, 456 pares, 789 bytes" para sua resposta.

Escreva uma função ou um programa completo. Entrada / saída / argumentos / resultado são matrizes int em qualquer formato razoável. A representação binária pode ser pequena ou grande endian - escolha uma.


Apêndice 1: Para facilitar um pouco o desafio, você pode assumir que as caixas com os IDs 0 e 1 contêm os valores 0 e 1. Isso fornece constantes, úteis, por exemplo, para negação ( x^1não é ")". Havia maneiras de contornar a falta de constantes, é claro, mas o resto do desafio já é difícil o suficiente, então vamos eliminar essa distração.


Apêndice 2: Para ganhar a recompensa, siga um destes procedimentos:

  • publique sua pontuação (chamadas, pares, bytes) e seu código antes do prazo

  • publique sua pontuação e um hash sha256 do seu código antes do prazo; depois publique o código real dentro de 23 horas após o prazo

ngn
fonte
Se eu traduzi isso para o meu idioma de escolha (Haskell), poderia usar a recursão de valor e ligar Fapenas uma vez. Certamente isso seria trapaça, mas não tenho certeza se seria uma boa trapaça ou má trapaça.
Christian Sievers
Sei que o estado global não é bem-vindo em Haskell, mas deixe-me perguntar isso como um experimento mental: se eu incrementasse um contador global na implementação de F, em quanto ele teria crescido no final? - esse é o meu entendimento do "número de chamadas".
NGN
Eu poderia fazer exatamente isso e diria 1. Mas não foi possível traduzir de volta para JavaScript usando seu código. Basicamente, eu diria y=f(x)e deixarei xdepender y.
Christian Sievers
Receio não entender como isso funcionaria. Você poderia mostrar um código de amostra, por favor? Meu Haskell é ruim, mas tenho certeza de que posso descobrir se posso brincar com o código.
NGN
Talvez possamos usar os seguintes tipos para modelar esse problema? data Box = B Int deriving (Show); f :: [[[Box]]] -> [Box]Vou precisar de mais tempo para descobrir como implementar f(Haskell força minúsculas aqui) - tentarei amanhã.
NGN

Respostas:

6

Python 3 , 5 chamadas, 92 pares, 922 bytes

Python 3 , 5 chamadas, 134 pares, 3120 bytes

Python 3 , 6 chamadas, 106 pares, 2405 bytes

[JavaScript (Node.js)], 9 chamadas, 91 pares, 1405 bytes

JavaScript (Node.js), 16 chamadas, 31 pares, 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

Experimente online!

PRIMEIRA VERSÃO Ok, isso não é golfe. É apenas uma adaptação do código do @ ngn.

A única idéia aqui é que você não precisa calcular o último transporte, pois descarta o estouro. Além disso, as chamadas de Fsão agrupadas por dois. Talvez eles possam ser agrupados de outra maneira, mas duvido que você possa reduzir significativamente o número de pares, devido à natureza do algoritmo de adição básico.

EDIT : Ainda não jogou golfe. O número de pares certamente poderia ser reduzido, e provavelmente o número de ligações também. Consulte https://gist.github.com/jferard/864f4be6e4b63979da176bff380e6c62 para obter uma "prova" com sympy.

EDIT 2 Mudei para Python porque é mais legível para mim. Agora que tenho a fórmula geral, acho que posso atingir o limite de 5 (talvez 4) chamadas.

EDIT 3 Aqui estão os tijolos básicos:

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

A fórmula geral é:

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

A versão expandida é:

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 chamadas parece o limite para mim. Agora tenho um pouco de trabalho para remover pares e jogar golfe!

EDIT 4 Joguei este aqui.

Versão não destruída:

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

Experimente online!

jferard
fonte
Muito bom :) Você encontrou as duas otimizações fáceis que deixei de propósito. "Duvido que você possa reduzir significativamente o número de pares" - observe que o primeiro critério para ganhar é o número de chamadas para F(). Garanto que existe uma maneira de reduzi-los significativamente (essa é a parte mais difícil deste desafio) e, em seguida, haverá espaço para otimizar o número de pares e, finalmente, é claro, jogar o código (mas esse é o critério menos importante).
ngn 27/08/17
OK, entendi! Cedo ou tarde, você tem algo assim: ... + x * y * z + .... Não podemos Favaliar isso, mas se tivermos calculado x * ycom a Fchamada anterior , basta fazer o seguinte: ... + (x * y) * z + ...(corresponde ao formato de F). Jogando com o sympy, consegui poupar uma chamada (passo1: calcular r0, c0, r1; passo2: calcular c1 e alguns valores auxiliares; passo3: calcular r2, c2, r3, c3) e agora estou procurando um general solução.
jferard
Sim, em outras palavras: os bits de saída são polinômios de grau maior que 2 nos bits de entrada. O produto interno pode combinar um polinômio m-grau e n-grau em um polinômio (m + n) de grau, no máximo. Não se apresse - em algumas horas poderei criar uma recompensa :)
NGN
Você pode considerar aproveitar o Apêndice 2 acima. Ou então: se alguém copiar seu código, remover um espaço e repassá-lo, tecnicamente eu terei que atribuir o bônus a eles.
NGN
2
Para o registro, é impossível usar menos de cinco chamadas, pois a solução requer um polinômio de grau 32. (O polinômio correspondente a qualquer função dos bits de entrada é único.)
Nitrodon
2

Haskell, 1 chamada (trapaça ???), 32 pares (poderia ser melhorado), 283 bytes (o mesmo)

Por favor, não fique com raiva de mim, não quero vencer com isso, mas fui encorajado nas observações ao desafio de explicar o que estava falando.

Tentei usar a mônada do estado para lidar com a adição de caixas e a contagem de chamadas e pares, e isso funcionou, mas não consegui fazer minha solução funcionar nessa configuração. Então fiz o que também foi sugerido nos comentários: apenas oculte os dados atrás de um construtor de dados e não espreite. (A maneira mais limpa seria usar um módulo separado e não exportar o construtor.) Esta versão tem a vantagem de ser muito mais simples.

Já que estamos falando de caixas de bits, eu coloco Boolvalores nelas. Eu defino zerocomo a caixa fornecida com o bit zero - a onenão é necessário.

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

Estamos usando a função de depuração tracepara ver com que frequênciaf foi chamado e com quantos pares. &&&olha para as caixas pela correspondência de padrões, a desigualdade /= usada nos Boolvalores é 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)

A testfunção pega um somador binário cego como primeiro argumento e, em seguida, dois números para os quais a adição é testada. Retorna Boolindicando se o teste foi bem-sucedido. Primeiro, as caixas de entrada são criadas e, em seguida, o somador é chamado, o resultado descompactado (com unB) e comparado com o resultado esperado.

Eu implementei dois adicionadores, a solução de amostra simple , para que possamos ver que a saída de depuração funciona corretamente e minha solução usando recursão de valor 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]) ] ]

Veja como eu estou definindo resem termos de si mesmo? Isso também é conhecido como amarrar o nó .

Agora podemos ver como f é chamado apenas uma vez:

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

Ou substitua valrec por simplepara fser chamado 32 vezes.

Experimente online! (a saída de rastreamento aparece em "Debug")

Peneiradores cristãos
fonte
Não há raiva aqui :) Então, se eu entendi direito, o argumento para fé uma lista preguiçosa e potencialmente infinita que se materializa à medida que você itera nela? Receio que isso seja contrário ao espírito do desafio - ele permite que você adie a decisão sobre o que passar como o i+1-st argumento até depois de obter o resultado correspondente ao i-th. É muito mais interessante descobrir quantas chamadas para fvocê precisará com totalmente materializado, argumentos imutáveis :)
NGN
Concordo. @jferard fez um trabalho incrível que não deve ser invalidado por esse truque. Embora fpossa receber entrada infinita (adicione fluxos de bits infinitos, yay!), Esse não é o ponto. Ah, e na verdade a tracemensagem garante que o comprimento seja finito e conhecido no início. Além disso, eu não diria que há uma decisão adiada: tudo foi planejado com antecedência, como exigido, estou apenas remexendo cegamente as caixas. E observe que não se trata de ordem dos argumentos: eu poderia alterá-lo para que resprimeiro contenha o resultado e depois os bits de transporte.
Christian Sievers
"Estou apenas remexendo cegamente as caixas" - Suponha que você tenha obtido uma caixa ligando f; você alimenta essa caixa como outro argumento na mesma chamada para f?
NGN
Sim eu quero. É disso que se trata a recursão de valor. Você tinha esse direito: é usar preguiça e o fato de poder usar argumentos que não são totalmente materializados (gosto dessa descrição). Dado o espírito óbvio do desafio, isso é - como anunciado - claramente trapaça. Se alguém pensa que é inventivo ou digno de nota, pode-se argumentar que é uma boa trapaça.
Christian Sievers
Certamente é do tipo bom - obviamente você não tem intenção de enganar aqui. Preguiça na programação funcional é um conceito bonito e tem seus usos válidos. Quando tentei aprender alguns Haskell há alguns anos, lembro-me de ficar muito impressionado com uma frase que "dá um nó" para os números de Fibonacci.
ngn 30/08/19
0

JavaScript, 32 chamadas, 32 pares, 388 bytes

Dyalog APL, 32 chamadas, 32 pares, 270 bytes

Esta é uma solução de amostra ingênua que pode servir como modelo.

Observe que a contagem de bytes deve incluir apenas a seção cercada com "BEGIN / END SOLUTION".

Explicação:

Eu escolhi a ordem de bits little-endian (x[0] é o bit menos significativo).

Observe que a adição de bit único mod 2 pode ser realizada como F([[[x,y],[x,y]]])(isto é: x*x ^ y*y- multiplicação mod 2 é idempotente) e multiplicação binária comoF([[[x],[y]]]) .

Atravessamos os bits do menos significativo para o mais significativo e, a cada passo, calculamos um bit de resultado e um carry.

#!/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)

O mesmo no Dyalog APL (mas usando IDs de caixa aleatórios):

⎕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)
ngn
fonte