[英]How do I combine consectuive numbers in a list into a range in Haskell?
我正在努力解決 Haskell 的問題,我很難確定這個特定任務的一般過程/算法。 我想要做的基本上是給 Haskell 一個列表 [1,2,3,5,6,9,16,17,18,19] 並讓它給我回 [1-3, 5, 6, 9, 16 -19] 所以本質上是將三個或更多連續數字轉換為最低數字 - 最高數字的范圍。 我認為我遇到的問題是與 Haskell 的功能范式搏斗的常見困難。 所以我真的很感激一個通用算法或如何從“哈斯克爾”的角度來看待這個問題的洞察力。
提前致謝。
如果我正確理解了這個問題,我們的想法是將輸入列表分成塊,其中塊是單個輸入元素或至少三個連續元素的范圍。
所以,讓我們首先定義一個數據類型來表示這些塊:
data Chunk a = Single a | Range a a
如您所見,類型在輸入元素的類型中是參數化的。
接下來,我們定義一個 function chunks
來實際從輸入元素列表構造塊列表。 為此,我們需要能夠比較輸入元素並獲得給定輸入元素(即其后繼元素)的直接連續元素。 因此,function 的類型為
chunks :: (Eq a, Enum a) => [a] -> [Chunk a]
實現相對簡單:
chunks = foldr go []
where
go x (Single y : Single z : cs) | y == succ x && z == succ y = Range x z : cs
go x (Range y z : cs) | y == succ x = Range x z : cs
go x cs = Single x : cs
我們從右到左遍歷列表,生成塊為 go。 如果輸入元素在其兩個直接連續元素之前(幫助程序 function go
的第一種情況)或者如果它在以它的直接連續元素開頭的范圍之前(第二種情況),我們將生成一個范圍。 否則,我們生成單個元素(最后一種情況)。
為了安排漂亮的 output,我們將類型構造函數Chunk
的應用程序聲明為 class Show
的實例(假設輸入元素的類型在Show
中):
instance Show a => Show (Chunk a) where
show (Single x ) = show x
show (Range x y) = show x ++ "-" ++ show y
回到問題的例子,我們有:
> chunks [1,2,3,5,6,9,16,17,18,19]
[1-3,5,6,9,16-19]
不幸的是,如果我們需要考慮有界元素類型,事情會稍微復雜一些。 此類類型具有未定義succ
的最大元素:
> chunks [maxBound, 1, 2, 3] :: [Chunk Int]
*** Exception: Prelude.Enum.succ{Int}: tried to take `succ' of maxBound
這表明我們應該從確定一個元素是否繼承另一個元素的特定方法中抽象出來:
chunksBy :: (a -> a -> Bool) -> [a] -> [Chunk a]
chunksBy succeeds = foldr go []
where
go x (Single y : Single z : cs) | y `succeeds` x && z `succeeds` y =
Range x z : cs
go x (Range y z : cs) | y `succeeds` x = Range x z : cs
go x cs = Single x : cs
現在,上面給出的chunks
的版本,可以用chunksBy
表示,只需編寫
chunks :: (Eq a, Enum a) => [a] -> [Chunk a]
chunks = chunksBy (\y x -> y == succ x)
此外,我們現在還可以實現有界輸入類型的版本:
chunks' :: (Eq a, Enum a, Bounded a) => [a] -> [Chunk a]
chunks' = chunksBy (\y x -> x /= maxBound && y == succ x)
這愉快地給了我們:
> chunks' [maxBound, 1, 2, 3] :: [Chunk Int]
[9223372036854775807,1-3]
首先,列表的所有元素必須屬於同一類型。 您的結果列表有兩種不同的類型。 Range
s(無論這意味着什么)和Int
s。 我們應該將一位數轉換為最低和最高相同的范圍。
這么說,您應該定義Range
數據類型並將您的Int
列表折疊到Range
列表中
data Range = Range {from :: Int , to :: Int}
intsToRange :: [Int] -> [Range]
intsToRange [] = []
intsToRange [x] = [Range x x]
intsToRange (x:y:xs) = ... -- hint: you can use and auxiliar acc which holds the lowest value and keep recursion till find a y - x differece greater than 1.
您還可以使用fold
等...來獲得一個非常haskelly的觀點
使用遞歸。 遞歸是信念的飛躍。 假設您已經編寫了定義,因此可以( “遞歸” )在完整問題的子問題上調用它,並將(遞歸計算的)子結果與剩余部分結合起來以獲得完整的解決方案——簡單:
ranges xs = let (leftovers, subproblem) = split xs
subresult = ranges subproblem
result = combine leftovers subresult
in
result
where
split xs = ....
combine as rs = ....
現在,我們知道了combine
中rs
的類型(即subresult
中ranges
ranges
的內容:
ranges :: [a] -> rngs
那么,我們如何split
輸入列表xs
呢? 面向類型的設計理念說,遵循類型。
xs
是a
s 的列表[a]
。 這種類型有兩種情況: []
或x:ys
與x:: a
和ys:: [a]
。 因此,將列表拆分為較小列表和一些剩余部分的最簡單方法是
split (x:xs) = (x, ys)
split [] = *error* "no way to do this" -- intentionally invalid code
注意到最后一種情況,我們將不得不調整整體設計以將其考慮在內。 但首先, rngs
類型可能是什么? 根據您的示例數據,它是rng
的列表,自然是rngs ~ [rng]
。
雖然是rng
類型,但我們有相當大的自由度讓它成為我們想要的任何東西。 我們必須考慮的情況是對和單例:
data Rng a = Single a
| Pair a a
.... 現在我們需要將鋸齒狀的部分組合成一張圖片。
將數字與從連續數字開始的范圍相結合是顯而易見的。
將一個數字與一個數字組合將有兩種明顯的情況,即這些數字是否連續。
我認為/希望你可以從這里開始。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.