尾递归与尾递归重构
我有这个方法,
private def getAddresses(data: List[Int], count: Int, len: Int): Tuple2[List[Address], List[Int]] = {
if (count == len) {
(List.empty, List.empty)
} else {
val byteAddress = data.takeWhile(_ != 0)
val newData = data.dropWhile(_ != 0).tail
val newCount = count + 1
val newPosition = byteAddress.length + 1
val destFlag = byteAddress.head
if (destFlag == SMEAddressFlag) {
(SMEAddress().fromBytes(byteAddress) :: getAddresses(newData, newCount, len)._1, newPosition :: getAddresses(newData, newCount, len)._2)
} else {
(DistributionList().fromBytes(byteAddress) :: getAddresses(newData, newCount, len)._1, newPosition :: getAddresses(newData, newCount, len)._2)
}
}
}
我很想重写它,因此
private def getAddresses(data: List[Int], count: Int, len: Int): Tuple2[List[Address], List[Int]] = {
if (count == len) {
(List.empty, List.empty)
} else {
val byteAddress = data.takeWhile(_ != 0)
val newData = data.dropWhile(_ != 0).tail
val newCount = count + 1
val newPosition = byteAddress.length + 1
val destFlag = byteAddress.head
val nextIter = getAddresses(newData, newCount, len)
if (destFlag == SMEAddressFlag) {
(SMEAddress().fromBytes(byteAddress) :: nextIter._1, newPosition :: nextIter._2)
} else {
(DistributionList().fromBytes(byteAddress) :: nextIter._1, newPosition :: nextIter._2)
}
}
}
我的问题是
- 第一个方法是尾递归吗?
- 它是如何运作的?我在最后一行调用该方法两次。它评估单独的方法调用什么吗?
- 哪个版本更高效,或者我怎样才能更高效地编写它。
如果代码有异味,请原谅我,我是 scala 新手。
谢谢
I have this method
private def getAddresses(data: List[Int], count: Int, len: Int): Tuple2[List[Address], List[Int]] = {
if (count == len) {
(List.empty, List.empty)
} else {
val byteAddress = data.takeWhile(_ != 0)
val newData = data.dropWhile(_ != 0).tail
val newCount = count + 1
val newPosition = byteAddress.length + 1
val destFlag = byteAddress.head
if (destFlag == SMEAddressFlag) {
(SMEAddress().fromBytes(byteAddress) :: getAddresses(newData, newCount, len)._1, newPosition :: getAddresses(newData, newCount, len)._2)
} else {
(DistributionList().fromBytes(byteAddress) :: getAddresses(newData, newCount, len)._1, newPosition :: getAddresses(newData, newCount, len)._2)
}
}
}
I am tempted to rewrite it thus
private def getAddresses(data: List[Int], count: Int, len: Int): Tuple2[List[Address], List[Int]] = {
if (count == len) {
(List.empty, List.empty)
} else {
val byteAddress = data.takeWhile(_ != 0)
val newData = data.dropWhile(_ != 0).tail
val newCount = count + 1
val newPosition = byteAddress.length + 1
val destFlag = byteAddress.head
val nextIter = getAddresses(newData, newCount, len)
if (destFlag == SMEAddressFlag) {
(SMEAddress().fromBytes(byteAddress) :: nextIter._1, newPosition :: nextIter._2)
} else {
(DistributionList().fromBytes(byteAddress) :: nextIter._1, newPosition :: nextIter._2)
}
}
}
My questions are
- Is the first one tail recursive?
- How does it work? I am calling the method twice in the last line. Does it evaluate to separate method calls what?
- Which version is more efficient, or how can I write it more efficiently.
Pardon me if the code smells I am new to scala.
Thanks
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
这些都不是尾递归。仅当递归调用仅作为某个执行路径上的最后一项发生时才会发生这种情况。如果您可以通过跳转到代码开头来替换调用,不保存状态但重新标记输入变量,那么它就是尾递归。 (在内部,这正是编译器所做的。)
要将普通递归函数转换为尾递归函数,当可能时,您需要传递任何存储的数据,如下所示:
尾递归版本比非尾递归版本更有效-尾递归版本,如果其他条件相同。 (在这种情况下,可能不是,因为列表必须在最后反转,但它有一个巨大的优势,如果您使用较大的
len
,它不会溢出堆栈。)一个方法两次总是运行两次。没有自动记忆方法调用的结果——这很难自动完成。
Neither of these is tail recursion. That only happens when recursive calls occur only as the very last item along some execution path. If you could replace the call by a jump to the start of the code, saving no state but relabeling the input variables, then it's tail recursion. (Internally, that's exactly what the compiler does.)
To convert an ordinary recursive function into a tail recursive function, when doing so is possible, you need to pass forward any stored data, like so:
Tail-recursive versions are more efficient than non-tail-recursive versions, if all else is equal. (In this case, it might not be since the list has to be reversed at the end, but it has the huge advantage that it won't overflow the stack if you use a large
len
.)Calling a method twice always runs it twice. There is no automatic memoization of results of method calls--this would be extremely difficult to do automatically.
为了使其更加“Scala”,您可以在
getAddresses
内部定义尾递归函数,如下所示Just to make it more 'Scala'y, you could define the tail-recursive function internally to the
getAddresses
like so由于所有输入都是不可变的,并且结果列表始终具有相同的长度,因此我想到了这个解决方案。
它不返回一对列表,而是返回一个对的列表。这使得递归一次变得很容易。如果您需要单独的列表,可以使用
map
来提取它们,但您也许可以重新构建程序的其他部分,以便更干净地处理成对的列表,而不是将 2 个列表作为参数一切都结束了。它不返回列表,而是返回延迟评估的流。这不是尾递归,但是延迟计算流的方式也可以防止堆栈溢出。如果您需要一个严格的列表,您可以对该函数的结果调用
toList
。演示了其他有用的技术,例如使用
span
与模式匹配来在单行代码中计算byteAddress
和newData
。如果有利于提高可读性,您可以添加回一些我删除的val
。Since all of your inputs are immutable, and your resulting lists will both always have the same length, I thought of this solution instead.
Instead of returning a pair of lists, it returns a list of pairs. This makes it easy to recurse once. If you need separate lists, you can use
map
to extract them, but you might be able to restruture other parts of your program to more cleanly work with a list of pairs rather than taking 2 lists as a parameter all over.Instead of returning a list, it returns a stream which is lazily evaluated. This isn't tail recursion, but the way that streams are lazily evaluated also prevents stack overflows. If you need a strict list, you can call
toList
on the result of this function.The demonstrates other useful techniques, for example the use of
span
with pattern matching to computebyteAddress
andnewData
in a single line of code. You can add back some of theval
s that I removed if it's useful to have their names for readability.::
,它不是递归调用,因此它不是尾递归。::
, which is not a recursive call, so it's not tail recursive.