Що таке ідіоматичний спосіб Scala "вилучити" один елемент із незмінного списку?


84

У мене є Список, який може містити елементи, які можна порівняти як рівні. Я хотів би отримати подібний список, але з одним елементом, вилученим. Тож із (A, B, C, B, D) я хотів би мати можливість "видалити" лише одну B, щоб отримати, наприклад, (A, C, B, D). Порядок елементів у результаті не має значення.

У мене є робочий код, написаний у Scala натхненним Ліспем. Чи є більш ідіоматичний спосіб зробити це?

Контекст - це карткова гра, де грають дві колоди стандартних карт, тому можуть бути дублікати карт, але все одно грати по одній.

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

Додано зауваження, що порядок списку результатів у цьому конкретному випадку значення не має.
Гавілан Комун,

отже, List[Card]у цьому питанні рука гравця?
Ken Bloom

@Ken Bloom, так, це правильна рука гравця.
Гавілан Комун,

Знаєте, я деякий час шукав подібне запитання, потім розмістив те саме запитання, а потім знайшов це, поки переглядав і чекав, поки люди відповідуть на моє. Думаю, я мав би проголосувати, щоб закрити власне питання зараз як дублікат. ;-)
Джо Карнахан

Це питання для Clojure: stackoverflow.com/questions/7662447 / ...
Gavilan Comun

Відповіді:


144

Я не бачив цієї можливості у відповідях вище, тому:

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 до списку, щоб було зрозуміло, що видаляється лише один елемент.
Frank S. Thomas

39

Ви можете використати filterNotметод.

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

21
Це видалить всі елементи, які дорівнюють "тест" - не те, про що просять;)
yǝsʞǝla

1
Насправді він буде робити саме те, що вам потрібно. Він поверне всі елементи зі списку, за винятком тих, які не рівні "тесту". Зверніть увагу, що в ньому використовується filterNot
btbvoy

14
Початкове питання полягало в тому, як видалити ОДИНИЙ екземпляр. Не у всіх випадках.
ty1824

@ Søren Mathiasen, якщо я хочу відфільтрувати кілька елементів, таких як послідовність, як val data = Seq ("тест", "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 або навпаки?
Гавілан Комун,

8
@James Petry - Якщо ви телефонуєте tailна порожній список ви отримаєте виняток: scala> List().tail java.lang.UnsupportedOperationException: tail of empty list. drop(1)у порожньому списку, однак повертає порожній список.
Frank S. Thomas

3
tailвидає виняток, якщо список порожній (тобто його немає head). drop(1)у порожньому списку просто дає ще один порожній список.
Рекс Керр,

8

На жаль, ієрархія колекцій отримала себе в трохи безладу з -на 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). Я думаю, це не те, що ти хотів.
Кен Блум,

@Ken Bloom - Справді. Це помилка в оригінальному алгоритмі, який я скопіював, недостатньо продумуючи. Виправлено зараз.
Рекс Керр,

Більше пропуску в специфікації запитання, оскільки порядок не має значення в моєму конкретному випадку. Приємно бачити версію, яка зберігає замовлення, хоча, дякую.
Гавілан Комун,

@Rex: що ви маєте на увазі під словом "фільтр" Чи не "неправильна річ"? Що це видалення всіх випадків? І чому воно видає попередження про зневажання? Дякую
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, але лише першу слід видалити.
тенші

Мені слід уважніше прочитати питання! виправив мою відповідь.
Євген Йокота

+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щоб зробити те саме.
Ken Bloom

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. ОП попросила "ідіоматичну" шкалу. Використання 2 varс та whileциклу не є ідіоматичною шкалою.
jwvh
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.