我有一个递归函数,它将Map作为单个参数。然后,它会向该Map添加新条目,并使用此较大的Map调用自身。请忽略现在的返回值。该功能尚未完成。这是代码:
def breadthFirstHelper( found: Map[AIS_State,(Option[AIS_State], Int)] ): List[AIS_State] = {
val extension =
for(
(s, v) <- found;
next <- this.expand(s) if (! (found contains next) )
) yield (next -> (Some(s), 0))
if ( extension.exists( (s -> (p,c)) => this.isGoal( s ) ) )
List(this.getStart)
else
breadthFirstHelper( found ++ extension )
}
在扩展程序中,新条目将添加到地图中。请注意,for语句生成可迭代的而不是映射。但是这些条目稍后会被添加到原始地图中以进行递归调用。在休息状态下,我需要测试是否在扩展名中生成了某个值。我尝试使用扩展上的exists方法执行此操作。但是从地图条目中提取值的语法(产量之后的东西)不起作用。
问题:
如何让我的中断条件(对if的布尔语句)起作用?
在这样的不可变Map上进行递归工作是个好主意吗?这是一个很好的功能风格吗?
答案 0 :(得分:4)
在函数中使用模式匹配(例如,针对Tuple2
)时,您需要使用大括号{}
和case
语句。
if (extension.exists { case (s,_) => isGoal(s) } )
以上也使用了这样一个事实:匹配时更清楚使用通配符_
获取任何允许值(您随后不关心)。 case xyz
被编译为PartialFunction
,后者又从Function1
扩展,因此可以用作exists
方法的参数。
至于样式,我不是函数式编程专家,但似乎它将被 scalac 编译成迭代形式(即它的尾递归)。没有什么说“地图的递归是坏的”所以为什么不呢?
注意 ->
是Any
上的一种方法(通过隐式转化),可以创建Tuple2
- 不是案例类< / strong>类似::
或!
,因此无法在case
模式匹配语句中使用。这是因为:
val l: List[String] = Nil
l match {
case x :: xs =>
}
真的是简写/糖
case ::(x, xs) =>
同样a ! b
相当于!(a, b)
。当然,您可能已经编写了自己的案例类->
...
Note2 :正如Daniel在下面所说的那样,在任何情况下都不能在函数定义中使用模式匹配;因此,虽然上述部分功能有效,但以下功能不是:
(x :: xs) =>
答案 1 :(得分:1)
对于我来说,无论Oxbow Lakes想到什么,这都让我感到有点费解。
我首先要澄清一点:在理解中没有中断条件。它们不是像C(或Java)for
那样的循环。
for-comprehension中的if
意味着什么是守卫。例如,假设我这样做:
for {i <- 1 to 10
j <- 1 to 10
if i != j
} yield (i, j)
当条件为假时,循环不会“停止”。它只是跳过该条件为false的迭代,并继续执行真正的迭代。这是另一个例子:
for {i <- 1 to 10
j <- 1 to 10
if i % 2 != 0
} yield (i, j)
你说你没有副作用,所以我可以跳过整章关于副作用和防守的理解。另一方面,阅读我最近在Strict Ranges上发表的博客文章并不是一个坏主意。
所以......放弃休息条件。它们可以使用,但它们不起作用。尝试以更实用的方式重新解释问题,并且需要替换其他内容。
接下来,Oxbow是正确的,因为在(s -> (p,c) =>
的对象上没有定义提取器,因此不允许->
,但是,唉,甚至不允许(a :: b) =>
,因为在功能文字参数声明中没有进行模式匹配。您必须简单地在=>
的左侧说明参数,而不进行任何类型的分解。但是,您可以这样做:
if ( extension.exists( t => val (s, (p,c)) = t; this.isGoal( s ) ) )
请注意,我将->
替换为,
。这是有效的,因为a -> b
是(a, b)
的语法糖,它本身就是Tuple2(a, b)
的语法糖。由于您既不使用p
也不使用c
,因此也适用:
if ( extension.exists( t => val (s, _) = t; this.isGoal( s ) ) )
最后,你的递归代码非常好,但可能没有针对尾递归进行优化。为此,您可以创建方法final
,也可以将递归函数设为私有方法。像这样:
final def breadthFirstHelper
或
def breadthFirstHelper(...) {
def myRecursiveBreadthFirstHelper(...) { ... }
myRecursiveBreadthFirstHelper(...)
}
在Scala 2.8上有一个名为@TailRec
的注释,它会告诉你函数是否可以尾递归。而且,实际上,似乎会有一个标志显示有关函数的警告,如果稍微更改,可以进行尾递归,如上所述。
修改强>
关于使用case
的Oxbow解决方案,这是一个函数或部分函数文字。它的类型取决于推理所需的内容。在那种情况下,因为那是exists
所采用的函数。但是,必须小心确保始终存在匹配,否则会出现异常。例如:
scala> List(1, 'c') exists { case _: Int => true }
res0: Boolean = true
scala> List(1, 'c') exists { case _: String => true }
scala.MatchError: 1
at $anonfun$1.apply(<console>:5)
... (stack trace elided)
scala> List(1, 'c') exists { case _: String => true; case _ => false }
res3: Boolean = false
scala> ({ case _: Int => true } : PartialFunction[AnyRef,Boolean])
res5: PartialFunction[AnyRef,Boolean] = <function1>
scala> ({ case _: Int => true } : Function1[Int, Boolean])
res6: (Int) => Boolean = <function1>
编辑2
Oxbow建议的解决方案确实使用模式匹配,因为它基于使用case
语句的函数文字,它使用模式匹配。当我说不可能时,我说的是语法x => s
。