我一直在使用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
答案 0 :(得分:1)
foldLeft
确实是正确的解决方案。为了改进代码,知道串联操作有两种变体可能很有用,其中一种变体特定于List。
List
是少数几个在Scala 2.8之后保留其原始功能导向:::
运算符的集合之一。您现在可以使用++
添加所有收藏集,包括Iterator
,但它们会遇到一个小问题。
使用列表时,您应该总是更喜欢:::
。有两个原因:集合类型的效率和类型安全性。
<强>效率强>
x ::: y ::: z
比x ++ 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
。