Scala中地图及其条目的问题

时间:2009-11-02 14:09:22

标签: scala functional-programming recursion

我有一个递归函数,它将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方法执行此操作。但是从地图条目中提取值的语法(产量之后的东西)不起作用。

问题:

  1. 如何让我的中断条件(对if的布尔语句)起作用?

  2. 在这样的不可变Map上进行递归工作是个好主意吗?这是一个很好的功能风格吗?

2 个答案:

答案 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