【问题标题】:Sorting a collection of collections by indices of inner collection elements in Scala在Scala中通过内部集合元素的索引对集合进行排序
【发布时间】:2020-03-18 12:09:44
【问题描述】:

让我们有一个集合如下:

type Row = IndexedSeq[Any]
type RowTable = IndexedSeq[Row]

val table: RowTable = IndexedSeq(
    IndexedSeq(2, "b", ... /* some elements of type Any*/),
    IndexedSeq(1, "a", ...),
    IndexedSeq(2, "c", ...))

RowTable 中的每个Row“具有相同的架构”,这意味着例如如果表中的第一行包含Int, String, ...,那么表中的第二行包含相同类型的元素相同的顺序,即Int, String, ...

我想根据Row 元素的给定索引和该元素的排序方向(升序或降序)对RowTable 中的Rows 进行排序。

例如,上面的集合将按这种方式排序,索引 0 升序和索引 1 降序,其余元素在排序中并不重要:

1, "a", ...
2, "c", ...
2, "b", ...

由于RowIndexedSeq[Any],我们不知道每个元素的类型来比较它;但是,我们知道它可能会被转换为 Comparable[Any],因此有 compareTo() 方法将其与另一行中相同索引下的元素进行比较。

如上所述,确定排序顺序的索引在我们开始排序之前是未知的。如何在 Scala 中编写代码?

【问题讨论】:

  • 拥有Any 几乎总是一种代码味道。这通常意味着设计错误。
  • 这是与 Java 和遗留代码的互操作;我也讨厌Any:\
  • 关于哪些字段排序以及如何排序的信息是静态的还是动态的?关于列数及其类型的信息是静态的还是动态的?
  • 两者都是动态的

标签: scala sorting


【解决方案1】:

(对不起,unidiomatic(可能没有编译)代码;我可能会根据要求提供帮助)

我们可以使用下面的代码对表格进行排序

table.sortWith {
   case (tupleL, tupleR) => isLessThan(tupleL, tupleR)
}

其中isLessThan 定义如下(对 Scala 来说是统一的,ik):

def isLessThan(tupleL: Row, tupleR: Row): Boolean = {
      var i = 0
      while (i < sortInfos.length) {
        val sortInfo = sortInfos(i)
        val result = tupleL(sortInfo.fieldIndex)
            .asInstanceOf[Comparable[Any]].compareTo(
          tupleR(sortInfo.fieldIndex)
            .asInstanceOf[Comparable[Any]])

        if (result != 0) {
          if (sortInfo.isDescending) {
            if (result > 0)
              return true
            else
              return false
          } else {
            if (result < 0)
              return true
            else
              return false
          }
        }

        i += 1
      }
      true
    }

其中sortInfosIndexedSeq[SortInfo]

case class SortInfo(val fieldIndex: Int, val isDescending: Boolean)

【讨论】:

    【解决方案2】:

    首先,比较一对Any是一个糟糕的设计。

    默认情况下,scala 不提供任何获取Ordering[Any] 的方法。因此,如果你想比较一对Any,你应该自己实现Ordering[Any]

    object AnyOrdering extends Ordering[Any] {
      override def compare(xRaw: Any, yRaw: Any): Int = {
        (xRaw, yRaw) match {
          case (x: Int, y: Int) => Ordering.Int.compare(x, y)
          case (_: Int, _) => 1
          case (_, _: Int) => -1
          ...
          case (x: String, y: String) => Ordering.String.compare(x, y)
          case (_: String, _) => 1
          case (_, _: String) => -1
          ...
          case (_, _) => 0
        }
      }
    }
    

    在您的示例中,您想递归比较两个 IndexedSeq[T]。 Scala 不提供任何递归Ordering,您也需要实现它:

    def recOrdering[T](implicit ordering: Ordering[T]): Ordering[IndexedSeq[T]] = new Ordering[IndexedSeq[T]] {
      override def compare(x: IndexedSeq[T], y: IndexedSeq[T]): Int = compareRec(x, y)
    
      @tailrec
      private def compareRec(x: IndexedSeq[T], y: IndexedSeq[T]): Int = {
        (x.headOption, y.headOption) match {
          case (Some(xHead), Some(yHead)) =>
            val compare = ordering.compare(xHead, yHead)
            if (compare == 0) {
              compareRec(x.tail, y.tail)
            } else {
              compare
            }
          case (Some(_), None) => 1
          case (None, Some(_)) => -1
        }
      }
    }
    

    之后,您终于可以对您的收藏进行排序了:

    table.sorted(recOrdering(AnyOrdering))
    

    【讨论】:

    • 感谢您的回答。但是,在问题中提到 Any 可以转换为 Comparable,您的答案省略了。此外,您的答案中还不清楚按索引排序的工作原理。
    • 您无法比较不同类型的 Comparable。因此,除了 Comparable 之间的排序之外,您还需要在类型之上构建排序。
    • 问题并没有那么清楚,抱歉,但每个子集合中元素的顺序是相同的
    • 你想要一个排序来查找每种特定类型的排序吗?
    • 这是错误的。同时拥有case (_: String, _) =&gt; 1case (_: Int, _) =&gt; 1 会使它变得不可预测,结果将取决于排序算法和项目的顺序。原则上,compare(a,b) == -compare(b,a) 对于任何输入都应该为真,但对于任何对 (String, Int) 都不是。
    【解决方案3】:

    这是Ordering[IndexedSeq[Any]] 的工作示例:

    val table: IndexedSeq[IndexedSeq[Any]] = IndexedSeq(
      IndexedSeq(2, "b", "a"),
      IndexedSeq(2, "b"),
      IndexedSeq("c", 2), 
      IndexedSeq(1, "c"),
      IndexedSeq("c", "c"), 
      //IndexedSeq((), "c"), //it will blow in runtime
      IndexedSeq(2, "a"),
    )
    
    
    implicit val isaOrdering:Ordering[IndexedSeq[Any]] = { (a, b) => 
     a.zip(b).filter {case (a, b)=> a != b}.collectFirst {
       case (a:Int, b:Int) => a compare b
       case (a:String, b:String) => a compare b
       case (a:String, b:Int) => 1 //prefere ints over strings
       case (a:Int, b:String) => -1 //prefere ints over strings
       case _ => throw new RuntimeException(s"cannot compare $a to $b")
     }.getOrElse(a.length compare b.length) //shorter will be first
    }
    
    println(table.sorted) //used implicitly
    println(table.sorted(isaOrdering)) 
    //Vector(Vector(1, c), Vector(2, a), Vector(2, b), Vector(2, b, a), Vector(c, 2), Vector(c, c))
    

    https://scalafiddle.io/sf/yvLEnYL/4

    或者如果你真的需要以某种方式比较不同的类型,这是我能找到的最好的:

    implicit val isaOrdering:Ordering[IndexedSeq[Any]] = { (a, b) => 
     a.zip(b).filter {case (a, b)=> a != b}.collectFirst {
       case (a:Int, b:Int) => a compare b
       case (a:String, b:String) => a compare b
       //add your known types here
       // ...
       //below is rule that cares about unknown cases. 
       //We don't know types at all, at best what we can do is compare equality.
       //If they are equal then return 0... if not we throw
       //this could be also very slow (don't tested)  
       case (a, b) => 
       //not nice but it is stable at least
        val ac = a.getClass.getName
        val bc = b.getClass.getName
        ac.compare(bc) match {
          case 0 => if (ac == bc) 0 else throw new RuntimeException(s"cannot compare $a to $b")
          case x => x
        } 
     }.getOrElse(a.length compare b.length) //shorter will be first
    }
    

    https://scalafiddle.io/sf/yvLEnYL/5

    当我们无法比较它们时,此实现将在运行时失败。

    【讨论】:

      猜你喜欢
      • 2015-01-26
      • 1970-01-01
      • 2016-08-28
      • 2013-02-25
      • 1970-01-01
      • 2018-05-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多