String 如何从迭代器中删除重复的元素?
这是我上一次的后续行动。String 如何从迭代器中删除重复的元素?,string,list,scala,collections,functional-programming,String,List,Scala,Collections,Functional Programming,这是我上一次的后续行动。 如何编写一个函数来过滤给定迭代器中相邻的重复项 def remove[A](it: Iterator[A]): Iterator[A] = ??? remove("aaabccbbad".iterator).toList.map(_.mkString) // "abcbad" 另外,当整个输入不适合内存时,该功能应能工作。这就是函数使用迭代器的原因。您可以使用以下内容: "aaabccbbad" .map(ch => s"${ch}") .red
如何编写一个函数来过滤给定迭代器中相邻的重复项
def remove[A](it: Iterator[A]): Iterator[A] = ???
remove("aaabccbbad".iterator).toList.map(_.mkString) // "abcbad"
另外,当整个输入不适合内存时,该功能应能工作。这就是函数使用迭代器的原因。您可以使用以下内容:
"aaabccbbad"
.map(ch => s"${ch}")
.reduce((s1, s2) => if(s1.takeRight(1) == s2) s1 else s1 + s2)
这导致
res0:String=abcbad
首先,为了方便起见,我在弦上施法。然后我将我已经拥有的最后一个字符与连续字符进行比较,如果它们不同,我将添加它
更一般地说,它可能是这样的:
stream.map(el => ListBuffer.empty.addOne(el))
.reduce((lb1, lb2) => if(lb1.last == lb2.last) lb1 else lb1.addAll(lb2))
.toList
def remove[A](it: Iterator[A]): Iterator[A] = split(it).flatMap(_.headOption)
有点太低了。但这确保了它只在需要时使用元素
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()
}
}
大致与上面相同,但改用
展开
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
而不是选项可以提高它们的效率,但它需要对原语进行特殊处理)我会将输入迭代器转换为重复列表的迭代器,然后将每个列表映射到其头部。为了做到这一点,我将使用前面问题中的两个函数:
- 函数
(建议)拆分重复的前缀,并返回一对前缀和其余前缀splitDupes:Iterator[A]=>(List[A],Iterator[A])
- 函数
(建议)使用split:Iterator[A]=>Iterator[List[A]
将给定迭代器转换为重复列表迭代器splitDupes
删除:
stream.map(el => ListBuffer.empty.addOne(el))
.reduce((lb1, lb2) => if(lb1.last == lb2.last) lb1 else lb1.addAll(lb2))
.toList
def remove[A](it: Iterator[A]): Iterator[A] = split(it).flatMap(_.headOption)
请参见下面的splitdupe
和split
实现,仅供参考:
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)
}
我认为尾部递归更容易:
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"
谢谢如果所有的输入都不在内存中,它会起作用吗?我更新了问题以提及内存条件。@Michael考虑到scala的流惰性,我会说是的,但我不是100%确定。我没有看到任何流。我错过什么了吗?@Michael你说得对,我没提过。您可以在流
或懒散列表
上使用此链,然后将惰性地计算连续元素。谢谢。不过,我希望避免实现hasNext
和next
。@Michael我建议您改为查看流媒体库。像fs2
&AkkaStreams
。谢谢,但我正在寻找一个没有3d派对库的解决方案。@Michael,你要求的东西太多了。使用高级combinator的高效惰性流不容易实现,因此存在提供此功能的强大库。@Michael我刚刚用更高级的备选方案编辑了我的问题。我担心这不会是惰性的。我认为解决方案是惰性的。迭代器不会转换为任何其他(非惰性)集合,因此它不会一次加载内存中的所有内容。使用的所有操作都来自迭代器类本身(主要生成新的迭代器),next()使元素可用。不过我还没有测试过。你有什么特别的理由认为它不会懒惰吗?我试过类似的方法,但它并不懒惰。我将检查此解决方案…我在val e=remaining.next()之后添加了println(e)
代码>。当我运行remove(“aaabccbbad.iterator)
时,它会打印“abcbad”的所有字符。这不是我所说的“懒惰”。我遗漏了什么吗?你说得对。实现并不是懒惰的。我又提出了一个解决方案,让它变得懒惰。