Qual è l'algoritmo di taglio ottimale dell'unghia del piede ebraico?


118

Sto lavorando al software per una macchina che taglierà automaticamente le unghie dei piedi, in modo che gli utenti possano semplicemente metterci i piedi dentro ed eseguirlo invece di doverlo fare manualmente mordendole o usando un tagliaunghie.

Una percentuale considerevole della nostra potenziale base di utenti sarà probabilmente ebrea e, evidentemente, esiste una tradizione sul non tagliare le unghie dei piedi ( o le unghie ) in ordine sequenziale

Sembra esserci un'opinione dissenziente sull'applicazione precisa di questa tradizione, ma riteniamo che le seguenti regole siano sufficienti per accogliere le persone le cui pratiche religiose vietano di tagliare le unghie dei piedi in ordine:

  • Non devono essere tagliate due unghie adiacenti
  • La sequenza di taglio sul piede sinistro non dovrebbe corrispondere alla sequenza sul piede destro
  • La sequenza di taglio su due cicli consecutivi non dovrebbe essere la stessa. Le sequenze non dovrebbero essere facilmente prevedibili, quindi l'hardcoding di una sequenza alternata non funziona.

Ecco come abbiamo deciso di numerare le dita dei piedi:

5 4 3 2 1  1 2 3 4 5
Left foot  Right foot

Ho scritto del codice per risolvere il problema, ma l'algoritmo utilizzato non è ottimale: infatti, la prestazione nel caso peggiore è O (∞) . Il modo in cui funziona è paragonabile a bogosort . Ecco una semplificazione dello pseudocodice del codice effettivamente utilizzato:

function GenerateRandomSequence
   sequence = Array[5]
   foreach (item in sequence)
       item = RandomNumberBetween(1,5)
   return sequence

function GetToenailCuttingOrder
   while (true)
      sequence = GenerateRandomSequence()
      if (!AllItemsAreUnique(sequence))
         continue
      if (NoTwoAdjacentItemsHaveConsecutiveNumbers(sequence))
         return sequence

do
    leftFootSequence = GetToenailCuttingOrder()
    rightFootSequence = GetToenailCuttingOrder()
until (leftFootSequence != rightFootSequence &&
       leftFootSequence != leftFootSequenceFromLastRun &&
       rightFootSequence != rightFootSequenceFromLastRun)

Fondamentalmente, genera sequenze casuali e controlla se soddisfano i criteri. Se non soddisfa i criteri, ricomincia. Non ci vuole una quantità di tempo ridicolmente lunga, ma è molto imprevedibile.

Mi rendo conto che il modo in cui lo sto facendo attualmente è piuttosto terribile, ma ho problemi a trovare un modo migliore. Qualcuno di voi può suggerire un algoritmo più elegante e performante?


28
Questo puzza come un problema con i compiti. Altrimenti, perché non codificare semplicemente la sequenza?
Michael Brown

24
Ho sentito parlare di mordere le unghie, ma le unghie dei piedi?
vola il

63
Il pensiero di una macchina per tagliare le unghie dei piedi è piuttosto spaventoso. Spero che questo sia davvero un compito a casa e non una tragedia dolorosa che aspetta di accadere.
Peter Recore

43
La sfida di programmazione qui è controllare la macchina che taglia le unghie dei piedi in modo che non ferisca le persone. Se c'è un programmatore in grado di risolvere quel problema, allora sicuramente quella persona può risolvere questo problema in due minuti.
vola il

41
Mi piace il fatto che una domanda sulla tradizione ebraica sia etichettata come (lingua) agnostica ... :-)
Steve Melnikoff

Risposte:


87

È possibile generare tutte le sequenze di taglio dell'unghia del piede possibili senza restrizioni e quindi filtrare tutte le sequenze che violano la regola ebraica. Fortunatamente, gli esseri umani hanno solo cinque dita per piede *, quindi ce ne sono solo 5! = 120 sequenze senza restrizioni.

Esempio di Python:

#seq is only valid when consecutive elements in the list differ by at least two.
def isValid(seq):
    for i in range(len(seq)-1):
        a = seq[i]
        b = seq[i+1]
        if abs(a-b) == 1:
            return False
    return True


from itertools import ifilter, permutations
validseqs = ifilter(isValid, permutations([1,2,3,4,5]))
for i in validseqs:
    print i

(1, 3, 5, 2, 4)
(1, 4, 2, 5, 3)
(2, 4, 1, 3, 5)
(2, 4, 1, 5, 3)
(2, 5, 3, 1, 4)
(3, 1, 4, 2, 5)
(3, 1, 5, 2, 4)
(3, 5, 1, 4, 2)
(3, 5, 2, 4, 1)
(4, 1, 3, 5, 2)
(4, 2, 5, 1, 3)
(4, 2, 5, 3, 1)
(5, 2, 4, 1, 3)
(5, 3, 1, 4, 2)

Per applicare la regola "nessuna ripetizione della stessa sequenza", puoi semplicemente scegliere quattro delle sequenze precedenti e usarle alternativamente. L'unico problema qui è che se conti i due alluci come "consecutivi", non puoi scegliere due sequenze che finiscono e iniziano con 1, rispettivamente.

* Potresti voler creare una variabile numberOfToesPerFoot, in modo da poterla cambiare facilmente in seguito se qualcuno dei tuoi clienti risulta avere meno dita dei piedi di quanto ti aspetti, o più.


22
Hai ragione! Non ho mai nemmeno considerato le persone con la polidattica . Sarebbe sbagliato escluderli.
Peter Olson

1
il caso con meno dita è coperto dall'algoritmo originale (sequenze accettabili per 5 dita sono accettabili per 4 dita). Sono quelle pazze dita extra che causano problemi;)
vola il

4
Soluzione molto bella! Tuttavia, mi avvicinerei a "nessuna ripetizione della stessa sequenza" in modo leggermente diverso. Basta fare in modo che la macchina ricordi quale sequenza ha usato per ultima e usarne una casuale (ma non la stessa) successiva. Funziona sia per il secondo piede che per i nuovi clienti ed è più casuale che restare con 4 sequenze.
Jakob

3
Si dovrebbe anche considerare le dita mancanti dall'amputazione, come il 3 ° dito mancante. Ciò causa problemi se, ad esempio, la rimozione del 3 ° dito ora fa sì che le dita 2 e 4 siano considerate sequenziali.
cdeszaq

2
E le persone con solo 2 dita in un piede? Possono tagliarsi le unghie dei piedi?
matiasg

26

Esiste un numero finito di sequenze che soddisfano le tue esigenze.

  1. Genera tutte le permutazioni di {1,2,3,4,5}. Sono solo 120.
  2. Rifiuta quelli che non soddisfano i requisiti e salva il set rimanente (in modo permanente).
  3. Scegli casualmente due sequenze diverse. Ricorda quali hai usato l'ultima volta.

EDIT: Se non si tratta davvero di dita dei piedi, ma di qualche problema casuale in cui il set può essere molto più grande di 5, lo spazio della sequenza diventa molto grande e la possibilità di ripetere la stessa sequenza sul secondo piede diventa molto piccola. Quindi generare sequenze casuali e rifiutarle se corrispondono è una buona idea. Generare sequenze casuali secondo alcune regole come "salta per due o tre, poi riempi gli spazi vuoti" sarà probabilmente più veloce della generazione di permutazioni casuali e test, e la possibilità di sovrapposizione sarà ancora piccola se il numero di "dita dei piedi" è grande .


20

In realtà, mi piace di più il tuo algoritmo originale.

Poiché 14 permutazioni su 120 funzionano, 106 su 120 non lo fanno. Quindi ogni controllo ha una probabilità di fallimento 106/120.

Ciò significa che il numero previsto di guasti è:

1*(106/120) + 2*(106/120)^2 + 3*(106/120)^3 + ...

Non è troppo difficile riassumere questa serie infinita:

S       = 1*x + 2*x^2 + 3*x^3 + ...

Moltiplicare per x:

x*S     =       1*x^2 + 2*x^3 + ...

Sottrarre:

S - x*S = x + x^2 + x^3 + ...

Moltiplica di nuovo per x e sottrai di nuovo:

x*S - x^2*S = x^2 + x^3 + ...
S - 2*x*S + x^2S = x
S*(1-x)^2 = x
S = x/(1-x)^2

Poiché x = 106/120, S = 64,9.

Quindi, in media , il tuo ciclo richiede solo 65 iterazioni per trovare una soluzione.

Qual è la probabilità che richieda, diciamo, mille iterazioni?

Bene, la probabilità di fallire ogni singola iterazione è 104/120, quindi la probabilità di fallire 1000 iterazioni è (104/120) ^ 1000, che è qualcosa come 10 ^ (- 63). Cioè, non lo vedrai mai accadere nella tua vita, e probabilmente non durante la vita dell'universo.

Nessuna tabella precalcolata, facile adattamento a diversi numeri di dita delle mani / dei piedi / mani / piedi, facile adattamento a diversi set di regole ... Cosa c'è che non va? Il decadimento esponenziale è una cosa meravigliosa.

[aggiornare]

Spiacenti, ho sbagliato la formula originale ... Dato che le mie probabilità non si sommano a 1. :-)

L'espressione corretta per il numero previsto di errori è:

0*(14/120) + 1*(106/120)*(14/120) + 2*(106/120)^2*(14/120) + ...

(Ad esempio, per ottenere esattamente due errori, sono necessari due errori seguiti da un successo . Due errori hanno probabilità (106/120) ^ 2; un successo ha probabilità (14/120); moltiplicarli insieme per ottenere il peso per il Termine "2".)

Quindi la mia S è spenta di un fattore (1-x) (cioè 14/120). Il numero effettivo atteso di guasti è solo x / (1-x) = 106/14 = 7,57. Quindi, in media, occorrono solo 8-9 iterazioni per trovare una soluzione (7,5 errori più un successo).

La mia matematica per il caso "1000 fallimenti" è ancora corretta, credo.


1
+1 per pensare fuori dagli schemi e dare un modo diverso di guardare al problema.
nalply

9

L'ovvio: trova un ordine che funzioni e codificalo. Ma non credo che tu voglia farlo.

Puoi generare permutazioni molto meglio del modo in cui lo stai facendo. Non è necessario eseguire il campionamento dei rifiuti. Usa uno shuffle di Fisher Yates su una permutazione inizialmente ordinata (1, 2, .. 5) e avrai una permutazione casuale. http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle

Ma in generale, il metodo di generazione e test mi sembra totalmente a posto, a condizione che la probabilità di generare una voce di successo sia abbastanza alta. Sono sicuro che ci sono molte sequenze valide secondo i tuoi criteri, una volta che passi a una permutazione casuale, dubito che dovrai fare molte iterazioni di rifiuto.


2

Niente di veramente nuovo qui, la stessa soluzione già postata da @Kevin, ma penso sia interessante vedere come si traduce in un linguaggio funzionale. In questo caso, Mathematica :

Extract[#,Position[Times @@ (Abs@#-1)&/@ Differences/@ #, Except@0, 1][[2 ;;]]]  
         &@ Permutations@Range@5

Alcune spiegazioni:

Permutations@Range@5 Calculates all permutations of {1, 2, 3, 4, 5}

Differences          Calculate the differences between adjacent elements
                     (we wish to discard all lists containing +1 or -1)

Times @@ (Abs@#-1)   Abs turns the -1s into +1s, and then to zeros by subtracting
                     one, then TIMES multiplies all elements, giving zero when 
                     the original result of "Differences" contained a +1 or a -1

Position ... Except@0 Returns the position of the non zero results

Extract              Returns the original elements according to the calculated 
                     positions

Il risultato finale è:

{{1, 3, 5, 2, 4}, {1, 4, 2, 5, 3}, {2, 4, 1, 3, 5}, {2, 4, 1, 5, 3}, 
 {2, 5, 3, 1, 4}, {3, 1, 4, 2, 5}, {3, 1, 5, 2, 4}, {3, 5, 1, 4, 2}, 
 {3, 5, 2, 4, 1}, {4, 1, 3, 5, 2}, {4, 2, 5, 1, 3}, {4, 2, 5, 3, 1}, 
 {5, 2, 4, 1, 3}, {5, 3, 1, 4, 2}}

modificare

Oppure, più difficile da spiegare, ma più breve:

Reap[ Table[ If[Times @@ (Abs@Differences@i - 1) != 0, Sow@i],
           {i, Permutations@Range@5}]][[2, 1]]

0

Non c'è davvero alcun motivo per introdurre la casualità in questo problema. Ci sono solo 14 sequenze che soddisfano questo problema e sicuramente un certo ordinamento di quelle sequenze soddisferebbe meglio il senso estetico che stai cercando di accogliere. Quindi, dovresti semplicemente ridurre questo problema a un algoritmo per scegliere una sequenza da quelle 14, probabilmente in un ordine preimpostato.

Implementazione Javascript dell'algoritmo per trovare il 14:

function swap (a, i, j) {
  var temp = a[i]
  a[i]=a[j]
  a[j]=temp
}

function permute (b, n, a) {
  if (n==4) {
    b.push(a.slice(0)) //copy array
  }
  else {
    for (var i = n; i < 5; i++) {
      swap(a,n,i)
      permute(b, n+1, a)
      swap(a,n,i)
    }
  }
}

var a = [1,2,3,4,5]
var b = []
var c = []

permute(b,0,a)

for (var i = 1; i < b.length-1; i++) {
  var good = true
  for (var j = 0; j < b[i].length; j++) {
    if (Math.abs(b[i][j-1]-b[i][j]) < 2 || Math.abs(b[i][j]-b[i][j+1]) < 2) {
      good = false
    }
  }
  if (good) c.push(b[i].join(''))
}

console.log(c)

EDIT: Il nuovo requisito che le sequenze devono essere generate in modo casuale non può essere facilmente soddisfatto. Il meglio che puoi fare probabilmente è generarli in modo pseudocasuale, il che è altrettanto deterministico quanto codificarli prima del tempo, e quindi non dovrebbe soddisfare le superstizioni di nessuno.

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.