如何遍历字符列表并操纵Haskell中的字符?

时间:2019-02-27 01:47:03

标签: haskell functional-programming

我正在尝试浏览列表中的字符列表,并对当前字符执行某些操作。我要完成的Java等效工作是:

public class MyClass {
    void repeat(String s) {
        String newString = "";

        for(int i = 0; i < s.length(); i++) {
          newString += s.charAt(i);
          newString += s.charAt(i);
        }

 public static void main(String args[]) {
    MyClass test = new MyClass();
    test.repeat("abc");
  }
}

2 个答案:

答案 0 :(得分:3)

关于函数式编程的最好的事情之一就是可以将像您这样的模式封装在一个高阶函数中。如果不适合,您仍然可以使用递归。

递归

首先,一个简单的递归解决方案。其背后的想法是,它就像一个for循环:

recursiveFunction [] = baseCase
recursiveFunction (char1:rest) = (doSomethingWith char1) : (recursiveFunction rest)

因此,我们以这种形式编写repeat函数。基本情况是什么?好吧,如果您repeat为空字符串,则将返回一个空字符串。什么是递归?在这种情况下,我们将第一个字符加倍,然后沿着字符串的其余部分递归。因此,这是一个递归解决方案:

repeat1 [] = []
repeat1 (c:cs) = c : c : (repeat1 cs)

高阶函数

当您开始编写更多Haskell时,您会发现这类递归解决方案通常适合一些重复模式。幸运的是,标准库包含针对这些类型的模式的几个预定义的递归函数:

  • fmap用于通过使用作为参数给出的函数将列表的每个元素映射到不同的值。例如,fmap (\x -> x + 1)1添加到列表的每个元素。不幸的是,它不能更改列表的长度,因此我们不能单独使用fmap
  • concat用于“展平”嵌套列表。例如,concat [[1,2],[3,4,5]][1,2,3,4,5]
  • foldr / foldl是两个更复杂且通用的函数。有关更多详细信息,请咨询Learn You a Haskell

这些似乎都不能直接满足您的需求。但是,我们可以同时使用concatfmap

repeat2 list = concat $ fmap (\x -> [x,x]) list

这个想法是fmap会发生变化,例如[1,2,3]到嵌套列表[[1,1],[2,2],[3,3]],然后嵌套在一起。这种从单个元素生成多个元素的模式非常普遍,以至于组合甚至都有一个特殊的名称:concatMap。您可以这样使用它:

repeat3 list = concatMap (\x -> [x,x]) list

就个人而言,这就是我在Haskell中编写repeat的方式。 (嗯,几乎:我会使用 eta-reduction 稍微简化一下。但是从您的角度来看这是无关紧要的。)这就是为什么我认为Haskell如此之多比许多其他语言更强大:这种7行Java方法是一行高度可读,惯用的Haskell!

答案 1 :(得分:0)

正如其他人所建议的那样,从列表理解开始可能是明智的:

-- | Repeat each element of a list twice.
double :: [x] -> [x]
double xs = [d | x <- xs, d <- [x, x]]

但是,无论x的值如何,理解中的第二个列表始终具有相同数量的元素,这意味着我们并不需要那么多的功能:Applicative接口就足够了。让我们以不同的方式开始理解:

double xs = xs >>= \x -> [x, x] >>= \d -> pure d

我们可以使用monad身分定律立即简化:

double xs = xs >>= \x -> [x, x]

现在我们切换到Applicative,但让我们为难的部分留个空洞:

double :: [x] -> [x]
double xs = liftA2 _1 xs [False, True]

编译器告诉我们

_1 :: x -> Bool -> x

由于内部/第二个列表的元素始终相同,并且始终来自当前的外部/第一个列表元素,因此我们不必关心Bool

double xs = liftA2 const xs [False, True]

实际上,我们甚至不需要能够来区分列表位置:

double xs = liftA2 const xs [(),()]

当然,我们有一个特殊的Applicative方法(<*),它对应于liftA2 const,所以我们来使用它:

double xs = xs <* [(),()]

然后,如果愿意,我们可以通过切换为“无积分”形式来避免提及xs

-- | Repeat each element of a list twice.
double :: [x] -> [x]
double = (<* [(),()])

现在进行测试:

main :: IO ()
main = print $ double [1..3]

这将打印[1,1,2,2,3,3]


double承认可疑价值略有泛化:

double :: Alternative f => f x -> f x
double = (<* join (<|>) (pure ()))

这将适用于序列和列表:

double (Data.Sequence.fromList [1..3]) = Data.Sequence.fromList [1,1,2,2,3,3]

但是对于其他一些Alternative实例可能会有些混乱:

double (Just 3) = Just 3