如何在Scala中按字典顺序对列表集合进行排序?

时间:2010-06-29 04:45:38

标签: sorting scala html-lists lexicographic

如果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]]。然而,据推测,执行此操作的“scala方式”是隐式def。

如何将List[A]隐式转换为Ordered[List[A]],假设A具有特征Ordered[A](以便上面的代码正常工作)? < / p>

我记得在List[A]个对象上使用词典排序,但我想要的代码可以适应其他顺序。

6 个答案:

答案 0 :(得分:19)

受Ben Lings的回答启发,我设法找出了一种似乎最简单的按字典顺序对列表进行排序的方法:添加行

import scala.math.Ordering.Implicits._

在进行List [Int]比较之前,确保隐式函数infixOrderingOps在范围内。

答案 1 :(得分:5)

(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
        }
    }
}

这里需要注意的重要一点是“view boundA <% Ordered[A],它确保A Ordered[A]不需要Predef,只是有办法做这个转换。令人高兴的是,Scala库的对象Int具有从RichIntOrdered[Int]的隐式转换,特别是{{1}} s。

其余代码只是实现了词典排序。

答案 2 :(得分:4)

灵感来自Ben Lings的回答,我写了我自己的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))

有人询问如何提供自己的比较功能;只需使用Ordering.fromLessThan:

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

Ordering.by允许您将值映射到已有Ordering实例的另一种类型。鉴于元组也是有序的,这对于案例类的字典比较非常有用。

举一个例子,让我们定义一个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)))

最后,让我们对具有更多成员的案例类做同样的事情,重用元组的比较器:

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

答案 3 :(得分:3)

在2.8中,您应该能够collection.sortedsorted采用隐式Ordering参数。任何实现Ordered的类型都有相应的Ordering(感谢隐式转换Ordering.ordered)。如果Ordering.IterableIterable[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]

答案 4 :(得分:2)

受Daniel的评论启发,这是一个递归版本:

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的列表,结果仍然适用。

答案 5 :(得分:0)

由于我已经用另一种方法实现了,所以这里是一个不使用return的非递归版本:

new Ordering[Seq[String]]() {
  override def compare(x: Seq[String], y: Seq[String]): Int = {
    x.zip(y).foldLeft(None: Option[Int]){ case (r, (v, w)) =>
        if(r.isDefined){
          r
        } else {
          val comp = v.compareTo(w)
          if(comp == 0) None
          else Some(comp)
        }
      }.getOrElse(x.size.compareTo(y.size))
    }
  }
相关问题