【问题标题】:How to split an iterator based on a condition on prev and curr elements?如何根据 prev 和 curr 元素的条件拆分迭代器?
【发布时间】:2015-04-19 03:49:20
【问题描述】:

我想将元素列表拆分为列表列表,以使内部列表中的相邻元素满足给定条件。

一个简单的条件是相邻元素相等。那么如果输入是List(1,1,1,2,2,3,3,3,3) 输出是List(List(1,1,1),List(2,2),List(3,3,3))

另一个条件可能是当前元素应该大于上一个元素。那么如果输入为List(1,2,3,1,4,6,5,7,8),则输出为List(List(1,2,3), List(1,4,6), List(5,7,8))。如果该方法可以作用于Iterator,那就太好了。该方法的typedef是

def method[A](lst:List[A], cond:(A,A)=>Boolean):List[List[A]]
def method[A](lst:Iterator[A], cond:(A,A)=>Boolean):Iterator[Iterator[A]]

【问题讨论】:

  • 我认为您的第一个示例没有多大意义。 “相邻元素相等”是什么意思? 2 的邻居在列表中的任何地方都不相等。对于List(..., prev, curr, next),应该prev == next 还是应该prev == curr?还是curr == next

标签: scala


【解决方案1】:

您可以在递归函数中将slidingspan 一起使用以获得所需的效果。这种快速而肮脏的版本效率较低,但比某些替代方案更简洁:

def method[A](lst: TraversableOnce[A], cond: (A, A) => Boolean): List[List[A]] = {
    val iterable = lst.toIterable
    iterable.headOption.toList.flatMap { head => 
        val (next, rest) = iterable.sliding(2).filter(_.size == 2).span(x => cond(x.head, x.last))
        (head :: next.toList.map(_.last)) :: method(rest.map(_.last), cond)
    }
}

如果你想懒惰地执行代码,你可以返回一个Iterator[List[A]]而不是List[List[A]]

def method[A](lst: TraversableOnce[A], cond: (A, A) => Boolean): Iterator[List[A]] = {
    val iterable = lst.toIterable
    iterable.headOption.toIterator.flatMap { head => 
        val (next, rest) = iterable.sliding(2).filter(_.size == 2).span(x => cond(x.head, x.last))
        Iterator(head :: next.toList.map(_.last)) ++ method(rest.map(_.last), cond)
    }
}  

你可以验证这是懒惰的:

val x = (Iterator.range(0, 10) ++ Iterator.range(3, 5) ++ Iterator.range(1, 3)).map(x => { println(x); x })
val iter = method(x, (x: Int, y: Int) => x < y) //Only prints 0-9, and then 3!
iter.take(2).toList //Prints more
iter.toList //Prints the rest

您可以通过返回 Iterator[Iterator[A]] 使其更懒惰:

def method[A](lst: TraversableOnce[A], cond: (A, A) => Boolean): Iterator[Iterator[A]] = {
    val iterable = lst.toIterable
    iterable.headOption.toIterator.flatMap { head => 
        val (next, rest) = iterable.sliding(2).filter(_.size == 2).span(x => cond(x.head, x.last))
        Iterator(Iterator(head) ++ next.toIterator.map(_.last)) ++ method(rest.map(_.last), cond)
    }
} 

作为一个相对无关的旁注,当您有这种形式的通用参数时,最好使用 2 个参数列表:

def method[A](lst: TraversableOnce[A])(cond: (A, A) => Boolean)

当你有 2 个这样的参数列表时,类型推断会更聪明一点:

//No need to specify parameter types on the anonymous function now!
method(List(1, 3, 2, 3, 4, 1, 8, 1))((x, y) => x < y).toList
//You can now even use underscore anonymous function notation!
method(List(1, 4, 2, 3, 4, 1, 8))(_ < _)

【讨论】:

  • 不过,它不起作用。 method(List(1, 2, 3, 1, 4, 6, 5, 7, 8),{(a:Int,b:Int) =&gt; a &lt; b} ) 给出 List(List(1, 2, 3), List(), List(1, 4, 6), List(5, 7 , 8)) (注意不需要的空列表 - == 也有一个)。而且它不适用于迭代器。
  • 运行您为我编写的代码会根据需要返回List(List(1, 2, 3), List(1, 4, 6), List(5, 7, 8))——您确定要运行这个确切的代码吗?至于Iterators,这个解决方案本身就依赖zip,所以不能和Iterators一起工作。当然,如果可以的话,您可以致电toList
  • @Paul 我已经更新了答案,现在它可以与Iterators 一起使用。
  • 是的,我确定(我从 Scala IDE 4.0 工作表中剪切并粘贴了代码和结果)。不过,您最新的修改版本可以使用
  • 我看到我们在方法中使用了 list.last。如果我有一个大迭代器,这不是问题。理想情况下,我会返回一个 Iterator[Iterator[A]]。但我喜欢这个解决方案。非常感谢
【解决方案2】:

这与您的要求很接近(我相信)。唯一的问题是它总是为结果生成一个列表列表,而不是基于输入类型:

  val iter = Iterator(1,1,2,2,2,3,3,3)
  val list = List(4,5,5,5,5,6,6)

  def same(a:Int,b:Int) = a == b
  def gt(a:Int, b:Int) = b > a

  println(groupByPred(iter, same))
  println(groupByPred(list, gt))

  def groupByPred[L <: TraversableOnce[T], T](trav:L, cond:(T,T) => Boolean):List[List[T]] = {
    val (ret, inner) = 
      trav.foldLeft((List.empty[List[T]], List.empty[T])){
        case ((acc, list), el) if list.isEmpty || cond(list.head, el) => (acc,el :: list)
        case ((acc, list), el)  => (list.reverse :: acc,el :: List.empty)
      }
    (inner.reverse :: ret).reverse
  }

如果您运行该代码,输出应如下所示:

List(List(1, 1), List(2, 2, 2), List(3, 3, 3))
List(List(4, 5), List(5), List(5), List(5, 6), List(6))

【讨论】:

    【解决方案3】:

    试试这个。

    将列表的头部作为列表列表的第一个元素的第一个元素。如果条件成立,则将内容添加到第一个列表中。如果不是,则以当前条目作为第一个元素启动一个新列表。

    内部列表和外部列表都以错误的顺序构造。所以反转外层列表的每个元素(带map),然后反转外层列表。

      val xs = List(1, 1, 1, 2, 2, 3, 3, 3, 3)
      val ys = List(1, 2, 3, 1, 4, 6, 5, 7, 8)
    
      def method[A](lst: List[A], cond: (A, A) => Boolean): List[List[A]] = {
        lst.tail.foldLeft(List(List(lst.head))) { (acc, e) =>
          if (cond(acc.head.head, e))
            (e :: acc.head) :: acc.tail
          else List(e) :: acc
        }.map(_.reverse).reverse
      }
    
    method(xs, { (a: Int, b: Int) => a == b })
    //> res0: List[List[Int]] = List(List(1, 1, 1), List(2, 2), List(3, 3, 3, 3))
    method(ys, { (a: Int, b: Int) => a < b })
    //> res1: List[List[Int]] = List(List(1, 2, 3), List(1, 4, 6), List(5, 7, 8))
    

    迭代器重载

    def method[A](iter:Iterator[A], cond: (A, A) => Boolean): List[List[A]] = {
     val h = iter.next
       iter.foldLeft(List(List(h))) { (acc, e) =>
         if (cond(acc.head.head, e))
           (e :: acc.head) :: acc.tail
         else List(e) :: acc
       }.map(_.reverse).reverse
     }
    
    method(xs.toIterator, { (a: Int, b: Int) => a == b })
    //> res0: List[List[Int]] = List(List(1, 1, 1), List(2, 2), List(3, 3, 3, 3))
    method(ys.toIterator, { (a: Int, b: Int) => a < b })
    //> res1: List[List[Int]] = List(List(1, 2, 3), List(1, 4, 6), List(5, 7, 8))
    

    适用于列表、迭代器和任何可以遍历一次的东西的更通用版本(此处向@cmbaxter 提供一些想法):

    def method[A, T <: TraversableOnce[A]](trav: T, cond: (A, A) => Boolean)
    : List[List[A]] = {
        trav.foldLeft(List(List.empty[A])) { (acc, e) =>
          if (acc.head.isEmpty || !cond(acc.head.head, e)) List(e) :: acc
          else (e :: acc.head) :: acc.tail
        }.map(_.reverse).reverse
      }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-17
      • 2019-01-28
      • 1970-01-01
      • 2020-03-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多