【问题标题】:How to find students with the best grades in a list?如何在列表中找到成绩最好的学生?
【发布时间】:2011-11-11 11:42:51
【问题描述】:

假设,我有一个Students 的列表。 Students 具有 name、birth date、grade 等字段。您如何在 Scala 中找到具有最佳 grade 的 Students?

例如:

List(Student("Mike", "A"), Student("Pete", "B"), Student("Paul", A))"

我想得到

列表(学生(“迈克”,“A”),学生(“保罗”,A))

显然,我可以找到max grade(上面列表中的“A”)然后filter列表

students.filter(_.grade == max_grade)

这个解决方案是O(N),但在列表中运行了两次。您能提出更好的解决方案吗?

【问题讨论】:

  • “成绩最好”是含糊的。你想要什么?前10名?前10名%? X 级以上?
  • @Kim,无论什么给他的任务最好的分数,这肯定是:)
  • 我的建议是忘记这个 O(N) 的业务,并担心你有多少次通过列表。除非您的班级有数百万学生并且该方法对时间要求很高,否则请使用最简单的代码来满足您的需求。

标签: scala list-comprehension


【解决方案1】:

遍历列表两次可能是最好的方法,但是如果您坚持只运行一次的解决方案,您可以使用折叠(这里适用于空列表):

(List[Student]() /: list){ (best,next) => best match {
  case Nil => next :: Nil
  case x :: rest =>
    if (betterGrade(x,next)) best
    else if (betterGrade(next,x)) next :: Nil
    else next :: best
}}

如果您不熟悉折叠,请在答案here 中对其进行描述。它们是在您传递集合(例如列表)时积累某些东西的一般方式。如果你不熟悉匹配,你可以用isEmpty 和head 做同样的事情。如果您希望学生按照他们在原始列表中出现的顺序排列,请在最后运行 .reverse。

【讨论】:

    【解决方案2】:

    使用 foldLeft 你只遍历学生列表一次:

    scala> students.foldLeft(List.empty[Student]) {
         | case (Nil, student) => student::Nil
         | case (list, student) if (list.head.grade == student.grade) => student::list
         | case (list, student) if (list.head.grade > student.grade) => student::Nil
         | case (list, _) => list
         | }
    res17: List[Student] = List(Student(Paul,A), Student(Mike,A))
    

    【讨论】:

      【解决方案3】:

      已经有 6 个答案了,但我还是觉得有必要补充一下:

      case class Lifted(grade: String, students: List[Student]) {
        def mergeBest(other: Lifted) = grade compare other.grade match {
          case  0 => copy(students = other.students ::: students)
          case  1 => other
          case -1 => this
        }
      }
      

      这个小案例类会将Student 提升到一个跟踪最佳成绩的对象和一个包含学生的列表单元格中。它还考虑了合并的逻辑:如果新学生有

      • 相同年级 => 将新学生合并到结果列表中,较短的放在最前面以提高效率 - 否则结果不会是 O(n)
      • 高年级 => 用新学生替换当前最好的学生
      • 低年级 => 保持当前最佳

      然后可以使用reduceLeft 轻松构造结果:

      val result = { 
        list.view.map(s => Lifted(s.grade, List(s)))
          .reduceLeft((l1, l2) => l1.mergeBest(l2))
          .students
      }
      // result: List[Student] = List(Student(Paul,A), Student(Mike,A))
      

      PS。我赞成您的问题 - 基于生成的响应的绝对数量

      【讨论】:

        【解决方案4】:

        您可以在学生列表中使用filter。

        case class Student(grade: Int)
        val students = ...
        val minGrade = 5
        students filter ( _.grade < minGrade)
        

        也适用于String 类型

        【讨论】:

        • 它一般不适用于字符串。例如"10" &lt; "2" 为真。
        • @LuigiPlinge 当然,谢谢。我想到了他的例子,将成绩表示为单个字符。
        【解决方案5】:

        排序后,可以使用takeWhile来避免第二次迭代。

        case class Student(grade: Int)
        val list = List(Student(1), Student(3), Student(2), Student(1), Student(25), Student(0), Student (25))
        
        val sorted = list.sortWith (_.grade > _.grade)
        sorted.takeWhile (_.grade == sorted(0).grade)
        

        它仍然对整个事情进行排序,即使是我们不感兴趣的 1、3、0 和 -1 级,在使用生奶油之前,它仍然是短代码。

        更新:

        可以并行执行的第二种方法是,拆分列表,并取每一边的最大值,然后只取较高的一个,如果有的话 - 否则两者都有:

        def takeMax (l: List[Student]) : List [Student] = l.size match {
            case 0 => Nil
            case 1 => l 
            case 2 => if (l(0).grade > l(1).grade) List (l(0)) else 
              if (l(0).grade < l(1).grade) List (l(1)) else List (l(0), l(1))
            case _ => {
              val left = takeMax (l.take (l.size / 2))
              val right= takeMax (l.drop (l.size / 2))
             if (left (0).grade > right(0).grade) left else 
             if (left (0).grade < right(0).grade) right else left ::: right }
        }
        

        当然,我们喜欢将学生以及比较他们两个的方法考虑在内。

        def takeMax [A] (l: List[A], cmp: ((A, A) => Int)) : List [A] = l.size match {
            case 0 | 1 => l 
            case 2     => cmp (l(0), l(1)) match {
              case 0 => l 
              case x => if (x > 0) List (l(0)) else List (l(1))
            }
            case _ => {
              val left = takeMax (l.take (l.size / 2), cmp)
              val right= takeMax (l.drop (l.size / 2), cmp)
              cmp (left (0), right (0)) match {
                case 0 => left ::: right
                case x => if (x > 0) left else right }
             }
        }
        
        def cmp (s1: Student, s2: Student) = s1.grade - s2.grade 
        takeMax (list, cmp) 
        

        【讨论】:

        • +1 用于分解策略(尽管由于列表具有线性访问权限,因此不太可能赢得性能;这仍然是一个好主意)。
        猜你喜欢
        • 2013-02-10
        • 1970-01-01
        • 1970-01-01
        • 2021-09-18
        • 1970-01-01
        • 2020-02-01
        • 2020-04-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多