Scala Partition / Collect Usage - PullRequest
36 голосов
/ 24 января 2011

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

Ответы [ 6 ]

78 голосов
/ 24 января 2011

collect (определено в TraversableLike и доступно во всех подклассах) работает с коллекцией и PartialFunction.Также так получилось, что набор предложений case, определенных внутри фигурных скобок, является частичной функцией (см. Раздел 8.5 Спецификации языка Scala [warning - 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
}

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

A намного лучшее решение - этоиспользуйте команду дважды:

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

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

7 голосов
/ 24 января 2011

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

List("a", 1, 2, "b", 19).partition { 
  case s:String => true
  case _ => false 
}
6 голосов
/ 24 января 2011

Подпись обычно используемого 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 в этом случае).

5 голосов
/ 25 января 2011

Я использую это. Одна хорошая вещь об этом - это объединяет разделение и отображение в одной итерации. Один недостаток заключается в том, что он выделяет кучу временных объектов (экземпляры 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)
2 голосов
/ 26 мая 2016

Я не мог найти удовлетворительное решение этой основной проблемы здесь.Мне не нужна лекция по 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])
1 голос
/ 07 октября 2018

Начиная с Scala 2.13, большинство коллекций теперь имеют метод 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)
...