Каков оптимальный еврейский алгоритм стрижки ногтей на ногах?

118

Я работаю над программным обеспечением для машины, которая будет автоматически обрезать ногти на ногах, чтобы пользователи могли просто положить в нее ноги и запустить ее, вместо того, чтобы делать это вручную, кусая их или используя кусачки для ногтей.

Значительный процент нашей потенциальной базы пользователей, скорее всего, будут евреями, и, очевидно, существует традиция не стричь ногти на ногах ( или ногтях ) в последовательном порядке.

Похоже, существует особое мнение о точном применении этой традиции, но мы считаем, что следующих правил достаточно, чтобы приспособить людей, чьи религиозные обычаи запрещают стрижку ногтей на ногах, по порядку:

  • Нельзя обрезать два соседних ногтя подряд
  • Последовательность резания на левой ступне не должна совпадать с последовательностью на правой ступне.
  • Последовательность резки в двух последовательных проходах не должна быть одинаковой. Последовательности не должны быть легко предсказуемыми, поэтому жесткое кодирование чередующейся последовательности не работает.

Вот как мы решили пронумеровать пальцы ног:

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

Я написал код для решения проблемы, но используемый алгоритм не оптимален: на самом деле, производительность в худшем случае составляет O (∞) . Принцип его работы сравним с богосортом . Вот упрощенный псевдокод фактически используемого кода:

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)

По сути, он генерирует случайные последовательности и проверяет, соответствуют ли они критериям. Если он не соответствует критериям, он начинается заново. Это не занимает до смешного много времени, но очень непредсказуемо.

Я понимаю, что то, что я делаю сейчас, довольно ужасно, но у меня проблемы с поиском лучшего способа. Может ли кто-нибудь из вас предложить более элегантный и производительный алгоритм?

Питер Олсон
источник
28
Это пахнет домашней работой. В противном случае, почему бы просто не закодировать последовательность?
Майкл Браун,
24
Я слышал, что грызут ногти, но ногти на ногах?
вылетает
63
Мысль о станке для стрижки ногтей на ногах довольно пугает. Я надеюсь, что это действительно домашнее задание, а не ожидаемая болезненная трагедия.
Питер Рекор,
43
Задача программирования здесь - управлять машиной, которая подстригает ногти на ногах, чтобы не травмировать людей. Если есть программист, способный решить эту проблему, то он наверняка сможет решить эту проблему за две минуты.
вылетает
41
Мне нравится, что вопрос о еврейской традиции помечен как агностический (языковой) ... :-)
Стив Мельникофф

Ответы:

87

Вы можете сгенерировать все возможные последовательности стрижки ногтей на ногах без ограничений, а затем отфильтровать все последовательности, которые нарушают еврейское правило. К счастью, у людей всего пять пальцев на ноге *, поэтому их всего пять! = 120 неограниченных последовательностей.

Пример 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)

Чтобы обеспечить соблюдение правила «Не повторять одну и ту же последовательность», вы можете просто выбрать четыре из указанных выше последовательностей и использовать их поочередно. Единственная загвоздка здесь в том, что если вы считаете два больших пальца ноги «последовательными», то вы не сможете выбрать две последовательности, которые заканчиваются и начинаются с 1 соответственно.

* Возможно, вы захотите создать переменную numberOfToesPerFoot, чтобы вы могли легко изменить ее позже, если у кого-то из ваших клиентов окажется меньше пальцев на ногах, чем вы ожидаете, или больше.

Kevin
источник
22
Ты прав! Я никогда даже не считал людей с полидактией . Исключать их было бы неправильно.
Питер Олсон,
1
случай меньшего количества пальцев покрывается исходным алгоритмом (допустимые последовательности для 5 пальцев допустимы для 4 пальцев). Проблемы возникают из-за этих сумасшедших лишних пальцев ног;)
летит
4
Очень красивое решение! Я бы подошел к «без повторов одной и той же последовательности» немного иначе. Просто заставьте машину запомнить, какая последовательность использовалась последней, а затем использовать случайную (но не такую). Это работает как для второй ноги, так и для новых клиентов, и это более случайное, чем использование 4 последовательностей.
Якоб
3
Также необходимо учитывать отсутствие пальцев на ноге в результате ампутации, например, отсутствие третьего пальца. Это вызывает проблемы, если, например, удаление 3-го пальца теперь приводит к тому, что пальцы 2 и 4 считаются последовательными.
cdeszaq 07
2
А как насчет людей, у которых всего два пальца на одной ноге? Им разрешено стричь ногти на ногах?
matiasg
26

Существует конечное количество последовательностей, удовлетворяющих вашим требованиям.

  1. Сгенерируйте все перестановки {1,2,3,4,5}. Их всего 120.
  2. Отклоните те, которые не удовлетворяют требованиям, и сохраните оставшийся набор (навсегда).
  3. Случайным образом выберите две разные последовательности. Вспомните, какие из них вы использовали в прошлый раз.

РЕДАКТИРОВАТЬ: если речь идет не о пальцах ног, а о какой-то случайной проблеме, когда набор может быть намного больше, чем 5, пространство последовательности становится очень большим, и вероятность повторения той же последовательности на второй ноге становится очень маленькой. Так что случайное создание последовательностей и их отклонение, если они совпадают, - хорошая идея. Генерация случайных последовательностей в соответствии с каким-то правилом, например «прыгать по два или по три, затем заполнять пробелы», вероятно, будет быстрее, чем генерация случайных перестановок и тестирования, и вероятность перекрытия все равно будет небольшой, если количество «пальцев» велико. ,

мухи
источник
20

На самом деле мне больше всего нравится ваш оригинальный алгоритм.

Поскольку 14 из 120 перестановок работают, 106 из 120 - нет. Таким образом, вероятность провала каждой проверки составляет 106/120.

Это означает, что ожидаемое количество отказов составляет:

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

Нетрудно суммировать этот бесконечный ряд:

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

Умножить на x:

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

Вычесть:

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

Снова умножаем на x и снова вычитаем:

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

Поскольку x = 106/120, S = 64,9.

Итак, в среднем вашему циклу требуется всего 65 итераций, чтобы найти решение.

Какова вероятность, что потребуется, скажем, тысяча итераций?

Что ж, вероятность неудачи любой отдельной итерации составляет 104/120, поэтому вероятность неудачной 1000 итераций составляет (104/120) ^ 1000, что примерно равно 10 ^ (- 63). То есть вы никогда не увидите, чтобы это произошло при вашей жизни, и, вероятно, не при жизни Вселенной.

Никаких предварительно вычисленных таблиц, легкая адаптация к разному количеству пальцев рук / ног / рук / ног, легкая адаптация к разным наборам правил ... Что не нравится? Экспоненциальный спад - замечательная вещь.

[Обновить]

Ой, я ошибся в исходной формуле ... Поскольку мои вероятности не равны 1. :-)

Правильное выражение ожидаемого числа отказов:

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

(Например, чтобы получить ровно два сбоя, вам нужны два сбоя, за которыми следует успех . Два сбоя имеют вероятность (106/120) ^ 2; один успех имеет вероятность (14/120); умножьте их вместе, чтобы получить вес для "2" срок.)

Таким образом, мой S выключен с коэффициентом (1-x) (т.е. 14/120). Фактическое ожидаемое количество отказов составляет всего x / (1-x) = 106/14 = 7,57. Таким образом, в среднем для поиска решения требуется всего 8-9 итераций (7,5 неудач плюс один успех).

Я думаю, что моя математика для случая «1000 отказов» все еще верна.

Nemo
источник
1
+1 за нестандартное мышление и другой взгляд на проблему.
nalply
9

Очевидное: найдите один, который работает, и жестко запрограммируйте его. Но я не думаю, что ты хочешь этого делать.

Вы можете генерировать перестановки намного лучше, чем вы это делаете. Вам не нужно делать выборку отбраковки. Используйте тасование Фишера Йетса для изначально отсортированной перестановки (1, 2, .. 5), и вы получите случайную перестановку. http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle

Но в целом методы генерации и тестирования мне кажутся вполне приемлемыми, если вероятность создания успешной записи достаточно высока. Я уверен, что существует множество допустимых последовательностей в соответствии с вашими критериями, и как только вы переключитесь на случайную перестановку, я сомневаюсь, что вам придется делать много итераций отклонения.

Роб Нойхаус
источник
2

Здесь нет ничего нового, такое же решение @Kevin уже было опубликовано, но я думаю, интересно посмотреть, как оно переводится на функциональный язык. В этом случае Mathematica :

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

Некоторые пояснения:

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

Конечный результат:

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

редактировать

Или, что труднее объяснить, но короче:

Reap[ Table[ If[Times @@ (Abs@Differences@i - 1) != 0, Sow@i],
           {i, Permutations@Range@5}]][[2, 1]]
Доктор велизарий
источник
0

На самом деле нет причин вводить случайность в эту проблему. Есть только 14 последовательностей, которые удовлетворяют этой проблеме, и, безусловно, некоторый порядок этих последовательностей лучше всего удовлетворит эстетический смысл, который вы пытаетесь приспособить. Таким образом, вам следует просто свести эту проблему к алгоритму выбора последовательности из этих 14, вероятно, в заранее установленном порядке.

Реализация Javascript алгоритма поиска 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)

РЕДАКТИРОВАТЬ: новое требование о том, что последовательности должны генерироваться случайным образом, не может быть легко выполнено. Лучшее, что вы, вероятно, можете сделать, - это сгенерировать их псевдослучайно, что столь же детерминировано, как и жесткое кодирование их заранее, и поэтому не должно удовлетворять чьи-либо суеверия.

Тэмзин Блейк
источник