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

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

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 :-).


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)

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

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)

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
 }

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.


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
}

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

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

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

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

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

    }

}

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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.