获取列表并生成所有变体,并替换一个元素

时间:2017-09-26 17:57:33

标签: haskell

我有二维字符地图的类型:

type Row = [Char]
type Mappy = [Row]

我想编写一个类似Mappy的函数:

[['o','o'],['o','o']]

并生成所有Mappy的列表,其中单个'o'元素替换为'i':

[ [['i','o'],['o','o']]
, [['o','i'],['o','o']]
, [['o','o'],['i','o']]
, [['o','o'],['o','i']]
]

这是我尝试过的:我认为我需要使用map函数,因为我需要遍历每个元素,但我不知道如何,因为map函数不跟踪它的位置正在努力。

type Row = [Char]
type Mappy = [Row]

func :: Mappy -> [Mappy]
func a = map (map someFunc a) a

someFunc :: Mappy -> Char -> Mappy
someFunc a b = if b == "o"
               then undefined
               else a

显然,我应该改变undefined,但我不知道怎么做。提前谢谢。

2 个答案:

答案 0 :(得分:3)

拉链很棒,而且有一篇有趣的博客文章 实施Conway's Game of Life using zippers and comonads in Haskell。在另一 如果这仍然是你学习Haskell的第一周,你可能会这样做 想要在星期四保存Comonads,对吧?

这是另一种使用简单递归和列表的方法 理解并没有复杂的Haskell特征。

首先,假设我们有一个很棒的功能:

varyOne :: (a -> [a]) -> [a] -> [[a]]
varyOne = undefined

的工作原理如下。给定函数f产生零或 元素a的更多变体,函数调用varyOne f xs 生成由于获取而导致的列表xs的所有变体 正好是xs中的一个元素,在列表中间说x,并将其替换为全部 由f x给出的变体。

此功能非常灵活。它可以生成所有变体的列表,这些变体是通过用常量强制替换元素得到的:

> varyOne (\x -> [3]) [1,2,3,4]
[[3,2,3,4],[1,3,3,4],[1,2,3,4],[1,2,3,3]]

通过为特定值返回单例变体和为其他值返回变量的空列表,它可以生成用'o'替换'i'的所有变体,同时抑制"变体& #34;无法替换的地方:

> let varyRow = varyOne (\c -> if c == 'o' then ['i'] else [])
> varyRow "ooxo"
["ioxo","oixo","ooxi"]

并且,因为varyRow本身会生成行的变体,所以它可以与varyOne一起使用来生成表格的变体,其中特定的已被其替换变体:

> varyOne varyRow ["ooo","oox","ooo"]
[["ioo","oox","ooo"],["oio","oox","ooo"],["ooi","oox","ooo"],
 ["ooo","iox","ooo"],["ooo","oix","ooo"],
 ["ooo","oox","ioo"],["ooo","oox","oio"],["ooo","oox","ooi"]]

事实证明,这个令人敬畏的功能非常容易编写:

varyOne :: (a -> [a]) -> [a] -> [[a]]
varyOne f (x:xs)
    = [y:xs | y <- f x] ++ [x:ys | ys <- varyOne f xs]
varyOne _ []     = []

第一个列表推导生成当前元素的所有变体。第二个列表推导使用递归varyOne调用生成涉及更改当前元素右侧的变体。

鉴于varyOne,我们可以定义:

replaceOne :: Char -> Char -> Mappy -> [Mappy]
replaceOne old new = varyOne (varyOne rep1)
  where rep1 x = if x == old then [new] else []

> replaceOne 'o' 'i' ["ooo","oox","ooo"]
[["ioo","oox","ooo"],["oio","oox","ooo"],["ooi","oox","ooo"]
,["ooo","iox","ooo"],["ooo","oix","ooo"]
,["ooo","oox","ioo"],["ooo","oox","oio"],["ooo","oox","ooi"]]

可能是您正在寻找的功能。

如果您希望无条件地用i替换单个元素,无论旧元素是什么,那么这将起作用:

> varyOne (varyOne (const ['i'])) ["ooo","oox","ooo"]
[["ioo","oox","ooo"],["oio","oox","ooo"],["ooi","oox","ooo"]
,["ooo","iox","ooo"],["ooo","oix","ooo"],["ooo","ooi","ooo"]
,["ooo","oox","ioo"],["ooo","oox","oio"],["ooo","oox","ooi"]]

答案 1 :(得分:2)

你想要的,年轻的BaasBartMans,是拉链。

data Zipper a = Zipper [a] a [a]

ofList :: [a] -> Maybe (Zipper a)
ofList [] = Nothing
ofList (a:as) = Just (Zipper [] a as)

拉链为您提供列表中某个位置的上下文,所以您 可以轻松地一次修改一个,前进和后退等。

我们可以从拉链中恢复列表:

instance Foldable Zipper where
  foldr f c (Zipper ls a rs) = foldl' (flip f) (f a (foldr f c rs)) ls

我们可以同时修改Zipper中的每个位置:

instance Functor Zipper where
  fmap f (Zipper ls a rs) = Zipper (fmap f ls) (f a) (fmap f rs)

或者只是关注的元素:

here :: Functor f => (a -> f a) -> Zipper a -> f (Zipper a)
here f (Zipper ls a rs) = fmap (\a' -> Zipper ls a' rs) (f a)

由于ZipperComonad,我们可以修改上下文中的每个元素:

instance Comonad Zipper where
  extract (Zipper _ a _) = a
  extend f z@(Zipper ls a rs) = Zipper ls' a' rs' where
    a' = f z
    ls' = unfoldr (fmap (\z' -> (f z', z')) . goLeft) z
    rs' = unfoldr (fmap (\z' -> (f z', z')) . goRight) z

使用它,我们可以构建一个在上下文中修改列表的每个元素的函数:

everywhere :: Alternative f => (a -> f a) -> [a] -> f [a]
everywhere f as = case ofList as of
  Nothing -> pure []
  Just z  -> asum $ extend (fmap toList . here f) z

适用于简单列表:

λ everywhere (\a -> [a+1]) [10,20,30]
[[11,20,30]
,[10,21,30]
,[10,20,31]]

嵌套列表:

λ everywhere (everywhere (\a -> [a+1])) [[10], [20,20], [30,30,30]]
[[[11],[20,20],[30,30,30]]
,[[10],[21,20],[30,30,30]]
,[[10],[20,21],[30,30,30]]
,[[10],[20,20],[31,30,30]]
,[[10],[20,20],[30,31,30]]
,[[10],[20,20],[30,30,31]]]