【问题标题】:Find String in Char iterator在 Char 迭代器中查找字符串
【发布时间】:2015-12-18 09:28:40
【问题描述】:

我有一个用例,我需要从 Char 的迭代器返回一个字符串,直到一个分隔符字符串(如果找到)。

合同:

  • 如果迭代器已用尽(仅在开始时),则返回 None
  • 如果找到分隔符字符串,返回它之前的所有字符(空字符串也可以),分隔符将被删除
  • 否则返回剩余的字符
  • 不要急于耗尽迭代器!

我确实有这个可行的解决方案,但感觉就像 Java(这是我来自的地方)

class MyClass(str: String) {
  def nextString(iterator: Iterator[Char]): Option[String] = {
    val sb = new StringBuilder
    if(!iterator.hasNext) return None
    while (iterator.hasNext) {
      sb.append(iterator.next())
      if (sb.endsWith(str)) return Some(sb.stripSuffix(str))
    }
    Some(sb.toString())
  }
}

有没有一种方法可以让我以更实用的方式做到这一点(最好不更改方法签名)?

更新:这是我的测试方法

val desmurfer = new MyClass("_smurf_")
val iterator: Iterator[Char] = "Scala_smurf_is_smurf_great_smurf__smurf_".iterator
println(desmurfer.nextString(iterator))
println(desmurfer.nextString(iterator))
println(desmurfer.nextString(iterator))
println(desmurfer.nextString(iterator))
println(desmurfer.nextString(iterator))
println
println(desmurfer.nextString("FooBarBaz".iterator))
println(desmurfer.nextString("".iterator))

输出:

Some(Scala)
Some(is)
Some(great)
Some()
None

Some(FooBarBaz)
None

【问题讨论】:

  • 您期望的样本输出是什么?
  • @S.Karthik 添加了示例输出
  • 请查看答案
  • 请查看答案

标签: scala


【解决方案1】:

这个怎么样:

scala> def nextString(itr: Iterator[Char], sep: String): Option[String] = {
     |    def next(res: String): String =
     |      if(res endsWith sep) res dropRight sep.size else if(itr.hasNext) next(res:+itr.next) else res
     |   if(itr.hasNext) Some(next("")) else None
     | }
nextString: (itr: Iterator[Char], sep: String)Option[String]

scala> val iterator: Iterator[Char] = "Scala_smurf_is_smurf_great".iterator
iterator: Iterator[Char] = non-empty iterator

scala> println(nextString(iterator, "_smurf_"))
Some(Scala)

scala> println(nextString(iterator, "_smurf_"))
Some(is)

scala> println(nextString(iterator, "_smurf_"))
Some(great)

scala> println(nextString(iterator, "_smurf_"))
None

scala> println(nextString("FooBarBaz".iterator, "_smurf_"))
Some(FooBarBaz)

【讨论】:

  • 这很好,但我猜:+ 应该是 O(n) 并且还有很多调用的内存问题。请在我尝试改进时查看我的答案(尽管要大得多)。由于简单,您的答案可能会更好
【解决方案2】:

这个呢?

def nextString(iterator: Iterator[Char]): Option[String] = {
    val t = iterator.toStream

    val index = t.indexOfSlice(s)
    if(t.isEmpty) None
    else if(index == -1) Some(t.mkString)
    else Some(t.slice(0,index).mkString)
  }

它通过了这个测试:

val desmurfer = new MyClass("_smurf_")
val iterator: Iterator[Char] = "Scala_smurf_is_smurf_great_smurf__smurf_".iterator
assert(desmurfer.nextString(iterator) == Some("Scala"))
assert(desmurfer.nextString(iterator) == Some("is"))
assert(desmurfer.nextString(iterator) == Some("great"))
assert(desmurfer.nextString(iterator) == Some(""))
assert(desmurfer.nextString(iterator) == None)

assert(desmurfer.nextString("FooBarBaz".iterator) == Some("FooBarBaz"))
assert(desmurfer.nextString("".iterator) == None)

更新:从第一个“if 条件子句”中删除了“index == -1 &&”。

【讨论】:

  • 确实很好。只是好奇:为什么要测试index 两次? t.isEmpty 是否可以同时 index != -1
【解决方案3】:

这似乎正在做你想做的事。 @Eastsun 的回答激励了我

val str = "hello"

  def nextString2(iterator: Iterator[Char]): Option[String] = {
    val maxSize = str.size
    @tailrec
    def inner(collected: List[Char], queue: Queue[Char]): Option[List[Char]] =
      if (queue.size == maxSize && queue.sameElements(str))
        Some(collected.reverse.dropRight(maxSize))
      else
        iterator.find(x => true) match {
          case Some(el) => inner(el :: collected, if (queue.size == maxSize) queue.dequeue._2.enqueue(el) else queue.enqueue(el))
          case None => Some(collected.reverse)
        }

    if (iterator.hasNext)
      inner(Nil, Queue.empty).map(_.mkString)
    else
      None
  }

  test(nextString2(Nil.iterator)) === None
  test(nextString2("".iterator)) === None
  test(nextString2("asd".iterator)) === Some("asd")
  test(nextString2("asfhello".iterator)) === Some("asf")
  test(nextString2("asehelloasdasd".iterator)) === Some("ase")

但老实说,我认为它太复杂而无法使用。有时你必须在 scala 中使用非 FP 的东西才能提高性能。

附:我不知道如何在它的第一个元素上匹配迭代器,所以我使用了 iterator.find(x => true) 这很丑。对不起。

附言一点解释。我递归地建立collected 来填充您正在搜索的元素。我还用最后一个str.size-elements 构建queue。然后我每次都在str 上检查这个队列。这可能不是做这些事情的最有效方式。如果您想要更多,您可以使用 Aho–Corasick 算法或类似算法。

P.P.P.S.而且我使用iterator作为状态,这可能不是FP方式

P.P.P.P.S.你也可以通过测试:

  val desmurfer = new MyClass("_smurf_")
  val iterator: Iterator[Char] = "Scala_smurf_is_smurf_great".iterator
  test(desmurfer.nextString2(iterator)) === Some("Scala")
  test(desmurfer.nextString2(iterator)) === Some("is")
  test(desmurfer.nextString2(iterator)) === Some("great")
  test(desmurfer.nextString2(iterator)) === None
  println()
  test(desmurfer.nextString2("FooBarBaz".iterator)) === Some("FooBarBaz")
  test(desmurfer.nextString2("".iterator)) === None

【讨论】:

    【解决方案4】:

    这是我发布的一个,只是因为它有点变形:) 我不建议实际使用它:

      class MyClass2(str: String) {
        val sepLength = str.length
        def nextString(iterator: Iterator[Char]): Option[String] = {
          if (!iterator.hasNext) return None
    
          val sit = iterator.sliding(sepLength)
          val prefix = sit.takeWhile(_.mkString != str).toList
    
          val prefixString = prefix.toList.map(_.head).mkString
          if (prefix.head.length < sepLength) Some(prefix.head.mkString)
          else if (!iterator.hasNext) Some(prefix.head.mkString + prefix.last.mkString)
          else Some(prefixString)
    
        }
      }
    

    这个想法是,通过在我们的底层迭代器上调用sliding(),我们可以获得一个序列,其中一个将是我们的分隔符,如果它存在的话。所以我们可以使用takeWhile 来查找分隔符。那么我们的分隔符之前的每个滑动字符串的第一个字符就是我们跳过的字符串。正如我所说,扭曲。

    我真的很想定义sliding,以便它产生所有长度为n的子序列,并在结尾处产生长度为n-1n-2....1的特定用途序列情况,但事实并非如此,最后可怕的 if 语句正在处理各种情况。

    它通过了测试用例:)

    【讨论】:

    • 目前为止我最喜欢这个。也看过 .sliding 但后来因为太复杂而拒绝了。
    • 在某些极端情况下,定界符是底层迭代器中的最后一件事或第一件事。在这些情况下它会搞砸。我可能会在以后解决这个问题。你可能需要更多的测试用例...
    【解决方案5】:

    更新:这无需将迭代器转换为字符串即可工作

    def nextString(iterator: Iterator[Char]): Option[String] = {
      if (iterator.isEmpty) None
      else Some(iterator.foldLeft("") { (result, currentChar) => if (res.endsWith(str)) result else result + currentChar})
    }
    

    【讨论】:

    • 如果我被允许转换为字符串,答案将是显而易见的。我的限制是我应该只根据需要使用尽可能多的字符(将其添加到合同中)
    • 我错过了那部分。我已经更新了我的答案,无需将迭代器转换为字符串
    • foldLeft 仍然消耗整个迭代器。
    • 是的,最好的解决方案显然是使用递归函数。 Eastsun的回答似乎很完美。
    【解决方案6】:

    一位同事提供了这个答案的构成,这是他最初的方法和我这边的一些修饰之间的混合。谢谢,埃文斯! 然后另一位同事也添加了一些意见。谢谢Ako :-)

    class MyClass(str: String) {
      def nextString(iterator: Iterator[Char]): Option[String] = {
    
        def nextString(iterator: Iterator[Char], sb: StringBuilder): Option[String] = {
          if (!iterator.hasNext || sb.endsWith(str)) {
            Some(sb.stripSuffix(str))
          } else {
            nextString(iterator, sb.append(iterator.next()))
          }
        }
    
        if (!iterator.hasNext) None
        else nextString(iterator, new StringBuilder)
      }
    }
    

    到目前为止,我最喜欢这种方法,所以我会在两天内接受它,除非到时候有更好的答案。

    【讨论】:

    • 你应该把它变成一个迭代器本身。 class MyIterator(iterator:Iterator[Char]) extends Iterator { def hasNext() = iterator.hasNext; def next() = {&lt;body of your nextString, mostly&gt;)}
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-21
    • 1970-01-01
    • 2012-10-23
    • 1970-01-01
    • 2011-06-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多