Was ist der optimale jüdische Algorithmus zum Schneiden von Zehennägeln?

118

Ich arbeite an der Software für eine Maschine, die automatisch die Zehennägel schneidet, sodass Benutzer einfach ihre Füße hineinlegen und sie ausführen können, anstatt sie manuell durch Beißen oder Verwenden von Nagelknipsern ausführen zu müssen.

Ein beträchtlicher Prozentsatz unserer potenziellen Nutzerbasis wird wahrscheinlich jüdisch sein, und es gibt offensichtlich eine Tradition, Zehennägel ( oder Fingernägel ) nicht in sequentieller Reihenfolge zu schneiden

Es scheint abweichende Meinungen über die genaue Anwendung dieser Tradition zu geben, aber wir glauben, dass die folgenden Regeln ausreichen, um Menschen unterzubringen, deren religiöse Praktiken das Schneiden von Zehennägeln verbieten:

  • Es sollten keine zwei benachbarten Zehennägel nacheinander geschnitten werden
  • Die Schnittsequenz am linken Fuß sollte nicht mit der Sequenz am rechten Fuß übereinstimmen
  • Die Schnittsequenz bei zwei aufeinanderfolgenden Läufen sollte nicht gleich sein. Die Sequenzen sollten nicht leicht vorhersehbar sein, daher funktioniert die Hardcodierung einer alternierenden Sequenz nicht.

So haben wir beschlossen, die Zehen zu nummerieren:

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

Ich habe Code geschrieben, um das Problem zu lösen, aber der verwendete Algorithmus ist nicht optimal: Tatsächlich ist die schlechteste Leistung O (∞) . Die Art und Weise, wie es funktioniert, ist vergleichbar mit Bogosort . Hier ist eine Pseudocode-Vereinfachung des tatsächlich verwendeten Codes:

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)

Grundsätzlich werden zufällige Sequenzen generiert und geprüft, ob diese die Kriterien erfüllen. Wenn es die Kriterien nicht erfüllt, beginnt es von vorne. Es dauert nicht lächerlich lange, ist aber sehr unvorhersehbar.

Mir ist klar, dass die Art und Weise, wie ich es derzeit mache, ziemlich schrecklich ist, aber ich habe Probleme, einen besseren Weg zu finden. Kann jemand von euch einen eleganteren und performanteren Algorithmus vorschlagen?

Peter Olson
quelle
28
Das riecht nach einem Hausaufgabenproblem. Warum nicht einfach die Sequenz fest codieren?
Michael Brown
24
Ich habe von beißenden Fingernägeln gehört, aber von Zehennägeln?
fliegt
63
Der Gedanke an eine Zehennagelschneidemaschine ist ziemlich beängstigend. Ich hoffe, dies sind in der Tat Hausaufgaben und keine schmerzhafte Tragödie, die darauf wartet, passiert zu werden.
Peter Recore
43
Die Herausforderung bei der Programmierung besteht darin, die Maschine so zu steuern, dass Zehennägel geschnitten werden, damit keine Personen verletzt werden. Wenn es einen Programmierer gibt, der dieses Problem lösen kann, kann diese Person dieses Problem sicherlich in zwei Minuten lösen.
fliegt
41
Ich mag die Tatsache, dass eine Frage zur jüdischen Tradition als (sprach-) agnostisch markiert ist ... :-)
Steve Melnikoff

Antworten:

87

Sie können alle möglichen Zehennagel-Schnittsequenzen ohne Einschränkungen generieren und dann alle Sequenzen herausfiltern, die gegen die jüdische Regel verstoßen. Glücklicherweise haben Menschen nur fünf Zehen pro Fuß *, also gibt es nur fünf! = 120 uneingeschränkte Sequenzen.

Python-Beispiel:

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

Um Ihre Regel "Keine Wiederholungen derselben Sequenz" durchzusetzen, können Sie einfach vier der oben genannten Sequenzen auswählen und abwechselnd verwenden. Der einzige Haken dabei ist, dass Sie, wenn Sie die beiden großen Zehen als "aufeinanderfolgend" zählen, nicht zwei Sequenzen auswählen können, die mit 1 enden bzw. beginnen.

* Möglicherweise möchten Sie eine Variable numberOfToesPerFoot erstellen, damit Sie sie später problemlos ändern können, wenn sich herausstellt, dass einer Ihrer Kunden weniger oder mehr Zehen als erwartet hat.

Kevin
quelle
22
Du hast recht! Ich habe noch nie über Menschen mit Polydakt nachgedacht . Es wäre falsch, sie auszuschließen.
Peter Olson
1
Der Fall mit weniger Zehen wird vom ursprünglichen Algorithmus abgedeckt (akzeptable Sequenzen für 5 Zehen sind für 4 Zehen akzeptabel). Es sind diese verrückten zusätzlichen Zehen, die Probleme verursachen;)
fliegt
4
Sehr schöne Lösung! Ich würde mich "keine Wiederholungen derselben Sequenz" etwas anders nähern. Denken Sie einfach daran, welche Sequenz zuletzt verwendet wurde, und verwenden Sie als Nächstes eine zufällige (aber nicht dieselbe) Sequenz. Das funktioniert sowohl für den zweiten Fuß als auch für neue Kunden und ist zufälliger als das Festhalten an 4 Sequenzen.
Jakob
3
Man müsste auch fehlende Zehen bei der Amputation in Betracht ziehen, wie zum Beispiel die fehlende 3. Zehe. Dies führt zu Problemen, wenn beispielsweise durch Entfernen des 3. Zehs die Zehen 2 und 4 nun als sequentiell betrachtet werden.
Cdeszaq
2
Was ist mit Menschen mit nur 2 Zehen in einem Fuß? Dürfen sie ihre Zehennägel schneiden?
Matiasg
26

Es gibt eine endliche Anzahl von Sequenzen, die Ihren Anforderungen entsprechen.

  1. Generieren Sie alle Permutationen von {1,2,3,4,5}. Es gibt nur 120.
  2. Lehnen Sie diejenigen ab, die die Anforderungen nicht erfüllen, und speichern Sie den verbleibenden Satz (dauerhaft).
  3. Wähle zufällig zwei verschiedene Sequenzen aus. Denken Sie daran, welche Sie zuletzt verwendet haben.

BEARBEITEN: Wenn es nicht wirklich um Zehen geht, sondern um ein zufälliges Problem, bei dem der Satz viel größer als 5 sein kann, wird der Sequenzraum sehr groß und die Chance, dieselbe Sequenz auf dem zweiten Fuß zu wiederholen, wird sehr gering. Es ist daher eine gute Idee, Sequenzen zufällig zu generieren und sie abzulehnen, wenn sie übereinstimmen. Das Erzeugen von Zufallssequenzen nach einer Regel wie "zu zweit oder zu dritt hüpfen, dann die Lücken ausfüllen" ist wahrscheinlich schneller als das Erzeugen von Zufallspermutationen und Tests, und die Wahrscheinlichkeit einer Überlappung ist immer noch gering, wenn die Anzahl der "Zehen" groß ist .

fliegt
quelle
20

Eigentlich gefällt mir Ihr ursprünglicher Algorithmus am besten.

Da 14 von 120 Permutationen funktionieren, funktionieren 106 von 120 nicht. Jeder Scheck hat also eine Wahrscheinlichkeit von 106/120, dass er fehlschlägt.

Das heißt, die erwartete Anzahl von Fehlern ist:

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

Nicht zu schwer, diese unendliche Reihe zusammenzufassen:

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

Mit x multiplizieren:

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

Subtrahieren:

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

Mit x erneut multiplizieren und erneut subtrahieren:

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

Da x = 106/120 ist, ist S = 64,9.

Im Durchschnitt benötigt Ihre Schleife also nur 65 Iterationen , um eine Lösung zu finden.

Wie hoch ist die Wahrscheinlichkeit, dass beispielsweise tausend Iterationen erforderlich sind?

Nun, die Wahrscheinlichkeit, dass eine einzelne Iteration fehlschlägt, beträgt 104/120, also beträgt die Wahrscheinlichkeit, dass 1000 Iterationen fehlschlagen, (104/120) ^ 1000, was ungefähr 10 ^ (- 63) entspricht. Das heißt, Sie werden es niemals in Ihrem Leben sehen und wahrscheinlich nicht im Leben des Universums.

Keine vorberechneten Tabellen, einfache Anpassung an unterschiedliche Anzahlen von Fingern / Zehen / Händen / Füßen, einfache Anpassung an unterschiedliche Regelsätze ... Was mag man nicht? Exponentieller Zerfall ist eine wunderbare Sache.

[aktualisieren]

Hoppla, ich habe die ursprüngliche Formel falsch verstanden ... Da sich meine Wahrscheinlichkeiten nicht zu 1 summieren :-)

Der richtige Ausdruck für die erwartete Anzahl von Fehlern lautet:

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

(Um genau zwei Fehler zu erhalten, benötigen Sie beispielsweise zwei Fehler, gefolgt von einem Erfolg . Zwei Fehler haben eine Wahrscheinlichkeit (106/120) ^ 2; ein Erfolg hat eine Wahrscheinlichkeit (14/120); multiplizieren Sie diese, um das Gewicht für die zu erhalten "2" Begriff.)

Mein S ist also um einen Faktor von (1-x) (dh 14/120) versetzt. Die tatsächlich erwartete Anzahl von Fehlern beträgt nur x / (1-x) = 106/14 = 7,57. Im Durchschnitt sind nur 8-9 Iterationen erforderlich , um eine Lösung zu finden (7,5 Fehler plus ein Erfolg).

Meine Mathematik für den Fall "1000 Fehler" ist immer noch korrekt, denke ich.

Nemo
quelle
1
+1 für das Denken über den Tellerrand hinaus und eine andere Sichtweise auf das Problem.
Nalply
9

Das Offensichtliche: Finden Sie eine Bestellung, die funktioniert, und codieren Sie sie fest. Aber ich glaube nicht, dass du das machen willst.

Sie können Permutationen viel besser erzeugen als Sie es tun. Sie müssen keine Ablehnungsabtastung durchführen. Verwenden Sie ein Fisher Yates-Shuffle für eine anfänglich sortierte Permutation (1, 2, .. 5), und Sie erhalten eine zufällige Permutation. http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle

Aber im Allgemeinen scheint mir die Generierungs- und Testmethode völlig in Ordnung zu sein, solange die Wahrscheinlichkeit, einen erfolgreichen Eintrag zu generieren, hoch genug ist. Ich bin mir sicher, dass es nach Ihren Kriterien viele gültige Sequenzen gibt. Wenn Sie zu einer zufälligen Permutation wechseln, bezweifle ich, dass Sie viele Ablehnungsiterationen durchführen müssen.

Rob Neuhaus
quelle
2

Hier gibt es nichts wirklich Neues, die gleiche Lösung, die @Kevin bereits veröffentlicht hat, aber ich finde es interessant zu sehen, wie sie in eine funktionale Sprache übersetzt wird. In diesem Fall ist Mathematica :

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

Einige Erklärungen:

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

Das Endergebnis ist:

{{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}}

Bearbeiten

Oder schwieriger zu erklären, aber kürzer:

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

Es gibt wirklich keinen Grund, Zufälligkeit in dieses Problem einzuführen. Es gibt nur 14 Sequenzen, die dieses Problem erfüllen, und sicherlich würde eine gewisse Reihenfolge dieser Sequenzen den ästhetischen Sinn, den Sie berücksichtigen möchten, am besten erfüllen. Daher sollten Sie dieses Problem nur auf einen Algorithmus zum Auswählen einer Sequenz aus diesen 14 reduzieren, wahrscheinlich in einer voreingestellten Reihenfolge.

Javascript-Implementierung eines Algorithmus zum Finden der 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: Die neue Anforderung, dass die Sequenzen zufällig generiert werden müssen, kann nicht einfach erfüllt werden. Das Beste, was Sie wahrscheinlich tun können, ist, sie pseudozufällig zu generieren, was genauso deterministisch ist, wie sie im Voraus hart zu codieren, und daher den Aberglauben von niemandem befriedigen sollte.

Tamzin Blake
quelle