我真的不知道如何描述我在做什么,但这个例子应该有所帮助:
val vals = Array( (0, true),
(1, true),
(2,true),
(3,true),
(4,false),
(5, true),
(6, true),
(7, false),
(8, true),
(9,true))
我希望识别每个“真实”区域中的第一个和最后一个元素,尽管在值更改时对数组进行分区也可以。我可以强制执行此操作,但在scala中执行此操作的最佳方法是什么?
答案 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))