2015-12-18 4 views
3

У меня есть прецедент, где мне нужно вернуть строку до разделителя. Строка (если найдена) из итератора Char.Найти строку в итераторе 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()) 
    } 
} 

Есть ли способ, что я могу сделать это в более функционально (в идеале без изменения подписи метода)?

Update: Вот как я проверить это

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 
+0

Каков выходной сигнал, который вы ожидаете? –

+0

@ S.Karthik добавлен образец вывода –

+0

, пожалуйста, проверьте ответ –

ответ

0

Коллега при условии, задатки этого ответа, который представляет собой смесь между его оригинальным подходом и некоторыми полирующими с моей стороны. Спасибо, Эванс! Затем другой коллега также добавил некоторые данные. Благодаря Ако :-)

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) 
    } 
} 

До сих пор, мне нравится этот подход лучше, так что я буду принимать его в течение двух дней, если не лучший ответ тогда.

+0

Вы должны превратить это в сам итератор. 'class MyIterator (итератор: Iterator [Char]) расширяет Iterator {def hasNext() = iterator.hasNext; def next() = {<тело вашей следующей строки, в основном>)} ' –

0

Обновлено: Это работает без преобразования итератор в строку

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}) 
} 
+0

Если мне разрешили конвертировать в String, ответ был бы очевиден. Ограничение, которое у меня есть, заключается в том, что я должен брать только столько символов, сколько необходимо (добавлено к контракту) –

+0

Я пропустил эту часть. Я обновил свой ответ, чтобы сделать это без преобразования итератора в String – James

+0

'foldLeft' все еще потребляет весь итератор. – Haspemulator

3

Как об этом один:

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) 
+0

Это хорошо, но я думаю, ': +' должен быть O (n), а также иметь проблемы с памятью с большим количеством вызовов. Пожалуйста, см. Мой ответ (гораздо больше, хотя), как моя попытка улучшить. Ваш ответ по-прежнему может быть лучше из-за простоты – Archeg

2

Это похоже на то, что вы хотите. @Eestsun ответ мотивировал меня

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") 

Но я честно считаю, что это слишком сложно использовать. Иногда вам нужно использовать не FP-материал в scala для повышения эффективности.

P.S. Я не знал, как сопоставить итератор на первом элементе, поэтому я использовал iterator.find(x => true), который является уродливым. Сожалею.

P.P.S. Немного объяснения. Я аккуратно создаю collected, чтобы заполнить элементы, которые вы ищите. И я также построю queue с последними str.size -элементами. Затем я просто проверяю эту очередь на str каждый раз. Это может быть не самый эффективный способ сделать это. Вы можете пойти с алгоритмом Ахо-Корасика или аналогом, если хотите большего.

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 
2

Вот один я отправляю только потому, что это немного деформирован :) Я бы не рекомендовал использовать его на самом деле:

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-1, n-2 .... 1 для этого конкретного случая использования, но это не делает, и ужасное, если утверждение в конце имеет дело с различными случаями.

Он проходит тестовые случаи :)

+0

Мне нравится этот пока еще один. Также посмотрел на .Sliding, но затем отклонил его как слишком сложный. –

+0

Есть крайние случаи, когда разделитель - это самое последнее или самое первое в базовом итераторе. В этих случаях это беспорядок. Я могу обойти это позже. Вероятно, вам нужно больше тестовых примеров ... –

3

Как насчет этого?

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) 

Обновлена ​​: удалено "индекс == -1 & &" от первого "если условия п".

+0

Да, мне нравится –

+1

Очень приятно. Просто любопытно: зачем тестировать 'index' дважды? Возможно ли для 't.isEmpty' в то же время' index! = -1'? – jwvh

+0

@jwvh да, спасибо, что проверка не нужна :) – nikiforo

Смежные вопросы