Come posso usare la mappa e ricevere anche un indice in Scala?


99

C'è qualche List / Sequence built-in che si comporta come mape fornisce anche l'indice dell'elemento?

Risposte:


148

Credo che stai cercando 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

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

Hai anche varianti come:

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

o:

List("Mary", "had", "a", "little", "lamb").zipWithIndex.foreach( (t) => println(t._2+" "+t._1) )

4
Bene, intendevo usare il zipWithIndexmetodo per ottenere l'indice all'interno di un ciclo / mappa / qualunque cosa.
ziggystar

Ad esempio il confronto con un whileciclo, che è probabilmente tra le opzioni più veloci.
ziggystar

Un Array e un ciclo while è probabilmente il più veloce possibile.
Viktor Klang

2
@ziggystar Se stai cercando prestazioni, devi rinunciare a un po 'di immutabilità. Guarda all'interno della funzione zipWithIndex. Utilizza solo una var per creare una nuova raccolta di coppie. È possibile utilizzare lo stesso metodo per incrementare una var senza creare la nuova raccolta, proprio come si farebbe in Java. Ma non è uno stile funzionale. Pensa se ne hai davvero bisogno.
Cristian Vrabie

Quindi sembra che anche zipWithIndex non sia uno stile funzionale. Ad ogni modo penso che dovrebbe essere detto che l'utilizzo di viewte dovrebbe essere in grado di impedire la creazione e l'attraversamento di una lista extra.
herman

55

Uso . mappa in. zipWithIndex

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

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

Risultato:

List("a(0)", "b(1)", "c(2)")

11
Questo è il primo esempio decente che ho visto che utilizzava un file mapcome richiesto invece di stampare solo all'interno di un file foreach.
Greg Chabala

10

Le soluzioni proposte soffrono del fatto che creano collezioni intermedie o introducono variabili non strettamente necessarie. In definitiva, tutto ciò che devi fare è tenere traccia del numero di passaggi di un'iterazione. Questo può essere fatto usando la memorizzazione. Il codice risultante potrebbe essere simile a

myIterable map (doIndexed(someFunction))

La doIndexed-Function avvolge la funzione interior che riceve sia un indice che gli elementi di myIterable. Questo potrebbe esserti familiare da JavaScript.

Ecco un modo per raggiungere questo scopo. Considera la seguente utilità:

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

Questo è già tutto ciò di cui hai bisogno. Puoi applicarlo ad esempio come segue:

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

che risulta nell'elenco

List(97, 99, 101)

In questo modo, puoi usare le solite funzioni Traversable a scapito di avvolgere la tua funzione effettiva. L'overhead è la creazione dell'oggetto memoizing e del contatore in esso contenuto. Altrimenti questa soluzione è buona (o cattiva) in termini di memoria o prestazioni come l'utilizzo di unindexed map. Godere!


1
Questa è una soluzione molto elegante al problema. +1 per non aver creato una collezione temporanea. Non funzionerà in una raccolta parallela, ma è comunque una soluzione molto carina.
fbl

5
Se non vuoi creare una raccolta temporanea, usa semplicemente al coll.view.zipWithIndexposto dicoll.zipWithIndex
tsuna

5

C'è CountedIteratorin 2.7.x (che puoi ottenere da un normale iteratore con .counted). Credo che sia stato deprecato (o semplicemente rimosso) nella 2.8, ma è abbastanza facile lanciarne uno tuo. Devi essere in grado di nominare l'iteratore:

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

Oppure, supponendo che la tua raccolta abbia un tempo di accesso costante, potresti mappare l'elenco degli indici invece della raccolta effettiva:

val ls = List("a","b","c")
0.until(ls.length).map( i => doStuffWithElem(i,ls(i)) )

1
Un modo più elegante per farlo è semplicemente: ls.indices.map(i => doStuffWithElem(i, ls(i))
Assil Ksiksi

3
@aksiksi Bene, visto che indicesè implementato perché0 until length è più o meno la stessa cosa: P
Cristian Vrabie

1
downvote a causa della complessità - l'iterazione con ls (i) richiede O (n ^ 2)
Moshe Bixenshpaner

1
@MosheBixenshpaner Abbastanza giusto. Il mio esempio Listera davvero scarso. Ho detto tuttavia che questo è adatto se la tua collezione ha un tempo di accesso costante. Avrei dovuto scegliere Array.
Cristian Vrabie

1

Usa .map in .zipWithIndex con la struttura dati 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"
}

Risultati

 List(
       Key: a - Value: hello with Index: 0, 
       Key: b - Value: world with Index: 1, 
       Key: c - Value: again with Index: 2
     )

1

Ci sono due modi per farlo.

ZipWithIndex: crea automaticamente un contatore che inizia con 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")
  }

L'output di entrambi i codici sarà:

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

Zip : usa il metodo zip con uno stream per creare un contatore. Questo ti dà un modo per controllare il valore iniziale.

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

Risultato:

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

0

Se hai bisogno di cercare anche i valori della mappa (come ho dovuto fare io):

val ls = List("a","b","c")
val ls_index_map = ls.zipWithIndex.toMap 
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.