嵌套列表Haskell迭代

时间:2017-03-20 13:57:00

标签: haskell iteration nested-lists

我需要在Haskell中实现嵌套列表操作。

f :: [[String]] -> [[String]]

我的输入是一个二维数组

[ [ ”A” , ”A” , ”A” ]  
, [ ”B” , ”B” , ”A” ]
, [ ”A” , ”A” , ”B” ] ]

我随意生成了这个列表。

A A A
B B A 
A A B

因此,在我的实现中,我需要执行以下操作。

  • 如果某个职位有A,且有2个或2个以上的“B”邻居,则会转为B.
  • 如果某个职位有B,并且它有两个或两个以上的“B”邻居,那么就会保持原样。
  • 如果某个职位有B,且其邻居少于2个“B”,则会转为A.

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

B B A
A B B
B B A

如果我打算使用C或C ++,我的算法会这样:

  1. 复制我的输入。

  2. 遍历2for循环中的两个列表,检查是否有语句对位置进行更改,每当我要进行更改时,我将更改第二个列表而不是第一个列表,以便遍历第一个列表不会影响其他“A”和“B”。

  3. 返回第二个清单。

  4. 问题是,在Haskell中,我无法使用迭代。我该如何解决这个问题?

1 个答案:

答案 0 :(得分:3)

正如我在评论中所说,递归是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左侧或右侧。让我们为Show制作U个实例,然后验证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就会完全符合我们的要求!

为一些初始级数据结构做好准备。我们将采用我们的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有点像怪物。忽略新类型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

最后,我们可以使用rule将<{1}}应用于每个值:

extend

这条规则有点无聊。一切都很快成为B&#39。

transition = extend rule

λ display (transition example)
[[B,B,A],[A,B,B],[B,B,A]]

创建不同的规则很容易。

λ 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]]] 来填充我们不关心的范围,并在考虑邻居时忽略它。