Как отсортировать коллекцию списков в лексикографическом порядке в Scala?



Если A имеет свойство Ordered[A], я хотел бы иметь код, который работает следующим образом



val collection: List[List[A]] = ... // construct a list of lists of As
val sorted = collection sort { _ < _ }


И получить что-то, где списки были отсортированы в лексикографическом порядке. Конечно, только потому, что A имеет признак Ordered[A], не означает, что List[A] имеет Признак Ordered[List[A]]. Предположительно, однако, 'скала' способом сделать это-неявное определение.



Как я неявно преобразую a List[A] в A Ordered[List[A]], предполагая, что A имеет признак Ordered[A] (так что код выше просто работает)?



Я имею в виду использование лексикографического упорядочения на объектах List[A], но мне бы хотелось код, который можно адаптировать к другим упорядочениям.
601   5  

5 ответов:

Вдохновленный ответом Бена Линга, я написал свою собственную версию sort:

def sort[A : Ordering](coll: Seq[Iterable[A]]) = coll.sorted

Что эквивалентно:

def sort[A](coll: Seq[Iterable[A]])(implicit ordering: Ordering[A]) = coll.sorted
Обратите внимание, что ordering неявно преобразуется в Ordering[Iterable[A]].

Примеры:

scala> def sort[A](coll: Seq[Iterable[A]])(implicit ordering: Ordering[A]) = coll.sorted
sort: [A](coll: Seq[Iterable[A]])(implicit ordering: Ordering[A])Seq[Iterable[A]]

scala> val coll = List(List(1, 3), List(1, 2), List(0), Nil, List(2))
coll: List[List[Int]] = List(List(1, 3), List(1, 2), List(0), List(), List(2))

scala> sort(coll)
res1: Seq[Iterable[Int]] = List(List(), List(0), List(1, 2), List(1, 3), List(2))

Был задан вопрос, как снабдить вашу собственную функцию сравнения; достаточно использовать упорядочивание.fromLessThan:

scala> sort(coll)(Ordering.fromLessThan(_ > _))
res4: Seq[Iterable[Int]] = List(List(), List(2), List(1, 3), List(1, 2), List(0))

Ordering.by позволяет сопоставить значение с другим типом, для которого уже существует экземпляр заказа. Учитывая, что также кортежи упорядочены, это может быть полезно для лексикографического сравнения классов падежей.

Чтобы сделать пример, давайте определим оболочку Int, применяем Ordering.by(_.v), где _.v извлекает базовое значение, и покажем, что мы получаем тот же результат:
scala> case class Wrap(v: Int)
defined class Wrap

scala> val coll2 = coll.map(_.map(Wrap(_)))
coll2: List[List[Wrap]] = List(List(Wrap(1), Wrap(3)), List(Wrap(1), Wrap(2)), List(Wrap(0)), List(), List(Wrap(2)))

scala> sort(coll2)(Ordering.by(_.v))
res6: Seq[Iterable[Wrap]] = List(List(), List(Wrap(0)), List(Wrap(1), Wrap(2)), List(Wrap(1), Wrap(3)), List(Wrap(2)))
Наконец, давайте сделаем то же самое для класса case с большим количеством членов, повторно используя компараторы для кортежей:
scala> case class MyPair(a: Int, b: Int)
defined class MyPair

scala> val coll3 = coll.map(_.map(MyPair(_, 0)))
coll3: List[List[MyPair]] = List(List(MyPair(1,0), MyPair(3,0)), List(MyPair(1,0), MyPair(2,0)), List(MyPair(0,0)), List(), List(MyPair(2,0)))

scala> sort(coll3)(Ordering.by(x => (x.a, x.b)))
res7: Seq[Iterable[MyPair]] = List(List(), List(MyPair(0,0)), List(MyPair(1,0), MyPair(2,0)), List(MyPair(1,0), MyPair(3,0)), List(MyPair(2,0)))

Вдохновленный ответом Бена Линга, я сумел придумать самый простой способ лексикографической сортировки списков: добавить строку

import scala.math.Ordering.Implicits._

Перед выполнением сравнения списка[Int] убедитесь, что неявная функция infixOrderingOps находится в области видимости.

(11 минут назад я на самом деле не знал, как это сделать, я надеюсь, что это считается нормальным, чтобы ответить на мой собственный вопрос.)

implicit def List2OrderedList[A <% Ordered[A]](list1: List[A]): Ordered[List[A]] = { 
    new Ordered[List[A]] {
        def compare(list2: List[A]): Int = {
            for((x,y) <- list1 zip list2) {
                val c = x compare y
                if(c != 0) return c
            }
            return list1.size - list2.size
        }
    }
}

Здесь важно отметить, что " вид связан' A <% Ordered[A], что гарантирует, что A не нужно само по себе Ordered[A], просто есть способ сделать это преобразование. К счастью, объект библиотеки Scala Predef имеет неявное преобразование из Ints в RichInts, которые, в частности, являются Ordered[Int]s.

Остальная часть кода просто реализует лексикографический заказ.

В 2.8, вы должны быть в состоянии просто сделать collection.sorted. sorted принимает неявный параметр Ordering. Любой тип, реализующий Ordered, имеет соответствующий Ordering (благодаря неявному преобразованию Ordering.ordered). Существует также неявное Ordering.Iterable, которое заставляет Iterable[T] иметь Ordering, Если T имеет Ordering.

Однако, если вы попробуете это сделать, это не сработает:

scala> def sort[A <: Ordered[A]](coll: List[List[A]]) = coll.sorted

<console>:5: error: could not find implicit value for parameter ord: Ordering[List[A]]
       def sort[A <: Ordered[A]](coll: List[List[A]]) = coll.sorted
                                                             ^

Вам нужно явно указать, что вы хотите Ordering[Iterable[A]]:

def sort[A <: Ordered[A]](coll: List[List[A]]) = coll.sorted[Iterable[A]]

Я не уверен, почему компилятор не может найти Ordering[Iterable[A]], Если тип элемента коллекции - List[A].

Вдохновленный комментарием Даниэля, вот рекурсивная версия:

implicit def toOrdered[A <% Ordered[A]](list1: List[A]): Ordered[List[A]] = { 
  @scala.annotation.tailrec
  def c(list1:List[A], list2:List[A]): Int = {
    (list1, list2) match {
      case (Nil, Nil) => 0
      case (x::xs, Nil) => 1
      case (Nil, y::ys) => -1
      case (x::xs, y::ys) => (x compare y) match {
        case 0 => c(xs, ys)
        case i => i
      }
    }
  }
  new Ordered[List[A]] {
    def compare(list2: List[A]): Int = c(list1, list2)
  }
}

В отношении комментария: Раньше я думал, что это скорее вопрос вкуса. Иногда легче проверить корректность рекурсивной функции, и, конечно, ваша версия достаточно коротка, чтобы не было убедительных причин предпочесть рекурсивную.

Тем не менее, я был заинтригован результатами работы. Поэтому я попытался сравнить его: см. http://gist.github.com/468435 . я был удивлен, увидев, что рекурсивная версия быстрее (при условии, что я сделал тест правильно). Результаты по-прежнему остаются верными для списка длиной около 10.

Comments

    Ничего не найдено.