我正在尝试编写一个函数,它将字符串中的单个字符添加到字符串列表中,例如
combine ", !" ["Hello", "", "..."] = ["Hello,", " ", "...!"]
我试过这个:
combine :: String -> [String] -> [String]
combine (y:ys) (x:xs) =
[x:y, combine ys xs]
我正在尝试编写一个函数,它将字符串中的单个字符添加到字符串列表中,例如
combine ", !" ["Hello", "", "..."] = ["Hello,", " ", "...!"]
我试过这个:
combine :: String -> [String] -> [String]
combine (y:ys) (x:xs) =
[x:y, combine ys xs]
一个简单的就是
combine :: [Char] -> [String] -> [String]
combine [] _ = []
combine _ [] = []
combine (c:cs) (x:xs) = x ++ [c] : combine cs xs
或者更简单地使用zipWith
combine :: [Char] -> [String] -> [String]
combine = zipWith (\c x -> x ++ [c])
我必须做一些额外的工作才能让它工作。我给你分解一下。
首先,我将函数的类型指定为[Char] -> [String] -> [String]
. 我可以使用String
第一个参数,但您在概念上操作的是字符列表和字符串列表,而不是字符串和字符串列表。
接下来,我必须为这个函数指定边缘情况。当任一参数为空列表时会发生什么[]
?简单的答案是然后结束计算,所以我们可以写
combine [] _ = []
combine _ [] = []
这里_
匹配任何东西,但是因为它没有在返回值中使用而将其丢弃。
接下来,对于函数的实际主体,我们要获取第一个字符和第一个字符串,然后将该字符附加到字符串的末尾:
combine (c:cs) (x:xs) = x ++ [c]
cs
但这对or没有任何xs
作用,我们的列表的其余部分(甚至不会使用上面的类型签名编译)。我们需要继续,因为我们正在生成一个列表,这通常是使用前置运算符完成的:
combine (c:cs) (x:xs) = x ++ [c] : combine cs xs
然而,这是一种非常常见的模式,以至于有一个辅助函数zipWith
可以为我们处理边缘情况。它的类型签名是
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
它同时遍历两个输入列表,将相应的元素传递给提供的函数。由于我们要应用的函数是\c x -> x ++ [c]
(变成一个 lambda 函数),我们可以把它放到zipWith
as
combine cs xs = zipWith (\c x -> x ++ [c]) cs xs
但是 Haskell 会让我们在可能的情况下放弃参数,所以我们可以将它减少到
combine :: [Char] -> [String] -> [String]
combine = zipWith (\c x -> x ++ [c])
就是这样!
当您想逐个元素地组合列表时,通常是zip
您正在查看的。在这种情况下,你确切地知道你想如何组合元素——这使它成为一个zipWith
.
zipWith
采用“组合函数”,然后使用所述组合函数创建一个组合两个列表的函数。让我们调用您的“组合”函数append
,因为它会在字符串的末尾添加一个字符。你可以这样定义它:
append char string = string ++ [char]
你明白这是如何工作的吗?例如,
append 'e' "nic" = "nice"
或者
append '!' "Hello" = "Hello!"
现在我们有了它,回想一下它zipWith
采用“组合函数”,然后创建一个使用该函数组合两个列表的函数。所以你的功能很容易实现为
combine = zipWith append
它将append
按您提供的列表中的每个元素执行,如下所示:
combine ", !" ["Hello", "", "..."] = ["Hello,", " ", "...!"]
你很亲密。你所拥有的有几个问题。
y 具有 Char 类型,x 具有 String 类型,它是 [Char] 的别名。这意味着您可以使用 y : x 将 y 添加到列表的顶部,但不能使用相同的:
运算符将 y 添加到列表的末尾。相反,您将 y 放入列表并加入列表。
x ++ [y]
还必须有一个基本情况,否则这个递归将继续,直到它在任何一个列表中都没有元素并崩溃。在这种情况下,我们可能没有想要添加的任何内容。
combine [] [] = []
最后,一旦我们创建了元素y ++ [x]
,我们希望将它添加到我们计算的其余项目的顶部。因此,我们:
习惯将其添加到我们的列表中。
combine :: String -> [String] -> [String]
combine [] [] = []
combine (x : xs) (y : ys) = (y ++ [x]) : (combine xs ys)
关于此代码的一个注意事项,如果字符串中的字符数与列表中的字符串数不同,那么这将崩溃。您可以通过多种方式处理这种情况,bheklilr 的回答解决了这个问题。
kqr 的答案也很完美,可能是实践中最好的答案。