Qu'est-ce qu'une manière Scala idiomatique de «supprimer» un élément d'une liste immuable?

84

J'ai une liste, qui peut contenir des éléments qui se compareront comme égaux. Je voudrais une liste similaire, mais avec un élément supprimé. Donc à partir de (A, B, C, B, D), je voudrais pouvoir "supprimer" 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 du code de travail, écrit d'une manière inspirée de Lisp dans Scala. Y a-t-il une manière plus idiomatique de faire cela?

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

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)
}
Gavilan Comun
la source
Ajout d'une note que l'ordre de la liste des résultats n'a pas d'importance dans ce cas spécifique.
Gavilan Comun le
donc le List[Card]dans cette question est la main d'un joueur?
Ken Bloom
@Ken Bloom, oui c'est la main d'un joueur.
Gavilan Comun
Vous savez, j'ai cherché une question comme celle-ci pendant un moment, puis j'ai posté la même question, puis j'ai trouvé celle-ci pendant que je parcourais et attendais que les gens répondent à la mienne. Je suppose que je devrais voter pour clore ma propre question maintenant en double. ;-)
Joe Carnahan
Cette question pour Clojure: stackoverflow.com/questions/7662447/…
Gavilan Comun

Réponses:

144

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)

Éditer:

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

Comme un charme :-).

Antonin Brettsnajdr
la source
18
Agréable! J'ajouterais 2 autres à la liste pour préciser qu'un seul élément est supprimé.
Frank
39

Vous pouvez utiliser la filterNotméthode.

val data = "test"
list = List("this", "is", "a", "test")
list.filterNot(elm => elm == data)
Søren Mathiasen
la source
21
Cela supprimera tous les éléments qui sont égaux à "test" - pas ce qui est demandé;)
yǝsʞǝla
1
En fait, il fera exactement ce dont vous avez besoin. Il renverra tous les éléments de la liste sauf ceux qui ne sont pas égaux à "test". Faites attention qu'il utilise filterNot
btbvoy
14
La question initiale était de savoir comment supprimer une seule instance. Pas toutes les instances.
ty1824
@ Søren Mathiasen si je veux filtrer plusieurs éléments comme une séquence comme val data = Seq ("test", "a"), comment faire?
BdEngineer
18

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)
}
Frank S. Thomas
la source
3
Il convient de noter que left ::: right.drop(1)c'est plus court que l'instruction if avec isEmpty.
Rex Kerr
2
Merci, y a-t-il des circonstances pour préférer .drop (1) à .tail, ou vice versa?
Gavilan Comun
8
Petry @ James - Si vous appelez tailsur 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.
Frank
3
taillève une exception si la liste est vide (c'est-à-dire qu'il n'y en a pas head). drop(1)sur une liste vide donne juste une autre liste vide.
Rex Kerr
8

Malheureusement, la hiérarchie des collections s'est un peu gâchée avec -on List. Car ArrayBuffercela fonctionne comme vous pourriez 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, Lists'est retrouvé avec une filterNotimplémentation -style et fait donc la "mauvaise chose" et vous lance un avertissement de dépréciation (assez sensible, car il s'agit en fait 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)

La chose la plus simple à faire est donc sans doute de la convertir Listen une collection qui fait cela correctement, puis de la reconvertir:

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

Alternativement, vous pouvez 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)
Rex Kerr
la source
removeInt(5,List(1,2,6,4,5,3,6,4,6,5,1))rendements List(4, 6, 2, 1, 3, 6, 4, 6, 5, 1). Je pense que ce n'est pas ce que tu voulais.
Ken Bloom le
@Ken Bloom - En effet. C'est une erreur dans l'algorithme d'origine, que j'ai copié sans trop réfléchir. Corrigé maintenant.
Rex Kerr
Plus une omission dans la spécification de la question, car l'ordre n'a pas d'importance dans mon cas spécifique. C'est bien de voir la version préservant la commande, merci.
Gavilan Comun
@Rex: qu'entendez-vous par «filtreNe fait pas la« mauvaise chose »»? Que cela supprime toutes les occurrences? Et pourquoi jette-t-il un avertissement d'obsolescence? Merci
teo
1
@teo - Il supprime toutes les occurrences (ce qui n'est pas ce qui est souhaité ici), et il est obsolète car il est sans doute cassé (ou peut-être que le comportement souhaité n'est pas clair - de toute façon, il est obsolète dans 2.9 et disparu dans 2.10).
Rex Kerr
5
 def removeAtIdx[T](idx: Int, listToRemoveFrom: List[T]): List[T] = {
    assert(listToRemoveFrom.length > idx && idx >= 0)
    val (left, _ :: right) = listToRemoveFrom.splitAt(idx)
    left ++ right
 }
Suat KARAKUSOGLU
la source
2

Que diriez-vous

def removeCard(c: Card, cards: List[Card]) = {
  val (head, tail) = cards span {c!=}   
  head ::: 
  (tail match {
    case x :: xs => xs
    case Nil => Nil
  })
}

Si vous voyez return, il y a quelque chose qui ne va pas.

Eugene Yokota
la source
1
Cela ne fait pas ce qu'il veut, c'est-à-dire supprimer uniquement la première instance dec
Ken Bloom
1
Cela supprimera toutes les cartes c, mais seulement la première devrait être supprimée.
tenshi
Je devrais lire les questions plus attentivement! corrigé ma réponse.
Eugene Yokota le
+1 pour "Si vous voyez retour, il y a un problème." C'est une leçon très importante de "Scala idiomatique" en soi.
Joe Carnahan
2
// throws a MatchError exception if i isn't found in li
def remove[A](i:A, li:List[A]) = {
   val (head,_::tail) = li.span(i != _)
   head ::: tail
}
Ken Bloom
la source
1

Comme solution possible, vous pouvez trouver l'index du premier élément approprié, puis supprimer l'élément à cet index:

def removeOne(l: List[Card], c: Card) = l indexOf c match {
    case -1 => l
    case n => (l take n) ++ (l drop (n + 1))
}
tenshi
la source
Voir ma réponse en utilisant spanpour faire la même chose.
Ken Bloom le
0

Juste une autre réflexion sur la façon de faire cela en utilisant un pli:

def remove[A](item : A, lst : List[A]) : List[A] = {
    lst.:\[List[A]](Nil)((lst, lstItem) => 
       if (lstItem == item) lst else lstItem::lst )
}
gdiz
la source
0

Solution générique de récursivité de queue:

def removeElement[T](list: List[T], ele: T): List[T] = {
    @tailrec
    def removeElementHelper(list: List[T],
                            accumList: List[T] = List[T]()): List[T] = {
      if (list.length == 1) {
        if (list.head == ele) accumList.reverse
        else accumList.reverse ::: list
      } else {
        list match {
          case head :: tail if (head != ele) =>
            removeElementHelper(tail, head :: accumList)
          case head :: tail if (head == ele) => (accumList.reverse ::: tail)
          case _                             => accumList
        }
      }
    }
    removeElementHelper(list)
  }
Shankar Shastri
la source
-3
val list : Array[Int] = Array(6, 5, 3, 1, 8, 7, 2)
val test2 = list.splitAt(list.length / 2)._2
val res = test2.patch(1, Nil, 1)
Huckleberry Finn
la source
-4
object HelloWorld {

    def main(args: Array[String]) {

        var months: List[String] = List("December","November","October","September","August", "July","June","May","April","March","February","January")

        println("Deleting the reverse list one by one")

        var i = 0

        while (i < (months.length)){

            println("Deleting "+months.apply(i))

            months = (months.drop(1))

        }

        println(months)

    }

}
Anbinson
la source
Pourriez-vous s'il vous plaît ajouter quelques explications (commentaires, description) sur la façon dont cela répond à la question?
rjp
4
1. Cette question a été posée et répondue il y a 5 ans. 2. Le PO a demandé une Scala «idiomatique». Utiliser 2 vars et une whileboucle n'est pas idiomatique Scala.
jwvh