【问题标题】:What is the optimal Jewish toenail cutting algorithm?什么是最佳的犹太趾甲切割算法?
【发布时间】:2011-10-14 14:21:28
【问题描述】:

我正在开发一种机器的软件,该机器会自动修剪脚趾甲,这样用户就可以简单地将脚放入其中并运行它,而不必通过咬脚趾或使用指甲剪来手动完成。

我们的潜在用户群中很大一部分可能是犹太人,而且很明显,有一个tradition about not trimming toenails (or fingernails) 按顺序排列

对于这一传统的精确应用似乎存在不同意见,但我们认为以下规则足以适应宗教习俗禁止按顺序剪脚趾甲的人:

  • 相邻的两个脚趾甲不得连续剪断
  • 左脚的切割顺序不应与右脚的顺序匹配
  • 两次连续运行的切割顺序不应相同。序列不应轻易预测,因此对交替序列进行硬编码是行不通的。

这就是我们决定给脚趾编号的方式:

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)

基本上,它会生成随机序列并检查它们是否符合条件。如果不符合标准,则重新开始。它不需要很长的时间,但它是非常不可预测的。

我意识到我目前的做法非常糟糕,但我很难想出更好的方法。你们中有人能推荐一个更优雅、更高效的算法吗?

【问题讨论】:

  • 这听起来像是一个家庭作业问题。否则,为什么不直接对序列进行硬编码?
  • 听说过咬指甲,但是咬脚趾甲?
  • 一想到剪趾甲机就很吓人。我希望这确实是功课,而不是等待发生的痛苦悲剧。
  • 这里的编程挑战是控制剪脚趾甲的机器,这样它就不会伤害到人。如果有一个程序员能够解决这个问题,那么这个人肯定可以在两分钟内解决这个问题。
  • 我喜欢这样一个事实,即有关犹太传统的问题被标记为(语言)不可知论... :-)

标签: algorithm language-agnostic


【解决方案1】:

您可以不受限制地生成所有可能的脚趾甲切割序列,然后过滤掉所有违反犹太规则的序列。幸运的是,人类每英尺只有五个脚趾*,所以只有 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 变量,以便以后如果您的任何客户的脚趾比您预期的少或多,您可以轻松地更改它。

【讨论】:

  • 你是对的!我什至从未考虑过使用polydactly 的人。排除它们是错误的。
  • 原始算法覆盖的脚趾数较少的情况(5 个脚趾的可接受序列对于 4 个脚趾是可接受的)。正是那些疯狂的额外脚趾导致了问题;)
  • 非常好的解决方案!我会接近“不重复相同序列”,但略有不同。只需让机器记住它最后使用的序列,然后使用随机序列(但不是相同的序列)。这对第二只脚和新客户都有效,而且比坚持使用 4 个序列更随机。
  • 人们还需要考虑因截肢而失去脚趾,例如第三脚趾缺失。这会导致问题,例如,移除第 3 个脚趾现在会导致第 2 和第 4 脚趾被认为是连续的。
  • 一只脚只有两个脚趾的人怎么办?他们可以剪脚趾甲吗?
【解决方案2】:

满足您要求的序列数量有限。

  1. 生成 {1,2,3,4,5} 的所有排列。只有 120 个。
  2. 拒绝不满足要求的并存储剩余的集合 (永久)。
  3. 随机选择两个不同的序列。记住您上次使用的那些。

编辑:如果这不是真的关于脚趾,而是关于某个集合可能远大于 5 的随机问题,那么序列空间会变得非常大,并且在第二只脚上重复相同序列的机会变得非常小.因此,随机生成序列并在匹配时拒绝它们是一个好主意。按照“三两跳,然后填空”这样的规则生成随机序列可能比生成随机排列和测试要快,如果“脚趾”的数量很大,重叠的机会仍然很小.

【讨论】:

    【解决方案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 次迭代即可找到解决方案。

    例如,一千次迭代的概率是多少?

    嗯,任何一次迭代失败的概率是 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 用于跳出框框思考并以不同的方式看待问题。
    【解决方案4】:

    显而易见:找到一个有效的订单,并对其进行硬编码。但我认为你不想这样做。

    你可以生成比你正在做的方式更好的排列。您不需要进行拒绝抽样。在最初排序的排列 (1, 2, .. 5) 上使用 Fisher Yates 洗牌,您将获得随机排列。 http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle

    但总的来说,生成和测试方法对我来说似乎完全没问题,只要生成成功条目的概率足够高。我敢肯定,根据您的标准,有许多有效序列,一旦您切换到随机排列,我怀疑您将不得不进行多次拒绝迭代。

    【讨论】:

      【解决方案5】:

      这里没有什么新东西,@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]]
      

      【讨论】:

        【解决方案6】:

        确实没有理由在这个问题中引入随机性。只有 14 个序列可以满足这个问题,并且这些序列的某些排序肯定会最好地满足您试图适应的审美感觉。因此,您应该将这个问题简化为从这 14 个序列中挑选一个序列的算法,可能是按照预先设定的顺序。

        求14的算法的Javascript实现:

        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)
        

        编辑: 序列必须随机生成的新要求不容易满足。您可能能做的最好的事情是伪随机生成它们,这与提前对它们进行硬编码一样具有确定性,因此不应满足任何人的迷信。

        【讨论】:

          猜你喜欢
          • 2021-09-04
          • 1970-01-01
          • 2011-06-29
          • 2020-09-16
          • 1970-01-01
          • 2010-09-20
          • 2019-06-19
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多