【问题标题】:How to remove repeated elements from an iterator?如何从迭代器中删除重复的元素?
【发布时间】:2020-05-21 18:09:52
【问题描述】:

这是我之前的question 的后续。
你将如何编写一个函数来从给定的迭代器中过滤掉相邻的重复项?

def remove[A](it: Iterator[A]): Iterator[A] = ???
remove("aaabccbbad".iterator).toList.map(_.mkString) // "abcbad"

附:当整个输入不适合内存时,该功能应该起作用。这就是函数使用迭代器的原因。

【问题讨论】:

    标签: string list scala collections functional-programming


    【解决方案1】:

    您可以使用以下内容:

    "aaabccbbad"
        .map(ch => s"${ch}")
        .reduce((s1, s2) => if(s1.takeRight(1) == s2) s1 else s1 + s2)
    

    这会导致

    res0: 字符串 = abcbad

    首先为方便起见,我将字符转换为字符串。然后我将我已经拥有的最后一个字符与连续字符进行比较,如果它们不同,我将附加它。

    一般情况下可能是这样的:

    stream.map(el => ListBuffer.empty.addOne(el))
        .reduce((lb1, lb2) => if(lb1.last == lb2.last) lb1 else lb1.addAll(lb2))
        .toList
    

    【讨论】:

    • 谢谢。如果所有输入都不适合内存,它会起作用吗?
    • 我更新了问题以提及内存状况。
    • @Michael 考虑到 scala 的流延迟我会说是的,但我不是 100% 确定。
    • 我没有看到任何流。我错过了什么吗?
    • @Michael 你是对的,我没有提到它。您可以在StreamLazyList 上使用此链,然后将懒惰地评估连续的元素。
    【解决方案2】:

    级别太低了。但这确保了它只会在需要时消耗元素。

    def remove[A](it: Iterator[A]): Iterator[A] =
      new Iterator[A] {
        private[this] var current: Option[A] = None
    
        override def hasNext: Boolean =
          it.hasNext || (current ne None)
    
        override def next(): A = {
          @annotation.tailrec
          def loop(): A = (it.nextOption(), current) match {
            case (Some(a), Some(c)) if (a == c) =>
              loop()
    
            case (sa @ Some(a), Some(c)) =>
              current = sa
              c
    
            case (sa @ Some(a), None) =>
              current = sa
              loop()
    
            case (None, Some(c)) =>
              current = None
              c
    
            case (None, None) =>
              Iterator.empty[A].next()
          }
    
          loop()
        }
      }
    

    或多或少与上述相同,但改用unfold

    def remove[A](it: Iterator[A]): Iterator[A] = {
      type State = (Option[A], Option[A]) // value -> current
    
      def process(state: State): Option[State] = state match {
        case (Some(a), sc @ Some(c)) if (a == c) =>
          Some(None -> sc)
    
        case (sa @ Some(a), sc @ Some(c)) =>
          Some(sc -> sa)
    
        case (sa @ Some(a), None) =>
          Some(None -> sa)
    
        case (None, sc @ Some(c)) =>
          Some(sc -> None)
    
        case (None, None) =>
          None
      }
    
      Iterator.unfold(it.nextOption() -> Option.empty[A]) { state =>
        process(state).map {
          case (value, current) =>
            (value -> (it.nextOption() -> current))
        }
      } collect {
        case Some(a) => a
      }
    }
    

    (使用 null 代替 Option 可以提高效率,但需要对原语进行特殊处理)

    【讨论】:

    • 谢谢。我想避免实现hasNextnext
    • @Michael 我建议您改用 Streaming 库。喜欢fs2 & AkkaStreams
    • 谢谢,但我正在寻找没有 3d 方库的解决方案。
    • @Michael 你要求的东西太多了。具有高级组合器的高效惰性流不容易实现,因此存在提供此功能的强大库。
    • @Michael 我刚刚用更高级的替代方案编辑了我的问题。
    【解决方案3】:

    我会将输入迭代器转换为重复列表的迭代器,然后将每个列表映射到其头部。为了做到这一点,我将使用我之前问题中的两个函数:

    • 函数splitDupes: Iterator[A] => (List[A], Iterator[A])(建议here)拆分重复的前缀并返回一对前缀和其余部分

    • 函数split: Iterator[A] => Iterator[List[A]](建议here)使用splitDupes将给定的迭代器转换为重复列表的迭代器。

    非常感谢Kolmar 的这些建议。使用它们我可以像这样实现remove

    def remove[A](it: Iterator[A]): Iterator[A] = split(it).flatMap(_.headOption)
    

    请参阅以下splitDupessplit 的实现,仅供参考:

    def splitDupes[A](it: Iterator[A]): (List[A], Iterator[A]) = {
      if (it.isEmpty) {
        (Nil, Iterator.empty)
      } else {
        val head = it.next()
        val (dupes, rest) = it.span(_ == head)
        (head +: dupes.toList, rest)
      }
    }
    
    def split[A](it: Iterator[A]): Iterator[List[A]] = {
      Iterator.iterate(splitDupes(it))(x => splitDupes(x._2)).map(_._1).takeWhile(_.nonEmpty)
    }
    

    【讨论】:

      【解决方案4】:

      我认为尾递归更容易:

      def remove[A](it: Iterator[A]): Iterator[A] = {
        def removeLoop(result: Iterator[A], remaining: Iterator[A]): Iterator[A] = {
          if(remaining.isEmpty) {
            result
          } else {
            val e = remaining.next();
            removeLoop(result ++ Iterator(e), remaining.dropWhile(a => a == e))
          }  
        }
      
        removeLoop(Iterator.empty[A], it)
      }
      
      remove("aaabccbbad".iterator).toList.map(_.toString).mkString // "abcbad"
      

      编辑:根据评论,上述实现并不懒惰。

      可能的惰性实现如下所示:

      def remove[A](it: Iterator[A]): Iterator[A] = new AbstractIterator[A] {
        var lastElement: A = _
      
        def hasNext: Boolean = it.hasNext
      
        def next(): A = {
          @scala.annotation.tailrec
          def nextLoop(lastElement: A, it: Iterator[A]): A = {
            val temp = it.next
            if(lastElement == temp)
              nextLoop(lastElement, it)
            else
              temp
          }
          lastElement = nextLoop(lastElement, it)
          lastElement
        }
      }
      
      remove("aaabccbbad".iterator).take(2).foreach(print) // "ab"
      remove("aaabccbbad".iterator).foreach(print) // "abcbad"
      

      【讨论】:

      • 恐怕不会偷懒。
      • 我认为解决方案是懒惰的。迭代器不会转换为任何其他(非惰性)集合,因此它不会一次将所有内容加载到内存中。使用的所有操作都来自迭代器类本身(主要生成新的迭代器),并且 next() 使元素可用。我还没有测试过。你有什么特别的理由认为它不会偷懒吗?
      • 我尝试过类似的方法,但它并不懒惰。我会检查这个解决方案...
      • 我在val e = remaining.next(); 之后添加了println(e)。当我运行remove("aaabccbbad".iterator) 时,它会打印“abcbad”的所有字符。这不是我所说的“懒惰”。我错过了什么吗?
      • 你是对的。实施并不懒惰。我又添加了一个解决方案让它变得懒惰。
      猜你喜欢
      • 2010-11-09
      • 1970-01-01
      • 2014-12-16
      • 2016-07-19
      • 2013-01-23
      • 1970-01-01
      • 2013-04-27
      • 2020-03-18
      • 2014-09-04
      相关资源
      最近更新 更多