简体   繁体   English

组合学:圣彼得博弈算法

[英]Combinatorics: St. Peter's Game algorithm

There's a combinatorics puzzle (as mentioned in Mathematics From the Birth of Numbers by Jan Gullberg) where if you line up fifteen members from two categories each (eg fifteen of category 0 and fifteen of category 1 for a total of 30 elements) mixed up in a certain order , then if you continuously go along this line in a circular fashion (ie wrapping around back to the start when you reach the end, continuing counting as you go) throwing out every ninth element, you'll eventually have just the elements of the one "favored" ( 1 ) category有一个组合谜题(如 Jan Gullberg的《来自数字的诞生的数学》中所提到的),如果你将来自两个类别的 15 个成员(例如0类的 15 个和1类的 15 个,总共30元素)排列在一起一定的顺序,然后如果你以循环方式沿着这条线连续 go (即当你到达终点时绕回起点,继续计数)扔掉每第九个元素,你最终将只有这些元素属于“受宠”( 1 )类

line = [1,1,1,1,0,0,0,0,0,1,1,0,1,1,1,...]

line (see the run-length encoded tuples version below) is the actual ordering, that if you throw out every ninth, line (请参阅下面的运行长度编码元组版本)是实际的排序,如果你每 9 次丢弃一次,

line = [1,1,1,1,0,0,0,0,1,1,0,1,1,1,...] -- 9th thrown out

you'll always be throwing out the "disfavored" 0 .你总是会扔掉“不受欢迎的” 0 If seen from the RLE tuples standpoint (where (0|1, n) encodes n consecutive occurrences of the 0 or the 1 ), (decrementing) from the tuple (0,x) , ie, decrementing the x , you'll eventually get down to just the (1,y) tuples, of course throwing out the fully depleted (0,0) tuples as well and recompacting the list as you go如果从 RLE 元组的角度来看(其中(0|1, n)编码n连续出现的01 ),从元组(0,x) (递减),即递减x ,你最终会只考虑(1,y)元组,当然也要扔掉完全耗尽的(0,0)元组,然后像 go 一样重新压缩列表

line = [(1,4),(0,5),(1,2),(0,1),(1,3),(0,1),(1,1),(0,2),(1,2),(0,3),(1,1),(0,2),(1,2),(0,1)]

I've got this to get started我有这个开始

tally = foldl (\acc elem -> if (snd(elem)+acc) >= 9
                            then (snd(elem)+acc)-9
                            else (snd(elem)+acc)) 0

and when I feed it line当我喂它line

tally [(1,4),(0,5),(1,2),(0,1),(1,3),(0,1),(1,1),(0,2),(1,2),(0,3),(1,1),(0,2),(1,2),(0,1)]

it takes the 4 of the first tuple, then adds the 5 of the second, gets 9 and resets the accumulator to start the "counting down the line" again.它取第一个元组的4 ,然后加上第二个元组的5 ,得到9并重置累加器以再次开始“倒计时”。 And so it accurately returns 3 which is, in fact, the leftover of the accumulator after going along for one pass and identifying the tuple with the ninth and resetting the accumulator.所以它准确地返回了3 ,这实际上是累加器在经过一轮并识别与第九个元组并重置累加器后的剩余部分。 My obvious problem is how to go beyond just identifying the ninth elements, and actually start decrementing the 0 tuples' elements, as well as throwing them out when they're down to (0,0) and re-running.我的明显问题是如何 go 不仅仅是识别第九个元素,实际上开始递减0元组的元素,以及当它们下降到(0,0)并重新运行时将它们扔掉。 I'm sure it would be easier to just build line as我敢肯定,建立line会更容易

line = [1,1,1,1,0,0,0,0,0,1,1,0,1,1,1,...]

and start chucking (i.. removing) the ninth, again, which should always be a 0 element, (eg, the first ninth has been eliminated from line并再次开始夹住(i..删除)第九个,它应该始终是一个0元素,(例如,第一个第九个已从line中删除

line = [1,1,1,1,0,0,0,0,1,1,0,1,1,1,...]

but this is more of a challenge because I essentially need a fold to be combined with a map -- which is what I want to learn, ie, a purely functional, no counters, etc., style.但这更具挑战性,因为我基本上需要折叠与 map 结合使用——这是我想学习的,即纯功能性、无计数器等样式。 Hints and help appreciated.提示和帮助表示赞赏。 Also, if someone in the combinatorics lore could shed some theory light on what's happening here, that would be nice, too.此外,如果组合学知识中的某个人可以对这里发生的事情提供一些理论依据,那也很好。

Using RLE complicates things.使用 RLE 会使事情复杂化。 All you need is counting:你所需要的只是计算:

line = [(1,4),(0,5),(1,2),(0,1),(1,3),(0,1),(1,1),
        (0,2),(1,2),(0,3),(1,1),(0,2),(1,2),(0,1)]
unRLE rle = [c | (c,n) <- rle, c <- replicate n c]
test = count9 1 15 [] $ unRLE line

count9 _ 0 rev line   = reverse rev ++ line
count9 9 n rev (0:xs) = count9 1 (n-1) rev xs
 -- removing 1 is error:
count9 9 n rev (1:xs) = error "attempt to remove 1"
count9 i n rev (x:xs) = count9 (i+1) n (x:rev) xs
count9 i n rev []     = count9 i n [] (reverse rev)

Running it运行它

> test
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]

You will need to tweak this if you want to see the state of the line on each 0 being removed.如果您想查看删除每个0line的 state,则需要对此进行调整。

Looking for maps and folds might be overconstraining things, because here's a cute no-frills function for you to start with:寻找地图和折叠可能会过度限制事情,因为这里有一个可爱的简洁 function 供您开始:

-- Remove the n-th element (zero-indexed) of a run-length encoded sequence of a.
chuck :: Int -> [(a, Int)] -> [(a, Int)]

Throw out the empty case;扔掉空箱子; we're not supposed to be here.我们不应该在这里。

chuck _ [] = error "unexpected empty list"

Otherwise, we're facing m identical elements a , and we want to delete the n -th element.否则,我们将面临m个相同的元素a ,我们想要删除第n个元素。 That depends on whether n < m (ie, whether the search stops in the middle of those m elements, or after).这取决于n < m是否(即搜索是否在这些m元素的中间或之后停止)。

If n < m , then we will remove one of those a .如果n < m ,那么我们将删除其中一个a We can also prepare the result in anticipation for the next cycle, which resumes right after that a we removed.我们还可以为下一个周期准备结果, a我们删除之后立即恢复。 We've actually skipped n other elements before it, and a good place to store these n elements is the end of the list, since we're supposed to circle back around at the end anyway.我们实际上已经跳过了它之前的n其他元素,并且存储这n元素的好地方是列表的末尾,因为无论如何我们都应该在末尾绕回。 We would need something more sophisticated if we wanted to count laps, but unless told otherwise, YAGNI.如果我们想计算圈数,我们需要更复杂的东西,但除非另有说明,否则 YAGNI。 There remain mn-1 elements, left at the front.剩下mn-1元素,留在前面。 A little helper rpt helps in the case where we are trying to append zero elements.在我们尝试 append 零元素的情况下,一个小助手rpt会有所帮助。

otherwise , we skip all m elements, store them in the back, and we have nm more to go. otherwise ,我们跳过所有m个元素,将它们存储在后面,并且我们有更多的nm到 go。

chuck n ((a,m) : l)
  | n < m = rpt a (m-n-1) ++ l ++ rpt a n
  | otherwise = chuck (n-m) (l ++ [(a,m)])
  where rpt a 0 = []
        rpt a n = [(a,n)]

Since the result is prepared for the next iteration, we can easily chain chuck to see the evolution of the line.由于结果是为下一次迭代准备的,我们可以很容易地链式chuck来查看线路的演变。 Note that elements are zero-indexed in this implementation, so chuck 8 chucks the "ninth" element.请注意,在此实现中元素是零索引的,因此chuck 8卡盘“第九个”元素。

ghci
> line
[(1,4),(0,5),(1,2),(0,1),(1,3),(0,1),(1,1),(0,2),(1,2),(0,3),(1,1),(0,2),(1,2),(0,1)]
> chuck 8 line
[(1,2),(0,1),(1,3),(0,1),(1,1),(0,2),(1,2),(0,3),(1,1),(0,2),(1,2),(0,1),(1,4),(0,4)]
> chuck 8 $ chuck 8 line
[(0,1),(1,2),(0,3),(1,1),(0,2),(1,2),(0,1),(1,4),(0,4),(1,2),(0,1),(1,3),(0,1),(1,1)]

This is a bit hard to follow.这有点难以理解。 At the very least, we should make sure that only 0 's are being chucked.至少,我们应该确保只有0被丢弃。 So let's count the elements:所以让我们计算元素:

tally :: [(Int,Int)] -> (Int, Int)
tally xs = (sum (map snd (filter ((== 0) . fst) xs)), sum (map snd (filter ((== 1) . fst) xs)))

The right side of the tally seems to remain constant, and there is less on the wrong side, as expected:正如预期的那样,计数的右侧似乎保持不变,而错误的一侧则更少:

> tally line
(15,15)
> tally $ chuck 8 line
(14,15)
> tally $ chuck 8 $ chuck 8 line
(13,15)

We can go faster with iterate , which repeatedly applies a function and returns all intermediate results in an infinite list:我们可以使用iterate更快地 go ,它重复应用 function 并在无限列表中返回所有中间结果:

> :t iterate
iterate :: (a -> a) -> a -> [a]

Iterate chuck 8 , tally up, only look until where we expect to stop (after removing all 15 elements on one side):迭代chuck 8 ,统计,只看我们期望停止的地方(在移除一侧的所有15个元素之后):

> take 16 $ map tally $ iterate (chuck 8) line
[(15,15),(14,15),(13,15),(12,15),(11,15),(10,15),(9,15),(8,15),(7,15),(6,15),(5,15),(4,15),(3,15),(2,15),(1,15),(0,15)]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM