Как я могу идиоматически "удалить" один элемент из списка в Scala и закрыть разрыв?

списки неизменяемы в Scala, поэтому я пытаюсь выяснить, как я могу "удалить" - действительно, создать новую коллекцию - этот элемент, а затем закрыть пробел, созданный в списке. Это звучит для меня как отличное место для использования карты, но я не знаю, как начать в этом случае.

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

позвольте мне добавить некоторые детали к проблеме. У меня есть четыре списка, которые связаны друг с другом по индексу; один список хранит имена курсов, один хранит время начала класса в простом формате int (т. е. 130), один хранит либо "am", либо "pm", и один хранит дни классов по int (так "MWF" evals to 1, "TR" evals to 2 и т. д.). Я не знаю, есть ли это лучший или" правильный " способ решить эту проблему, но это все инструменты, которые у меня есть (студент первого курса comp sci, который не программировал серьезно, так как мне было 16). Я пишу функцию для удаления соответствующего элемента из каждого списка, и все, что я знаю, это то, что 1) индексы соответствуют и 2) пользователь вводит имя курса. Как удалить соответствующий элемент из каждого списка с помощью filterNot? Я не думаю, что знаю достаточно о каждом списке, чтобы использовать функции более высокого порядка их.

6 ответов


чтобы ответить на ваш вопрос прямо, я думаю, что вы ищете patch, например, для удаления элемента с индексом 2 ("c"):

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

здесь Nil это то, на что мы его заменяем, и 1 - количество символов для замены.

но, если вы это сделаете:

у меня есть четыре списка, которые связаны друг с другом по индексу; один в списке хранятся имена курсов, в одном - время начала занятий простой формат int (ie 130), один хранит или " am " или "pm" , и одно сохраняет дни занятий по int

у вас будет плохое время. Я предлагаю вам использовать case class:

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

и затем хранить их в Set[Course]. (Хранение времени и дней как Ints тоже не отличная идея - посмотрите на .)


Это вариант использования 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)

вы хотите использовать map при преобразовании всех элементов списка.


сначала несколько примечаний:

  1. List не является индексной структурой. Все индексно-ориентированные операции на нем занимают линейное время. Для индексно-ориентированных алгоритмов Vector гораздо лучший кандидат. Фактически, если ваш алгоритм требует индексов, это верный признак того, что вы действительно не подвергаете функциональные возможности Scala.

  2. map служит для преобразования коллекции элементов "A" в ту же коллекцию элементов " B " с помощью a передано в функции трансформатора от одного " A "до одного"B". Он не может изменить количество результирующих элементов. Вероятно, вы перепутали map С fold или reduce.

чтобы ответить на ваш обновленный вопрос

хорошо, вот функциональное решение, которое эффективно работает в списках:

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

но есть загвоздка. Я на самом деле был очень удивлен, узнав, что функции, используемые в этом решении, которые являются настолько основными для функциональные языки, отсутствовали в стандартной библиотеке Scala. Scala имеет их для 2 и 3-арных кортежей, но не для других.

чтобы решить эту проблему, вам необходимо импортировать следующие неявные расширения.

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

для этой реализации требуется Scala 2.10, поскольку она использует новую функцию классов эффективных значений для сутенерства существующих типов.

я фактически включил их в небольшую библиотеку расширений под названием SExt, после в зависимости от вашего проекта, на котором вы сможете их иметь, просто добавив import sext._ заявление.

конечно, если вы хотите, вы можете просто написать эти функции непосредственно в раствор:

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

удаление и фильтрация элементов списка

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

давайте удалим пару классов:

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

вы также можете использовать remove, но он устарел в пользу фильтра или filterNot.

если вы хотите удалить по индексу, вы можете связать каждый элемент в списке с упорядоченным индексом, используя zipWithIndex. Итак,courses.zipWithIndex становится:

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

чтобы удалить второй элемент из этого, вы можете обратиться к индексу в кортеж с courses.filterNot(_._2 == 1) что дает список:

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

наконец, еще один инструмент, чтобы использовать indexWhere, чтобы найти индекс произвольного элемента.

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

повторное обновление

я пишу функцию для удаления соответствующего элемента из каждого списки, и все я знаю, что 1) индексы соответствуют и 2) пользователь вводит имя курса. Как я могу удалить соответствующий элемент из каждого списка с помощью filterNot?

подобно обновлению Никиты, вы должны "объединить" элементы каждого списка. Таким образом, курсы, меридиемы, дни и время должны быть помещены в кортеж или класс для хранения связанных элементов. Затем вы можете отфильтровать элемент кортежа или поле класса.

объединение соответствующих элементов в Кортеж выглядит следующим образом с этими данными образца:

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)

объединить их с zip:

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

эта мерзость сплющивает вложенные кортежи в кортеж. Есть способы получше.

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

хороший список кортежей для работы.

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

наконец, мы можем фильтровать на основе имени курса с помощью 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))


во-первых, вы правы, чтобы спросить, должны ли вы иметь четыре списка - в принципе, это звучит как то, что вам нужно, это объект, который представляет собой курс:

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

С помощью которого вы можете определить индивидуальный курс

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

есть лучшие способы определить этот тип (лучшие представления 12-часового время, более четкий способ представления дней недели и т. д.), Но я не буду отклоняться от вашего первоначального утверждения проблемы.

учитывая это, у вас будет один список курсов:

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

и если вы хотите найти и удалить все курсы, которые соответствуют данному имени, вы бы написать:

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

эквивалентно, используя синтаксический сахар подчеркивания в Scala для литералов функций:

courses.filterNot(_.name == courseToRemove)

если был риск, что больше чем один курс может иметь то же имя (или что вы фильтруете на основе некоторых частичных критериев, используя регулярное выражение или префикс) и что вы хотите удалить только первое вхождение, тогда вы можете определить свою собственную функцию для этого:

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

использование 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)