[algorithm] 최적의 유대인 발톱 절단 알고리즘은 무엇입니까?

저는 발톱을 자동으로 다듬는 기계를위한 소프트웨어를 개발 중입니다. 사용자는 발톱을 물거나 손톱깎이를 사용하여 수동으로 할 필요없이 간단히 발을 넣고 실행할 수 있습니다.

잠재적 인 사용자 기반의 상당 부분이 유대인 일 가능성이 높으며, 발톱 ( 또는 손톱 )을 순차적으로 다듬지 않는 전통 이 있습니다.

이 전통의 정확한 적용에 대해 반대 의견이있는 것 같지만, 우리는 종교적 관습이 발톱을 순서대로 자르는 것을 금지하는 사람들을 수용하기에 다음 규칙으로 충분하다고 생각합니다.

  • 인접한 발톱 두 개를 연속적으로 절단해서는 안됩니다.
  • 왼쪽 발의 절단 순서가 오른쪽 발의 순서와 일치하지 않아야합니다.
  • 두 번 연속 실행되는 절단 순서는 동일하지 않아야합니다. 시퀀스는 쉽게 예측할 수 없어야하므로 교대 시퀀스를 하드 코딩하는 것은 작동하지 않습니다.

이것이 우리가 발가락 번호를 결정한 방법입니다.

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

문제를 해결하기 위해 코드를 작성했지만 사용 된 알고리즘은 차선책입니다. 사실 최악의 경우 성능은 O (∞) 입니다. 작동 방식은 bogosort 와 비슷합니다 . 다음은 사용 된 실제 코드의 의사 코드 단순화입니다.

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)

기본적으로 무작위 시퀀스를 생성하고 기준을 충족하는지 확인합니다. 기준을 충족하지 않으면 다시 시작됩니다. 엄청나게 긴 시간이 걸리지는 않지만 예측할 수 없습니다.

나는 현재 내가하고있는 방식이 꽤 끔찍하다는 것을 알고 있지만 더 나은 방법을 찾는 데 어려움을 겪고 있습니다. 더 우아하고 성능이 좋은 알고리즘을 제안 할 수 있습니까?



답변

제한없이 가능한 모든 발톱 절단 시퀀스를 생성 한 다음 유태인 규칙을 위반하는 모든 시퀀스를 필터링 할 수 있습니다. 운 좋게도 인간은 발당 발가락이 5 개 *이므로 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)

“동일한 시퀀스의 반복 없음”규칙을 적용하려면 위의 시퀀스 중 4 개를 선택하고 교대로 사용할 수 있습니다. 여기서 유일한 문제는 엄지 발가락 두 개를 “연속”으로 계산하면 각각 1로 끝나고 시작하는 두 개의 시퀀스를 선택할 수 없다는 것입니다.

* numberOfToesPerFoot 변수를 만들 수 있으므로 클라이언트가 예상보다 발가락이 적거나 그 이상인 것으로 판명되면 나중에 쉽게 변경할 수 있습니다.


답변

요구 사항을 충족하는 한정된 수의 시퀀스가 ​​있습니다.

  1. {1,2,3,4,5}의 모든 순열을 생성합니다. 120 개 밖에 없습니다.
  2. 요구 사항을 충족하지 않는 것은 거부하고 나머지 세트는 영구적으로 저장합니다.
  3. 두 개의 다른 시퀀스를 무작위로 선택합니다. 지난번에 사용한 것을 기억하십시오.

편집 : 이것이 발가락에 관한 것이 아니라 세트가 5보다 훨씬 클 수있는 임의의 문제에 관한 경우 시퀀스 공간이 매우 커지고 두 번째 발에서 동일한 시퀀스를 반복 할 가능성이 매우 작아집니다. 따라서 무작위로 시퀀스를 생성하고 일치하는 경우 거부하는 것이 좋습니다. “2 ~ 3 개씩 홉한 다음 공백 채우기”와 같은 규칙에 따라 임의 시퀀스를 생성하는 것이 임의 순열 및 테스트를 생성하는 것보다 빠르며 “발가락”수가 많으면 중첩 가능성이 여전히 작습니다. .


답변

사실 저는 당신의 원래 알고리즘이 가장 마음에 듭니다.

순열 120 개 중 14 개가 작동하므로 120 개 중 106 개는 작동하지 않습니다. 따라서 각 검사는 실패 할 확률이 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 번의 반복 만 필요합니다 .

1,000 번의 반복이 걸릴 확률은 얼마입니까?

음, 단일 반복이 실패 할 확률은 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 회 실패 + 1 회 성공).

“1000 회 실패”사례에 대한 내 수학은 여전히 ​​옳다고 생각합니다.


답변

분명한 것은 작동하는 주문을 하나 찾아서 하드 코딩하는 것입니다. 하지만 나는 당신이 그렇게하고 싶지 않다고 생각합니다.

순열을 수행하는 방식보다 훨씬 더 잘 생성 할 수 있습니다. 거부 샘플링을 수행 할 필요가 없습니다. 처음에 정렬 된 순열 (1, 2, .. 5)에 Fisher Yates 셔플을 사용하면 임의 순열이 생깁니다. http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle

그러나 일반적으로 생성 및 테스트 방법은 성공적인 항목을 생성 할 확률이 충분히 높은 한 나에게 완전히 괜찮아 보입니다. 기준에 따라 유효한 시퀀스가 ​​많이 있다고 확신합니다. 일단 무작위 순열로 전환하면 많은 거부 반복을 수행해야 할 것 같지 않습니다.


답변

여기에 정말 새로운 것은 없지만 @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]]


답변

이 문제에 무작위성을 도입 할 이유가 없습니다. 이 문제를 충족하는 시퀀스는 14 개 뿐이며, 이러한 시퀀스의 일부 순서는 수용하려는 미적 감각을 가장 잘 충족시킬 것입니다. 따라서이 문제를 미리 설정된 순서대로 14 개의 시퀀스에서 선택하는 알고리즘으로 줄여야합니다.

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)

편집 : 시퀀스를 무작위로 생성해야한다는 새로운 요구 사항을 쉽게 충족 할 수 없습니다. 당신이 할 수있는 최선의 방법은 그것들을 의사 랜덤으로 생성하는 것입니다. 이것은 미리 하드 코딩하는 것만 큼 결정적이기 때문에 누구의 미신도 만족 시켜서는 안됩니다.


답변