2015-01-07 3 views
1

Другого Scala новичка вопрос, так как я не получаю, как добиться этого в функциональном пути (в основном идет от языка сценариев фона):Scala - изменить строки в списке на основе их количества вхождений

У меня есть список строк:

val food-list = List("banana-name", "orange-name", "orange-num", "orange-name", "orange-num", "grape-name")

и где они дублированы, я хотел бы добавить увеличивающееся число в строку и получить, что в списке похож на входной список, например, так:

List("banana-name", "orange1-name", "orange1-num", "orange2-name", "orange2-num", "grape-name")

Я сгруппировал их, чтобы получить отсчеты для них:

val freqs = list.groupBy(identity).mapValues(v => List.range(1, v.length + 1))

Который дает мне:

Map(orange-num -> List(1, 2), banana-name -> List(1), grape-name -> List(1), orange-name -> List(1, 2))

Порядок списка важно (его должен быть в исходном порядке food-list), поэтому я знаю, что для меня проблематично использовать карту на этом этапе. Ближайший я чувствую, что я получил в раствор:

food-list.map{l => 

    if (freqs(l).length > 1){ 

      freqs(l).map(n => 
          l.split("-")(0) + n.toString + "-" + l.split("-")(1)) 

    } else { 
     l 
    } 
} 

Это, конечно, дает мне шаткий вывод, так как я отображающий список частот от слов значения в freqs

List(banana-name, List(orange1-name, orange2-name), List(orange1-num, orange2-num), List(orange1-name, orange2-name), List(orange1-num, orange2-num), grape-name)

Как это делается в режиме Scala fp, не прибегая к неуклюжим для петель и счетчиков?

+0

Можно ли считать каждый раз предыдущими вхождениями заданного значения, или это было бы слишком (O (n^2)) медленно? –

+0

для моих целей, я не буду иметь слишком много списков, чтобы это имело заметную разницу, поэтому предыдущие случаи не были бы проблемой, но попытка выяснить, что эффективный подсчет в Scala, исходящий из Python, не очень очевиден для я на данный момент – roy

+0

Метод 'count' (http://www.scala-lang.org/api/current/index.html#[email protected](p:A=>Boolean):Int) доступный на 'Seq's, так что я думаю, может быть очень похож на Python. Во всяком случае, мое решение в конце не использовало его, хотя оно требовало «обратного», поэтому оно не является самым эффективным. –

ответ

3

Если индексы имеют важное значение, иногда лучше, чтобы следить за их явно с помощью zipWithIndex (очень похожий на Питона enumerate):

food-list.zipWithIndex.groupBy(_._1).values.toList.flatMap{ 
    //if only one entry in this group, don't change the values 
    //x is actually a tuple, could write case (str, idx) :: Nil => (str, idx) :: Nil 
    case x :: Nil => x :: Nil 
    //case where there are duplicate strings 
    case xs => xs.zipWithIndex.map { 
    //idx is index in the original list, n is index in the new list i.e. count 
    case ((str, idx), n) => 
     //destructuring assignment, like python's (fruit, suffix) = ... 
     val Array(fruit, suffix) = str.split("-") 
     //string interpolation, returning a tuple 
     (s"$fruit${n+1}-$suffix", idx) 
    } 
//We now have our list of (string, index) pairs; 
//sort them and map to a list of just strings 
}.sortBy(_._2).map(_._1) 
3

Эффективное и простое:

val food = List("banana-name", "orange-name", "orange-num", 
      "orange-name", "orange-num", "grape-name") 

def replaceName(s: String, n: Int) = { 
    val tokens = s.split("-") 
    tokens(0) + n + "-" + tokens(1) 
} 

val indicesMap = scala.collection.mutable.HashMap.empty[String, Int] 
val res = food.map { name => 
    { 
    val n = indicesMap.getOrElse(name, 1) 
    indicesMap += (name -> (n + 1)) 
    replaceName(name, n) 
    } 
} 
+0

Работает, но изменчивая карта не полностью идиоматична. – lmm

+0

Но здесь очень интересно, так как это можно сделать * на лету * –

+0

@JeanLogeart Вы не обрабатываете случай с именем banana, когда список содержит только одно вхождение. Вам нужен дополнительный шаг заранее. –

0

Here является попытаться предоставить то, что вы ожидали от foldLeft:

foodList.foldLeft((List[String](), Map[String, Int]()))//initial value 
    ((a/*accumulator, list, map*/, v/*value from the list*/)=> 
     if (a._2.isDefinedAt(v))//already seen 
      (s"$v+${a._2(v)}" :: a._1, a._2.updated(v, a._2(v) + 1)) 
     else 
      (v::a._1, a._2.updated(v, 1))) 
    ._1/*select the list*/.reverse/*because we created in the opposite order*/ 
+0

Да, эта версия помещает номер в неправильное место, хотя это будет усложнять без необходимости, другие ответы были выполнены очень хорошо. :) –

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