Я изучаю Scala, так как он хорошо соответствует моим потребностям, но мне трудно элегантно структурировать код. Я нахожусь в ситуации, когда у меня есть List
x
и я хочу создать два List
s: один, содержащий все элементы SomeClass
и один содержит все элементы, которые не принадлежат SomeClass
.
val a = x collect {case y:SomeClass => y}
val b = x filterNot {_.isInstanceOf[SomeClass]}
Прямо сейчас мой код выглядит так. Тем не менее, он не очень эффективен, поскольку он повторяет x
дважды, и код почему-то кажется немного хакерским. Есть ли лучший (более элегантный) способ ведения дел?
Можно предположить, что SomeClass
не имеет подклассов.
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)
Используйте 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)
Если список содержит только подклассы 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)
Просто хотел расширить ответ 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, после того, как я еще немного поиграю.