我必须编写一个执行以下操作的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
版本的帮助。
答案 0 :(得分:13)
不要以某种方式尝试适合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
答案 1 :(得分:3)
dotProduct xs z = map (\(x,y) -> (x*z,y)) xs
(\(x,y) -> (x*z,y))
部分是一个函数,它接受一对并返回一个与旧的一对的新对,除了它的第一个组件乘以z
。 map
函数接受一个函数并将其应用于列表中的每个元素。因此,如果我们将(\(x,y) -> (x*z,y))
函数传递给map
,它会将该函数应用于xs
中的每个元素。
虽然你确定你的第一个是正确的吗?通常定义点积运算,使得它采用两个向量,将相应的组件相乘,然后将它们加在一起。像这样:
dotProduct xs ys = sum $ zipWith (*) xs ys
答案 2 :(得分:2)
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
将处理它。