Транспонирование произвольной коллекции коллекций в Scala
Мне часто приходится транспонировать "прямоугольную" коллекцию коллекций в Scala, например: список карт, Карта списков, карта карт, набор списков, карта наборов и т. д. Поскольку коллекции можно равномерно рассматривать как отображение из определенного домена в CO-домен (например: List[A] / Array[A]-это отображение из домена Int в CO-домен, Set[A] - это отображение из домена A в логический Co-домен и т. д.), Я хотел бы написать чистую, общую функцию для выполнения операции транспонирования (например,: поверните карту списков к транспонированному списку карт). Однако у меня возникли проблемы, потому что, кроме оператора (), Scala, похоже, не имеет унифицированного API для абстрактного просмотра коллекций как сопоставлений ?
поэтому я в конечном итоге пишу отдельную транспонирование для каждого типа коллекции коллекций следующим образом:
def transposeMapOfLists[A,B]( mapOfLists: Map[A,List[B]] ) : List[Map[A,B]] = {
val k = ( mapOfLists keys ) toList
val l = ( k map { mapOfLists(_) } ) transpose;
l map { v => ( k zip v ) toMap }
}
def transposeListOfMaps[A,B]( listOfMaps: List[Map[A,B]]) : Map[A,List[B]] = {
val k = ( listOfMaps(0) keys ) toList
val l = ( listOfMaps map { m => k map { m(_) } } ) transpose;
( k zip l ) toMap
}
def transposeMapOfMaps[A,B,C]( mapOfMaps: Map[A,Map[B,C]] ) : Map[B,Map[A,C]] = {
val k = ( mapOfMaps keys ) toList
val listOfMaps = k map { mapOfMaps(_) }
val mapOfLists = transposeListOfMaps( listOfMaps )
mapOfLists map { p => ( p._1, ( k zip p._2 ) toMap ) }
}
может ли кто-нибудь помочь мне объединить эти методы в одну общую транспонированную коллекцию коллекций ? Это также поможет мне (и я уверен, что другие) узнать некоторые полезные функции Scala в процессе.
ps: я проигнорировал обработку исключений и предположил, что входная коллекция коллекций является прямоугольной, т. е. все элементы домена внутренних коллекций составляют один и тот же набор.
1 ответов
Я уверен, что следующая грязная версия с использованием классов типов может быть очищена много, но она работает как быстрое доказательство концепции. Я не вижу простого способа получить возвращаемые типы без зависимых типов методов (я уверен, что это возможно), поэтому вам придется использовать -Xexperimental
:
trait Mapping[A, B, C] {
type M[D] <: PartialFunction[A, D]
def domain(c: C): Seq[A]
def fromPairs[D](ps: Seq[(A, D)]): M[D]
def codomain(c: C)(implicit ev: C <:< PartialFunction[A, B]) =
domain(c).map(c)
def toPairs(c: C)(implicit ev: C <:< PartialFunction[A, B]) =
domain(c).map(a => (a, c(a)))
}
implicit def seqMapping[A, B <: Seq[A]] = new Mapping[Int, A, B] {
type M[C] = Seq[C]
def domain(c: B) = 0 until c.size
def fromPairs[C](ps: Seq[(Int, C)]) = ps.sortBy(_._1).map(_._2)
}
implicit def mapMapping[A, B, C <: Map[A, B]] = new Mapping[A, B, C] {
type M[D] = Map[A, D]
def domain(c: C) = c.keys.toSeq
def fromPairs[D](ps: Seq[(A, D)]) = ps.toMap
}
def transpose[A, B, C, M, N](m: M)(implicit
pev: M <:< PartialFunction[A, N],
qev: N <:< PartialFunction[B, C],
mev: Mapping[A, N, M],
nev: Mapping[B, C, N]
) = nev.fromPairs(nev.domain(mev.codomain(m).head).map(b =>
b -> mev.fromPairs(mev.toPairs(m).map { case (a, c) => a -> c(b) })
))
и теперь для некоторых тестов:
scala> println(transpose(List(Map("a" -> 1, "b" -> 13), Map("b" -> 99, "a" -> 14))))
Map(a -> Vector(1, 14), b -> Vector(13, 99))
scala> println(transpose(Map('a' -> List(1, 2, 3), 'z' -> List(4, 5, 6))))
Vector(Map(a -> 1, z -> 4), Map(a -> 2, z -> 5), Map(a -> 3, z -> 6))
scala> println(transpose(Map("x" -> Map(4 -> 'a, 99 -> 'z), "y" -> Map(4 -> 'b, 99 -> 's))))
Map(4 -> Map(x -> 'a, y -> 'b), 99 -> Map(x -> 'z, y -> 's))
таким образом, он работает по желанию.