簡體   English   中英

嵌套列表Haskell迭代

[英]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

所以在我的實現中我需要做以下事情。

  • 如果一個倉位有A,並且它有兩個或兩個以上的“B”鄰居,它將轉向B.
  • 如果一個位置有B,並且它有兩個或兩個以上的“B”鄰居,那么它就像它一樣。
  • 如果一個倉位有B,並且它的鄰居少於2個“B”,它將轉向A.

因此,在第一步之后,我的表將如下所示。

B B A
A B B
B B A

如果我打算使用C或C ++,我的算法會喜歡這樣:

  1. 復制我的輸入。

  2. 遍歷2for循環中的兩個列表,檢查if語句是否要對位置進行更改,每當我要進行更改時,我將更改第二個列表而不是第一個列表,以便遍歷第一個列表不會影響另一個“A”和“B”。

  3. 返回第二個清單。

問題是,在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實例,然后驗證rightUleftU正常工作。 我們顯然無法打印無限列表,因此我們只需從每一側獲取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]

看起來很有效。 這些函數名, duplicateextend不是任意選擇的(至少是我)。 這些函數是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在哪里,我們實際上不必寫它。 上面給出的定義是默認的。

這是很多工作,但現在我們將能夠輕松解決原始問題! 看一下這個。 我將創建一個包含AB值的數據結構,以及一個我們不關心的值, 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM