Можно ли использовать один вызов collect
для создания 2 новых списков? Если нет, как это сделать, используя partition
?
Scala Использование раздела/сбора
Ответ 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)