因此,我得到了一个得分列表,并希望从中创建一个排名列表。如果分数相同,他们将分享一个排名。
例如,如果我有一个得分列表,例如
[100, 100, 50, 50, 20]
生成的列表将是
[(100, 1), (100, 1), (50, 2), (50, 2), (20, 3)]
我想这是一个相当简单的任务,但是我还没有解决它。我试图通过模式匹配或折叠来做到这一点,但是没有任何运气。
我上次失败的方法如下:
scores = [100, 100, 50, 50, 20, 10]
ranks = foldr (\x acc -> if x == (fst $ last acc)
then last acc:acc
else (x, (+1) $ snd $ last acc):acc) [(head scores, 1)] scores
感谢您的帮助。
答案 0 :(得分:5)
此解决方案与Willem的解决方案非常相似,不同之处在于它未明确使用递归。许多样式指南,包括the Haskell wiki,建议如果存在涉及高阶函数的简单实现,则避免显式递归。在您的情况下,您的函数是scanl
的非常简单的用法,它使用一个累加值折叠一个列表(在您的情况下,累加器是当前的排名和得分),并存储中间结果。
ranks :: Eq a => [a] -> [(a, Int)]
-- Handle the empty case trivially.
ranks [] = []
-- Scan left-to-right. The first element of the result should always
-- have rank 1, hence the `(x, 1)' for the starting conditions.
ranks (x:xs) = scanl go (x, 1) xs
-- The actual recursion is handled by `scanl'. `go' just
-- handles each specific iteration.
where go (curr, rank) y
-- If the "current" score equals the next element,
-- don't change the rank.
| curr == y = (curr, rank)
-- If they're not equal, increment the rank and
-- move on.
| otherwise = (y, rank + 1)
通过避免显式递归,可以一目了然地看到函数的作用。我可以看一下,立即看到scanl
,并且知道该函数将以某种状态(等级)从左到右遍历列表,并产生中间结果。
答案 1 :(得分:3)
我们可以编写一个保持状态的递归算法:它正在分配的当前等级。每次算法都看两个元素。如果下一个元素相同,则排名不递增,否则递增。
因此,我们可以像这样实现它:
rank :: Eq a => [a] -> [(a, Int)]
rank = go 1
where go i (x:xr@(x2:_)) = (x, i) : go'
where go' | x == x2 = go i xr
| otherwise = go (i+1) xr
go i [x] = [(x, i)]
go _ [] = []
因此,我们指定rank = go 1
,从而通过1
“ 初始化”状态。每次我们使用go
检查列表是否至少包含两个元素。在这种情况下,我们首先发出状态为(x, i)
的第一个元素,然后对其余xr
执行递归。根据第一个元素x
是否等于第二个元素x2
,我们增加或不增加状态。如果列表仅包含一个元素x
,则我们返回[(x, i)]
;如果列表根本不包含任何元素,则返回空列表。
请注意,这是假设分数已经按照降序排列(或者是从“最佳”到“最差”的顺序,因为在某些游戏中“ 得分”有时是负面的) 。但是,如果不是这种情况,我们可以使用sort
步骤作为预处理。
答案 2 :(得分:2)
这是一个简单的单行代码,将一些现成的文件与列表理解一起放置。
import Data.List
import Data.Ord (Down (..))
rank :: Ord a => [a] -> [(a, Int)]
rank xs = [(a, i) | (i, as) <- zip [1..] . group . sortBy (comparing Down) $ xs
, a <- as]
如果列表已经按照相反的顺序排序,则可以省略sortBy (comparing Down)
。