从Scala数组中提取区域

时间:2011-11-16 03:45:35

标签: algorithm scala functional-programming partitioning

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

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

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

6 个答案:

答案 0 :(得分:5)

如果您不介意添加一些基础设施来处理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上获得异常。

答案 1 :(得分:2)

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

在这种特殊情况下,由于您要丢弃所有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))
    }
  }
}

答案 2 :(得分: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 :(得分:2)

@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 :(得分:1)

使用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

答案 5 :(得分:1)

折叠解决方案:

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))