Scala: фильтрация по типу - PullRequest
14 голосов
/ 26 августа 2010

Я изучаю Scala, так как он хорошо соответствует моим потребностям, но мне трудно элегантно структурировать код. Я нахожусь в ситуации, когда у меня есть List x и я хочу создать два List s: один, содержащий все элементы SomeClass, и один, содержащий все элементы, которые не имеют SomeClass.

val a = x collect {case y:SomeClass => y}
val b = x filterNot {_.isInstanceOf[SomeClass]}

Прямо сейчас мой код выглядит так. Тем не менее, он не очень эффективен, поскольку он повторяет x дважды, и код почему-то кажется немного хакерским. Есть ли лучший (более элегантный) способ ведения дел?

Можно предположить, что SomeClass не имеет подклассов.

Ответы [ 5 ]

8 голосов
/ 26 августа 2010

EDITED

Хотя использование простого partition возможно, оно теряет информацию о типе, сохраненную collect в вопросе.

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

import collection.mutable.ListBuffer

def partition[X,A,B](xs: List[X])(f: X=>Either[A,B]): (List[A],List[B]) = {
  val as = new ListBuffer[A]
  val bs = new ListBuffer[B]
  for (x <- xs) {
    f(x) match {
      case Left(a) => as += a
      case Right(b) => bs += b
    }
  }
  (as.toList, bs.toList)
}

Тогда типы сохраняются:

scala> partition(List(1,"two", 3)) {
  case i: Int => Left(i)
  case x => Right(x)
}

res5: (List[Int], List[Any]) = (List(1, 3),List(two))

Конечно,решение может быть улучшено с помощью сборщиков и всех улучшенных элементов сбора :).

Для полноты моего старого ответа с использованием простого partition:

val (a,b) = x partition { _.isInstanceOf[SomeClass] }

Например:

scala> val x = List(1,2, "three")
x: List[Any] = List(1, 2, three)

scala> val (a,b) = x partition { _.isInstanceOf[Int] }
a: List[Any] = List(1, 2)
b: List[Any] = List(three)
5 голосов
/ 26 августа 2010

Просто хотел бы расширить ответ mkneissl с помощью «более общей» версии, которая должна работать со многими различными коллекциями в библиотеке:

scala> import collection._
import collection._

scala> import generic.CanBuildFrom
import generic.CanBuildFrom

scala> def partition[X,A,B,CC[X] <: Traversable[X], To, To2](xs : CC[X])(f : X => Either[A,B])(
     |   implicit cbf1 : CanBuildFrom[CC[X],A,To], cbf2 : CanBuildFrom[CC[X],B,To2]) : (To, To2) = {
     |   val left = cbf1()
     |   val right = cbf2()
     |   xs.foreach(f(_).fold(left +=, right +=))
     |   (left.result(), right.result())
     | }
partition: [X,A,B,CC[X] <: Traversable[X],To,To2](xs: CC[X])(f: (X) => Either[A,B])(implicit cbf1: scala.collection.generic.CanBuildFrom[CC[X],A,To],implicit cbf2: scala.collection.generic.CanBuildFrom[CC[X],B,To2])(To, To2)

scala> partition(List(1,"two", 3)) {                                                                
     |   case i: Int => Left(i)                                                                     
     |   case x => Right(x)                                                                         
     | }
res5: (List[Int], List[Any]) = (List(1, 3),List(two))

scala> partition(Vector(1,"two", 3)) {
     |   case i: Int => Left(i)       
     |   case x => Right(x)           
     | }
res6: (scala.collection.immutable.Vector[Int], scala.collection.immutable.Vector[Any]) = (Vector(1, 3),Vector(two))

Только одно примечание: метод разбиения похож, но нам нужно захватить несколько типов:

X -> Оригинальный тип для предметов в коллекции.

A -> Тип элементов в левом разделе

B -> Тип элементов в правом разделе

CC -> «Конкретный» тип коллекции (Vector, List, Seq и т. Д.). Этот должен иметь более высокий род. Возможно, мы могли бы обойти некоторые проблемы с выводом типов (см. Ответ Адриана здесь: http://suereth.blogspot.com/2010/06/preserving-types-and-differing-subclass.html), но мне было лень;)

Кому -> Полный тип коллекции на левой стороне

To2 -> Полный тип коллекции с правой стороны

Наконец, забавные неявные параметры CanBuildFrom - это то, что позволяет нам создавать конкретные типы, такие как List или Vector, в общем. Они встроены во все основные библиотечные коллекции.

По иронии судьбы, вся причина магии CanBuildFrom в том, чтобы правильно обрабатывать битовые наборы. Поскольку я требую, чтобы CC был выше, мы получаем это забавное сообщение об ошибке при использовании раздела:

scala> partition(BitSet(1,2, 3)) {    
     |   case i if i % 2 == 0  => Left(i)
     |   case i if i % 2 == 1 => Right("ODD")
     | }
<console>:11: error: type mismatch;
 found   : scala.collection.BitSet
 required: ?CC[ ?X ]
Note that implicit conversions are not applicable because they are ambiguous:
 both method any2ArrowAssoc in object Predef of type [A](x: A)ArrowAssoc[A]
 and method any2Ensuring in object Predef of type [A](x: A)Ensuring[A]
 are possible conversion functions from scala.collection.BitSet to ?CC[ ?X ]
       partition(BitSet(1,2, 3)) {

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

4 голосов
/ 26 августа 2010

Использование list.partition:

scala> val l = List(1, 2, 3)
l: List[Int] = List(1, 2, 3)

scala> val (even, odd) = l partition { _ % 2 == 0 }
even: List[Int] = List(2)
odd: List[Int] = List(1, 3)

EDIT

Для разбиения по типам используйте этот метод:

def partitionByType[X, A <: X](list: List[X], typ: Class[A]): 
    Pair[List[A], List[X]] = {
    val as = new ListBuffer[A]
    val notAs = new ListBuffer[X]
    list foreach {x =>
      if (typ.isAssignableFrom(x.asInstanceOf[AnyRef].getClass)) {
        as += typ cast x 
      } else {
        notAs += x
      }
    }
    (as.toList, notAs.toList)
}

Использование:

scala> val (a, b) = partitionByType(List(1, 2, "three"), classOf[java.lang.Integer])
a: List[java.lang.Integer] = List(1, 2)
b: List[Any] = List(three)
2 голосов
/ 26 августа 2010

Если список содержит только подклассы AnyRef, из-за метода getClass. Вы можете сделать это:

scala> case class Person(name: String)                                                           
defined class Person

scala> case class Pet(name: String)                                                              
defined class Pet

scala> val l: List[AnyRef] = List(Person("Walt"), Pet("Donald"), Person("Disney"), Pet("Mickey"))
l: List[AnyRef] = List(Person(Walt), Pet(Donald), Person(Disney), Pet(Mickey))

scala> val groupedByClass = l.groupBy(e => e.getClass)
groupedByClass: scala.collection.immutable.Map[java.lang.Class[_],List[AnyRef]] = Map((class Person,List(Person(Walt), Person(Disney))), (class Pet,List(Pet(Donald), Pet(Mickey))))

scala> groupedByClass(classOf[Pet])(0).asInstanceOf[Pet]
res19: Pet = Pet(Donald)
0 голосов
/ 02 октября 2018

Начиная с Scala 2.13, большинство коллекций теперь имеют метод partitionMap, который разделяет элементы на основе функции, которая возвращает либо Right, либо Left.

Это позволяет нам сопоставить шаблон с данным типом (здесь Person), который мы преобразовываем как Right, чтобы поместить его в список right результирующего кортежа раздела. И другие типы могут быть преобразованы как Left s для разделения в левой части:

// case class Person(name: String)
// case class Pet(name: String)
val (pets, persons) =
  List(Person("Walt"), Pet("Donald"), Person("Disney")).partitionMap {
    case person: Person => Right(person)
    case pet: Pet       => Left(pet)
  }
// persons: List[Person] = List(Person(Walt), Person(Disney))
// pets: List[Pet] = List(Pet(Donald))
...