Cách Scala thành ngữ để “loại bỏ” một phần tử khỏi Danh sách bất biến là gì?


84

Tôi có một Danh sách, có thể chứa các phần tử sẽ được so sánh như nhau. Tôi muốn có một Danh sách tương tự, nhưng đã loại bỏ một phần tử. Vì vậy, từ (A, B, C, B, D) tôi muốn có thể "loại bỏ" chỉ một B để có được ví dụ: (A, C, B, D). Thứ tự của các phần tử trong kết quả không quan trọng.

Tôi có mã làm việc, được viết theo cách lấy cảm hứng từ Lisp trong Scala. Có cách nào dễ hiểu hơn để làm điều này không?

Bối cảnh là một trò chơi đánh bài mà hai bộ bài tiêu chuẩn đang diễn ra nên có thể có những quân bài trùng nhau nhưng vẫn được chơi một lượt.

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

Đã thêm một lưu ý rằng thứ tự của Danh sách kết quả không quan trọng trong trường hợp cụ thể này.
Gavilan Comun

vì vậy List[Card]trong câu hỏi này là một tay của người chơi?
Ken Bloom

@Ken Bloom, vâng, đó là bàn tay của người chơi.
Gavilan Comun

Bạn biết đấy, tôi đã tìm kiếm một câu hỏi như thế này trong một thời gian, sau đó đăng cùng một câu hỏi, sau đó tìm thấy câu hỏi này trong khi tôi đang duyệt và chờ mọi người trả lời. Đoán rằng tôi nên bỏ phiếu để đóng câu hỏi của riêng tôi bây giờ như một bản sao. ;-)
Joe Carnahan

Câu hỏi này dành cho Clojure: stackoverflow.com/questions/7662447/…
Gavilan Comun

Câu trả lời:


144

Tôi chưa thấy khả năng này trong các câu trả lời ở trên, vì vậy:

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)

Biên tập:

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

Như một cái duyên :-).


18
Đẹp! Tôi sẽ thêm 2 phần tử khác vào danh sách để làm rõ rằng chỉ một phần tử bị xóa.
Frank S. Thomas

39

Bạn có thể sử dụng filterNotphương pháp này.

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

21
Điều này sẽ loại bỏ tất cả các yếu tố đó đều bình đẳng để "test" - không phải những gì được yêu cầu;)
yǝsʞǝla

1
Trên thực tế nó sẽ làm chính xác những gì bạn cần. Nó sẽ trả về tất cả các phần tử từ danh sách ngoại trừ những phần tử không bằng "test". Hãy chú ý rằng nó sử dụng filterNot
btbvoy

14
Câu hỏi ban đầu là làm thế nào để xóa một phiên bản SINGLE. Không phải tất cả các trường hợp.
ty1824

@ Søren Mathiasen nếu tôi muốn lọc ra nhiều phần tử như chuỗi như val data = Seq ("test", "a"), làm thế nào để làm điều đó?
BdEngineer

18

Bạn có thể thử điều này:

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)

Và như phương pháp:

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

3
Cần lưu ý rằng left ::: right.drop(1)nó ngắn hơn câu lệnh if với isEmpty.
Rex Kerr

2
Cảm ơn, có trường hợp nào thích .drop (1) hơn .tail hay ngược lại không?
Gavilan Comun

8
@ James Petry - Nếu bạn gọi tailvào một danh sách trống bạn sẽ có được một ngoại lệ: scala> List().tail java.lang.UnsupportedOperationException: tail of empty list. drop(1)trên một danh sách trống tuy nhiên trả về một danh sách trống.
Frank S. Thomas

3
tailném một ngoại lệ nếu danh sách trống (tức là không có head). drop(1)trên một danh sách trống chỉ mang lại một danh sách trống khác.
Rex Kerr

8

Thật không may, hệ thống phân cấp các bộ sưu tập có mình thành một chút của một mớ hỗn độn với -trên List. Vì ArrayBuffernó hoạt động giống như bạn có thể hy vọng:

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

nhưng, thật đáng buồn, Listkết thúc với filterNotviệc triển khai một kiểu và do đó thực hiện "điều sai" đưa ra cảnh báo không dùng nữa cho bạn (đủ hợp lý, vì nó thực sự được nhập vào 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)

Vì vậy, điều dễ dàng nhất để làm là chuyển đổi Listthành một bộ sưu tập thực hiện đúng điều này, và sau đó chuyển đổi trở lại:

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

Ngoài ra, bạn có thể giữ nguyên logic của mã mà bạn có nhưng làm cho văn phong trở nên thành ngữ hơn:

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))lợi tức List(4, 6, 2, 1, 3, 6, 4, 6, 5, 1). Tôi nghĩ rằng đây không phải là những gì bạn muốn.
Ken Bloom

@Ken Bloom - Thật vậy. Đó là một lỗi trong thuật toán ban đầu, tôi đã sao chép mà không suy nghĩ đủ. Đã sửa ngay.
Rex Kerr

Thêm một thiếu sót trong thông số câu hỏi, vì thứ tự không quan trọng trong trường hợp cụ thể của tôi. Tốt để xem phiên bản duy trì đơn đặt hàng mặc dù, cảm ơn.
Gavilan Comun

@Rex: ý của bạn là 'filterNo' 'điều sai trái "' là gì? Đó là nó loại bỏ tất cả các lần xuất hiện? Và tại sao nó lại đưa ra cảnh báo không dùng nữa? Cảm ơn
teo

1
@teo - Nó loại bỏ tất cả các lần xuất hiện (không phải là điều mong muốn ở đây) và nó không được chấp nhận vì nó được cho là bị hỏng (hoặc có lẽ hành vi mong muốn không rõ ràng - theo cách nào đó, nó không được chấp nhận trong 2.9 và biến mất trong 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

Làm thế nào về

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

Nếu bạn thấy return, có điều gì đó không ổn.


1
Điều này không làm những gì ông muốn, mà là để loại bỏ chỉ dụ đầu tiên củac
Ken Bloom

1
Thao tác này sẽ xóa tất cả các thẻ c, nhưng chỉ nên xóa đầu tiên.
tenshi

Tôi nên đọc các câu hỏi cẩn thận hơn! đã sửa lại câu trả lời của tôi.
Eugene Yokota

+1 cho "Nếu bạn thấy trả lại, có điều gì đó không ổn". Đó là một bài học "thành ngữ Scala" rất quan trọng.
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

Là một trong những giải pháp khả thi, bạn có thể tìm chỉ mục của phần tử phù hợp đầu tiên và sau đó xóa phần tử tại chỉ mục này:

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

Xem câu trả lời của tôi bằng cách sử dụng spanđể làm điều tương tự.
Ken Bloom

0

Chỉ là một suy nghĩ khác về cách thực hiện việc này bằng cách sử dụng nếp gấp:

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

0

Giải pháp đệ quy đuôi chung:

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)

    }

}

Bạn có thể vui lòng thêm một số giải thích (nhận xét, mô tả) về cách điều này trả lời câu hỏi?
rjp

4
1. Câu hỏi này đã được hỏi, và đã trả lời, cách đây 5 năm. 2. OP đã yêu cầu Scala "thành ngữ". Sử dụng 2 vars và một whilevòng lặp không phải là Scala thành ngữ.
jwvh
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.