Jak mogę korzystać z mapy i otrzymać indeks również w Scali?

99

Czy istnieje wbudowana lista / sekwencja, która zachowuje się jak mapi zapewnia również indeks elementu?

Geo
źródło

Odpowiedzi:

148

Myślę, że szukasz 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

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

Masz również odmiany, takie jak:

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

lub:

List("Mary", "had", "a", "little", "lamb").zipWithIndex.foreach( (t) => println(t._2+" "+t._1) )
Viktor Klang
źródło
4
Cóż, miałem na myśli użycie zipWithIndexmetody, aby uzyskać indeks wewnątrz pętli / mapy / cokolwiek.
ziggystar
Na przykład w porównaniu do whilepętli, która jest prawdopodobnie jedną z najszybszych opcji.
ziggystar
Tablica i pętla while są prawdopodobnie tak szybkie, jak to tylko możliwe.
Viktor Klang
2
@ziggystar Jeśli szukasz wydajności, musisz poświęcić trochę niezmienności. Zajrzyj do funkcji zipWithIndex. Po prostu używa var do zbudowania nowej kolekcji par. Możesz użyć tej samej metody inkrementacji zmiennej bez tworzenia nowej kolekcji, tak jak w Javie. Ale to nie jest funkcjonalny styl. Pomyśl, czy naprawdę tego potrzebujesz.
Cristian Vrabie
Wydaje się więc, że nawet sam zipWithIndex nie jest funkcjonalnym stylem. W każdym razie myślę, że należy wspomnieć, że używanie viewciebie powinno być w stanie zapobiec tworzeniu i przechodzeniu przez dodatkową listę.
herman
55

Posługiwać się . mapa w. zipWithIndex

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

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

Wynik:

List("a(0)", "b(1)", "c(2)")
Paulo Check
źródło
11
To pierwszy przyzwoity przykład, jaki widziałem, w którym zastosowano mapżądanie zamiast po prostu drukowania wewnątrz pliku foreach.
Greg Chabala
10

Proponowane rozwiązania obarczone są tym, że tworzą zbiory pośrednie lub wprowadzają zmienne, które nie są bezwzględnie konieczne. Ostatecznie wszystko, co musisz zrobić, to śledzić liczbę kroków iteracji. Można to zrobić za pomocą zapamiętywania. Wynikowy kod może wyglądać tak

myIterable map (doIndexed(someFunction))

doIndexed-Function owija funkcję wewnętrznego, który odbiera zarówno wskaźnik się elementów myIterable. Może to być znane z JavaScript.

Oto sposób na osiągnięcie tego celu. Rozważ następujące narzędzie:

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

To już wszystko, czego potrzebujesz. Możesz to zastosować na przykład w następujący sposób:

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

co powoduje wyświetlenie listy

List(97, 99, 101)

W ten sposób możesz używać zwykłych funkcji Traversable kosztem opakowywania efektywnej funkcji. Narzut to tworzenie obiektu do zapamiętywania i znajdującego się w nim licznika. W przeciwnym razie to rozwiązanie jest tak samo dobre (lub złe) pod względem pamięci lub wydajności, jak użycie niezindeksowanego map. Cieszyć się!

Matt Brasen
źródło
1
To bardzo eleganckie rozwiązanie problemu. +1 za nie tworzenie tymczasowej kolekcji. Nie będzie działać w równoległej kolekcji, ale nadal bardzo fajne rozwiązanie.
fbl
5
Jeśli nie chcesz tworzyć kolekcji tymczasowej, po prostu użyj coll.view.zipWithIndexzamiastcoll.zipWithIndex
tsuna
5

Jest CountedIteratorw 2.7.x (który można uzyskać z normalnego iteratora z .counted). Wydaje mi się, że w wersji 2.8 został wycofany (lub po prostu usunięty), ale dość łatwo jest wprowadzić własny. Musisz być w stanie nazwać iterator:

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)
Rex Kerr
źródło
3

Lub zakładając, że Twoja kolekcja ma stały czas dostępu, możesz zmapować listę indeksów zamiast rzeczywistej kolekcji:

val ls = List("a","b","c")
0.until(ls.length).map( i => doStuffWithElem(i,ls(i)) )
Cristian Vrabie
źródło
1
Bardziej eleganckim sposobem jest po prostu: ls.indices.map(i => doStuffWithElem(i, ls(i))
Assil Ksiksi
3
@aksiksi Cóż, widząc, że indicesjest zaimplementowany, ponieważ0 until length jest to prawie to samo: P
Cristian Vrabie
1
głos przeciwny ze względu na złożoność - iteracja z ls (i) zabiera O (n ^ 2)
Moshe Bixenshpaner
1
@MosheBixenshpaner W porządku. Mój przykład Listbył rzeczywiście kiepski. Wspomniałem jednak, że jest to odpowiednie, jeśli Twoja kolekcja ma stały czas dostępu. Powinienem był wybrać Array.
Cristian Vrabie,
1

Użyj .map w .zipWithIndex ze strukturą danych Map

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

Wyniki

 List(
       Key: a - Value: hello with Index: 0, 
       Key: b - Value: world with Index: 1, 
       Key: c - Value: again with Index: 2
     )
fgfernandez0321
źródło
1

Można to zrobić na dwa sposoby.

ZipWithIndex: tworzy licznik automatycznie zaczynający się od 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")
  }

Wynik obu kodów będzie:

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

Zip : użyj metody zip ze strumieniem, aby utworzyć licznik. Daje to sposób kontrolowania wartości początkowej.

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

Wynik:

1 is Sun
2 is Mon
3 is Tue
4 is Wed
5 is Thu
6 is Fri
7 is Sat
Szaleńcy
źródło
0

Jeśli potrzebujesz przeszukać również wartości mapy (tak jak musiałem):

val ls = List("a","b","c")
val ls_index_map = ls.zipWithIndex.toMap 
Piwo Yair
źródło