Что такое идиоматический способ Scala «удалить» один элемент из неизменяемого списка?


86

У меня есть список, который может содержать элементы, которые будут сравниваться как равные. Мне нужен аналогичный список, но с удалением одного элемента. Итак, из (A, B, C, B, D) я хотел бы иметь возможность «удалить» только один B, чтобы получить, например, (A, C, B, D). Порядок элементов в результате не имеет значения.

У меня есть рабочий код, написанный на Scala в стиле Lisp. Есть ли более идиоматический способ сделать это?

Контекст - это карточная игра, в которой участвуют две колоды стандартных карт, поэтому могут быть дубликаты карт, но все равно играть по одной.

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

Итак, List[Card]в этом вопросе рука игрока?
— Кен Блум

@ Кен Блум, да, это рука игрока.
— Gavilan Comun

Вы знаете, я какое-то время искал подобный вопрос, затем опубликовал тот же вопрос, а затем нашел этот, пока просматривал и ждал, пока люди ответят на мой. Думаю, я должен проголосовать, чтобы закрыть свой вопрос как дубликат. ;-)
— Джо Карнахан

Этот вопрос для Clojure: stackoverflow.com/questions/7662447/…
— Gavilan Comun

Ответы:


147

Я не видел такой возможности в ответах выше, поэтому:

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)

Редактировать:

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

Как колдовство :-).


18
Ницца! Я бы добавил в список еще 2, чтобы было ясно, что удаляется только один элемент.
— Фрэнк С. Томас

40

Вы можете использовать этот filterNotметод.

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

22
Это удалит все элементы, которые равны "test" - а не то, о чем просят;)
— yǝsʞǝla

1
На самом деле он сделает именно то, что вам нужно. Он вернет все элементы из списка, кроме тех, которые не равны "test". Обратите внимание, что он использует filterNot
— btbvoy

14
Первоначальный вопрос заключался в том, как удалить ОДИН экземпляр. Не все экземпляры.
— ty1824 02

@ Søren Mathiasen, если я хочу отфильтровать несколько элементов, например последовательность, например val data = Seq ("test", "a"), как это сделать?
— BdEngineer

18

Вы можете попробовать это:

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)

И как метод:

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

3
Стоит отметить, что left ::: right.drop(1)он короче, чем оператор if с isEmpty.
— Рекс Керр,

2
Спасибо, есть ли какие-либо обстоятельства, по которым можно предпочесть .drop (1) над .tail или наоборот?
— Gavilan Comun

8
@James Petry - Если вы звоните tailна пустой список вы получите исключение: scala> List().tail java.lang.UnsupportedOperationException: tail of empty list. drop(1)однако в пустом списке возвращает пустой список.
— Фрэнк С. Томас,

3
tailвыдает исключение, если список пуст (т.е. его нет head). drop(1)в пустом списке просто дает еще один пустой список.
— Рекс Керр

8

К сожалению, иерархия коллекций немного запуталась из-за -on List. Потому что ArrayBufferэто работает так, как вы могли бы надеяться:

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

но, к сожалению, Listзакончился filterNotреализацией -стилем и, таким образом, делает "неправильную вещь" и выдает вам предупреждение об устаревании (достаточно разумно, поскольку это действительно так 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)

Так что, возможно, проще всего преобразовать Listв коллекцию, которая делает это правильно, а затем снова преобразовать:

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

Как вариант, вы можете сохранить логику кода, который у вас есть, но сделать стиль более идиоматическим:

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))урожайность List(4, 6, 2, 1, 3, 6, 4, 6, 5, 1). Я думаю, это не то, что вы хотели.
— Кен Блум,

@ Кен Блум - Верно. Это ошибка исходного алгоритма, который я скопировал, не задумываясь. Исправлено сейчас.
— Рекс Керр,

Скорее упущение в спецификации вопроса, так как порядок не имеет значения в моем конкретном случае. Приятно видеть версию с сохранением порядка, спасибо.
— Gavilan Comun

@Rex: что ты имеешь в виду под "filterNot"? Что удаляет все вхождения? И почему он выдает предупреждение об устаревании? Спасибо
— teo

1
@teo - удаляет все вхождения (что здесь нежелательно), и он устарел, потому что он, возможно, не работает (или, возможно, желаемое поведение неясно - в любом случае, оно устарело в 2.9 и исчезло в 2.10).
— Рекс Керр

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

Как насчет

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

Если видите return, что-то не так.


1
Это не делает то , что он хочет, что удалить только первый экземплярc
— Кен Блум

1
Это удалит все карты c, но только сначала следует удалить.
— tenshi

Я должен прочитать вопросы внимательнее! поправил свой ответ.
— Юджин Йокота,

+1 за «Если вы видите возврат, значит что-то не так». Это сам по себе очень важный урок "идиоматической Scala".
— Джо Карнахан

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

В качестве одного из возможных решений вы можете найти индекс первого подходящего элемента, а затем удалить элемент по этому индексу:

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

См. Мой ответ, использующий spanто же самое.
— Кен Блум,

0

Еще одна мысль о том, как это сделать с помощью складки:

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

0

Общее решение для рекурсии хвоста:

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


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

    }

}

Не могли бы вы добавить некоторые пояснения (комментарии, описание), как это отвечает на вопрос?
— rjp

4
1. На этот вопрос был задан и дан ответ 5 лет назад. 2. OP запросил "идиоматическую" Scala. Использование 2 varс и whileцикла - это не идиоматика Scala.
— jwvh
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.