¿Cuál es una forma idiomática de Scala para "eliminar" un elemento de una lista inmutable?

84

Tengo una lista, que puede contener elementos que se compararán como iguales. Me gustaría una Lista similar, pero con un elemento eliminado. Entonces, de (A, B, C, B, D) me gustaría poder "eliminar" solo una B para obtener, por ejemplo, (A, C, B, D). No importa el orden de los elementos en el resultado.

Tengo código de trabajo, escrito en una forma inspirada en Lisp en Scala. ¿Hay alguna forma más idiomática de hacer esto?

El contexto es un juego de cartas en el que están en juego dos mazos de cartas estándar, por lo que puede haber cartas duplicadas, pero aún así se juegan una a la vez.

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)
}
Gavilán Comun
fuente
Se agregó una nota de que el orden de la Lista de resultados no importa en este caso específico.
Gavilan Comun
¿Entonces List[Card]en esta pregunta es la mano de un jugador?
Ken Bloom
@Ken Bloom, sí, esa es la mano de un jugador.
Gavilan Comun
Sabes, busqué una pregunta como esta por un tiempo, luego publiqué la misma pregunta, luego encontré esta mientras buscaba y esperaba que la gente respondiera la mía. Supongo que debería votar para cerrar mi propia pregunta ahora como duplicado. ;-)
Joe Carnahan
Esta pregunta para Clojure: stackoverflow.com/questions/7662447/…
Gavilan Comun

Respuestas:

144

No he visto esta posibilidad en las respuestas anteriores, entonces:

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)

Editar:

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

Como un encanto :-).

Antonin Brettsnajdr
fuente
18
¡Agradable! Agregaría otros 2 a la lista para dejar en claro que solo se elimina un elemento.
Frank S. Thomas
39

Podrías usar el filterNotmétodo.

val data = "test"
list = List("this", "is", "a", "test")
list.filterNot(elm => elm == data)
Søren Mathiasen
fuente
21
Esto eliminará todos los elementos que sean iguales a "prueba", no lo que se solicita;)
yǝsʞǝla
1
De hecho, hará exactamente lo que necesita. Devolverá todos los elementos de la lista excepto aquellos que no sean iguales a "prueba". Preste atención a que utiliza filterNot
btbvoy
14
La pregunta original era cómo eliminar una instancia ÚNICA. No todas las instancias.
ty1824
@ Søren Mathiasen si quiero filtrar varios elementos como secuencia como val data = Seq ("prueba", "a"), ¿cómo hacerlo?
BdEngineer
18

Puedes probar esto:

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)

Y como método:

def removeInt(i: Int, li: List[Int]) = {
   val (left, right) = li.span(_ != i)
   left ::: right.drop(1)
}
Frank S. Thomas
fuente
3
Vale la pena señalar que left ::: right.drop(1)es más corto que la declaración if con isEmpty.
Rex Kerr
2
Gracias, ¿existe alguna circunstancia para preferir .drop (1) sobre .tail, o viceversa?
Gavilan Comun
8
@ James Petry - Si llama tailen una lista vacía se obtiene una excepción: scala> List().tail java.lang.UnsupportedOperationException: tail of empty list. drop(1)en una lista vacía, sin embargo, devuelve una lista vacía.
Frank S. Thomas
3
taillanza una excepción si la lista está vacía (es decir, no hay head). drop(1)en una lista vacía solo produce otra lista vacía.
Rex Kerr
8

Por desgracia, la jerarquía de colecciones tiene en sí en un poco de un desastre con -el List. Porque ArrayBufferfunciona tal como esperas:

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

pero, lamentablemente, Listterminó con una filterNotimplementación de estilo y, por lo tanto, hace "lo incorrecto" y le arroja una advertencia de desaprobación (lo suficientemente sensible, ya que en realidad está funcionando 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)

Entonces, posiblemente, lo más fácil de hacer es convertir Listen una colección que lo haga bien y luego volver a convertir:

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

Alternativamente, puede mantener la lógica del código que tiene pero hacer que el estilo sea más idiomático:

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
fuente
removeInt(5,List(1,2,6,4,5,3,6,4,6,5,1))rendimientos List(4, 6, 2, 1, 3, 6, 4, 6, 5, 1). Creo que esto no es lo que querías.
Ken Bloom
@Ken Bloom - De hecho. Es un error en el algoritmo original, que copié sin pensarlo lo suficiente. Arreglado ahora.
Rex Kerr
Más una omisión en la especificación de la pregunta, ya que el orden no importa en mi caso específico. Es bueno ver la versión que conserva el orden, gracias.
Gavilan Comun
@Rex: ¿a qué te refieres con 'filterNot hace lo "incorrecto"'? ¿Que está eliminando todas las ocurrencias? ¿Y por qué lanza una advertencia de desaprobación? Gracias
teo
1
@teo: elimina todas las apariciones (que no es lo que se desea aquí), y está en desuso porque podría decirse que está roto (o tal vez el comportamiento deseado no está claro; de cualquier manera, está en desuso en 2.9 y pasó en 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
fuente
2

Qué tal si

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

Si ve return, algo anda mal.

Eugene Yokota
fuente
1
Esto no hace lo que él quiere, que es eliminar solo la primera instancia dec
Ken Bloom
1
Esto eliminará todas las tarjetas c, pero solo se debe eliminar la primera.
tenshi
¡Debería leer las preguntas con más atención! corrigió mi respuesta.
Eugene Yokota
+1 para "Si ves regresar, hay algo mal". Esa es una lección muy importante de "Scala idiomática" en sí misma.
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
fuente
1

Como una posible solución, puede encontrar el índice del primer elemento adecuado y luego eliminar el elemento en este índice:

def removeOne(l: List[Card], c: Card) = l indexOf c match {
    case -1 => l
    case n => (l take n) ++ (l drop (n + 1))
}
tenshi
fuente
Vea mi respuesta usando spanpara hacer lo mismo.
Ken Bloom
0

Solo otro pensamiento sobre cómo hacer esto usando un pliegue:

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

Solución genérica de recursividad de cola:

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
fuente
-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)
Finn arándano
fuente
-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
fuente
¿Podría agregar alguna explicación (comentarios, descripción) sobre cómo esto responde a la pregunta?
rjp
4
1. Esta pregunta fue formulada y respondida hace 5 años. 2. El OP pidió Scala "idiomática". El uso de 2 varsy un whilebucle no es un lenguaje idiomático de Scala.
jwvh