Comment puis-je utiliser la carte et recevoir également un index dans Scala?

99

Y a-t-il une liste / séquence intégrée qui se comporte comme mapet fournit également l'index de l'élément?

Géo
la source

Réponses:

148

Je crois que vous recherchez 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

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

Vous avez également des variantes comme:

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

ou:

List("Mary", "had", "a", "little", "lamb").zipWithIndex.foreach( (t) => println(t._2+" "+t._1) )
Viktor Klang
la source
4
Eh bien, je voulais dire utiliser la zipWithIndexméthode pour obtenir l'index dans une boucle / map / peu importe.
ziggystar
Par exemple en comparant à une whileboucle, qui est probablement parmi les options les plus rapides.
ziggystar
Un tableau et une boucle while sont probablement aussi rapides que possible.
Viktor Klang
2
@ziggystar Si vous recherchez des performances, vous devez faire un compromis sur l'immuabilité. Regardez à l'intérieur de la fonction zipWithIndex. Il utilise simplement un var pour créer une nouvelle collection de paires. Vous pouvez utiliser la même méthode pour incrémenter une variable sans créer la nouvelle collection, comme vous le feriez en Java. Mais ce n'est pas un style fonctionnel. Pensez si vous en avez réellement besoin.
Cristian Vrabie
Il semble donc que même zipWithIndex lui-même ne soit pas un style fonctionnel. Quoi qu'il en soit, je pense qu'il convient de mentionner que l'utilisation de viewvous devrait être en mesure d'empêcher la création et la traversée d'une liste supplémentaire.
herman
55

Utilisation . carte en. zipWithIndex

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

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

Résultat:

List("a(0)", "b(1)", "c(2)")
Paulo Check
la source
11
C'est le premier exemple décent que j'ai vu qui utilisait un mapcomme demandé au lieu de simplement imprimer dans un fichier foreach.
Greg Chabala
10

Les solutions proposées souffrent du fait qu'elles créent des collections intermédiaires ou introduisent des variables qui ne sont pas strictement nécessaires. En fin de compte, tout ce que vous avez à faire est de suivre le nombre d'étapes d'une itération. Cela peut être fait en utilisant la mémorisation. Le code résultant pourrait ressembler à

myIterable map (doIndexed(someFunction))

La fonction doIndexed-Function enveloppe la fonction intérieure qui reçoit à la fois un index et les éléments de myIterable. Cela peut vous être familier à partir de JavaScript.

Voici un moyen d'atteindre cet objectif. Considérez l'utilitaire suivant:

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

C'est déjà tout ce dont vous avez besoin. Vous pouvez appliquer ceci par exemple comme suit:

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

qui aboutit à la liste

List(97, 99, 101)

De cette façon, vous pouvez utiliser les fonctions Traversable habituelles au détriment de l'emballage de votre fonction effective. La surcharge est la création de l'objet de mémorisation et du compteur qui s'y trouve. Sinon, cette solution est aussi bonne (ou mauvaise) en termes de mémoire ou de performances que l'utilisation de non indexé map. Prendre plaisir!

Matt Brasen
la source
1
C'est une solution très élégante au problème. +1 pour ne pas avoir créé de collection temporaire. Ne fonctionnera pas dans une collection parallèle, mais reste une très bonne solution.
fbl le
5
Si vous ne souhaitez pas créer de collection temporaire, utilisez simplement à la coll.view.zipWithIndexplace decoll.zipWithIndex
tsuna
5

Il existe CountedIteratoren 2.7.x (que vous pouvez obtenir à partir d'un itérateur normal avec .counted). Je pense qu'il est obsolète (ou simplement supprimé) dans la version 2.8, mais il est assez facile de lancer le vôtre. Vous devez être en mesure de nommer l'itérateur:

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
la source
3

Ou, en supposant que votre collection a un temps d'accès constant, vous pouvez mapper la liste des index au lieu de la collection réelle:

val ls = List("a","b","c")
0.until(ls.length).map( i => doStuffWithElem(i,ls(i)) )
Cristian Vrabie
la source
1
Une façon plus élégante de le faire est simplement: ls.indices.map(i => doStuffWithElem(i, ls(i))
Assil Ksiksi
3
@aksiksi Eh bien, vu que cela indicesest implémenté,0 until length c'est à peu près la même chose: P
Cristian Vrabie
1
downvote en raison de la complexité - itérer avec ls (i) prend O (n ^ 2)
Moshe Bixenshpaner
1
@MosheBixenshpaner Très bien. Mon exemple Listétait vraiment médiocre. J'ai cependant mentionné que cela convient si votre collection a un temps d'accès constant. J'aurais dû choisir Array.
Cristian Vrabie
1

Utilisez .map dans .zipWithIndex avec la structure de données 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"
}

Résultats

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

Il y a deux façons de le faire.

ZipWithIndex: crée un compteur commençant automatiquement par 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")
  }

La sortie des deux codes sera:

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

Zip : utilisez la méthode zip avec un Stream pour créer un compteur. Cela vous donne un moyen de contrôler la valeur de départ.

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

Résultat:

1 is Sun
2 is Mon
3 is Tue
4 is Wed
5 is Thu
6 is Fri
7 is Sat
Madstuffs
la source
0

Si vous avez également besoin de rechercher les valeurs de la carte (comme je devais le faire):

val ls = List("a","b","c")
val ls_index_map = ls.zipWithIndex.toMap 
Bière Yair
la source