Scala中的函数编程练习指导

时间:2016-09-03 18:48:09

标签: list scala functional-programming

我一直在使用Scala作为学习函数式编程和编写更好代码的手段。但是,我对某些练习有一些疑问,或者有更好的方法来解决它们。

以下是一些练习和我的解决方案/见解/疑惑:

HARD - 编写一个将列表列表连接到一个列表中的函数。它的运行时应该是所有列表总长度的线性。尝试使用我们已定义的函数。

我的溶胶:

//concatenates list of lists in a single list
def concatLists(x: List[List[Int]]):List[Int] = x.foldLeft( List[Int]() )( (x,y)=> x ++ y )

我觉得使用foldLeft和连接运算符是正确的方法,运行时感觉线性,因为我们一次将一个元素附加到折叠的结果,所以时间是线性的输入大小,我说对了吗?或者由于List被实现为链表,++运算符效率不高?如果没有,我该如何改进呢?

练习16:编写一个通过加1来转换整数列表的函数 每个元素。 (提醒:这应该是一个返回新函数的纯函数 列表!)和 练习18:编写一个函数映射,它概括了修改列表中的每个元素,同时保持列表的结构。这是它的签名:def map [A​​,B](l:List [A])(f:A => B):List [B]

//Adds one - exercise 16
def add1(x:List[Int]):List[Int]= for(elem <- x) yield elem+1

//implements map - exercise 18
def map[A,B](l: List[A])(f: A => B): List[B]=for(elem <- l) yield f(elem)

我之前在hackerrank和类似网站的某些“玩具练习”中使用过yield,根据我的理解,它用于序列理解并为结果序列添加一个新元素,就像在Python中一样,序列假设在函数签名中声明的返回类型,这是正确的吗?

我想知道的是,这些实现在功能方面是否正确,或者是否有其他/更好的方法来编写它们。

编辑:之前练习中已经“定义”的功能有:foldLeft,foldRight,append,sum,product,head and tail

3 个答案:

答案 0 :(得分:1)

foldLeft确实是正确的解决方案。为了改进代码,知道串联操作有两种变体可能很有用,其中一种变体特定于List。

List是少数几个在Scala 2.8之后保留其原始功能导向:::运算符的集合之一。您现在可以使用++添加所有收藏集,包括Iterator,但它们会遇到一个小问题。

使用列表时,您应该总是更喜欢:::。有两个原因:集合类型的效率和类型安全性。

<强>效率

x ::: y ::: zx ++ y ++ z快,因为:::是正确的关联。 x ::: y ::: z被解析为x ::: (y ::: z),其在算法上比(x ::: y) ::: z快,因为后一种变体需要O(| x |)更多步骤。

强制执行类型安全

Scala 2.8中引入的++允许您将任意两个集合添加到一起,这可能会带来非常有趣的事情。

scala> List(4, 5) ++ "ab"
res0: List[AnyVal] = List(4, 5, a, b)

然而,:::运算符将正确地强制执行内部类型和集合类型。正确的关联性使它们在语义上不同,因为++append:::将“预先”。

scala> List(1, 2, 3) ++ List(4, 5)
res0: List[Int] = List(1, 2, 3, 4, 5)

:::的行为会有所不同,因为呼叫是在右侧评估的。

scala> List(4, 5) ::: List(1, 2, 3)
res1: List[Int] = List(1, 2, 3, 4, 5)

<强>结论

def concatLists(
  x: List[List[Int]]
): List[Int] = (x :\ List.empty[T]) { (acc, l) => acc ::: l }

答案 1 :(得分:0)

我认为赋值的目的不是使用scala函数的构建,而是使用函数式编程之美来自己实现它们。

对于列表concat,我会说你可以尝试尾递归(https://en.wikipedia.org/wiki/Tail_call)方法。

def concatLists(x: List[List[Int]]): List[Int] = {
    @tailrec
    def concat(accu: List[Int], rest: List[List[Int]]): List[Int] = {
      rest match {
        case Nil => accu
        case head :: tail => concat(head ++ accu,tail)
      }
    }
    concat(Nil,x)
  }

注意:tailrec注释验证进程是否真的是尾递归的,并且将使用尾调用优化进行编译。

要练习16:好吧,map在scala中做得非常好。好吧,你也可以递归地做,只是为了测试自己。

要练习18:同样在这里,你应该重新实现Map for list。一个很好的灵感点是始终检查scala地图的原始实现。无论如何,通过实现递归解决方案,您可以很好地将每个递归步骤中的f应用于元素并将其添加到您的控件中。

快乐的黑客攻击!

答案 2 :(得分:-1)

添加到@ Mika'il的答案,为练习16和18提供了很好的答案。您可以使用flatten创建单个List而不是折叠它。 List(List(1, 2), List(3, 4)).flatten生成List(1, 2, 3, 4);和flatMap如果你需要提供一些转换。事实上,flatMap相当于调用map然后调用flatten