Qual è un modo idiomatico di Scala per "rimuovere" un elemento da una lista immutabile?


86

Ho un elenco, che può contenere elementi che verranno confrontati come uguali. Vorrei un elenco simile, ma con un elemento rimosso. Quindi da (A, B, C, B, D) vorrei essere in grado di "rimuovere" solo un B per ottenere ad esempio (A, C, B, D). L'ordine degli elementi nel risultato non ha importanza.

Ho del codice funzionante, scritto in modo ispirato al Lisp in Scala. C'è un modo più idiomatico per farlo?

Il contesto è un gioco di carte in cui sono in gioco due mazzi di carte standard, quindi potrebbero esserci carte duplicate ma ancora giocate una alla volta.

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

Aggiunta una nota che l'ordine della lista dei risultati non ha importanza in questo caso specifico.
Gavilan Comun

quindi List[Card]in questa domanda è la mano di un giocatore?
Ken Bloom

@Ken Bloom, sì, è proprio la mano di un giocatore.
Gavilan Comun

Sai, ho cercato una domanda come questa per un po ', poi ho pubblicato la stessa domanda, poi ho trovato questa mentre stavo navigando e aspettando che le persone rispondessero alla mia. Immagino che dovrei votare per chiudere la mia domanda ora come duplicato. ;-)
Joe Carnahan

Risposte:


147

Non ho visto questa possibilità nelle risposte sopra, quindi:

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)

Modificare:

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

A meraviglia :-).


18
Bello! Aggiungerei un altro 2 all'elenco per chiarire che viene rimosso solo un elemento.
Frank S. Thomas

40

Potresti usare il filterNotmetodo.

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

22
Questo rimuoverà tutti gli elementi che sono uguali a "test" - non ciò che è richiesto;)
yǝsʞǝla

1
In realtà farà esattamente ciò di cui hai bisogno. Restituirà tutti gli elementi dalla lista tranne quelli che non sono uguali a "test". Fai attenzione che utilizza filterNot
btbvoy

14
La domanda originale era come rimuovere un'istanza SINGOLA. Non tutti i casi.
ty1824

@ Søren Mathiasen se voglio filtrare più elementi come sequenza come val data = Seq ("test", "a"), come si fa?
BdEngineer

18

Potresti provare questo:

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)

E come metodo:

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

3
Vale la pena notare che left ::: right.drop(1)è più breve dell'istruzione if con isEmpty.
Rex Kerr

2
Grazie, c'è qualche circostanza in cui preferire .drop (1) su .tail o viceversa?
Gavilan Comun

8
@James Petry - Se si chiama tailsu un elenco vuoto si ottiene un'eccezione: scala> List().tail java.lang.UnsupportedOperationException: tail of empty list. drop(1)su un elenco vuoto tuttavia restituisce un elenco vuoto.
Frank S. Thomas

3
tailgenera un'eccezione se l'elenco è vuoto (cioè non c'è head). drop(1)su un elenco vuoto produce solo un altro elenco vuoto.
Rex Kerr

8

Purtroppo, la gerarchia collezioni sé entrato in un po 'di confusione con -il List. Perché ArrayBufferfunziona proprio come potresti sperare:

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

ma, purtroppo, si è Listconclusa con filterNotun'implementazione in stile e quindi fa la "cosa sbagliata" e ti lancia un avvertimento di deprecazione (abbastanza sensato, dal momento che in realtà è filterNoting):

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)

Quindi, probabilmente, la cosa più semplice da fare è convertire Listin una raccolta che funzioni correttamente e quindi riconvertire di nuovo:

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

In alternativa, potresti mantenere la logica del codice che hai ma rendere lo stile più idiomatico:

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))rendimenti List(4, 6, 2, 1, 3, 6, 4, 6, 5, 1). Penso che questo non sia quello che volevi.
Ken Bloom

@Ken Bloom - Infatti. È un errore nell'algoritmo originale, che ho copiato senza pensarci abbastanza. Risolto ora.
Rex Kerr

Più un'omissione nella specifica della domanda, poiché l'ordine non ha importanza nel mio caso specifico. È bello vedere la versione con conservazione dell'ordine, grazie.
Gavilan Comun

@ Rex: cosa intendi con 'filtroNon fa la "cosa sbagliata"'? Che sta rimuovendo tutte le occorrenze? E perché genera un avviso di deprecazione? Grazie
teo

1
@teo - Rimuove tutte le occorrenze (che non è ciò che si desidera qui) ed è deprecato perché è discutibilmente rotto (o forse il comportamento desiderato non è chiaro - in entrambi i casi, è deprecato in 2.9 e andato in 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

Che ne dite di

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

Se vedi return, c'è qualcosa che non va.


1
Questo non fa quello che vuole, ovvero rimuovere solo la prima istanza dic
Ken Bloom

1
Questo rimuoverà tutte le carte c, ma solo le prime dovrebbero essere rimosse.
tenshi

Dovrei leggere le domande con più attenzione! ha corretto la mia risposta.
Eugene Yokota

+1 per "Se vedi il ritorno, c'è qualcosa che non va". Questa è di per sé una lezione "idiomatica alla Scala" molto importante.
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

Come una possibile soluzione puoi trovare l'indice del primo elemento adatto e quindi rimuovere l'elemento in questo indice:

def removeOne(l: List[Card], c: Card) = l indexOf c match {
    case -1 => l
    case n => (l take n) ++ (l drop (n + 1))
}

Vedi la mia risposta usando spanper fare la stessa cosa.
Ken Bloom

0

Solo un altro pensiero su come farlo usando una piega:

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

0

Soluzione generica per la ricorsione della coda:

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)

    }

}

Potresti aggiungere qualche spiegazione (commenti, descrizione) su come questo risponde alla domanda?
rjp

4
1. Questa domanda è stata posta e ha risposto 5 anni fa. 2. L'OP ha chiesto Scala "idiomatica". Usare 2 se varun whileciclo non è idiomatico Scala.
jwvh
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.