【问题标题】:Extracting regions from a Scala Array从 Scala 数组中提取区域
【发布时间】:2011-12-30 02:11:49
【问题描述】:

我真的不知道如何描述我在做什么,但这个例子应该会有所帮助:

val vals = Array( (0, true), 
                  (1, true), 
                  (2,true), 
                  (3,true), 
                  (4,false), 
                  (5, true), 
                  (6, true), 
                  (7, false), 
                  (8, true), 
                  (9,true))

我希望识别每个“真实”区域中的第一个和最后一个元素,尽管在值更改时对数组进行分区也可以。我可以强制执行此操作,但是在 scala 中执行此操作的最佳方法是什么?

【问题讨论】:

    标签: algorithm scala functional-programming partitioning


    【解决方案1】:

    如果您不介意添加一些基础架构来处理 groupedWhile 功能,您可以从 Rex Kerr 的 answer on extending scala collection 中窃取信息。在答案的第二部分使用处理数组的部分。

    那就轻而易举:

    scala> vals.groupedWhile(_._2 == _._2).filter(_.head._2 == true).map{g => 
      (g.head, g.last)}.foreach(println)
    
    ((0,true),(3,true))
    ((5,true),(6,true))
    ((8,true),(9,true))
    

    编辑:

    我想出了一个不需要groupedWhile 的解决方案。它基于使用Iterator.iterate,它从种子开始并重复应用span 函数来提取具有相同布尔属性的下一组元素。在这种情况下,种子是下一组的元组和要处理的剩余部分:

    type Arr = Array[(Int, Boolean)] // type alias for easier reading
    
    val res = Iterator.iterate[(Arr, Arr)]((Array(), vals)){ case (same, rest) => 
      // repeatedly split in (same by boolean, rest of data)
      // by using span and comparing against head
      rest.span(elem => elem._2 == rest.head._2)
    }.drop(1).takeWhile{ case (same, _) =>        // drop initial empty seed array
      same.nonEmpty                               // stop when same becomes empty
    }.collect{ case (same, _) if same.head._2 == true =>
      // keep "true" groups and extract (first, last)
      (same.head, same.last)                     
    }.foreach(println)                            // print result
    

    打印与上面相同的结果。请注意,空数组的 span 不会调用谓词,因此如果 rest 为空,我们不会在 rest.head 上得到异常。

    【讨论】:

    • 我最终使用了该解决方案的衍生产品。反复使用 Span 效果很好。
    【解决方案2】:

    对于非递归解决方案,您可以这样做:

    val ss = (0, false) +: vals :+ (0, false) sliding 2 toList
    val starts = ss filter (i => !i(0)._2 && i(1)._2) map (_(1))
    val ends   = ss filter (i => !i(1)._2 && i(0)._2) map (_(0))
    

    它为您提供开始和结束元素的列表。

    对于地区,您可以将列表压缩在一起:

    scala> starts zip ends foreach println
    ((0,true),(3,true))
    ((5,true),(6,true))
    ((8,true),(9,true))
    

    【讨论】:

      【解决方案3】:
      @tailrec
      def regions(l: Seq[(Int, Boolean)], nl : Seq[(Int, Int)]): Seq[(Int, Int)] = 
          if (l.isEmpty) nl
          else if (!l.head._2) regions(l dropWhile (!_._2), nl)
          else {
              val (first, rest) = l span (_._2)
              regions(rest, (first.head._1, first.last._1) +: nl)
          }
      

      采用丹尼尔的原始答案并进行尾递归。

      【讨论】:

        【解决方案4】:

        嗯,有很多关于将列表划分为子列表的问题。它们通常是递归的,尽管也存在迭代解决方案。一旦你有了它,获得第一个和最后一个是微不足道的。

        在这种特殊情况下,由于您要丢弃所有 false 子列表,因此您可以专攻。例如:

        def regions(l: Seq[(Int, Boolean)]): Seq[(Int, Int)] = 
          if (l.isEmpty) Seq.empty
          else if (!l.head._2) regions(l dropWhile (!_._2))
          else {
            val (first, rest) = l span (_._2)
            (first.head._1, first.last._1) +: regions(rest)
          }
        

        我可能会反复使用来自 Scalaz 的unfold。代码看起来几乎一样:

        def regions(l: Seq[(Int, Boolean)]): Seq[(Int, Int)] = {
          l.unfold[Seq, (Int, Int)] { ll =>
            val tl = ll dropWhile (!_._2)
            if (tl.isEmpty) None
            else {
              val (first, rest) = tl span (_._2)
              Some(((first.head._1, first.last._1), rest))
            }
          }
        }
        

        【讨论】:

        • @huynhjl 添加到答案中。
        • 我喜欢展开的版本,我认为一旦你明白了这个概念就会更容易理解。不幸的是,它会因大量结果(〜10000)而遭受堆栈溢出:(请注意,有一个小错字( Some 行中的括号不匹配)。递归版本也不是尾递归,但我认为它很容易成为尾递归。
        【解决方案5】:

        使用 foldLeft:

        type Region = (Int, Boolean)
        type RegionPair = (Region, Region)
        type Acc = (Option[Region], Region, List[RegionPair])
        val initAcc: Acc = (None, (0, false), Nil) //optional first, dummy last, empty regions
        
        def groupRegions(s: Acc, region: Region): Acc = {
          val (first, last, regions) = s
          first match {
            case Some(f) => 
              if (!region._2) (None, last, (f, last) :: regions)
              else (first, region, regions)
            case _ => (Some(region), region, regions)
          } 
        }
        
        val (remFirst, remLast, regions) = (initAcc /: vals)(groupRegions)
        
        val solution = remFirst map ((_, remLast) :: regions) getOrElse regions //reverse if you want
        

        【讨论】:

          【解决方案6】:

          折叠解决方案:

          val result = vals.foldLeft(List(List.empty[Int])) {
            case (head::tail, (x, true)) => (x::head)::tail
            case (xs, (_, false)) => Nil::xs
          }.map { xs => xs.head::xs.last::Nil }
          result: List[List[Int]] = List(List(9, 8), List(6, 5), List(3, 0))
          

          每次遇到false,我们都会在累加器中添加一个新的List。

          以下版本效率更高(但可能不太清楚):

          val result = vals.foldLeft(List(List.empty[Int])) {
            case (head::tail, (x, true)) => head match {
              case Nil => (x::Nil)::tail
              case last::Nil => (x::last::Nil)::tail
              case first::last => (x::last)::tail
            }
            case (xs, (_, false)) => Nil::xs
          }
          result: List[List[Int]] = List(List(9, 8), List(6, 5), List(3, 0))
          

          【讨论】:

            猜你喜欢
            • 2022-01-16
            • 1970-01-01
            • 1970-01-01
            • 2015-01-22
            • 1970-01-01
            • 1970-01-01
            • 2017-01-08
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多