93 votes

Quelle est la façon idiomatique en Scala de "supprimer" un élément d'une liste immuable ?

J'ai une liste, qui peut contenir des éléments qui seront comparés comme égaux. Je voudrais une liste similaire, mais avec un élément en moins. Ainsi, à partir de (A, B, C, B, D), j'aimerais pouvoir "retirer" un seul B pour obtenir, par exemple, (A, C, B, D). L'ordre des éléments dans le résultat n'a pas d'importance.

J'ai un code fonctionnel, écrit en Scala d'une manière inspirée de Lisp. Existe-t-il un moyen plus idiomatique pour faire cela ?

Le contexte est un jeu de cartes où deux jeux de cartes standard sont en jeu, il peut donc y avoir être des cartes en double mais toujours jouées une par une.

def removeOne(c: Card, left: List[Card], right: List[Card]): List[Card] = {
  if (Nil == right) {
    return left
  }
  if (c == right.head) {
    return left ::: right.tail
  }
  return removeOne(c, right.head :: left, right.tail)
}

def removeCard(c: Card, cards: List[Card]): List[Card] = {
  return removeOne(c, Nil, cards)
}

0 votes

Ajout d'une note indiquant que l'ordre de la liste de résultats n'a pas d'importance dans ce cas spécifique.

0 votes

Donc le List[Card] dans cette question est la main d'un joueur ?

0 votes

@Ken Bloom, oui, c'est bien la main d'un joueur.

158voto

Antonin Brettsnajdr Points 1883

Je n'ai pas vu cette possibilité dans les réponses ci-dessus, donc.. :

scala> def remove(num: Int, list: List[Int]) = list diff List(num)
remove: (num: Int,list: List[Int])List[Int]

scala> remove(2,List(1,2,3,4,5))
res2: List[Int] = List(1, 3, 4, 5)

Edit :

scala> remove(2,List(2,2,2))
res0: List[Int] = List(2, 2)

Comme un charme :-).

19 votes

Joli ! J'ajouterais un autre 2 à la liste pour qu'il soit clair qu'un seul élément est retiré.

42voto

Søren Mathiasen Points 144

Vous pourriez utiliser le filterNot méthode.

val data = "test"
list = List("this", "is", "a", "test")
list.filterNot(elm => elm == data)

25 votes

Cela supprimera tous les éléments qui sont égaux à "test" - ce qui n'est pas ce qui est demandé ;)

1 votes

En fait, il fera exactement ce dont vous avez besoin. Elle retournera tous les éléments de la liste sauf ceux qui ne sont pas égaux à "test". Faites attention au fait qu'elle utilise filterNot

17 votes

La question initiale était de savoir comment supprimer une instance UNIQUE. Pas toutes les instances.

18voto

Frank S. Thomas Points 2071

Vous pouvez essayer ceci :

scala> val (left,right) = List(1,2,3,2,4).span(_ != 2)
left: List[Int] = List(1)
right: List[Int] = List(2, 3, 2, 4)

scala> left ::: right.tail                            
res7: List[Int] = List(1, 3, 2, 4)

Et comme méthode :

def removeInt(i: Int, li: List[Int]) = {
   val (left, right) = li.span(_ != i)
   left ::: right.drop(1)
}

3 votes

Il convient de noter que left ::: right.drop(1) est plus courte que l'instruction if avec isEmpty .

2 votes

Merci, y a-t-il une raison de préférer .drop(1) à .tail, ou vice versa ?

8 votes

@James Petry - Si vous appelez tail sur une liste vide, vous obtenez une exception : scala> List().tail java.lang.UnsupportedOperationException: tail of empty list . drop(1) sur une liste vide renvoie cependant une liste vide.

8voto

Rex Kerr Points 94401

Malheureusement, la hiérarchie des collections s'est mise dans un sale état avec - en List . Pour ArrayBuffer cela fonctionne comme on peut l'espérer :

scala> collection.mutable.ArrayBuffer(1,2,3,2,4) - 2
res0: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 3, 2, 4)

mais, malheureusement, List s'est retrouvé avec un filterNot -et fait donc la "mauvaise chose". y vous envoie un avertissement de dépréciation (ce qui est assez logique, car il s'agit en fait de filterNot ) :

scala> List(1,2,3,2,4) - 2                          
warning: there were deprecation warnings; re-run with -deprecation for details
res1: List[Int] = List(1, 3, 4)

Donc la chose la plus simple à faire est de convertir List dans une collection qui fait ça bien, et puis se reconvertir à nouveau :

import collection.mutable.ArrayBuffer._
scala> ((ArrayBuffer() ++ List(1,2,3,2,4)) - 2).toList
res2: List[Int] = List(1, 3, 2, 4)

Vous pouvez également conserver la logique du code que vous avez mais rendre le style plus idiomatique :

def removeInt(i: Int, li: List[Int]) = {
  def removeOne(i: Int, left: List[Int], right: List[Int]): List[Int] = right match {
    case r :: rest =>
      if (r == i) left.reverse ::: rest
      else removeOne(i, r :: left, rest)
    case Nil => left.reverse
  }
  removeOne(i, Nil, li)
}

scala> removeInt(2, List(1,2,3,2,4))
res3: List[Int] = List(1, 3, 2, 4)

0 votes

removeInt(5,List(1,2,6,4,5,3,6,4,6,5,1)) donne List(4, 6, 2, 1, 3, 6, 4, 6, 5, 1) . Je pense que ce n'est pas ce que vous vouliez.

0 votes

@Ken Bloom - En effet. C'est une erreur dans l'algorithme original, que j'ai copié sans y réfléchir suffisamment. Corrigé maintenant.

0 votes

C'est plutôt une omission dans la spécification de la question, car l'ordre n'a pas d'importance dans mon cas spécifique. Il est bon de voir la version préservant l'ordre, merci.

5voto

Suat KARAKUSOGLU Points 173
 def removeAtIdx[T](idx: Int, listToRemoveFrom: List[T]): List[T] = {
    assert(listToRemoveFrom.length > idx && idx >= 0)
    val (left, _ :: right) = listToRemoveFrom.splitAt(idx)
    left ++ right
 }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X