Sommatore binario cieco


10

Immagina di avere due scatole B(x)e B(y), ognuna contenente un bit sconosciuto - 0 o 1, e una macchina in Fgrado di eseguire i raggi X e produrre una terza scatola per B(x^y)( xor ). Fpuò anche calcolare B(x*y)( e ). In effetti, questi sono solo casi speciali della singola operazione che la macchina può eseguire - prodotto interno ciascuno , indicato con F()sotto.

Per due matrici della stessa lunghezza

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

il prodotto interno è definito come

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

" Ciascuno " significa che F()può elaborare più coppie di x[], y[]in una volta sola. La coppia da x[]e y[]deve avere la stessa lunghezza; x[]-s e y[]-s di coppie diverse non sono necessariamente necessari.

Le caselle sono rappresentate da ID interi univoci.

Potrebbe apparire un'implementazione del prodotto interno ciascuno in 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);
  })
}

(Per favore traduci quanto sopra nella tua lingua preferita.)

Dato l'accesso a F()un'implementazione appropriata per la tua lingua (ma nessun accesso a Ho B()) e dati due matrici di ID casella che rappresentano le rappresentazioni binarie a 16 bit di due numeri interi ae il btuo compito è produrre ID casella per la rappresentazione binaria a 16 bit di a+b(eliminazione dell'overflow) con il numero minimo di F()chiamate.

F()Vince la soluzione che chiama il minor numero di volte. I legami verranno interrotti contando il numero totale di x[],y[]coppie F()chiamate: meno è meglio. Se ancora legato, la dimensione del tuo codice (esclusa l'implementazione di F()e dei suoi aiutanti) determina il vincitore nel tradizionale modo di giocare a golf. Si prega di utilizzare un titolo come "MyLang, 123 chiamate, 456 coppie, 789 byte" per la risposta.

Scrivi una funzione o un programma completo. Input / output / argomenti / risultato sono array int in qualsiasi formato ragionevole. La rappresentazione binaria può essere little o big endian: scegline una.


Appendice 1: Per rendere la sfida leggermente più semplice, puoi presumere che le caselle con ID 0 e 1 contengano i valori 0 e 1. Questo ti dà costanti, utili ad esempio per la negazione ( x^1è "non"). Ci sono stati modi per aggirare la mancanza di costanti, ovviamente, ma il resto della sfida è comunque abbastanza difficile, quindi eliminiamo questa distrazione.


Appendice 2: per vincere la taglia, devi effettuare una delle seguenti operazioni:

  • pubblica il tuo punteggio (chiamate, coppie, byte) e il tuo codice prima della scadenza

  • pubblica il tuo punteggio e un hash sha256 del tuo codice prima della scadenza; quindi pubblicare il codice effettivo entro 23 ore dopo la scadenza


Se lo traducessi nella mia lingua preferita (Haskell), potrei usare la ricorsione del valore e chiamare Fsolo una volta. Sarebbe sicuramente un imbroglio, ma non sono sicuro che sarebbe un buon imbroglio o un cattivo imbroglio.
Christian Sievers,

So che lo stato globale non è il benvenuto in Haskell, ma permettetemi di chiederlo come esperimento mentale: se incrementassi un contatore globale nell'implementazione di F, di quanto sarebbe cresciuto alla fine? - questa è la mia comprensione del "numero di chiamate".
ngn,

Potrei fare esattamente questo, e direi 1. Ma non è stato possibile tradurlo in JavaScript usando il tuo codice. In sostanza, direi y=f(x)e lasciamo xdipendere y.
Christian Sievers,

Temo di non capire come funzionerebbe. Potresti mostrare un codice di esempio, per favore? Il mio Haskell è scarso, ma sono sicuro di riuscire a capire se posso giocare con il codice.
ngn,

Forse possiamo usare i seguenti tipi per modellare questo problema? data Box = B Int deriving (Show); f :: [[[Box]]] -> [Box]Avrò bisogno di più tempo per capire come implementare f(le forze di Haskell sono minuscole qui): domani proverò.
ngn,

Risposte:


6

Python 3 , 5 chiamate, 92 coppie, 922 byte

Python 3 , 5 chiamate, 134 coppie, 3120 byte

Python 3 , 6 chiamate, 106 coppie, 2405 byte

[JavaScript (Node.js)], 9 chiamate, 91 coppie, 1405 byte

JavaScript (Node.js), 16 chiamate, 31 coppie, 378 byte

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

Provalo online!

PRIMA VERSIONE Va bene che non è golf. È solo un adattamento del codice di @ngn.

L'unica idea qui è che non è necessario calcolare l'ultimo carry poiché si elimina l'overflow. Inoltre, le chiamate di Fsono raggruppate per due. Forse possono essere raggruppati in un altro modo, ma dubito che tu possa ridurre significativamente il numero di coppie, a causa della natura dell'algoritmo di addizione di base.

EDIT : ancora non golf. Il numero di coppie potrebbe certamente essere ridotto, e probabilmente anche il numero di chiamate. Vedi https://gist.github.com/jferard/864f4be6e4b63979da176bff380e6c62 per una "prova" con sympy.

EDIT 2 Passato a Python perché è più leggibile per me. Ora ho la formula generale, penso che potrei raggiungere il limite di 5 (forse 4) chiamate.

EDIT 3 Ecco i mattoni di base:

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

La formula generale è:

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

La versione estesa è:

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 chiamate mi sembrano il limite. Ora ho un po 'di lavoro per rimuovere le coppie e giocare a golf!

EDIT 4 Ho giocato a golf questo.

Versione non golfata:

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

Provalo online!


Molto bene :) Hai trovato le due facili ottimizzazioni che avevo lasciato di proposito. "Dubito che tu possa ridurre significativamente il numero di coppie" - nota che il primo criterio per vincere è il numero di chiamate a F(). Garantisco che c'è un modo per ridurli significativamente (questa è la parte più difficile di questa sfida), e poi ci sarà spazio per ottimizzare il numero di coppie e, ovviamente, giocare a golf il codice (ma questo è il criterio meno importante).
ngn,

Ok ho capito! Prima o poi, hai qualcosa del genere: ... + x * y * z + .... Non possiamo usarlo Fper valutarlo, ma se abbiamo calcolato x * ycon la Fchiamata precedente , dobbiamo solo fare: ... + (x * y) * z + ...(corrisponde al formato di F). Giocando con Sympy, sono riuscito a risparmiare una chiamata (passaggio 1: calcolo r0, c0, r1; passaggio2: calcolo c1 e alcuni valori aux; passaggio3: calcolo r2, c2, r3, c3) e ora sto cercando un generale soluzione.
jferard,

Sì, in altre parole: i bit di uscita sono polinomi di grado superiore a 2 nei bit di ingresso. Il prodotto interno può combinare un polinomio di grado m e n in un polinomio di grado (m + n), al massimo. Non abbiate fretta - tra poche ore sarò in grado di organizzare una taglia :)
ngn

Potresti prendere in considerazione la possibilità di approfittare dell'appendice 2 di cui sopra. Oppure: se qualcuno copia il tuo codice, rimuove uno spazio e lo riposiziona, tecnicamente dovrò assegnare loro il bonus.
ngn,

2
Per la cronaca, è impossibile utilizzare meno di cinque chiamate, poiché la soluzione richiede un polinomio di grado 32. (Il polinomio corrispondente a qualsiasi funzione dei bit di input è unico.)
Nitrodon

2

Haskell, 1 chiamata (barare ???), 32 coppie (potrebbe essere migliorato), 283 byte (stesso)

Per favore, non essere arrabbiato con me, non voglio vincere con questo, ma sono stato incoraggiato nei commenti alla sfida per spiegare di cosa stavo parlando.

Ho provato a usare la monade di stato per gestire l'aggiunta di caselle e il conteggio di chiamate e coppie, e ha funzionato, ma non sono riuscito a far funzionare la mia soluzione in quell'impostazione. Quindi ho fatto ciò che è stato anche suggerito nei commenti: basta nascondere i dati dietro un costruttore di dati e non sbirciare. (Il modo più semplice sarebbe usare un modulo separato e non esportare il costruttore.) Questa versione ha il vantaggio di essere molto più semplice.

Dato che stiamo parlando di scatole di bit, ho inserito dei Boolvalori in esse. Definisco zerola casella data con il bit zero - a onenon è necessario.

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

Stiamo usando la funzione di debug traceper vedere quanto spesso fveniva chiamato e con quante coppie. &&&esamina le caselle per corrispondenza dei modelli, la disuguaglianza /= utilizzata sui Boolvalori è 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)

La testfunzione accetta un sommatore binario cieco come primo argomento, quindi due numeri per i quali viene testata l'aggiunta. Restituisce Boolun'indicazione se il test ha avuto esito positivo. Prima vengono create le caselle di input, quindi viene chiamato il sommatore, il risultato viene decompresso (con unB) e confrontato con il risultato previsto.

Ho implementato due componenti aggiuntivi, la soluzione di esempio simple, in modo da poter vedere che l'output di debug funziona correttamente e la mia soluzione utilizzando la ricorsione del valore 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]) ] ]

Vedi come sto definendo resin termini di se stesso? Questo è anche noto come legare il nodo .

Ora possiamo vedere come fviene chiamato solo una volta:

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

O sostituisci valreccon simpleper vedere fessere chiamato 32 volte.

Provalo online! (l'output di traccia viene visualizzato in "Debug")


Niente rabbia qui :) Quindi, se ho capito bene, l'argomento fè un elenco pigro, potenzialmente infinito, che si materializza mentre si scorre attraverso di esso? Temo che ciò sia contrario allo spirito della sfida: ti consente di rinviare la decisione su cosa passare come primo i+1argomento fino a quando non avrai ottenuto il risultato corrispondente al i-esimo. È molto più interessante scoprire di quante chiamate favrai bisogno con argomenti completamente materializzati e immutabili :)
ngn,

Sono d'accordo. @jferard ha fatto un lavoro straordinario che non dovrebbe essere invalidato da un simile trucco. Anche se fpotrebbe richiedere input infiniti (aggiungi flussi di bit infiniti, yay!), Non è questo il punto. Oh, e in realtà il tracemessaggio assicura che la lunghezza sia finita e conosciuta all'inizio. Inoltre, non direi che c'è una decisione differita: tutto è stato pianificato in anticipo, come richiesto, sto solo mescolando ciecamente le scatole. E nota che non riguarda l'ordine degli argomenti: potrei cambiarlo in modo che rescontenga prima il risultato e poi i bit di riporto.
Christian Sievers,

"Sto solo mescolando ciecamente caselle" - Supponi di aver ottenuto una casella da chiamare f; rispondi a quella casella come un altro argomento nella stessa chiamata a f?
ngn,

Sì, certamente. Questo è il valore della ricorsione. Avevi ragione: usa la pigrizia e il fatto che posso usare argomenti che non sono completamente materializzati (mi piace quella descrizione). Dato l'ovvio spirito della sfida, questo è - come annunciato - chiaramente barare. Se uno pensa che sia inventivo o degno di nota, si potrebbe obiettare che è un buon imbroglio.
Christian Sievers,

È certamente del tipo buono - ovviamente non hai intenzione di ingannare qui. La pigrizia nella programmazione funzionale è un concetto bellissimo e ha i suoi usi validi. Quando ho provato ad imparare un po 'di Haskell qualche anno fa, ricordo di essere rimasto molto colpito da un singolo esempio che "lega il nodo" per i numeri di Fibonacci.
ngn,

0

JavaScript, 32 chiamate, 32 coppie, 388 byte

Dyalog APL, 32 chiamate, 32 coppie, 270 byte

Questa è una soluzione di esempio ingenua che può fungere da modello.

Si noti che il conteggio dei byte deve includere solo la sezione racchiusa tra "BEGIN / END SOLUTION".

Spiegazione:

Ho scelto l'ordine dei bit little-endian ( x[0]è il bit meno significativo).

Si noti che l'addizione a bit singolo mod 2 può essere realizzata come F([[[x,y],[x,y]]])(ovvero: x*x ^ y*y- la moltiplicazione mod 2 è idempotente) e la moltiplicazione binaria come F([[[x],[y]]]).

Attraversiamo i bit dal meno significativo al più significativo e ad ogni passo calcoliamo un bit di risultato e un 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)

Lo stesso in Dyalog APL (ma usando ID casuali casuali):

⎕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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.