【问题标题】:Haskell: Map function with tuplesHaskell:带有元组的映射函数
【发布时间】:2011-08-23 18:25:51
【问题描述】:

我必须编写一个 Haskell 程序来执行以下操作:

Main> dotProduct [(1,3),(2,5),(3,3)]  2
[(2,3),(4,5),(6,3)]

无论有没有map 函数,我都必须这样做。 我已经在没有map 的情况下做到了,但我不知道用map 做到这一点。

我的dotProduct 没有map 功能:

dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)]
dotProduct [] _ = []
dotProduct [(x,y)] z = [(x*z,y)]
dotProduct ((x,y):xys) z = (x*z,y):dotProduct (xys) z

所以我真的需要map 版本的帮助。

【问题讨论】:

  • 您提供的版本中大约有一半的代码只是重新实现 map。你明白map 是做什么的吗?你看过它的源代码吗?
  • 嗯,我已经使用 Map 完成了一些简单的基本问题......这就是我所知道的一切。所以你是对的,我需要更深入的了解。
  • 我当然鼓励它。如果您查看它们的实现,Haskell 中的大多数标准库函数都非常简单且易于理解。希望我回答中的工作示例将有助于更好地理解map
  • dotProduct x = map $ first (*x) 怎么样

标签: haskell tuples map-function


【解决方案1】:

与其开始尝试以某种方式适应 map,不如考虑如何简化和概括当前功能。从此开始:

dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)]
dotProduct [] _ = []
dotProduct [(x,y)] z = [(x*z,y)]
dotProduct ((x,y):xys) z = (x*z,y):dotProduct (xys) z

首先,我们将使用(:) 构造函数重写第二种情况:

dotProduct ((x,y):[]) z = (x*z,y):[]

使用第一种情况扩展结果中的[]

dotProduct ((x,y):[]) z = (x*z,y):dotProduct [] z

比较这与第三种情况,我们可以看到它们是相同的,除了当xys[] 时专用。因此,我们可以简单地完全消除第二种情况:

dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)]
dotProduct [] _ = []
dotProduct ((x,y):xys) z = (x*z,y):dotProduct (xys) z

接下来,泛化函数。首先,我们重命名它,并让dotProduct 调用它:

generalized :: [(Float, Integer)] -> Float -> [(Float, Integer)]
generalized [] _ = []
generalized ((x,y):xys) z = (x*z,y):generalized (xys) z

dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)]
dotProduct xs z = generalized xs z

首先,我们通过运算对其进行参数化,专门用于dotProduct的乘法:

generalized :: (Float -> Float -> Float) -> [(Float, Integer)] -> Float -> [(Float, Integer)]
generalized _ [] _ = []
generalized f ((x,y):xys) z = (f x z,y):generalized f (xys) z

dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)]
dotProduct xs z = generalized (*) xs z

接下来,我们可以观察到两件事:generalized 不再直接依赖于算术,因此它可以在任何类型上工作;唯一一次使用z 是作为f 的第二个参数,所以我们可以将它们组合成一个函数参数:

generalized :: (a -> b) -> [(a, c)] -> [(b, c)]
generalized _ [] = []
generalized f ((x,y):xys) = (f x, y):generalized f (xys)

dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)]
dotProduct xs z = generalized (* z) xs

现在,我们注意到f 仅用于元组的第一个元素。这听起来很有用,所以我们将其提取为一个单独的函数:

generalized :: (a -> b) -> [(a, c)] -> [(b, c)]
generalized _ [] = []
generalized f (xy:xys) = onFirst f xy:generalized f (xys)

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

dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)]
dotProduct xs z = generalized (* z) xs

现在我们再次观察到,在generalized 中,f 仅与onFirst 一起使用,因此我们再次将它们组合成一个函数参数:

generalized :: ((a, c) -> (b, c)) -> [(a, c)] -> [(b, c)]
generalized _ [] = []
generalized f (xy:xys) = f xy:generalized f (xys)

dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)]
dotProduct xs z = generalized (onFirst (* z)) xs

我们再次观察到generalized 不再依赖于包含元组的列表,因此我们让它适用于任何类型:

generalized :: (a -> b) -> [a] -> [b]
generalized _ [] = []
generalized f (x:xs) = f x : generalized f xs

现在,将generalized 的代码与以下代码进行比较:

map :: (a -> b) -> [a] -> [b]
map _ []     = []
map f (x:xs) = f x : map f xs

事实证明,还存在一个稍微更通用的 onFirst 版本,所以我们将把它和 generalized 替换为它们的标准库等价物:

import Control.Arrow (first)

dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)]
dotProduct xs z = map (first (* z)) xs

【讨论】:

  • 我很惊讶你的解释!!我会仔细阅读。非常感谢您不厌其烦地写出这样的答案!
  • 我赞成!作为 Haskell 的初学者,我非常感谢这样的深入解释!非常感谢!
【解决方案2】:
dotProduct xs z = map (\(x,y) -> (x*z,y)) xs

(\(x,y) -> (x*z,y)) 部分是一个函数,它接受一对并返回一个与旧对相似的新对,不同之处在于它的第一个组件乘以 zmap 函数接受一个函数并将其应用于列表中的每个元素。因此,如果我们将(\(x,y) -> (x*z,y)) 函数传递给map,它将将该函数应用于xs 中的每个元素。

虽然你确定你的第一个是正确的?点积运算通常被定义为它采用两个向量,将相应的分量相乘,然后将它们相加。像这样:

dotProduct xs ys = sum $ zipWith (*) xs ys

【讨论】:

  • 如果你要回答一个明确家庭作业的问题,那么真的要求你至少解释 i> 答案,而不是仅仅丢掉一行代码而不是别的什么?
  • 非常感谢!!我总是在 lambda 抽象方面遇到麻烦......是的,“dotProduct”这个名字不太适合这个问题,但这是应该做的。 (对不起我的英语)
【解决方案3】:

EEVIAC 已经发布了答案,所以我将解释如何自己想出答案。您可能知道,map 具有类型签名(a -> b) -> [a] -> [b]。现在,dotProduct 的类型为 [(Float, Integer)] -> Float -> [(Float, Integer)],你会在其中的某个地方调用 map,所以它必须看起来像这样:

dotProduct theList z = map (??? z) theList

其中???Float -> (Float, Integer) -> (Float, Integer) 类型的函数 - 这直接来自map 的类型签名以及我们将z 传递给函数的事实,我们必须这样做,因为有没有其他地方可以使用它。

map 和高阶函数的问题通常是你必须记住高阶函数的作用并“简单地”为它提供正确的函数。由于 map 将给定函数应用于列表中的所有元素,因此您的函数只需要使用一个元素,您可以忘记列表的所有内容 - map 会处理它。

【讨论】:

    猜你喜欢
    • 2012-09-08
    • 2012-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多