【问题标题】:Why does maxBy return only single item?为什么 maxBy 只返回单个项目?
【发布时间】:2011-11-22 19:31:17
【问题描述】:

跟进this question 我想知道为什么 maxBy of Traversable[T] 返回单个值T 而不是T 的序列(列表或类似)。这看起来是一个很常见的案例。例如(来自上一个问题):

查看学生的成绩列表

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

我想得到

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

有人知道maxBy 的任何标准实现吗,它返回一系列找到的项目?

【问题讨论】:

  • 那不是(降序)排序吗?
  • @delnan 不,我需要 O(N) 算法。
  • 请澄清。我读到您当前的问题,要求提供一种方法,从大小为 m 的集合中提供 n 个最大的项目。这在 O(n) 中显然是不可能的,而且通常很像排序。
  • 所以你真的想让所有的项目都等于最大的项目吗?那么就明确的说出来吧!无论如何,我认为在 O(N) 最坏情况下(这通常是所有人都关心的)是不可能的,因为最大项目的数量不是恒定的。
  • @delnan 确定是的。请参阅我的答案,它只遍历列表一次。 (当然假设比较函数是O(1)时间复杂度)

标签: scala collections


【解决方案1】:

没有单一的命令。我所知道的最短的——它将所有东西分组,而不仅仅是作为中间值的最大值——是

xs.groupBy(f).maxBy(_._1)._2

为了提高效率,折叠是很好的通用工具,用于查找总和和最大值以及各种类似的东西。基本上,任何时候你需要在收集一些答案的同时遍历你的集合,使用折叠。在这种情况下,

(xs.head /: xs.tail) {
  (biggest, next) => if (f(biggest) < f(next)) next else biggest
}

如果您不介意为每个元素重新评估函数两次,将执行maxBy(f),而

((xs.head, f(xs.head)) /: xs.tail) {
  case (scored, next) =>
    val nextscore = f(next)
    if (scored._2 < nextscore) (next, nextscore)
    else scored
}._1

每个元素只进行一次评估。如果要保留序列,可以将其修改为

(Seq(xs.head) /: xs.tail) {
  (bigs, next) =>
    if (f(bigs.head) > f(next)) bigs
    else if (f(bigs.head) < f(next)) Seq(next)
    else bigs :+ next
}

保留列表(相应的单一评估表留给读者作为练习)。

最后,如果您愿意使用一些可变变量(希望像我这样在代码块中很好地隐藏这里有)

val result = {
  var bigs = xs.take(0).toList
  var bestSoFar = f(xs.head)
  xs.foreach{ x =>
    if (bigs.isEmpty) bigs = x :: bigs
    else {
      val fx = f(x)
      if (fx > bestSoFar) {
        bestSoFar = fx
        bigs = List(x)
      }
      else if (fx == bestSoFar) bigs = x :: bigs
    }
  }
  bigs
}

(顺便说一句,这将以相反的顺序返回)。

【讨论】:

    【解决方案2】:

    如果你有

    case class Student(name: String, grade: String)
    val students = List(Student("Mike", "A"), Student("Pete", "B"), Student("Paul", "A"))
    

    那么这是一个非常简单的 O(N) 解决方案,不涉及构建任何中间列表:

    val bestGrade = students.minBy(_.grade).grade
    students.filter(_.grade == bestGrade)    //List(Student(Mike,A), Student(Paul,A))
    

    由于字符串的顺序,我们在这里使用minBy

    作为一种方法:

    def multiMinBy[A,B](xs: Traversable[A])(f: A => B)(implicit ord: Ordering[B]) = {
      val minVal = f(xs minBy f)
      xs filter (f(_) == minVal)
    }
    
    scala> multiMinBy(students)(_.grade)
    res26: Traversable[Student] = List(Student(Mike,A), Student(Paul,A))
    

    【讨论】:

      【解决方案3】:

      我知道的标准库中没有函数。

      maxBy' :: (a -> a -> Ordering) -> [a] -> [a]
      maxBy' _ [] = undefined
      maxBy' f (x:xs) = foldr step [x] xs
        where step y acc@(z:_) = case f y z of
                GT -> [y]
                EQ -> y:acc
                LT -> acc
      

      [编辑] 哎呀,这是一个 Scala 问题 :)

      翻译成 Scala,给定一个列表 xs 和一个比较器 compare

      (List(xs.head) /: xs.tail) { (acc, y) =>
        y compare acc.head match {
          case 1  => List(y)
          case 0  => y :: acc
          case -1 => acc
        }
      }
      

      【讨论】:

      • 您为什么要为 Scala 问题编写 Haskell 答案?在库中可用的内容、编写以各种不同方式对集合进行操作的函数的难易程度以及用于比较的操作符等方面存在一些重要差异。
      • @RexKerr:哇,我脑子有问题。我看到了 Traversible 和 maxBy,我的大脑进入了 Haskell 模式。我从我的提要阅读器访问了这个,并且出于某种原因认为这是一个 Haskell 问题。
      【解决方案4】:

      学生和学生名单:

      class Student (val name: String, val grade: String) {
        override def toString = grade + "::" + name
      }
      val students = List (new Student ("Mike", "A"), new Student ("Pete", "B"), new Student ("Paul", "A"))
      

      函数式、tairecursive 解决方案,在 T 列表上参数化以及比较 2 个 T 的方法:

      // ext: extreme, o: other, s:sample(student)
      @tailrec
      def collectExtreme [T](l: List[T], ext: ((T, T) => Int), carry: List[T]=List.empty) : List[T] =
        l match {
          case Nil => carry
          case s :: xs => carry match {
            case Nil => collectExtreme (xs, ext, List (s))
            case o :: _ => ext (s, o) match {
              case 0 => collectExtreme (xs, ext, s :: carry)
              case -1=> collectExtreme (xs, ext, l)
              case 1 => collectExtreme (xs, ext, carry)
            }
          }
        }
      def cmp (s: Student, o: Student): Int = s.grade(0) - o.grade(0) 
      
      collectExtreme (students, cmp) 
      

      也只在集合上运行 1 次。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-12-21
        • 1970-01-01
        • 1970-01-01
        • 2013-03-05
        • 2021-06-08
        • 2020-05-15
        • 2013-11-25
        相关资源
        最近更新 更多