【问题标题】:How to sort lines on a value in them如何根据其中的值对行进行排序
【发布时间】:2014-01-14 08:05:47
【问题描述】:

我是一个新手,在使用 PHP 多年后尝试学习 Scala。我要解决的问题非常简单,但不确定如何在 Scala 中解决。基本上,我正在从具有人名、姓氏和成绩的文件中读取内容。我需要阅读文件并按等级对名称进行排序。

文件是这样的。

Aoe Samore 3.1
Boe Sbmore 2.2
Coe Scmore 3.9
Doe Sdmore 2.4
Eoe Semore 3.5
Foe Sfmore 2.6
Goe Sgmore 3.7
Hoe Shmore 2.9
Ioe Simore 3.1
Joe Sjmore 1.2
Koe Skmore 3.2
Loe Slmore 4.0

最终结果应该是这样显示的

Loe Slmore 4
Coe Scmore 3.9
Goe Sgmore 3.7
Eoe Semore 3.5
Koe Skmore 3.2
Aoe Samore 3.1
Ioe Simore 3.1
Hoe Shmore 2.9
Foe Sfmore 2.6
Doe Sdmore 2.4
Boe Sbmore 2.2
Joe Sjmore 1.2

这就是我在 PHP 中的做法

$content = file_get_contents('grd.txt');
$lines = explode("\n",$content);

$grades_with_information = [];

foreach($lines as $line) {
    $temp = explode(' ',$line);
    $temp[2] = (float)$temp[2];
    $grades_with_information[] = $temp;
}
usort($grades_with_information, function($a, $b) {
    if ($a[2] == $b[2]) {
        return 0;
    }
    return ($a[2] > $b[2]) ? -1 : 1;
});

foreach($grades_with_information as $grade_with_information){
    echo  $grade_with_information[0].' '
          .$grade_with_information[1].' '
          .$grade_with_information[2]
          .'<br>';

}

到目前为止,我们如何在 Scala 中做到这一点,我已经做到了

val source = scala.io.Source.fromFile("grd.txt")
val lines = source.getLines()
for (a <- lines) {

}
source.close()

有什么建议/帮助/线索吗?

【问题讨论】:

  • 将标题从“如何在 Scala 中对多维进行排序”更改为“如何根据其中的值对行进行排序”。

标签: arrays list scala hashmap


【解决方案1】:
val lines = source.getLines().toSeq.sortBy(x=>x.split(" ")(2).toFloat).reverse

【讨论】:

  • 11.02.0 的顺序不正确 - 问题中有 (float)$temp[2]
  • 非常感谢您的帮助。我不知道你可以在 Scala 中做这样的事情。 :)
【解决方案2】:

如果这是一次性代码并且您不关心相同等级的行如何排序,那么

lines.toArray.sortBy( -_.split(' ')(2).toFloat )

其中lines 是输入行的任何Traversable[String],例如

scala.io.Source.fromFile("grd.txt").getLines

会对它们进行排序。请注意,在空格 character ' ' 上拆分比在包含单个空格 " " 的字符串上拆分更有效。并且因为我们为比较取反等级值,所以我们不必在排序后反转结果。

但您不希望为真正的项目贡献这样的代码 - 对于大型数据集来说效率极低,仅对数据进行部分排序,而且不是特别清晰。

通常,您希望以某种方式对具有相同等级的行进行排序,例如按姓氏升序排列,然后是名字。通常最简单的方法是按照您希望对它们进行排序的方式将数据转换为元组 - 元组的 compare 方法逐段比较 - 然后将排序后的元组映射到所需的最终形式:

lines.map( _ split ' ' ).map{
  case Array(first,last,grade) => ( -grade.toFloat, last, first )
}.toArray.sorted.map{ case (g,l,f) => (f,l,-g) }

这种方法也比使用调用toFloat 的函数调用sortBy 更有效,因为这会导致每个值被转换多次——每次将值与另一个值进行比较。最好先转换数据,然后在性能很重要时对其进行排序。

当然可以通过使用 x(0)、x(1) 和 x(2) 来缩短上面的内容,而不是将数组解构为 first、last 和 grade,但是使用名称绑定会更清楚.

我们安排通过对等级值求反来对等级进行降序排序,但这并不适用于非数字数据类型。更一般地,您可以通过显式的Ordering 提供自己的compare 方法,在这种情况下,您最好以最终形式创建元组并编写处理该问题的方法(避免将最终的map 放入最终形式的数据):

lines.map( _ split ' ' ).map{
  case Array(first,last,grade) => ( first, last, grade.toFloat )
}.toArray.sorted( {
  type T = (String,String,Float)  // first name, last name, grade
  new Ordering[T] {
    // grade DESCENDING, then last name, then first name
    def compare( a:T, b:T ) = {
      val cmp3 = b._3 compare a._3  // descending, so b first
      if ( cmp3 == 0 ) {
        val cmp2 = a._2 compare b._2
        if ( cmp2 == 0 ) a._1 compare b._1 else cmp2
      } else
        cmp3
    }
  }
} )

如果有很多条目并且此代码对性能至关重要,您可能应该使用Sorting.quickSort 对数组进行快速就地排序:

val grades = lines.map( _ split ' ' ).map{
  case Array(first,last,grade) => ( first, last, grade.toFloat )
}.toArray

scala.util.Sorting.quickSort(grades)( {
  type T = (String,String,Float)
  new Ordering[T] {
    // grade DESCENDING, then last name, then first name
    def compare( a:T, b:T ) = {
      val cmp3 = b._3 compare a._3  // descending, so b to a
      if ( cmp3 == 0 ) {
        val cmp2 = a._2 compare b._2
        if ( cmp2 == 0 ) a._1 compare b._1 else cmp2
      } else
        cmp3
    }
  }
} )

这对于大型数据集更好,原因有两个:

  1. 快速排序算法比合并排序更快(这是sorted 在通过java.util.Arrays.sort 调用一系列对象时为您提供的算法)。但是请注意,快速排序不是一种稳定的排序!我们在这里需要稳定排序,因为我们在所有字段上进行比较,所以我们可以使用快速排序。
  2. sorted 方法将复制数组并对其进行排序。这意味着,如果您有数百万行,那么堆上就有两个巨大的对象,而您实际上只需要一个——在我们完成排序后,我们不需要未排序的数据。像这样的大对象通常直接从分代存储中分配出来,在被收集之前它们会在其中停留一段时间。就地排序可以防止不必要的堆滥用,更不用说不必要地复制数据所花费的时间了。

compare 方法很丑,因为我们只是使用元组;如果是我,我会定义一个案例类来保存数据并将数据映射到它的实例中:

case class Grade( first:String, last:String, grade:Float )
val grades = lines.map( _ split ' ' ).map{
  case Array(first,last,grade) => Grade( first, last, grade.toFloat )
}.toArray

那么你可以更干净地编写compare方法:

scala.util.Sorting.quickSort(grades)(
  new Ordering[Grade] {
    def compare( a:Grade, b:Grade ) = {
      val gradeCmp = b.grade compare a.grade  // descending, so b to a
      if ( gradeCmp == 0 ) {
        val lastCmp = a.last compare b.last
        if ( lastCmp == 0 ) a.first compare b.first else lastCmp
      } else
        gradeCmp
    }
  }
)

【讨论】:

  • 非常感谢您的详细回答。您的评论无疑对我更好地理解函数式编程和 Scala 有很大帮助。谢谢。
【解决方案3】:
def getDouble(line: String) = line.split(" ").apply(2).toDouble

for (l <- lines.toSeq.sortBy(i => - getDouble(i)))
  println(l)
  1. 使用toSeqIterator转换为内存中的集合。
  2. 按取反的双精度值排序。
  3. 打印行或排序集合。

【讨论】:

    【解决方案4】:

    用三引号粘贴数据。

    """Aoe Samore 3.1
    Boe Sbmore 2.2
    Coe Scmore 3.9
    Doe Sdmore 2.4
    Eoe Semore 3.5
    Foe Sfmore 2.6
    Goe Sgmore 3.7
    Hoe Shmore 2.9
    Ioe Simore 3.1
    Joe Sjmore 1.2
    Koe Skmore 3.2
    Loe Slmore 4.0"""
    

    然后做一些字符串操作,按第二列排序,然后反向得到降序。

    scala> data.lines.map(s => s.split(" ")).toList.sortBy(row => row(2).toFloat).reverse
    
    res16: List[Array[String]] = List(Array(Loe, Slmore, 4.0), Array(Coe, Scmore, 3.9), Array(Goe, Sgmore, 3.7), Array(Eoe, Semore, 3.5), Array(Koe, Skmore, 3.2), Array(Ioe, Simore, 3.1), Array(Aoe, Samore, 3.1), Array(Hoe, Shmore, 2.9), Array(Foe, Sfmore, 2.6), Array(Doe, Sdmore, 2.4), Array(Boe, Sbmore, 2.2), Array(Joe, Sjmore, 1.2))
    

    要将其转换为所需的字符串格式,请进行更多字符串操作。

    scala> res16.map(_.mkString(" ")).mkString("\n")
    res17: String = 
    Loe Slmore 4.0
    Coe Scmore 3.9
    Goe Sgmore 3.7
    Eoe Semore 3.5
    Koe Skmore 3.2
    Ioe Simore 3.1
    Aoe Samore 3.1
    Hoe Shmore 2.9
    Foe Sfmore 2.6
    Doe Sdmore 2.4
    Boe Sbmore 2.2
    Joe Sjmore 1.2
    

    【讨论】:

    • 11.02.0 的顺序不正确 - 问题中有 (float)$temp[2]
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-06
    • 1970-01-01
    • 1970-01-01
    • 2023-01-27
    • 1970-01-01
    相关资源
    最近更新 更多