[英]Nested List Haskell Iteration
我需要在Haskell中實現嵌套列表操作。
f :: [[String]] -> [[String]]
我的輸入是一個二維數組
[ [ ”A” , ”A” , ”A” ]
, [ ”B” , ”B” , ”A” ]
, [ ”A” , ”A” , ”B” ] ]
我隨意生成了那個列表。
A A A
B B A
A A B
所以在我的實現中我需要做以下事情。
因此,在第一步之后,我的表將如下所示。
B B A
A B B
B B A
如果我打算使用C或C ++,我的算法會喜歡這樣:
復制我的輸入。
遍歷2for循環中的兩個列表,檢查if語句是否要對位置進行更改,每當我要進行更改時,我將更改第二個列表而不是第一個列表,以便遍歷第一個列表不會影響另一個“A”和“B”。
返回第二個清單。
問題是,在Haskell中,我不能使用迭代。 我怎么解決這個問題?
正如我在評論中所說,遞歸是Haskell中的循環原語。 但是,Haskell為我們提供了很多功能來構建更加用戶友好的抽象,而不是直接使用遞歸。 正如@Lazersmoke所提到的,當您根據集合中的其他值(例如值的鄰居)更新集合的每個單獨值時,Comonad是一個很好的抽象。
網上有很多關於Comonad課程的例子,但Monad令人遺憾地黯然失色。 所以這是我嘗試平均得分。
這將是一個很長的帖子,所以讓我從結果開始。 這是來自GHCi:
λ display example
[[A,A,A],[B,B,A],[A,A,B]]
λ display (transition example)
[[B,B,A],[A,B,B],[B,B,A]]
好的,現在讓我們開始做生意吧。 首先是一些管理事項:
module Main where
import Control.Comonad -- from the comonad package
我將仔細嘗試解釋每一件作品,但可能需要一段時間才能看到更大的畫面。 首先,我們將創建一個通常稱為拉鏈的有趣數據結構,並為其實現Functor
實例。
data U x = U [x] x [x] deriving Functor
instance Functor U where
fmap f (U as x bs) = U (fmap f as) (f x) (fmap f bs)
這種數據結構似乎並不那么特別。 這就是我們如何使用U
來讓它變得很酷。 因為Haskell是惰性的,所以我們可以使用U
構造函數的無限列表。 例如, i1 = U [-1,-2..] 0 [1,2..]
表示所有整數。 但這並不是全部。 還有另一條信息:中心點為0.我們也可以將所有整數表示為i2' = U [0,-1..] 1 [2,3..]
。 這些值幾乎相同; 他們只是換了一個。 事實上,我們可以創建將一個轉換為另一個的函數。
rightU (U a b (c:cs)) = U (b:a) c cs
leftU (U (a:as) b c) = U as a (b:c)
如您所見,我們可以通過重新排列元素將U
向左或向右滑動 。 讓我們為U
創建一個Show
實例,然后驗證rightU
和leftU
正常工作。 我們顯然無法打印無限列表,因此我們只需從每一側獲取3個元素。
instance Show x => Show (U x) where
show (U as x bs) = (show . reverse . take 3) as ++ (show x) ++ (show . take 3) bs
λ i1
[-3,-2,-1]0[1,2,3]
λ leftU i2
[-3,-2,-1]0[1,2,3]
λ i2
[-2,-1,0]1[2,3,4]
λ rightU i1
[-2,-1,0]1[2,3,4]
讓我們回顧一下我們的最終目標。 我們希望有一個數據結構,我們可以根據所有鄰居更新每個值。 讓我們看看如何使用我們的U
數據結構。 假設我們想用其鄰居的總和替換每個數字。 首先,讓我們編寫一個函數來計算U
當前位置的鄰居:
sumOfNeighbors :: U Int -> Int
sumOfNeighbors (U (a:_) _ (b:_)) = a + b
只是為了驗證它是否有效:
λ sumOfNeighbors i1
0
λ sumOfNeighbors i2
2
不幸的是,這只給我們一個結果。 我們希望將此功能應用於每個可能的位置。 好吧U
有一個Functor
實例,所以我們可以通過它來fmap
一個函數。 如果我們的函數有類似Int -> Int
的類型,那么它會很好用,但它實際上是U Int -> Int
。 但是,如果我們可以將U Int
轉換為U (U Int)
呢? 然后fmap sumOfNeighbors
將完全按照我們的fmap sumOfNeighbors
!
為一些初始級數據結構做好准備。 我們將采用我們的U Int
並創建一個U (U Int)
,如下所示:
-- not real Haskell. just for illustration
U [leftU u, (leftU . leftU) u, (leftU . leftU . leftU) u..] u [rightU u, (rightU . rightU) u, (rightU . rightU . rightU) u..]
這個新U (U a)
是原來的U a
。 當我們向左滑動時,我們得到原始的U a
向左滑動,同樣向右滑動。 換句話說,新U (U a)
包含原始U a
所有左右幻燈片。以下是我們如何做到這一點:
duplicate :: U a -> U (U a)
duplicate u = U lefts u rights
where lefts = tail $ iterate leftU u
rights = tail $ iterate rightU u
我們可以使用duplicate
來編寫我們想要的函數:
extend :: (U a -> b) -> U a -> U b
extend f = fmap f . duplicate
我們來試試吧。
λ extend sumOfNeighbors i1
[-6,-4,-2]0[2,4,6]
看起來很有效。 這些函數名, duplicate
和extend
不是任意選擇的(至少是我)。 這些函數是Comonad
類型類的一部分。 我們一直在為我們的U
數據類型實現它。
class Functor w => Comonad w where
extract :: w a -> a
duplicate :: w a -> w (w a)
extend :: (w a -> b) -> w a -> w b
唯一缺少的是extract
對於U
是微不足道的:
extract (U _ x _) = x
可能並不清楚這門課程有多么有用。 讓我們繼續看看如何處理二維情況。 我們可以用拉鏈拉鏈做二維。 也就是說, U (U a)
左右移動內拉鏈,上下移動移動外拉鏈。
newtype V a = V { getV :: U (U a) }
instance Functor V where
fmap f = V . (fmap . fmap) f . getV
-- shift the 'outer' zipper
up :: V a -> V a
up = V . leftU . getV
down :: V a -> V a
down = V . rightU . getV
-- shift the 'inner' zippers
left :: V a -> V a
left = V . fmap leftU .getV
right :: V a -> V a
right = V . fmap rightU . getV
這是Comonad對V
的看法:
instance Comonad V where
extract = extract . extract . getV
duplicate = fmap V . V . dup . dup . getV
where dup u = U (lefts u) r (right u)
lefts u = tail $ iterate (fmap leftU) u
rights u = tail $ iterate (fmap rightU) u
extract
功能相當簡單; 它只需挖掘兩層拉鏈即可獲取當前值。 另一方面, duplicate
是一種怪物。 忽略newtype V
,它的類型將是duplicate :: U (U a) -> U (U (U (U a)))
。 dup
輔助函數的目的是添加U
層。 它被調用兩次。 然后我們將其包裝在V
以獲得V (U (U a))
。 然后fmap V
包裹內部U (U a)
以得到結果V (V a)
。
哦順便說一句,如果你想知道extend
在哪里,我們實際上不必寫它。 上面給出的定義是默認的。
這是很多工作,但現在我們將能夠輕松解決原始問題! 看一下這個。 我將創建一個包含A
和B
值的數據結構,以及一個我們不關心的值, C
:
data Token = A | B | C deriving (Eq,Show)
這里有一些東西可以使構建和顯示V
更容易。
-- a list of U's containing nothing but x's
filled x = repeat $ U (repeat x) x (repeat x)
type Triple a = (a,a,a)
-- create a U with the middle values a, b, and c, and all the other values the defaulted to d
toU :: a -> Triple a -> U a
toU d (a,b,c) = U (a : repeat d) b (c : repeat d)
-- create a V centered on the 9 given values and default all other values to d
toV :: a -> Triple (Triple a) -> V a
toV d (as, bs, cs) = V (U x y z)
where x = (toU d as) : filled d
y = toU d bs
z = (toU d cs) : filled d
display :: Show a => V a -> [[a]]
display v = fmap g [ [up . left, up, up . right]
, [left, id, right]
, [down . left, down , down . right] ]
where g = fmap (extract . ($ v))
這是示例的樣子:
example = toV C ((A,A,A)
,(B,B,A)
,(A,A,B))
規則通過以下方式實施:
-- move into each neighboring position and get the value in that position
neighbors :: V a -> [a]
neighbors v = fmap (extract . ($ v)) positions
where positions = [ up . left
, up
, up . right
, left
, right
, down . left
, down
, down . right ]
numberOfBs :: V Token -> Int
numberOfBs = length . filter (==B) . neighbors
rule :: V Token -> Token
rule v = case extract v of
C -> C -- C's remain C's forever
_ -> if numberOfBs v >= 2 then B else A
最后,我們可以使用extend
對每個值應用rule
:
transition = extend rule
λ display (transition example)
[[B,B,A],[A,B,B],[B,B,A]]
這條規則有點無聊。 一切都很快成為B的。
λ take 10 $ fmap display (iterate transition example)
[[[A,A,A],[B,B,A],[A,A,B]],[[B,B,A],[A,B,B],[B,B,A]],[[B,B,B],[B,B,B],[B,B,B]],[[B,B,B],[B,B,B],[B,B,B]],[[B,B,B],[B,B,B],[B,B,B]],[[B,B,B],[B,B,B],[B,B,B]],[[B,B,B],[B,B,B],[B,B,B]],[[B,B,B],[B,B,B],[B,B,B]],[[B,B,B],[B,B,B],[B,B,B]],[[B,B,B],[B,B,B],[B,B,B]]]
創建不同的規則很容易。
rule2 :: V Token -> Token
rule2 v = case extract v of
C -> C
A -> if numberOfBs v >= 2 then B else A
B -> if numberOfBs v >= 4 then A else B
λ take 10 $ fmap display (iterate (extend rule2) example)
[[[A,A,A],[B,B,A],[A,A,B]],[[B,B,A],[B,B,B],[B,B,B]],[[B,A,B],[A,A,A],[B,A,B]],[[B,B,B],[B,B,B],[B,B,B]],[[B,A,B],[A,A,A],[B,A,B]],[[B,B,B],[B,B,B],[B,B,B]],[[B,A,B],[A,A,A],[B,A,B]],[[B,B,B],[B,B,B],[B,B,B]],[[B,A,B],[A,A,A],[B,A,B]],[[B,B,B],[B,B,B],[B,B,B]]]
很酷,對嗎? 最后我想提一下。 您是否注意到我們沒有寫任何特殊情況來處理邊緣? 由於數據結構是無限的,我們只是填充了我們不關心C
值的范圍,並在考慮鄰居時忽略它。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.