2011-01-24 5 views

ответ

75

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 

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

+3

Очень приятное объяснение, но то, что я думаю, что 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' будет строителем для списка всех остальных. –

+3

Я знаю, что нужно OP, и я также знаю, что это вопрос домашней работы (недавно упоминалось о переполнении стека), поэтому я с удовольствием дам много теории, не решив ее вообще. Что касается collectAndPartition, я уже писал это, хотя я назвал метод 'collate'.Если кто-то учит scala до уровня, на котором студенты должны работать с CanBuildFrom, тогда я буду очень удивлен, это больше, чем большинство людей, которые в настоящее время используют scala на производстве. –

+0

Это было очень полезно. Но я все еще думаю ... можно ли отделить, например, положительные и отрицательные ценности, не делая «военного преступления», как вы писали раньше? Я просто любезен, потому что я уже сделал домашнюю работу с использованием раздела. Охх ... И кстати, спасибо за помощь! –

6

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

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

@coubeatczech - Поскольку раздел возвращает '(List [A], список [A])'. Это все, что он может сделать, поскольку ввод представляет собой «Список [A]» и индикаторную функцию «A => Boolean'. Он не знает, что функция индикатора может быть специфичной для конкретного типа. –

+1

@Rex Я определил свой собственный метод 'collate' для сутенера на коллекции, который решает именно эту проблему. В 'List [A]' подпись использования используется 'collate [B] (fn: PartialFunction [A, B]): (List (B), List (A))', очевидно, * действительная * подпись немного более волосатое, чем это, поскольку я также использую 'CanBuildFrom' –

5

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

3

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

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