Scala: фильтрация по типу

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

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

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

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

14
задан 26 August 2010 в 05:13
поделиться

4 ответа

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))

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

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

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)
8
ответ дан 1 December 2019 в 13:20
поделиться

Используйте 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)
4
ответ дан 1 December 2019 в 13:20
поделиться

Если список содержит только подклассы 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)
2
ответ дан 1 December 2019 в 13:20
поделиться

Просто хотел расширить ответ 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), но Мне было лень ;)

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

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, после того, как я еще немного поиграю.

5
ответ дан 1 December 2019 в 13:20
поделиться
Другие вопросы по тегам:

Похожие вопросы: