Який оптимальний алгоритм різання нігтів на нігтях?


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

Ви можете створити всі можливі послідовності різання нігтів без обмежень, а потім відфільтрувати всі послідовності, що порушують єврейське правило. На щастя, у людини лише п’ять пальців на стопу *, тож їх всього 5! = 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, тому ви можете її легко змінити пізніше, якщо у когось із ваших клієнтів виявиться менше пальців, ніж ви очікували, або більше.


22
Ти маєш рацію! Я ніколи навіть не вважав людей полідактичними . Було б неправильно їх виключати.
Пітер Олсон

1
менший випадок пальців ніг охоплюється оригінальним алгоритмом (прийнятні послідовності для 5 пальців ноги прийнятні для 4 пальців). Це ті божевільні додаткові пальці, які викликають проблеми;)
летить

4
Дуже приємне рішення! Я хотів би підійти до "жодних повторів однієї і тієї ж послідовності", дещо іншим. Просто змусьте машину запам'ятати, яку послідовність вона використовувала останньою, і використовуйте випадкову (але не ту саму) наступну. Це працює як для другої стопи, так і для нових клієнтів, і це більше випадково, ніж дотримуватися 4 послідовностей.
Якоб

3
Також слід врахувати відсутні ампутації пальців, наприклад, відсутні 3-го пальця. Це спричиняє проблеми, якщо, наприклад, видалення 3-го пальця ноги змушує пальці 2 і 4 вважати послідовними.
cdeszaq

2
Що з людьми, які мають лише 2 пальці на одній нозі? Чи дозволяється їм різати нігті?
matiasg

26

Існує обмежена кількість послідовностей, які відповідають вашим вимогам.

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

EDIT: Якщо мова йде не про пальці ніг, а про якусь випадкову проблему, коли набір може бути набагато більшим, ніж 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

Оскільки х = 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 відмов" все ще є правильною, я думаю.


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)

EDIT: Нову вимогу, що послідовності повинні генеруватися випадковим чином, не можна легко виконати. Найкраще, що ви можете зробити, це створити їх псевдовипадково, що так само детерміновано, як і жорстке кодування їх достроково, і тому не повинно задовольняти нічиї забобони.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.