(Acest articol a fost publicat pentru prima dată pe R Funcționeazăși cu amabilitate a contribuit la R-bloggeri). (Puteți raporta problema legată de conținutul acestei pagini aici)
Doriți să vă distribuiți conținutul pe R-bloggeri? dați clic aici dacă aveți un blog, sau aici dacă nu aveți.
Problema celor douăsprezece monede este o problemă notoriu de grea, care vine în multe arome. Nu știu de unde vine inițial, dar a atras destul de multă atenție din partea matematicienilor la mijlocul secolului al XX-lea. Se pare că unele versiuni ale acestuia ar fi putut chiar să fi distras atenția oamenilor de știință de la cercetările lor în domeniul apărării în timpul celui de-al Doilea Război Mondial!
Problema a fost populară de ambele maluri ale Atlanticului în timpul celui de-al Doilea Război Mondial; s-a sugerat chiar că ar trebui aruncat peste Germania în încercarea de a sabota efortul lor de război…(Guy și Nowakowski 1995)
Iată versiunea pe care mi-am propus să o rezolv:
Ai douăsprezece monede, exact identice ca aspect; dar, eventual, unul este contrafăcut (o să-l numim un „dud”). Nu știi dacă un dud este prezent și nici dacă este mai greu sau mai ușor decât monedele bune. Poți determina în trei cântăriri pe o cântar: (1) dacă există un dud, (2) dacă da, care monedă și (3) greutatea ei relativă (mai ușoară sau mai grea decât monedele bune)?
Un detaliu suplimentar:
- Puteți presupune că monedele sunt etichetate individual (de exemplu, de la 1 la 12)1. Etichetele înseamnă că puteți urmări ce monede au fost în tăvile din stânga sau din dreapta ale cântarului, sau pe masă, pentru fiecare cântărire individuală.
Atunci când specialiștii în puzzle-uri încearcă să rezolve această problemă, în general vin cu ceea ce voi numi o soluție „arborele de decizie”: odată ce ați făcut o cântărire, următoarea cântărire depinde de rezultatul celei anterioare. O astfel de soluție pentru arborele de decizie este aici. Însă în 1946, Freeman J. Dyson a venit cu o soluție elegantă, „inutilă”. (Dyson 1946). Prin „incuviință” înțeleg că soluția lui Dyson predefinește o succesiune de cântăriri, care la sfârșit este garantat să identifice greșeala (dacă există una) și greutatea sa relativă, sau raportează că nu există greșeală.
Dyson mai arată că douăsprezece monede este cel mult pe care îl poți rezolva în trei cântăriri, în condițiile pe care le-am dat în enunțul problemei. Bănuiesc (deși nu văd o dovadă pentru asta și mi-e prea lene să o dovedesc eu) că, dacă relaxăm problema, să presupunem că există este un dud, și să încercăm să-l identificăm, dar nu neapărat greutatea lui, putem rezolva până la treisprezece monede în trei cântăriri. Dyson arată că, dacă aveți o monedă suplimentară despre care se știe că este bună, puteți rezolva 13 monede necunoscute în trei cântăriri și puteți determina greutatea relativă a rătăcirii.
Algoritmul lui Dyson
Algoritmul lui Dyson rezolvă de fapt o versiune mai generală a problemei monedelor:
Aveți monede, la apariția exact identice; dar, eventual, unul este contrafăcut (o să-l numim un „dud”). Nu știi dacă un dud este prezent și nici dacă este mai greu sau mai ușor decât monedele bune. Poti determina in cântăriri pe o cântar: (1) dacă există un dud, (2) dacă da, care monedă și (3) greutatea ei relativă (mai ușoară sau mai grea decât monedele bune)?
Dyson arată că cel mai mare număr de monede care se poate rezolva în cântăririle este . Adică 3 monede în 2 cântăriri, 12 monede în 3 cântăriri, 39 de monede în 4 cântăriri și așa mai departe. Algoritmul pe care îl voi arăta în această postare rezolvă cazul în care aveți exact monede. Voi discuta cazul general în care într-un articol următor.
Ideea de bază a algoritmului lui Dyson este să eticheteze fiecare monedă după reprezentarea sa trinară și apoi să programeze cântăririle în așa fel încât, cu reprezentarea adecvată a rezultatelor măsurătorilor, cântăririle „scriu” eticheta monedei dud, dacă există. Există o etichetă cunoscută pentru cazul în care nu există greșeală.
Algoritmul original al lui Dyson a folosit reprezentări standard de bază 3, unde cifrele sunt (0, 1, 2). Am decis să implementez versiunea mea folosind trinar semnat, unde cifrele sunt (-1, 0, 1). Aceasta este reprezentarea pe care am folosit-o pentru puzzle-ul cu patru greutăți și mi se pare mai naturală pentru problemele cântarelor. Ca bonus, face și algoritmul lui Dyson mai ușor de descris.
Algoritmul
Voi descrie procedura cu exemplul (3 monede, 2 cântăriri), deoarece este ușor de rezolvat manual. Apoi vă voi indica un cod pentru cazuri mai mari.
1: Etichetați monedele cu numerele de la 1 la și convertiți-le în trinar semnat folosind cifre. Am descris mai înainte procedura de conversie în trinar semnat, aici. Pentru arata cam asa:
1 = (0 1) 2 = (1 -1) 3 = (1 0)
2: Setați reprezentările trinare să se rotească „înainte”.
Spunem că un număr în baza 3 se rotește „înainte” (Dyson l-a numit „în sensul acelor de ceasornic”) dacă prima schimbare de cifre crește cu 1, modulo 3. Deci succesiunea înainte este (Rețineți că și ). În R, arată cam așa, presupunând că ați implementat numărul de bază 3 ca vector:
is_forward = function (ptrinary) { delta = diff(ptrinary) # get only the nonzero diffs delta = delta(!(delta==0)) if(length(delta)==0) stop("constant vector") # check if we are rotating forward, and return delta(1) %% 3 == 1 }
Dacă un număr trinar semnat se rotește înainte, apoi negație se rotește înapoi și invers. Deci înlocuiți oricare dintre etichetele monedelor care se rotesc înapoi cu negarea sa:
1 = (0 1) : rotates forward 2 = (1 -1): rotates forward 3 = (1 0): rotates backward, so replace with (-1 0) (-3)
Reprezentarea tuturor monedelor cu numere care se rotesc înainte elimină ambiguitatea care apare din faptul că nu se știe dacă moneda dud este mai grea sau mai ușoară decât monedele bune: dacă balanța înclină panoul din stânga în jos, asta ar putea fi din cauză că un dud greu este în stânga. tigaie, sau pentru că în tigaia din dreapta se află un dud mai ușor. După cum vom vedea, setarea tuturor etichetelor monedelor să se rotească înainte înseamnă că o greșeală grea va produce un model de comportamente care „exclude” locația etichetei, în timp ce o greșeală ușoară va explica negația etichetei.
3: Planificați programul de cântărire.
Acum haideți să etichetăm pozițiile posibile pe (sau în afara) cântarului. înseamnă tigaia din stânga, înseamnă tigaia potrivită și înseamnă masa. Programul de cântărire este astfel încât pentru Cântărirea, o monedă dată se află în locația descrisă de eticheta ei a-a cifră (numărând de la stânga, deci cifra celor este ultima). Pentru exemplul nostru, acesta oferă programul:
1 = (0 1): table, then right pan 2 = (1 -1): right pan, then left pan 3 = (-1 0): left pan, then table
Folosind notația pe scară pe care am folosit-o pentru problemele de echilibru, arată astfel:
First weighing: (coin 3 | coin 2) ------------------ coin 1 Second weighing: (coin 2 | coin 1) ----------------- coin 3
Rețineți că programul de cântărire poate fi calculat în avans dacă știți (şi ), chiar înainte de a vedea un anumit set de monede. De asemenea, poate fi folosit din nou și din nou, pentru orice set de monede. Aceasta este ceea ce ne referim când numim acest algoritm „inutil”.
4: Cântăriți monedele conform programului
Vom înregistra rezultatul fiecărei cântăriri, ca tigaie a fost mai grea (daca exista): înseamnă că tigaia din stânga este mai grea, înseamnă că tigaia dreaptă este mai grea, înseamnă că cântarul este echilibrat. Aceasta înseamnă că dacă Moneda este o greșeală grea, își va scrie propria etichetă în trinar semnat, ca . O greșeală ușoară va explica negarea etichetei. Puteți recupera reprezentarea zecimală a rezultatului final, ca
Atunci locația răului este iar dud-ul este greu dacă se rotește înainte și este ușor dacă se rotește înapoi. Dacă nu există greșeală, atunci .
Rețineți că semnul de NU este dacă dud-ul este greu sau ușor; este dacă reprezentarea trinară originală a rotit înainte sau înapoi.
Și asta este! Să trecem prin câteva exemple.
- Fără greșeală
- Scala: (echilibrat, echilibrat), deci : nu prost
- Dud-ul este moneda 1 și este ușor
- Scala: (echilibrat, stânga), deci şi se rotește înapoi: monedă 1, lumină.
- Rasul este moneda 2 și este greu
- Scară: (dreapta, stânga), deci şi se rotește înainte: moneda 2, grea.
- Rasul este moneda 3 și este greu
- Scala: (stânga, echilibrată), deci şi se rotește înainte: moneda 3, grea.
Înapoi la cele 12 monede
Așa că acum putem rezolva problema la care ne pasă de fapt. S-ar putea face manual, dar codul este mai ușor. Am codul R pentru cazul special aici.
Să arătăm reprezentările înainte ale monedelor.
source("dyson_signed_specialcase.R") nrounds = 3 ncoins = (3^nrounds - 3)/2 # 12 # Show the forward representations of the coins get_forward_representation(nrounds)
(,1) (,2) (,3) (1,) 0 0 1 (2,) 0 1 -1 (3,) 0 1 0 (4,) 0 1 1 (5,) 1 -1 -1 (6,) 1 -1 0 (7,) 1 -1 1 (8,) -1 0 1 (9,) -1 0 0 (10,) -1 0 -1 (11,) 1 1 -1 (12,) -1 -1 0
În implementarea mea, precompilez programul de cântărire din timp, astfel încât să pot cântări monede din nou și din nou. The precompile
apeluri de funcții get_forward_representation
intern și returnează o listă. Fiecare element al listei este programul de cântărire pentru acea cântărire, ca un cadru de date, cu coloane (left, table, right)
. Fiecare coloană a cadrului de date conține monedele care intră în acea poziție pentru acea cântărire.
Cset = precompile(ncoins, nrounds) # show the schedule knitr::kable(Cset((1)), caption = "Weighing 1")
8 | 1 | 5 |
9 | 2 | 6 |
10 | 3 | 7 |
12 | 4 | 11 |
knitr::kable(Cset((2)), caption = "Weighing 2")
5 | 1 | 2 |
6 | 8 | 3 |
7 | 9 | 4 |
12 | 10 | 11 |
knitr::kable(Cset((3)), caption = "Weighing 3")
2 | 3 | 1 |
5 | 6 | 4 |
10 | 9 | 7 |
11 | 12 | 8 |
Acum putem găsi prostii. Nu contează cât cântăresc monedele, atâta timp cât toate monedele bune cântăresc aceeași cantitate. Funcția mea find_dud
returnează o valoare D
astfel încât abs(D)
dă indicele dudului și sign(D)
dă greutatea relativă a dud-ului; negativ înseamnă că dud-ul este ușor, iar pozitiv înseamnă că dud-ul este greu. Cu alte cuvinte, !! Dar notația este concisă și ușor de scris cecuri pentru.
# a good coin weighs 1 unit coins = numeric(ncoins) + 1 epsilon = 0.5 # check the no dud case find_dud(coins, Cset)
# check a specific case idud = 7 relative_wt = -1 # make it lighter coins(idud) = 1 + epsilon*relative_wt find_dud(coins, Cset)
# verify this works in all possible cases for(idud in 1:ncoins) { for(relative_wt in c(-1, 1)) { actual = idud*relative_wt # location and direction coins = numeric(ncoins) + 1 coins(idud) = 1 + epsilon*relative_wt A = find_dud(coins, Cset) stopifnot(actual==A) } }
Și aceasta este soluția monede în problema cântăririlor, pentru cazul special când . În următoarea postare, voi descrie cazul general, când .
Nina Zumel este un cercetător de date cu sediul în San Francisco, cu peste 20 de ani de experiență în învățarea automată, statistică și analiză. Ea este co-fondatoare a firmei de consultanță în știința datelor Win-Vector LLC și (împreună cu John Mount) co-autoarea cărții Practical Data Science cu R, aflată la a doua ediție.