遍历Haskell中的三元组列表

时间:2018-10-12 06:23:42

标签: haskell recursion functional-programming

plusOne :: [[(Int, Int, Int)]] -> [[(Int, Int, Int)]]

给出一个三元组列表的列表。 如果我想遍历列表并+1到Int值,该如何处理?我不确定这是否是应该使用地图的情况。

有人可以指出我正确的方向吗?

3 个答案:

答案 0 :(得分:7)

拆分功能。清单很简单;每个map个。但是元组不会遍历它们的方式,例如Python,因此需要解压缩才能访问元素;通用编程可以做到这一点,而模式匹配则容易得多。元组可以容纳各种类型的字段,因此map之类的内容无法访问所有字段。我们可以专门为三元组创建自己的地图类似物:

map3t :: (x -> y) -> (x, x, x) -> (y, y, y)
map3t f (a, b, c) = (f a, f b, f c)

现在我们可以检查类型的每个级别并分别处理:

op :: Int -> Int
op = (+1)
t3 :: (Int, Int, Int) -> (Int, Int, Int)
t3 = map3t op
lt3 :: [(Int, Int, Int)] -> [(Int, Int, Int)]
lt3 = map t3
llt3 :: [[(Int, Int, Int)]] -> [[(Int, Int, Int)]]
llt3 = map lt3

这不是递归,尽管map可以使用递归来实现(Data.List.map是)。每个函数为内部级别调用不同的函数。

答案 1 :(得分:1)

这是一个不太普通的方式示例,说明了如何使用map访问嵌套列表,也可以使用lambda函数在3tuples上将其匹配:

fun :: (Num a, Num b, Num c) => [[(a, b, c)]] -> [[(a, b, c)]]
fun xs = map (map(\(x,y,z) -> (x+1,y+1,z+1))) xs

优点:轻松易懂的oneliner解决特定问题

缺点:对于应用于元素的功能而言,它不是通用的,随着更复杂和更大的输入结构,可能变得模糊且无法控制。

使用硬编码功能进行映射,迫使您为每个操作制作一个新映射。因此,在这种情况下,更好的方法是重构函数本身,即:

fun2 f xs = map (map(op f)) xs
    where
    op f' (x,y,z) = (f' x,f' y, f' z)

使op具有可以对特定类型进行操作的功能。

使函数的签名在操作类型上更为通用:(请注意,我们无法再确定之前为数字的x,y,z类型(由于+1操作)为我们提供了该函数的更通用版本,也使我们更加负责正确地匹配类型,没有对整数进行字符串操作等。)

fun2 :: (t -> c) -> [[(t, t, t)]] -> [[(c, c, c)]]

答案 2 :(得分:1)

定义一个合适的函子来包装您的元组。

data Three a = Three {getThree :: (a, a, a)} deriving (Show, Functor)

如果您不想使用DeriveFunctor扩展名,则定义很简单:

instance Functor Three where
  fmap f (Three (x, y, z)) = Three (f x, f y, f z)

然后,您可以简单地将plusOne定义为

>>> plusOne = let f = getThree . fmap (+1) . Three in fmap (fmap f)

其中f是一个包装三元组,将(+1)映射到每个元素并解包结果的函数。这会映射到您的列表列表中:

> x = [[(1, 2, 3), (4,5,6)], [(7,8,9)]]
> plusOne x
[[(2,3,4),(5,6,7)],[(8,9,10)]]

您还可以使用Data.Functor.Compose来消除fmap的一个级别(或者,至少将其隐藏在另一组名称后面以打破单调):

> getCompose . fmap (getThree . fmap (+1) . Three) . Compose $ x
[[(2,3,4),(5,6,7)],[(8,9,10)]]

我们两次应用了相同的包装/ fmaping /展开方法。我们可以通过一个辅助函数将其抽象化

-- wrap, map, and unwrap
wmu pre post f = post . fmap f . pre

plusOne = wmu Compose getCompose $ wmu Three getThree $ (+1)

一个人可能会注意到wmudimap(专门用于(->))之间的相似性:

wmu pre post = dimap pre post . fmap

如果您首先可以将通用元组替换为自定义产品类型,那么一切就变得更加简单。

data Triplet a = Triplet a a a

-- Can be derived as well
instance Functor Triplet where
    fmap f (Triplet x y z) = Triplet (f x) (f y) (f z)

plusOne :: [[Triplet Int]] -> [[Triplet Int]]
plusOne = fmap (fmap (fmap (+1)))