如何按字典顺序比较Scala元组?

18

给定两个元素数量相同的元组,如何进行词典顺序比较?似乎应该像下面的代码片段一样简单,但实际上并不是。有没有简单的示例可以说明如何做到这一点?

var x = (1,2,3) < (1,2,4)

如果它们是列表,我可以定义一个递归函数来比较列表的头部,直到找到一个差异或列表的末尾,但我不认为我可以那样做元组。

3个回答

29

这并不简单,因为虽然

var x = (1,2,3) < (1,2)

看起来相当简单。

var x = (1,false,3) < (1,2)

如果存在非有序类型,应该如何处理?如果在同一个元组位置上有不同的类型,应该怎么办?

你是否要求所有类型都相同?如果是这样,那么你就没有一个元组。元组的全部意义就是它的元数是固定的(你静态地知道它的大小),而且每个元素可以是不同的类型。

如果我遇到这个问题——我会尽力避免这种情况——我会使用 Shapeless 将元组转换成类似于 HLists 的东西,然后尝试在其上进行比较。

编辑

啊,现在这很容易解决:

import scala.math.Ordering.Implicits._
var x = (1,2,3) < (1,2,4)

这些额外的隐式值不会自动提供,因为在某些情况下它们可能导致隐式值发散。


很好的观点,Daniel。我已经编辑了我的问题,以表明比较元组始终具有相同的arity。至于非有序类型,我的元组应该具有符号(这就是我选择元组而不是列表的原因),但是因为我没有找到任何排序符号的方法,所以我将使用数字。那么我应该使用列表吗?如果我真的需要向列表中添加符号怎么办? - lasaro
1
@lasaro 好的,既然你简化了问题,那么就有一个相对简单的解决方案。请查看编辑后的答案。 - Daniel C. Sobral
谢谢你的简明示例,Daniel。 - lasaro
1
这在2.11.8版本中似乎不起作用,但如果按建议import scala.math.Ordered.orderingToOrdered导入,它就能工作 https://dev59.com/JWIk5IYBdhLWcg3wWtFz - Moodragonx

14

如果你只需要使用<,那么Daniel的解决方案是可行的。但是如果你需要一个compare方法,可以像下面这样做(例如)。

Daniel的解决方案适用于使用<的情况,但如果您需要一个compare方法,则可以执行以下操作(例如)。

implicitly[Ordering[Tuple2[Int, Int]]].compare((1,2), (2,3))

对于所有具有可比较部分的元组,都定义了顺序。


2

最简单的方法是在它们上面定义一个隐式的Ordering[T],但是你必须将这个排序器传递给sort函数(或任何其他想要进行比较的函数)。还可以通过隐式传递它。

另一种方法是通过隐式转换扩展元组类的<操作符:

implicit def compareTuple[T](lhs: (T,T)) = new {
   def <(rhs: (T,T)) = lhs._1<rhs._1 || (lhs._1==rhs._1 && lhs._2<rhs._2)
}

编辑: 如果你还想拥有其他比较运算符,你可以通过继承Ordered[T]类来获得:

implicit def compareTuple[T](lhs: (T,T)) = new Ordered[(T,T)] {
   def compare(rhs: (T,T)) = ...
}

编辑2: 如果您还需要比较不同大小的元组,可以使用所有元组类中定义的productIterator函数(请参见文档),它允许您获得元组的迭代器。这样,您就可以像处理列表一样编写函数。

编辑3: 下面是一个示例:

implicit def compareTuple[T <: Product](lhs: T) = new Ordered[T] {
    def compare[U <: Product](rhs: U) = {
        def compare(lhs: Any, rhs: Any) = ...
        def iteratorCompare(lhs: Iterator[Any], rhs: Iterator[Any]):Int = 
            if(!lhs.hasNext)
                if(!rhs.hasNext)
                    0
                else
                    -1
            else if(!rhs.hasNext)
                1
            else
                compare(lhs.next,rhs.next)
        iteratorCompare(lhs.productIterator,rhs.productIterator)
    }
}

但是使用这种方法必须要注意类型。因为函数不知道元组元素的类型(在同一个元组中它们可以是不同的),它只能提供一个Iterator[Any]。所以你需要定义一个compare(Any, Any)函数来处理你想要的内容。


1
你可以尝试启用-Xexperimental选项,这将选择LUB而不是Any - soc
谢谢Heinzi的完整回答,我从中学到了很多,但对于我的问题和Scala技能水平而言,Daniel的回答更为直接。 - lasaro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接