Как я могу использовать карту и получать индекс в Scala?

99

Есть ли какой-либо встроенный список / последовательность, который ведет себя как mapи также предоставляет индекс элемента?

Гео
источник

Ответы:

148

Я полагаю, вы ищете zipWithIndex?

scala> val ls = List("Mary", "had", "a", "little", "lamb")
scala> ls.zipWithIndex.foreach{ case (e, i) => println(i+" "+e) }
0 Mary
1 had
2 a
3 little
4 lamb

От: http://www.artima.com/forums/flat.jsp?forum=283&thread=243570

У вас также есть такие варианты, как:

for((e,i) <- List("Mary", "had", "a", "little", "lamb").zipWithIndex) println(i+" "+e)

или:

List("Mary", "had", "a", "little", "lamb").zipWithIndex.foreach( (t) => println(t._2+" "+t._1) )
Виктор Кланг
источник
4
Ну, я имел в виду использовать zipWithIndexметод для получения индекса внутри цикла / карты / чего угодно.
ziggystar 02
Например, по сравнению с whileциклом, который, вероятно, является одним из самых быстрых вариантов.
ziggystar 02
Цикл Array и while, вероятно, работает настолько быстро, насколько это возможно.
Виктор Кланг
2
@ziggystar Если вы ищете производительность, вам нужно пожертвовать некоторой неизменностью. Загляните внутрь функции zipWithIndex. Он просто использует var для создания новой коллекции пар. Вы можете использовать тот же метод увеличения переменной без создания новой коллекции, как и в Java. Но это не функциональный стиль. Подумайте, действительно ли вам это нужно.
Cristian Vrabie,
Кажется, что даже zipWithIndex не является функциональным стилем. В любом случае, я думаю, следует упомянуть, что использование viewyou должно иметь возможность предотвратить создание и перемещение дополнительного списка.
Herman
56

Используйте. карта в формате . zipWithIndex

val myList = List("a", "b", "c")

myList.zipWithIndex.map { case (element, index) => 
   println(element, index) 
   s"${element}(${index})"
}

Результат:

List("a(0)", "b(1)", "c(2)")
Пауло Чек
источник
11
Это первый достойный пример, который я видел, когда использовался mapкак запрошенный, а не просто печатался внутри foreach.
Грег
10

Предлагаемые решения страдают тем фактом, что они создают промежуточные коллекции или вводят переменные, которые не являются строго необходимыми. В конечном итоге все, что вам нужно сделать, это отслеживать количество шагов итерации. Это можно сделать с помощью мемоизации. Результирующий код может выглядеть как

myIterable map (doIndexed(someFunction))

В doIndexed-Функция обертывания функции интерьера , который принимает как индекс в элементыmyIterable . Это может быть вам знакомо по JavaScript.

Вот способ достичь этой цели. Рассмотрим следующую утилиту:

object TraversableUtil {
    class IndexMemoizingFunction[A, B](f: (Int, A) => B) extends Function1[A, B] {
        private var index = 0
        override def apply(a: A): B = {
            val ret = f(index, a)
            index += 1
            ret
        }
    }

    def doIndexed[A, B](f: (Int, A) => B): A => B = {
        new IndexMemoizingFunction(f)
    }
}

Это уже все, что вам нужно. Вы можете применить это, например, следующим образом:

import TraversableUtil._
List('a','b','c').map(doIndexed((i, char) => char + i))

что приводит к списку

List(97, 99, 101)

Таким образом, вы можете использовать обычные Traversable-функции за счет обертывания вашей эффективной функции. Накладные расходы - это создание мемоизирующего объекта и счетчика в нем. В противном случае это решение будет таким же хорошим (или плохим) с точки зрения памяти или производительности, как и неиндексированный map. Наслаждайтесь!

Мэтт Бразен
источник
1
Это очень элегантное решение проблемы. +1 за то, что не создал временную коллекцию. Не будет работать в параллельной коллекции, но все же очень хорошее решение.
fbl
5
Если вы не хотите создавать временную коллекцию, просто используйте coll.view.zipWithIndexвместоcoll.zipWithIndex
tsuna
5

Есть CountedIteratorв 2.7.x (который вы можете получить из обычного итератора с .counted). Я считаю, что он устарел (или просто удален) в версии 2.8, но его достаточно легко свернуть самостоятельно. Вам нужно иметь возможность назвать итератор:

val ci = List("These","are","words").elements.counted
scala> ci map (i => i+"=#"+ci.count) toList
res0: List[java.lang.String] = List(These=#0,are=#1,words=#2)
Рекс Керр
источник
3

Или, если ваша коллекция имеет постоянное время доступа, вы можете сопоставить список индексов вместо фактической коллекции:

val ls = List("a","b","c")
0.until(ls.length).map( i => doStuffWithElem(i,ls(i)) )
Кристиан Враби
источник
1
Более элегантный способ сделать это просто: ls.indices.map(i => doStuffWithElem(i, ls(i))
Ассиль Ксикси
3
@aksiksi Что ж, вижу, что indicesэто реализовано, поскольку0 until length это почти то же самое: P
Cristian Vrabie
1
отрицательный голос из-за сложности - итерация с ls (i) занимает O (n ^ 2)
Моше Биксеншпанер
1
@MosheBixenshpaner Достаточно честно. Мой пример Listбыл действительно плохим. Однако я упомянул, что это подходит, если ваша коллекция имеет постоянное время доступа. Надо было выбрать Array.
Cristian
1

Используйте .map в .zipWithIndex со структурой данных карты

val sampleMap = Map("a" -> "hello", "b" -> "world", "c" -> "again")

val result = sampleMap.zipWithIndex.map { case ((key, value), index) => 
    s"Key: $key - Value: $value with Index: $index"
}

Полученные результаты

 List(
       Key: a - Value: hello with Index: 0, 
       Key: b - Value: world with Index: 1, 
       Key: c - Value: again with Index: 2
     )
fgfernandez0321
источник
1

Это можно сделать двумя способами.

ZipWithIndex: автоматически создает счетчик, начиная с 0.

  // zipWithIndex with a map.
  val days = List("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat")
  days.zipWithIndex.map {
        case (day, count) => println(s"$count is $day")
  }
  // Or use it simply with a for.
  for ((day, count) <- days.zipWithIndex) {
        println(s"$count is $day")
  }

Вывод обоих кодов будет:

0 is Sun
1 is Mon
2 is Tue
3 is Wed
4 is Thu
5 is Fri
6 is Sat

Zip : используйте метод zip с Stream, чтобы создать счетчик. Это дает вам возможность контролировать начальное значение.

for ((day, count) <- days.zip(Stream from 1)) {
  println(s"$count is $day")
}

Результат:

1 is Sun
2 is Mon
3 is Tue
4 is Wed
5 is Thu
6 is Fri
7 is Sat
Безумные вещи
источник
0

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

val ls = List("a","b","c")
val ls_index_map = ls.zipWithIndex.toMap 
Яир Пиво
источник