在Scala中折断或短路折叠

时间:2009-10-20 15:21:20

标签: scala functional-programming

我在Scala中编写了一个简单的深度优先搜索,其中包含递归函数:

search(labyrinth, path, goal)

其中迷宫是问题的规范(如图或其他),path是一个列表,它保存到目前为止所采用的路径,目标是目标状态的规范。如果找不到路径,该函数将返回作为List的目标路径和Nil。

该功能扩展,例如找到所有合适的下一个节点(候选者),然后必须递归调用自己。

我是通过

来做到的
candidates.foldLeft(Nil){ 
  (solution, next) => 
    if( solution == Nil ) 
      search( labyrinth, next :: path, goal ) 
    else 
      solution 
}

请注意,我省略了一些不必要的细节。到目前为止一切正常。但是一旦在foldLeft调用中找到了解决方案,这个解决方案就会被if语句的else部分复制。有没有办法通过打破foldLeft或使用不同的函数而不是foldLeft来避免这种情况?实际上我可能会写一个版本的foldLeft,它会在我自己返回“not Nil”时中断。但是API中有一个吗?

3 个答案:

答案 0 :(得分:4)

我不确定我是否理解将回路短路的愿望。迭代候选人是否代价高昂?候选人名单可能很大吗?

也许您可以使用“查找”方法:

candidates.find { c =>
  Nil != search( labyrinth, c :: path, goal )
} match {
  case Some(c) => c :: path
  case None => Nil
}

如果搜索空间的潜在深度很大,您可能会溢出堆栈(拟合,给定此站点名称)。但是,这是另一篇文章的主题。

对于咯咯笑声,这是一个实际的可运行实现。我不得不介绍一个局部可变变量(fullPath),主要是因为懒惰,但我相信你可以把它们拿走。

object App extends Application {

    // This impl searches for a specific factor in a large int
    type SolutionNode = Int
    case class SearchDomain(number: Int) {
        def childNodes(l: List[Int]): List[Int] = {
            val num = if (l.isEmpty) number else l.head
            if (num > 2) {
                (2 to (num - 1)) find {
                    n => (num % n)==0
                } match {
                    case Some(i) => List(i, num / i)
                    case None => List()
                }
            }
            else List()
        }
    }
    type DesiredResult = Int


    def search ( labyrinth: SearchDomain, path: List[SolutionNode], goal: DesiredResult ): List[SolutionNode] = {

        if ( !path.isEmpty && path.head == goal ) return path
        if ( path.isEmpty ) return search(labyrinth, List(labyrinth.number), goal)

        val candidates: List[SolutionNode] = labyrinth.childNodes(path)
        var fullPath: List[SolutionNode] = List()
        candidates.find { c =>
            fullPath = search( labyrinth, c :: path, goal )
            !fullPath.isEmpty
        } match {
            case Some(c) => fullPath
            case None => Nil
        }
    }

    // Is 5 a factor of 800000000?
    val res = search(SearchDomain(800000000), Nil, 5)
    println(res)

}

答案 1 :(得分:0)

我喜欢Mitch Blevins解决方案,因为它与您的算法完美匹配。您可能对my own solution感兴趣的另一个迷宫问题。

答案 2 :(得分:0)

好的,这里有很多解释,您的问题缺乏一定程度的特异性。

我将尝试指出所有假设。


现有函数:(第一个假设:类型签名


type Labyrinth // Not particularly important to logic

type Position // The grid "position" of the path

def search(labyrinth: Labyrinth, path: List[Position], goal: Position): List[Position]


<块引用>

函数扩展,例如找到所有合适的下一个节点(候选节点),然后必须递归调用自己。

假设:以下代码在 search 函数内


def search(labyrinth: Labyrinth, path: List[Position], goal: Position): List[Position] = {
    ...
    return candidates.foldLeft(Nil){ 
        (solution, next) => 
            if( solution == Nil ) 
                search( labyrinth, next :: path, goal ) 
            else 
                solution 
    }
}

<块引用>

请注意,我省略了一些不必要的细节。

如果我的假设是正确的,那么也许,但我仍然需要推断出一些东西。

在寻求实施方面的帮助时,永远不要假设实施细节的必要性,特殊性使回答问题变得更加简单。


<块引用>

有没有办法通过破坏 foldLeft 或使用不同的函数而不是 foldLeft 来避免这种情况?

为什么不使用递归?

def search(labyrinth: Labyrinth, path: List[Position], goal: Position): List[Position] = {
    ...
    val candidates: List[Position] = ... // However you calculate those

    def resultPathOpt(candidates: List[position]): Option[List[Position]] = {
        candidates match {
                // No candidates
            case Nil => None
                // At least one candidate
            case firstCandidate :: restOfCandidates => {
                    // Search
                search(labyrinth, firstCandidate :: path, goal) match {
                        // Search didn't pan out, no more candidates, done
                    case Nil if restOfCandidates.isEmpty => None
                        // Search didn't pan out, try other candidates
                    case Nil => resultPathOpt(restOfCandidates)
                        // Search panned out, done
                    case validPath => Some(validPath)
                }
        }
    }

    val maybeValidResult: Option[List[Position]] = resultPathOpt(candidates)

    maybeValidResult.getOrElse(List.empty)
}

现在,一旦我们找到“有效”路径,请注意我提供的逻辑中没有对此进行检查,因此我不太确定这是否会在您实施它和我实施时结束这个,但这些概念仍然有效。