如何从 Scala 的列表中惯用地“删除”单个元素并缩小差距?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12864505/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 04:37:12  来源:igfitidea点击:

How can I idiomatically "remove" a single element from a list in Scala and close the gap?

scalalistfilterfunctional-programming

提问by jkeys

Lists are immutable in Scala, so I'm trying to figure out how I can "remove" - really, create a new collection - that element and then close the gap created in the list. This sounds to me like it would be a great place to use map, but I don't know how to get started in this instance.

列表在 Scala 中是不可变的,所以我想弄清楚如何“删除” - 实际上,创建一个新集合 - 该元素,然后关闭列表中创建的间隙。这听起来像是使用地图的好地方,但我不知道在这种情况下如何开始。

Courses is a list of strings. I need this loop because I actually have several lists that I will need to remove the element at that index from (I'm using multiple lists to store data associated across lists, and I'm doing this by simply ensuring that the indices will always correspond across lists).

Courses 是一个字符串列表。我需要这个循环,因为我实际上有几个列表,我需要从中删除该索引处的元素(我使用多个列表来存储跨列表关联的数据,我这样做是通过简单地确保索引始终对应列表)。

  for (i <- 0 until courses.length){
    if (input == courses(i) {
    //I need a map call on each list here to remove that element
    //this element is not guaranteed to be at the front or the end of the list
    }
  }
}

Let me add some detail to the problem. I have four lists that are associated with each other by index; one list stores the course names, one stores the time the class begins in a simple int format (ie 130), one stores either "am" or "pm", and one stores the days of the classes by int (so "MWF" evals to 1, "TR" evals to 2, etc). I don't know if having multiple this is the best or the "right" way to solve this problem, but these are all the tools I have (first-year comp sci student that hasn't programmed seriously since I was 16). I'm writing a function to remove the corresponding element from each lists, and all I know is that 1) the indices correspond and 2) the user inputs the course name. How can I remove the corresponding element from each list using filterNot? I don't think I know enough about each list to use higher order functions on them.

让我为问题添加一些细节。我有四个通过索引相互关联的列表;一个列表存储课程名称,一个以简单的 int 格式(即 130)存储课程开始的时间,一个存储“am”或“pm”,一个用 int 存储课程的天数(所以“MWF” evals 为 1,“TR” evals 为 2,等等)。我不知道有多个 this 是解决这个问题的最佳或“正确”方法,但这些是我拥有的所有工具(自 16 岁以来就没有认真编程的一年级计算机科学学生)。我正在编写一个函数来从每个列表中删除相应的元素,我所知道的是 1) 索引对应和 2) 用户输入课程名称。如何使用 filterNot 从每个列表中删除相应的元素?我不

回答by pedrofurla

This is the use case of filter:

这是以下用例filter

scala> List(1,2,3,4,5)
res0: List[Int] = List(1, 2, 3, 4, 5)

scala> res0.filter(_ != 2)
res1: List[Int] = List(1, 3, 4, 5)

You want to use map when you are transforming all the elements of a list.

当您转换列表的所有元素时,您希望使用 map。

回答by Luigi Plinge

To answer your question directly, I think you're looking for patch, for instance to remove element with index 2 ("c"):

要直接回答您的问题,我认为您正在寻找patch,例如删除索引为 2(“c”)的元素:

List("a","b","c","d").patch(2, Nil, 1)      // List(a, b, d)

where Nilis what we're replacing it with, and 1is the number of characters to replace.

这里Nil就是我们正在与替换它,1是字符数来代替。

But, if you do this:

但是,如果你这样做:

I have four lists that are associated with each other by index; one list stores the course names, one stores the time the class begins in a simple int format (ie 130), one stores either "am" or "pm", and one stores the days of the classes by int

我有四个通过索引相互关联的列表;一个列表存储课程名称,一个以简单的 int 格式(即 130)存储课程开始的时间,一个存储“am”或“pm”,一个用 int 存储课程的天数

you're going to have a bad time. I suggest you use a case class:

你会过得很糟糕。我建议你使用case class

case class Course(name: String, time: Int, ampm: String, day: Int)

and then store them in a Set[Course]. (Storing time and days as Ints isn't a great idea either - have a look at java.util.Calendarinstead.)

然后将它们存储在Set[Course]. (将时间和天数存储为Ints 也不是一个好主意 - 请看一下java.util.Calendar。)

回答by Nikita Volkov

First a few sidenotes:

先说几个旁注:

  1. Listis not an index-based structure. All index-oriented operations on it take linear time. For index-oriented algorithms Vectoris a much better candidate. In fact if your algorithm requires indexes it's a sure sign that you're really not exposing Scala's functional capabilities.

  2. mapserves for transforming a collection of items "A" to the same collection of items "B" using a passed in transformer function from a single "A" to single "B". It cannot change the number of resulting elements. Probably you've confused mapwith foldor reduce.

  1. List不是基于索引的结构。对它的所有面向索引的操作都需要线性时间。对于面向索引的算法Vector是一个更好的候选者。事实上,如果你的算法需要索引,这肯定表明你并没有暴露 Scala 的功能。

  2. map用于使用从单个“A”到单个“B”的传入转换器函数将项目“A”的集合转换为项目“B”的相同集合。它不能改变结果元素的数量。可能你混淆mapfoldreduce

To answer on your updated question

回答您更新的问题

Okay, here's a functional solution, which works effectively on lists:

好的,这是一个功能解决方案,它在列表上有效:

val (resultCourses, resultTimeList, resultAmOrPmList, resultDateList)
  = (courses, timeList, amOrPmList, dateList)
      .zipped
      .filterNot(_._1 == input)
      .unzip4

But there's a catch. I actually came to be quite astonished to find out that functions used in this solution, which are so basic for functional languages, were not present in the standard Scala library. Scala has them for 2 and 3-ary tuples, but not the others.

但有一个问题。我实际上非常惊讶地发现在这个解决方案中使用的函数对于函数式语言来说是非常基础的,而在标准 Scala 库中却没有。Scala 有它们用于 2 元和 3 元元组,但没有其他元组。

To solve that you'll need to have the following implicit extensions imported.

要解决这个问题,您需要导入以下隐式扩展。

implicit class Tuple4Zipped 
  [ A, B, C, D ] 
  ( val t : (Iterable[A], Iterable[B], Iterable[C], Iterable[D]) ) 
  extends AnyVal 
  {
    def zipped 
      = t._1.toStream
          .zip(t._2).zip(t._3).zip(t._4)
          .map{ case (((a, b), c), d) => (a, b, c, d) }
  }

implicit class IterableUnzip4
  [ A, B, C, D ]
  ( val ts : Iterable[(A, B, C, D)] )
  extends AnyVal
  {
    def unzip4
      = ts.foldRight((List[A](), List[B](), List[C](), List[D]()))(
          (a, z) => (a._1 +: z._1, a._2 +: z._2, a._3 +: z._3, a._4 +: z._4)
        )
  }

This implementation requires Scala 2.10 as it utilizes the new effective Value Classes feature for pimping the existing types.

此实现需要 Scala 2.10,因为它利用新的有效值类功能来拉取现有类型。

I have actually included these in a small extensions library called SExt, after depending your project on which you'll be able to have them by simply adding an import sext._statement.

我实际上已经将这些包含在一个名为SExt的小型扩展库中,在取决于您的项目之后,您只需添加一条import sext._语句就可以拥有它们。

Of course, if you want you can just compose these functions directly into the solution:

当然,如果您愿意,您可以将这些函数直接组合到解决方案中:

val (resultCourses, resultTimeList, resultAmOrPmList, resultDateList)
  = courses.toStream
      .zip(timeList).zip(amOrPmList).zip(dateList)
      .map{ case (((a, b), c), d) => (a, b, c, d) }
      .filterNot(_._1 == input)
      .foldRight((List[A](), List[B](), List[C](), List[D]()))(
        (a, z) => (a._1 +: z._1, a._2 +: z._2, a._3 +: z._3, a._4 +: z._4)
      )

回答by Brian

Removing and filtering List elements

删除和过滤列表元素

In Scala you can filter the list to remove elements.

在 Scala 中,您可以过滤列表以删除元素。

scala> val courses = List("Artificial Intelligence", "Programming Languages", "Compilers", "Networks", "Databases")
courses: List[java.lang.String] = List(Artificial Intelligence, Programming Languages, Compilers, Networks, Databases)

Let's remove a couple of classes:

让我们删除几个类:

courses.filterNot(p => p == "Compilers" || p == "Databases")

You can also use remove but it's deprecated in favor of filter or filterNot.

您也可以使用 remove,但它已被弃用,以支持 filter 或 filterNot。

If you want to remove by an index you can associate each element in the list with an ordered index using zipWithIndex. So, courses.zipWithIndexbecomes:

如果要按索引删除,可以使用 将列表中的每个元素与有序索引相关联zipWithIndex。于是,courses.zipWithIndex变成:

List[(java.lang.String, Int)] = List((Artificial Intelligence,0), (Programming Languages,1), (Compilers,2), (Networks,3), (Databases,4))

List[(java.lang.String, Int)] = List((Artificial Intelligence,0), (Programming Languages,1), (Compilers,2), (Networks,3), (Databases,4))

To remove the second element from this you can refer to index in the Tuple with courses.filterNot(_._2 == 1)which gives the list:

要从中删除第二个元素,您可以参考courses.filterNot(_._2 == 1)提供列表的元组中的索引:

res8: List[(java.lang.String, Int)] = List((Artificial Intelligence,0), (Compilers,2), (Networks,3), (Databases,4))

res8: List[(java.lang.String, Int)] = List((Artificial Intelligence,0), (Compilers,2), (Networks,3), (Databases,4))

Lastly, another tool is to use indexWhereto find the index of an arbitrary element.

最后,另一个工具是用来indexWhere查找任意元素的索引。

courses.indexWhere(_ contains "Languages") res9: Int = 1

courses.indexWhere(_ contains "Languages") res9: Int = 1

Re your update

重新更新

I'm writing a function to remove the corresponding element from each lists, and all I know is that 1) the indices correspond and 2) the user inputs the course name. How can I remove the corresponding element from each list using filterNot?

我正在编写一个函数来从每个列表中删除相应的元素,我所知道的是 1) 索引对应和 2) 用户输入课程名称。如何使用 filterNot 从每个列表中删除相应的元素?

Similar to Nikita's update you have to "merge" the elements of each list. So courses, meridiems, days, and times need to be put into a Tuple or class to hold the related elements. Then you can filter on an element of the Tuple or a field of the class.

与 Nikita 的更新类似,您必须“合并”每个列表的元素。所以课程、经络、天数和时间需要放入一个元组或类中来保存相关元素。然后您可以过滤元组的元素或类的字段。

Combining corresponding elements into a Tuple looks as follows with this sample data:

使用此示例数据将相应元素组合成一个元组如下所示:

val courses = List(Artificial Intelligence, Programming Languages, Compilers, Networks, Databases)
val meridiems = List(am, pm, am, pm, am)
val times = List(100, 1200, 0100, 0900, 0800)
val days = List(MWF, TTH, MW, MWF, MTWTHF)

Combine them with zip:

将它们与 zip 结合起来:

courses zip days zip times zip meridiems

courses zip days zip times zip meridiems

val zipped = List[(((java.lang.String, java.lang.String), java.lang.String), java.lang.String)] = List((((Artificial Intelligence,MWF),100),am), (((Programming Languages,TTH),1200),pm), (((Compilers,MW),0100),am), (((Networks,MWF),0900),pm), (((Databases,MTWTHF),0800),am))

val zipped = List[(((java.lang.String, java.lang.String), java.lang.String), java.lang.String)] = List((((Artificial Intelligence,MWF),100),am), (((Programming Languages,TTH),1200),pm), (((Compilers,MW),0100),am), (((Networks,MWF),0900),pm), (((Databases,MTWTHF),0800),am))

This abomination flattens the nested Tuples to a Tuple. There are better ways.

这种憎恶将嵌套的元组扁平化为元组。有更好的方法。

zipped.map(x => (x._1._1._1, x._1._1._2, x._1._2, x._2)).toList

zipped.map(x => (x._1._1._1, x._1._1._2, x._1._2, x._2)).toList

A nice list of tuples to work with.

一个很好的元组列表。

List[(java.lang.String, java.lang.String, java.lang.String, java.lang.String)] = List((Artificial Intelligence,MWF,100,am), (Programming Languages,TTH,1200,pm), (Compilers,MW,0100,am), (Networks,MWF,0900,pm), (Databases,MTWTHF,0800,am))

List[(java.lang.String, java.lang.String, java.lang.String, java.lang.String)] = List((Artificial Intelligence,MWF,100,am), (Programming Languages,TTH,1200,pm), (Compilers,MW,0100,am), (Networks,MWF,0900,pm), (Databases,MTWTHF,0800,am))

Finally we can filter based on course name using filterNot. e.g. filterNot(_._1 == "Networks")

最后,我们可以使用filterNot. 例如filterNot(_._1 == "Networks")

List[(java.lang.String, java.lang.String, java.lang.String, java.lang.String)] = List((Artificial Intelligence,MWF,100,am), (Programming Languages,TTH,1200,pm), (Compilers,MW,0100,am), (Databases,MTWTHF,0800,am))

List[(java.lang.String, java.lang.String, java.lang.String, java.lang.String)] = List((Artificial Intelligence,MWF,100,am), (Programming Languages,TTH,1200,pm), (Compilers,MW,0100,am), (Databases,MTWTHF,0800,am))

回答by iainmcgin

The answer I am about to give might be overstepping what you have been taught so far in your course, so if that is the case I apologise.

我即将给出的答案可能超出了您迄今为止在课程中学到的内容,因此如果是这种情况,我深表歉意。

Firstly, you are right to question whether you should have four lists - fundamentally, it sounds like what you need is an object which represents a course:

首先,您质疑是否应该有四个列表是正确的 - 从根本上说,听起来您需要的是一个代表课程的对象:

/**
 * Represents a course.
 * @param name the human-readable descriptor for the course
 * @param time the time of day as an integer equivalent to 
 *             12 hour time, i.e. 1130
 * @param meridiem the half of the day that the time corresponds 
 *                 to: either "am" or "pm"
 * @param days an encoding of the days of the week the classes runs.
 */
case class Course(name : String, timeOfDay : Int, meridiem : String, days : Int)

with which you may define an individual course

您可以用它来定义一个单独的课程

val cs101 = 
  Course("CS101 - Introduction to Object-Functional Programming", 
         1000, "am", 1)

There are better ways to define this type (better representations of 12-hour time, a clearer way to represent the days of the week, etc), but I won't deviate from your original problem statement.

有更好的方法来定义这种类型(更好地表示 12 小时时间,更清晰地表示星期几等),但我不会偏离您最初的问题陈述。

Given this, you would have a single list of courses:

鉴于此,您将只有一个课程列表:

val courses = List(cs101, cs402, bio101, phil101)

And if you wanted to find and remove allcourses that matched a given name, you would write:

如果您想查找并删除与给定名称匹配的所有课程,您可以这样写:

val courseToRemove = "PHIL101 - Philosophy of Beard Ownership"
courses.filterNot(course => course.name == courseToRemove)

Equivalently, using the underscore syntactic sugar in Scala for function literals:

等效地,在 Scala 中将下划线语法糖用于函数字面量:

courses.filterNot(_.name == courseToRemove)

If there was the risk that more than one course might have the same name (or that you are filtering based on some partial criteria using a regular expression or prefix match) and that you only want to remove the first occurrence, then you could define your own function to do that:

如果存在多个课程可能具有相同名称的风险(或者您正在使用正则表达式或前缀匹配根据某些部分标准进行过滤)并且您只想删除第一次出现,那么您可以定义您的自己的功能来做到这一点:

def removeFirst(courses : List[Course], courseToRemove : String) : List[Course] =
  courses match {
    case Nil => Nil
    case head :: tail if head == courseToRemove => tail
    case head :: tail => head :: removeFirst(tail)
  }

回答by Bruno Lee

Use the ListBuffer is a mutable List like a java list

使用 ListBuffer 是一个像 java 列表一样的可变列表

 var l =  scala.collection.mutable.ListBuffer("a","b" ,"c")
 print(l) //ListBuffer(a, b, c)
 l.remove(0)
 print(l) //ListBuffer(b, c)