Scala 递归列表连接
我有一个函数,它返回整数列表中元素之间的距离列表:Scala 递归列表连接,scala,functional-programming,Scala,Functional Programming,我有一个函数,它返回整数列表中元素之间的距离列表: def dists(l: List[Int]) = { //@annotation.tailrec def recurse(from: Int, rest: List[Int]): List[Int] = rest match { case Nil => Nil case to :: tail => to - from :: recurse(to, tail) } l match { cas
def dists(l: List[Int]) = {
//@annotation.tailrec
def recurse(from: Int, rest: List[Int]): List[Int] = rest match {
case Nil => Nil
case to :: tail => to - from :: recurse(to, tail)
}
l match {
case first :: second :: _ => recurse(first, l.tail)
case _ => Nil
}
}
::
阻止我使用@tailrec
注释,尽管对递归的调用似乎处于尾部位置
是否有@tailrec
兼容的方式进行连接
我可以使用累加器,但我必须反转输入或输出,对吗
编辑:我对递归方法特别感兴趣。我的具体用例有点复杂,因为对recurse
的一次调用可以向结果列表中添加几个项目:
=> item1 :: item2:: recurse(...)
距离函数只是一个例子来说明这个问题。我不确定您是否可以使用累加器变量来确保正确的尾部调用优化。我用一个累加器模拟重做,最后是一个反转。您可以通过执行append而不是prepend来消除末尾的反转,但是我相信prepend/reverse组合在创建更大的列表时会更有效
object TailRec {
def dists(l: List[Int]) = {
@annotation.tailrec
def recurse(from: Int, rest: List[Int], acc:List[Int]): List[Int] = rest match {
case Nil => acc
case to :: tail =>
val head = to - from
recurse(to, tail, head :: acc)
}
val result = l match {
case first :: second :: _ => recurse(first, l.tail, List())
case _ => Nil
}
result.reverse
}
def main(args: Array[String]) {
println(dists(List(1,5,8,14,19,21)))
}
}
现在,您可以使用列表中提供的开箱即用功能执行此dists
功能,如下所示:
List(1,5,8,14,19,21).sliding(2).filterNot(_.isEmpty).map(list => list.last - list.head)
这可能会降低效率,但更简洁。这不是对原始请求的回复,而是问题的替代解决方案
您只需将同一个列表“移位”一个位置,然后将生成的压缩列表映射到元组元素的差异即可
编码
def dist(l: List[Int]) = l.zip(l drop 1) map { case (a,b) => b - a}
如果你无法理解发生了什么,我建议拆分操作并在REPL上进行探索
scala> val l = List(1,5,8,14,19,21)
l: List[Int] = List(1, 5, 8, 14, 19, 21)
scala> l zip (l drop 1)
res1: List[(Int, Int)] = List((1,5), (5,8), (8,14), (14,19), (19,21))
scala> res1 map { case (a, b) => b - a }
res2: List[Int] = List(4, 3, 6, 5, 2)
如果要对列表的相邻元素进行操作,滑动
是您的朋友:
def dist(list: List[Int]) = list.sliding(2).collect{case a::b::Nil => b-a}.toList
抱歉,这有点晚了,Haskell和ML中存在的标准模式在scala中也可以使用。这与cmbaxter的答案非常相似,但我想我应该添加它以供参考,这样的东西在我开始Scala时会对我有很大帮助
在列表中递归时始终使用累加器模式。此外,我在这里使用了curried形式来定义函数,而不是tuple形式
同样与您的代码相关,我将所有模式匹配语句放在递归函数本身内部,而不是外部val
def build (l1: List[Int]) (acc: List[Int]): List[Int] =
l1 match {
case Nil => acc
case h::t => build (t) (acc.::(h))
case _ => acc
} //> build: (l1: List[Int])(acc: List[Int])List[Int]
val list1 = List(0, 1, 2) //> list1 : List[Int] = List(0, 1, 2)
val list2 = List(3, 4, 5, 6) //> list2 : List[Int] = List(3, 4, 5, 6)
val b = build(list1)(list2) //> b : List[Int] = List(6, 5, 4, 3, 0, 1, 2)
我已经发现很多次,积累和逆转比我尝试的其他选择更快。这取决于具体情况,但不要害怕逆转。我认为积累+逆转是目前的做法。谢谢!除了累加和反转之外,通常的替代方法是差异列表的概念,即累加一个函数,该函数将通过追加而不是列表本身来构建列表。不过,这在JVM上可能不太管用……谢谢你的回答,但我对递归方法更感兴趣。我的具体用例有点复杂,一次调用recurse
就可以在结果列表中添加几个项:=>item1::item2::recurse(…)
距离函数只是一个示例来演示这个问题。我建议您将此注释显式添加到原始帖子中,为了避免人们错误地投票给这个答案,还请记住,若您可以使用高级操作编写显式递归,则通常不鼓励显式递归(因为它们在其他结构上工作,并且可能比您所做的更优化)。在您的例子中,如果每次迭代需要发出多个元素,您可以很容易地切换到flatMap
,而不是map
。我认为scala作为fp语言将提供一种比累积+反转更优雅的方法来解决这样一个基本问题,但我会接受它。谢谢我没有仔细考虑这里的细节,但是累积和反转是否是“正确”的解决方案取决于手头的算法,而不是所使用的语言。嗯,至少它不取决于所使用的特定语言,而是取决于它的评估策略。例如,在Haskell中,您可能会以非常不同的方式处理此问题,因为大多数人认为的尾部递归形式在这里不太常见。