2013-11-19 3 views
0

У меня есть этот рабочий код, чтобы создать карту между символами в строке и список, содержащий индексы.Что представляет собой более функциональный способ создания карты списка?

scala> "Lollipop".zipWithIndex.foldLeft(Map[Char, List[Int]]())((acc, t) => acc + (t._1 -> (acc.getOrElse(t._1, List[Int]()) :+ t._2))) 
res122: scala.collection.immutable.Map[Char,List[Int]] = Map(i -> List(4), L -> List(0), l -> List(2, 3), p -> List(5, 7), o -> List(1, 6)) 

Но использование acc.getOrElse выглядит императивом. Есть ли более функциональный способ, который скрывает это от пользователя?

ответ

5
for { 
    (c, l) <- "Lollipop".zipWithIndex.groupBy{ _._1 } 
} yield c -> l.map{ _._2 } 
// Map(i -> Vector(4), L -> Vector(0), l -> Vector(2, 3), p -> Vector(5, 7), o -> Vector(1, 6)) 

После groupBy{ _._1 } вы получите Map[Char, Seq[(Char, Int)]]. Поэтому вам нужно преобразовать пары (Char, Int) в Int, используя p => p._2 или просто _._2.

Вы можете использовать mapValues так:

"Lollipop".zipWithIndex.groupBy{ _._1 }.mapValues{ _.map{_._2} } 

Но mapValues создает ленивую коллекцию, чтобы вы могли получить проблему производительности в случае множественного доступа к одному элементу с помощью ключа.

+0

Спасибо! Таким образом, значительная функция преобразования 'List' в' Map' - 'groupBy'. – mparaz

2

Альтернатива заключается в использовании значения по умолчанию для вашей карты (переписан код немного более явным):

val empty = Map.empty[Char, List[Int]].withDefaultValue(List.empty) 
"Lollipop".zipWithIndex.foldLeft(empty) { 
    case (acc, (char, position)) => { 
    val positions = acc(char) :+ position 
    acc + (char -> positions) 
    } 
} 
+0

Спасибо! Хотя это необходимо с помощью 'acc (char)' lookup. – mparaz

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