Возможно ли использовать один звонок для collect
, чтобы сделать 2 новых списка? Если нет, как это сделать, используя partition
?Scala Partition/Collect Usage
ответ
collect
(определяется на TraversableLike и доступен во всех подклассах) работает с коллекцией и PartialFunction
. Кроме того, просто так получилось, что куча тематических статей, определенных внутри скобок являются частичная функция (смотрите раздел 8.5 Scala Language Specification[предупреждения - 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
Надеюсь, что подводит итог тому, как два метода могут помочь вам здесь!
Не знаю, как это сделать с collect
без использования изменяемых списков, но partition
можно использовать по шаблону, а также (чуть более многословной)
List("a", 1, 2, "b", 19).partition {
case s:String => true
case _ => false
}
@coubeatczech - Поскольку раздел возвращает '(List [A], список [A])'. Это все, что он может сделать, поскольку ввод представляет собой «Список [A]» и индикаторную функцию «A => Boolean'. Он не знает, что функция индикатора может быть специфичной для конкретного типа. –
@Rex Я определил свой собственный метод 'collate' для сутенера на коллекции, который решает именно эту проблему. В 'List [A]' подпись использования используется 'collate [B] (fn: PartialFunction [A, B]): (List (B), List (A))', очевидно, * действительная * подпись немного более волосатое, чем это, поскольку я также использую 'CanBuildFrom' –
Сигнатура обычно используемой 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
в этом случае).
Я использую это. Одна хорошая вещь об этом заключается в том, что он объединяет разбиение и отображение на одной итерации. Один недостаток заключается в том, что она выделит кучу временных объектов (Either.Left
и Either.Right
экземпляров)
/**
* Splits the input list into a list of B's 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)
Я не смог найти удовлетворительного решения этой основной задачи здесь. Мне не нужна лекция на 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])
Очень приятное объяснение, но то, что я думаю, что OP хочет, представляет собой комбинацию' collect' и 'partition', которая возвращает кортеж списка собранных значений и списка всех остальных. 'def collectAndPartition [A, B] (pf: PartialFunction [A, B]): (Список [B], Список [A])'. Это, вероятно, было бы наиболее элегантно достигнуто с помощью встроенной библиотечной функции, то есть в источнике 'collect' в TraversableLike у нас есть' for (x <- this) if (pf.isDefinedAt (x)) b + = pf (x) ' , в конце этого можно было бы просто нажать «else a + = x», где 'a' будет строителем для списка всех остальных. –
Я знаю, что нужно OP, и я также знаю, что это вопрос домашней работы (недавно упоминалось о переполнении стека), поэтому я с удовольствием дам много теории, не решив ее вообще. Что касается collectAndPartition, я уже писал это, хотя я назвал метод 'collate'.Если кто-то учит scala до уровня, на котором студенты должны работать с CanBuildFrom, тогда я буду очень удивлен, это больше, чем большинство людей, которые в настоящее время используют scala на производстве. –
Это было очень полезно. Но я все еще думаю ... можно ли отделить, например, положительные и отрицательные ценности, не делая «военного преступления», как вы писали раньше? Я просто любезен, потому что я уже сделал домашнюю работу с использованием раздела. Охх ... И кстати, спасибо за помощь! –