Как реализовать стандартную среднюю функцию в scala?

17

Кажется простой проблемой для любого конкретного типа Number i.e. Double / Integer, но его трудно записать в общем случае.

implicit def iterebleWithAvg(data:Iterable[Double]) = new {
    def avg:Double = data.sum / data.size
}

Как реализовать это для любого числа (Int, Float, Double, BigDecemial)?

    
задан yura 15.04.2012 в 09:25
источник

2 ответа

29

Вам нужно передать неявный Numeric , который позволит суммировать и преобразовать в Double:

def average[T]( ts: Iterable[T] )( implicit num: Numeric[T] ) = {
  num.toDouble( ts.sum ) / ts.size
}

Компилятор предоставит вам правильный экземпляр:

scala> average( List( 1,2,3,4) )
res8: Double = 2.5

scala> average( 0.1 to 1.1 by 0.05 )
res9: Double = 0.6000000000000001

scala> average( Set( BigInt(120), BigInt(1200) ) )
res10: Double = 660.0

Вы можете использовать функцию для определения неявного представления (если вы распространяете неявную числовую зависимость):

implicit def iterebleWithAvg[T:Numeric](data:Iterable[T]) = new {
  def avg = average(data)
}

scala> List(1,2,3,4).avg
res13: Double = 2.5
    
ответ дан paradigmatic 15.04.2012 в 09:50
источник
  • неявный класс IterebleWithAvg [T: числовой] (данные: Iterable [T]) {def avg = average (data)} должны быть предпочтительными на сегодняшний день. –  Erik Allik 25.04.2014 в 18:35
  • Мы также должны обрабатывать нулевую длину итерации, вызывая ошибку. –  Marboni 09.01.2016 в 23:03
13

Вот как я его определяю в своем коде.

Вместо использования Numeric , я использую Fractional , так как Fractional определяет операцию деления ( Numeric не обязательно имеет деление). Это означает, что когда вы вызываете .avg , вы получите тот же тип, что и вы, а не всегда получаете Double .

Я также определяю его по всем коллекциям GenTraversableOnce , чтобы он работал, например, Iterator .

class EnrichedAvgFractional[A](self: GenTraversableOnce[A]) {
  def avg(implicit num: Fractional[A]) = {
    val (total, count) = self.toIterator.foldLeft((num.zero, num.zero)) {
      case ((total, count), x) => (num.plus(total, x), num.plus(count, num.one))
    }
    num.div(total, count)
  }
}
implicit def enrichAvgFractional[A: Fractional](self: GenTraversableOnce[A]) = new EnrichedAvgFractional(self)

Обратите внимание, что если мы дадим ему коллекцию Double , мы вернемся к Double , и если мы дадим ей BigDecimal , мы вернемся назад BigDecimal . Мы могли бы даже определить наш собственный номер Fractional (который я делаю иногда), и он будет работать для этого.

scala> Iterator(1.0, 2.0, 3.0, 4.0, 5.0).avg
res0: Double = 3.0

scala> Iterator(1.0, 2.0, 3.0, 4.0, 5.0).map(BigDecimal(_)).avg
res1: scala.math.BigDecimal = 3.0

Однако Int не является видом Fractional , что означает, что нет смысла получать Int и результат усреднения Int s, поэтому у нас должен быть специальный случай для Int , которое преобразуется в Double .

class EnrichedAvgInt(self: GenTraversableOnce[Int]) {
  def avg = {
    val (total, count) = self.toIterator.foldLeft(0, 0) {
      case ((total, count), x) => (total + x, count + 1)
    }
    total.toDouble / count
  }
}
implicit def enrichAvgInt(self: GenTraversableOnce[Int]) = new EnrichedAvgInt(self)

Таким образом, усреднение Int s дает нам Double :

scala> Iterator(1, 2, 3, 4, 5).avg
res2: Double = 3
    
ответ дан dhg 15.04.2012 в 18:36
источник