我有这个方法
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的新手。
由于
答案 0 :(得分:6)
这些都不是尾递归。只有当递归调用仅作为某个执行路径中的最后一个项时才会发生。如果您可以通过跳转到代码的开头来替换调用,保存没有状态但重新标记输入变量,那么它是尾递归。 (在内部,这正是编译器所做的。)
要将普通的递归函数转换为尾递归函数,当这样做时,您需要传递任何存储的数据,如下所示:
private def getAddresses(
data: List[Int], count: Int, len: Int, // You had this already
addresses: List[Address] = Nil, // Build this as we go, starting with nothing
positions: List[Int] = Nil // Same here
): (List[Address], List[Int]) {
if (count==len) {
(addresses.reverse, positions.reverse) // Return what we've built, reverse to fix order
}
else {
val (addr,rest) = data.span(_ != 0)
val newdata = rest.tail
val position = addr.length + 1
val flag = addr.head
val address = (
if (flag) SMEAddress().fromBytes(addr)
else DistributionList().fromBytes(addr)
)
getAddresses(newdata, count+1, len, address :: addresses, position :: positions)
}
}
如果其他所有版本相同,则尾递归版本比非尾递归版本更有效。 (在这种情况下,它可能不是因为列表必须在最后反转,但它具有巨大的优势,如果你使用大的len
它不会溢出堆栈。)
两次调用方法总是运行两次。没有方法调用结果的自动记忆 - 这将是非常难以自动完成的。
答案 1 :(得分:1)
只是为了让它更加'Scala'y,你可以在getAddresses
内部定义尾递归函数,就像这样
def getAddresses(data: List[Int], len: Int) = {
def inner(count: Int, addresses: List[Address] = Nil, positions: List[Int] = Nil): (List[Address], List[Int]) = {
if (count == len) {
(addresses.reverse, positions.reverse)
} else {
val (byteAddress, rest) = data.span(_ != 0)
val newData = rest.tail
val newPosition = byteAddress.length + 1
val destFlag = byteAddress.head
val newAddress = (if (destFlag == SMEAddressFlag) SMEAddress else DistributionList()).fromBytes(byteAddress)
inner(count+1, newAddress :: addresses, newPosition :: positions)
}
}
inner(0) //Could have made count have a default too
}
答案 2 :(得分:1)
由于您的所有输入都是不可变的,并且您生成的列表总是具有相同的长度,所以我想到了这个解决方案。
private def getAddresses(data:List[Int], count:Int, len:Int):Stream[(Address,Int)] = {
if (count == len) {
Stream.empty
}else{
val (byteAddress, _::newData) = data.span(_ != 0)
val newAddress =
if (byteAddress.head == SMEAddressFlag) SMEAddress().fromBytes(byteAddress)
else DistributionList().fromBytes(byteAddress)
(newAddress, byteAddress.length + 1) #:: getAddresses(newData, count+1, len)
}
它不返回一对列表,而是返回一对列表。这样可以轻松递归一次。如果您需要单独的列表,可以使用map
来提取它们,但是您可以重新构造程序的其他部分,以便更清晰地使用对列表,而不是将2个列表作为参数全部使用。
它不返回列表,而是返回一个懒惰评估的流。此不是尾递归,但是对流进行延迟计算的方式也可以防止堆栈溢出。如果您需要严格的列表,可以在此函数的结果上调用toList
。
演示了其他有用的技巧,例如使用span
模式匹配来计算单行代码中的byteAddress
和newData
。您可以添加一些我删除的val
,如果它们的名称具有可读性是有用的。
答案 3 :(得分:0)
::
,这不是递归调用,因此它不是尾递归。