Подтвердить что ты не робот

Scala Использование раздела/сбора

Можно ли использовать один вызов collect для создания 2 новых списков? Если нет, как это сделать, используя partition?

4b9b3361

Ответ 1

collect (определенный в TraversableLike и доступен во всех подклассах) работает с коллекцией и PartialFunction. Также бывает так, что куча аргументов case, определенных внутри фигурных скобок, является частичной функцией (см. Раздел 8.5 Scala Спецификация языка [предупреждение - PDF])

Как и при обработке исключений:

try {
  ... do something risky ...
} catch {
  //The contents of this catch block are a partial function
  case e: IOException => ...
  case e: OtherException => ...
}

Это удобный способ определения функции, которая будет принимать только некоторые значения заданного типа.

Рассмотрите возможность использования его в списке смешанных значений:

val mixedList = List("a", 1, 2, "b", 19, 42.0) //this is a List[Any]
val results = mixedList collect {
  case s: String => "String:" + s
  case i: Int => "Int:" + i.toString
}

Аргумент метода collect равен PartialFunction[Any,String]. PartialFunction, потому что он не определен для всех возможных входов типа Any (который является типом List) и String, потому что это возвращает все предложения.

Если вы попытались использовать map вместо collect, двойное значение в конце mixedList вызовет MatchError. Использование collect просто отменяет это, а также любое другое значение, для которого PartialFunction не определен.

Одним из возможных вариантов использования было бы применение различной логики к элементам списка:

var strings = List.empty[String]
var ints = List.empty[Int]
mixedList collect {
  case s: String => strings :+= s
  case i: Int => ints :+= i
}

Хотя это всего лишь пример, использование многими изменяемыми переменными, как это, многие считают военным преступлением. Поэтому, пожалуйста, не делайте этого!

Гораздо лучшее решение - использовать коллекцию дважды:

val strings = mixedList collect { case s: String => s }
val ints = mixedList collect { case i: Int => i }

Или, если вы точно знаете, что список содержит только два типа значений, вы можете использовать partition, который разбивает коллекции на значения в зависимости от того, соответствуют ли они предикату:

//if the list only contains Strings and Ints:
val (strings, ints) = mixedList partition { case s: String => true; case _ => false }

Ловушка заключается в том, что оба strings и ints имеют тип List[Any], хотя вы можете легко вернуть их к чему-то более типичному (возможно, используя collect...)

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

val intList = List(2,7,9,1,6,5,8,2,4,6,2,9,8)
val (big,small) = intList partition (_ > 5)
//big and small are both now List[Int]s

Надеюсь, что подводит итог тому, как два метода могут помочь вам здесь!

Ответ 2

Не уверен, как это сделать с помощью collect без использования измененных списков, но partition может также использовать сопоставление шаблонов (только немного более подробный)

List("a", 1, 2, "b", 19).partition { 
  case s:String => true
  case _ => false 
}

Ответ 3

Подпись обычно используемого collect на, скажем, Seq, есть

collect[B](pf: PartialFunction[A,B]): Seq[B]

что действительно является частным случаем

collect[B, That](pf: PartialFunction[A,B])(
  implicit bf: CanBuildFrom[Seq[A], B, That]
): That

Итак, если вы используете его в режиме по умолчанию, ответ отрицательный, конечно нет: вы получаете ровно одну последовательность из него. Если вы следуете CanBuildFrom через Builder, вы увидите, что можно было бы сделать That фактически двумя последовательностями, но не было бы способа сказать, в какую последовательность должен идти элемент, поскольку частичная функция может только "да, я принадлежу" или "нет, я не принадлежу".

Итак, что вы делаете, если хотите иметь несколько условий, в результате которых ваш список разбивается на кучу разных частей? Один из способов - создать функцию индикатора A => Int, где ваш A отображается в пронумерованный класс, а затем используйте groupBy. Например:

def optionClass(a: Any) = a match {
  case None => 0
  case Some(x) => 1
  case _ => 2
}
scala> List(None,3,Some(2),5,None).groupBy(optionClass)
res11: scala.collection.immutable.Map[Int,List[Any]] = 
  Map((2,List(3, 5)), (1,List(Some(2))), (0,List(None, None)))

Теперь вы можете искать свои под-списки по классу (0, 1 и 2 в этом случае). К сожалению, если вы хотите игнорировать некоторые входы, вам все равно придется помещать их в класс (например, вы, вероятно, не заботитесь о нескольких копиях None в этом случае).

Ответ 4

Я использую это. Одна хорошая вещь об этом заключается в том, что он объединяет разбиение и отображение на одной итерации. Один из недостатков заключается в том, что он выделяет кучу временных объектов (экземпляры Either.Left и Either.Right)

/**
 * Splits the input list into a list of B and a list of C's, depending on which type of value the mapper function returns.
 */
def mapSplit[A,B,C](in: List[A])(mapper: (A) => Either[B,C]): (List[B], List[C]) = {
  @tailrec
  def mapSplit0(in: List[A], bs: List[B], cs: List[C]): (List[B], List[C]) = {
    in match {
      case a :: as =>
        mapper(a) match {
          case Left(b)  => mapSplit0(as, b :: bs, cs     )
          case Right(c) => mapSplit0(as, bs,      c :: cs)
        }
      case Nil =>
        (bs.reverse, cs.reverse)
    }
  }

  mapSplit0(in, Nil, Nil)
}

val got = mapSplit(List(1,2,3,4,5)) {
  case x if x % 2 == 0 => Left(x)
  case y               => Right(y.toString * y)
}

assertEquals((List(2,4),List("1","333","55555")), got)

Ответ 5

Я не мог найти удовлетворительного решения этой основной проблемы здесь. Мне не нужна лекция на collect, и мне все равно, если это кто-то домашнее задание. Кроме того, мне не нужно что-то, что работает только для List.

Итак, вот мой удар. Эффективен и совместим с любыми TraversableOnce, четными строками:

implicit class TraversableOnceHelper[A,Repr](private val repr: Repr)(implicit isTrav: Repr => TraversableOnce[A]) {

  def collectPartition[B,Left](pf: PartialFunction[A, B])
  (implicit bfLeft: CanBuildFrom[Repr, B, Left], bfRight: CanBuildFrom[Repr, A, Repr]): (Left, Repr) = {
    val left = bfLeft(repr)
    val right = bfRight(repr)
    val it = repr.toIterator
    while (it.hasNext) {
      val next = it.next
      if (!pf.runWith(left += _)(next)) right += next
    }
    left.result -> right.result
  }

  def mapSplit[B,C,Left,Right](f: A => Either[B,C])
  (implicit bfLeft: CanBuildFrom[Repr, B, Left], bfRight: CanBuildFrom[Repr, C, Right]): (Left, Right) = {
    val left = bfLeft(repr)
    val right = bfRight(repr)
    val it = repr.toIterator
    while (it.hasNext) {
      f(it.next) match {
        case Left(next) => left += next
        case Right(next) => right += next
      }
    }
    left.result -> right.result
  }
}

Пример использования:

val (syms, ints) =
  Seq(Left('ok), Right(42), Right(666), Left('ko), Right(-1)) mapSplit identity

val ctx = Map('a -> 1, 'b -> 2) map {case(n,v) => n->(n,v)}
val (bound, unbound) = Vector('a, 'a, 'c, 'b) collectPartition ctx
println(bound: Vector[(Symbol, Int)], unbound: Vector[Symbol])

Ответ 6

Начиная Scala 2.13, большинство коллекций теперь снабжены Either[A1,A2]):(CC[A1],CC[A2]) rel="nofollow noreferrer"> partitionMap метод, который делит элементы на основе функции, которая возвращает либо Right или Left.

Это позволяет нам сопоставлять шаблоны на основе типа (который позволяет collect определенные типы в разделенных списках) или любого другого шаблона:

 val (strings, ints) =
   List("a", 1, 2, "b", 19).partitionMap {
     case s: String => Left(s)
     case x: Int    => Right(x)
   }
 // strings: List[String] = List("a", "b")
 // ints: List[Int] = List(1, 2, 19)