使用Scala中的不可变值查找路径是不是存在于图形中

Posted

技术标签:

【中文标题】使用Scala中的不可变值查找路径是不是存在于图形中【英文标题】:Find if Path Exists in Graph using immutable values in Scala使用Scala中的不可变值查找路径是否存在于图形中 【发布时间】:2021-10-27 20:04:19 【问题描述】:

如何使用 immutables 而不是 var 编写以下代码?

问题:

有一个具有 n 个顶点的双向图,其中每个顶点标记为从 0 到 n - 1(包括)。图中的边表示为一个二维整数数组边,其中每个边[i] = [ui, vi] 表示顶点 ui 和顶点 vi 之间的双向边。每个顶点对最多由一条边连接,并且没有一个顶点与自己有一条边。

您想确定是否存在从顶点开始到顶点结束的有效路径。

给定边和整数 n、start 和 end,如果从 start 到 end 存在有效路径,则返回 true,否则返回 false。

示例 1:

输入:n = 3,边 = [[0,1],[1,2],[2,0]],开始 = 0,结束 = 2 输出:真 解释:从顶点0到顶点2有两条路径:

0 → 1 → 2 0 → 2 列表项

解决方案

package com.example

object Solution 

  var visited: Seq[Int] = Nil

  def validPath(n: Int, edges: Array[Array[Int]], start: Int, end: Int) : Boolean =

    if(edges.length == 0)
      return true
    val finalMap = edges.foldLeft(Map.empty[Int, Seq[Int]])  case(result, edge) =>
      val keyVal = result.getOrElse(edge(0) , Nil) :+ edge(1)
      val updatedMap = (result + (edge(0)-> keyVal ))
      val keyVal1 = updatedMap.getOrElse(edge(1) , Nil) :+ edge(0)
      (updatedMap + (edge(1)-> keyVal1 ))
    
    helper(finalMap , end, start)
  

  def helper(map:Map[Int, Seq[Int]],  end: Int, start: Int): Boolean = 

    println(visited)
    if(visited.contains(start)) 
      false
    
    else 
     val resultList =  map.get(start)
      resultList match 
        case Some(l) =>  if (l.contains(end)) 
          true
         else 
          l.foldLeft(false) (a , b) =>
            visited = (visited :+ start).distinct
            a || helper(map, end, b)
          
        
        case None => false
      
    o
  

  def main(args: Array[String]): Unit = 
//    val input =   Array(Array(3,12),Array(26,84),Array(10,43),Array(68,47),Array(33,10),Array(87,35),Array(41,96),Array(70,92),Array(38,31),Array(88,59),Array(7,30),Array(89,26),Array(95,25),Array(66,28),Array(14,24),Array(86,11),Array(83,65),Array(14,4),Array(67,7),Array(89,45),Array(52,73),Array(47,85),Array(86,53),Array(68,81),Array(43,68),Array(87,78),Array(94,49),Array(70,21),Array(11,82),Array(60,93),Array(22,32),Array(69,99),Array(7,1),Array(41,46),Array(73,94),Array(98,52),Array(68,0),Array(69,89),Array(37,72),Array(25,50),Array(72,78),Array(96,60),Array(73,95),Array(7,69),Array(97,19),Array(46,75),Array(8,38),Array(19,36),Array(64,41),Array(61,78),Array(97,14),Array(54,28),Array(6,18),Array(25,32),Array(34,77),Array(58,60),Array(17,63),Array(98,87),Array(13,76),Array(58,53),Array(81,74),Array(29,6),Array(37,5),Array(65,63),Array(89,56),Array(61,18),Array(23,34),Array(76,29),Array(73,76),Array(11,63),Array(98,0),Array(54,14),Array(63,7),Array(87,32),Array(79,57),Array(72,0),Array(94,16),Array(85,16),Array(12,91),Array(14,17),Array(30,45),Array(42,41),Array(82,69),Array(24,28),Array(31,59),Array(11,88),Array(41,89),Array(48,12),Array(92,76),Array(84,64),Array(19,64),Array(21,32),Array(30,19),Array(47,43),Array(45,27),Array(31,17),Array(53,36),Array(88,3),Array(83,7),Array(27,48),Array(13,6),Array(14,40),Array(90,28),Array(80,85),Array(29,79),Array(10,50),Array(56,86),Array(82,88),Array(11,99),Array(37,55),Array(62,2),Array(55,92),Array(51,53),Array(9,40),Array(65,97),Array(25,57),Array(7,96),Array(86,1),Array(39,93),Array(45,86),Array(40,90),Array(58,75),Array(99,86),Array(82,45),Array(5,81),Array(89,91),Array(15,83),Array(93,38),Array(3,93),Array(71,28),Array(11,97),Array(74,47),Array(64,96),Array(88,96),Array(4,99),Array(88,26),Array(0,55),Array(36,75),Array(26,24),Array(84,88),Array(58,40),Array(77,72),Array(58,48),Array(50,92),Array(62,68),Array(70,49),Array(41,71),Array(68,6),Array(64,91),Array(50,81),Array(35,44),Array(91,48),Array(21,37),Array(62,98),Array(64,26),Array(63,51),Array(77,55),Array(25,13),Array(60,41),Array(87,79),Array(75,17),Array(61,95),Array(30,82),Array(47,79),Array(28,7),Array(92,95),Array(91,59),Array(94,85),Array(24,65),Array(91,31),Array(3,9),Array(59,58),Array(70,43),Array(95,13),Array(30,96),Array(51,9),Array(16,70),Array(29,94),Array(37,22),Array(35,79),Array(14,90),Array(75,9),Array(2,57),Array(81,80),Array(61,87),Array(69,88),Array(98,79),Array(18,70),Array(82,19),Array(36,27),Array(49,62),Array(67,75),Array(62,77),Array(83,96),Array(92,37),Array(95,22),Array(46,97),Array(35,0),Array(44,79),Array(82,89),Array(68,94),Array(96,31),Array(92,34),Array(25,0),Array(46,36),Array(38,84),Array(21,0),Array(0,80),Array(72,44),Array(56,97),Array(86,26),Array(94,57),Array(25,6),Array(81,13),Array(66,63),Array(57,5),Array(72,49),Array(46,86),Array(95,16),Array(95,37),Array(14,89),Array(44,22),Array(60,39),Array(37,47),Array(58,86),Array(89,96),Array(38,83),Array(51,91),Array(72,70),Array(14,82),Array(60,30),Array(58,39),Array(57,22),Array(95,70),Array(44,76),Array(5,68),Array(15,69),Array(33,61),Array(81,32),Array(21,68),Array(73,20),Array(22,72),Array(83,8),Array(15,54),Array(93,42),Array(68,95),Array(55,72),Array(33,92),Array(5,49),Array(17,96),Array(44,77),Array(24,53),Array(2,98),Array(33,81),Array(32,43),Array(20,16),Array(67,84),Array(98,35),Array(58,11),Array(72,5),Array(3,59),Array(78,79),Array(6,0),Array(26,71),Array(96,97),Array(18,92),Array(1,36),Array(78,0),Array(63,15),Array(20,43),Array(32,73),Array(37,76),Array(73,16),Array(76,23),Array(50,44),Array(68,2),Array(14,86),Array(69,65),Array(95,98),Array(53,64),Array(6,76),Array(7,11),Array(14,84),Array(62,50),Array(83,58),Array(78,92),Array(37,0),Array(13,55),Array(12,86),Array(11,59),Array(41,86),Array(27,26),Array(94,43),Array(20,78),Array(0,73),Array(58,90),Array(69,36),Array(62,34),Array(65,26),Array(32,85))
   val input = Array(Array(0,4))
    val result  = Solution.validPath(5, input,0,4)
    println(result)

  





【问题讨论】:

【参考方案1】:

我认为这是可行的(非常有限的测试),并且没有 var 或可变集合。

它不会进行深度优先搜索,因为您的问题已被标记,而是来自似乎不是必需的问题描述。

import scala.collection.immutable.IntMap

def validPath(edgCnt: Int
             ,edges: Array[Array[Int]]
             ,start: Int, end: Int): Boolean = 
  val nxtSet = 
    edges.head
         .indices
         .map(x => IntMap.from(edges.groupBy(_(x)).map
                           case (k,v) => k -> v.flatten.toSet))
         .reduce(_.unionWith(_, (k,a,b) => (a++b) - k))

  def loop(from:Set[Int], to:Set[Int], open:Set[Int]):Boolean =
    from.exists(to) || to.nonEmpty &&
      loop(to, from.flatMap(nxtSet).filter(open), open diff to)

  loop(Set(start), Set(end), Set.range(0,edgCnt) - start)

我在这里使用IntMap 只是因为它有方便的unionWith() 方法。不知道为什么其他系列没有。

【讨论】:

这回答了我的问题。 DFS 不是我只是在寻找不可变实现的要求【参考方案2】:

我个人更喜欢将图建模为 Map[Node, NonEmptyList[Node]],其中键值对代表边。

使用该表示搜索路径非常简单:

import cats.data.NonEmptyList

type Graph[A] = Map[A, NonEmptyList[A]]

object findPath 
  private sealed trait SearchState
  private final case object Initial extends SearchState
  private final case object FoundStart extends SearchState
  private final case object FoundEnd extends SearchState
  

  def apply[A](graph: Graph[A])(start: A, end: A): Boolean = 
    def newState(currentState: SearchState, nextElem: A): SearchState =
      (currentState, nextElem) match 
        case (Initial, `start`) => FoundStart
        case (FoundStart, `end`) => FoundEnd
        case _ => currentState
      
    
    @annotation.tailrec
    def loop(remainingSteps: List[(SearchState, A, Set[A])]): Boolean =
      remainingSteps match 
        case (currentState, nextElem, visited) :: tail =>
          newState(currentState, nextElem) match 
            case FoundEnd =>
              true
            
            case newState =>
              val newVisited = visited + nextElem
              val newSteps =
                graph
                  .get(key = nextElem)
                  .fold(ifEmpty = List.empty[A])(_.toList)
                  .collect 
                    case a if (!newVisited.contains(a)) =>
                      (newState, a, newVisited)
                  
            
              loop(newSteps reverse_::: tail)
          
          
        
        case Nil =>
          false
      
    
    loop(
      remainingSteps = graph.keysIterator.map(a => (Initial, a, Set.empty[A])).toList
    )
  

如何从当前输入创建这样的Map 留给读者练习


可以看到运行here的代码

【讨论】:

【参考方案3】:

这是一个不可变的解决方案,也满足应用 bfs

object Solution 
  def validPath(n: Int, edges: Array[Array[Int]], start: Int, end: Int): Boolean = 
    if(start == end) true
    else if(edges.isEmpty) false
    else bfs(build(edges), start, end)
  


  import scala.collection.immutable.Queue
  def bfs(map: Map[Int, List[Int]], start: Int, end: Int): Boolean = 
    def go(queue: Queue[Int], seen: Set[Int]): (Queue[Int], Set[Int]) = 
      if (queue.isEmpty) (queue, seen)
      else 
        val (node, remQueue) = queue.dequeue
        val updateSet = seen + node
        val (q, s) = map(node).foldLeft(remQueue, updateSet) 
          case ((q, s), n) =>
            val q2 = if (!s.contains(n)) q.enqueue(n) else q
            (q2, s)
        
        go(q, s)
      
    
    val (_, seen) = go(Queue.empty[Int].enqueue(start), Set.empty[Int])

    if(seen.contains(start) && seen.contains(end)) true else false
  


  def build(edges: Array[Array[Int]]): Map[Int, List[Int]] = 
    edges.foldLeft(Map[Int, List[Int]]())  (acc, edge) => 
      appendMap(appendMap(acc, edge(1), edge(0)), edge(0), edge(1))
    



  def appendMap(map: Map[Int, List[Int]], k: Int, v: Int): Map[Int, List[Int]] = 
    map.get(k) match 
      case Some(list) => 
        val r = list :+ v
        map + (k -> r)
      case None => map + (k -> List(v))
    
  
             

它也传递 leetcode :)

【讨论】:

以上是关于使用Scala中的不可变值查找路径是不是存在于图形中的主要内容,如果未能解决你的问题,请参考以下文章

对于更新不依赖于先前值的不可变集合,是不是有任何理由更喜欢 Interlocked 而不是 volatile?

Scala 系列—— 集合类型综述

2021年大数据常用语言Scala(十七):基础语法学习 Set

Scala - 数组

可选参数值是不是可能依赖于 Scala 中的另一个参数

Scala 中不可变集实现的性能