Was ist eine idiomatische Scala-Methode, um ein Element aus einer unveränderlichen Liste zu entfernen?

84

Ich habe eine Liste, die Elemente enthalten kann, die als gleich verglichen werden. Ich hätte gerne eine ähnliche Liste, aber mit einem entfernten Element. Also möchte ich aus (A, B, C, B, D) nur ein B "entfernen" können, um zB (A, C, B, D) zu erhalten. Die Reihenfolge der Elemente im Ergebnis spielt keine Rolle.

Ich habe Arbeitscode, der in Scala von Lisp inspiriert ist. Gibt es einen idiomatischeren Weg, dies zu tun?

Der Kontext ist ein Kartenspiel, bei dem zwei Kartenspiele mit Standardkarten im Spiel sind. Es kann also doppelte Karten geben, die aber immer noch einzeln gespielt werden.

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
quelle
Es wurde ein Hinweis hinzugefügt, dass die Reihenfolge der Ergebnisliste in diesem speziellen Fall keine Rolle spielt.
Gavilan Comun
Also ist das List[Card]in dieser Frage die Hand eines Spielers?
Ken Bloom
@ Ken Bloom, ja, das ist die richtige Hand eines Spielers.
Gavilan Comun
Weißt du, ich habe eine Weile nach einer solchen Frage gesucht, dann dieselbe Frage gestellt und diese gefunden, während ich mich umgesehen und darauf gewartet habe, dass die Leute meine beantworten. Ich denke, ich sollte abstimmen, um meine eigene Frage jetzt als Duplikat zu schließen. ;-)
Joe Carnahan
Diese Frage an Clojure: stackoverflow.com/questions/7662447/…
Gavilan Comun

Antworten:

143

Ich habe diese Möglichkeit in den obigen Antworten nicht gesehen, also:

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)

Bearbeiten:

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

Wie ein Zauber :-).

Antonin Brettsnajdr
quelle
17
Nett! Ich würde der Liste weitere 2 hinzufügen, um zu verdeutlichen, dass nur ein Element entfernt wird.
Frank S. Thomas
37

Sie könnten die filterNotMethode verwenden.

val data = "test"
list = List("this", "is", "a", "test")
list.filterNot(elm => elm == data)
Søren Mathiasen
quelle
20
Dadurch werden alle Elemente entfernt, die gleich "test" sind - nicht das, wonach gefragt wird;)
yǝsʞǝla
1
Eigentlich wird es genau das tun, was Sie brauchen. Es werden alle Elemente aus der Liste zurückgegeben, mit Ausnahme derjenigen, die nicht gleich "test" sind. Achten
btbvoy
14
Die ursprüngliche Frage war, wie eine SINGLE-Instanz entfernt werden kann. Nicht alle Instanzen.
ty1824
17

Sie könnten dies versuchen:

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)

Und als Methode:

def removeInt(i: Int, li: List[Int]) = {
   val (left, right) = li.span(_ != i)
   left ::: right.drop(1)
}
Frank S. Thomas
quelle
3
Es ist erwähnenswert, dass dies left ::: right.drop(1)kürzer ist als die if-Anweisung mit isEmpty.
Rex Kerr
2
Danke, gibt es einen Umstand, der es vorzieht, .drop (1) gegenüber .tail zu bevorzugen oder umgekehrt?
Gavilan Comun
8
@ James Petry - Wenn Sie taileine leere Liste aufrufen, erhalten Sie eine Ausnahme : scala> List().tail java.lang.UnsupportedOperationException: tail of empty list. drop(1)Auf einer leeren Liste wird jedoch eine leere Liste zurückgegeben.
Frank S. Thomas
3
taillöst eine Ausnahme aus, wenn die Liste leer ist (dh es gibt keine head). drop(1)auf einer leeren Liste ergibt nur eine andere leere Liste.
Rex Kerr
8

Leider hat die Sammlungen Hierarchie selbst in ein bisschen ein Durcheinander mit -auf List. Denn ArrayBufferes funktioniert so, wie Sie vielleicht hoffen:

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

aber leider Listendete mit einer filterNotImplementierung im Stil und macht somit das "Falsche" und wirft eine Abwertungswarnung auf Sie (vernünftig genug, da es tatsächlich ist 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)

Am einfachsten ist es also, Listin eine Sammlung zu konvertieren , die dies richtig macht, und dann wieder zurück zu konvertieren:

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

Alternativ können Sie die Logik des Codes beibehalten, den Sie haben, aber den Stil idiomatischer gestalten:

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
quelle
removeInt(5,List(1,2,6,4,5,3,6,4,6,5,1))ergibt List(4, 6, 2, 1, 3, 6, 4, 6, 5, 1). Ich denke, das ist nicht das, was du wolltest.
Ken Bloom
@ Ken Bloom - In der Tat. Es ist ein Fehler im ursprünglichen Algorithmus, den ich kopiert habe, ohne genug darüber nachzudenken. Jetzt behoben.
Rex Kerr
Eher eine Auslassung in der Fragenspezifikation, da die Reihenfolge in meinem speziellen Fall keine Rolle spielt. Gut zu sehen, die auftragserhaltende Version, danke.
Gavilan Comun
@Rex: Was meinst du mit 'filterNot macht das "Falsche"'? Dass es alle Vorkommen entfernt? Und warum wird eine Abwertungswarnung ausgegeben? Danke
teo
1
@teo - Es entfernt alle Vorkommen (was hier nicht erwünscht ist) und ist veraltet, weil es möglicherweise defekt ist (oder vielleicht ist das gewünschte Verhalten unklar - so oder so ist es in 2.9 veraltet und in 2.10 verschwunden).
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
quelle
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
quelle
1

Als eine mögliche Lösung können Sie den Index des ersten geeigneten Elements finden und dann das Element an diesem Index entfernen:

def removeOne(l: List[Card], c: Card) = l indexOf c match {
    case -1 => l
    case n => (l take n) ++ (l drop (n + 1))
}
Tenshi
quelle
Siehe meine Antwort span, um dasselbe zu tun.
Ken Bloom
1

Wie wäre es mit

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

Wenn Sie sehen return, stimmt etwas nicht.

Eugene Yokota
quelle
1
Dies tut nicht, was er will, nämlich nur die erste Instanz vonc
Ken Bloom
1
Dadurch werden alle Karten entfernt c, aber nur die ersten sollten entfernt werden.
Tenshi
Ich sollte die Fragen genauer lesen! korrigierte meine Antwort.
Eugene Yokota
+1 für "Wenn Sie die Rückkehr sehen, stimmt etwas nicht." Das ist eine sehr wichtige "idiomatische Scala" -Lektion für sich.
Joe Carnahan
0

Nur ein weiterer Gedanke, wie man das mit einer Falte macht:

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

Generische Schwanzrekursionslösung:

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
quelle
-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
quelle
-5
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
quelle
Könnten Sie bitte eine Erklärung (Kommentare, Beschreibung) hinzufügen, wie dies die Frage beantwortet?
rjp
4
1. Diese Frage wurde vor 5 Jahren gestellt und beantwortet. 2. Das OP bat um eine "idiomatische" Scala. Die Verwendung von 2 vars und einer whileSchleife ist keine idiomatische Scala.
JWVH