2012-04-15 37 views
17

Có vẻ như vấn đề dễ dàng đối với bất kỳ loại Số cụ thể nào là Số/Số nguyên nhưng rất khó để viết trong trường hợp chung.Làm thế nào để thực hiện chức năng chung trung bình trong scala?

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

Làm cách nào để thực hiện điều này cho bất kỳ loại số nào (Int, Float, Double, BigDecemial)?

Trả lời

29

Bạn cần phải vượt qua một ngầm Numeric mà sẽ cho phép tổng kết và chuyển đổi thành Double:

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

Trình biên dịch sẽ cung cấp các ví dụ chính xác cho bạn:

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 

Bạn có thể sử dụng chức năng để xác định chế độ xem ẩn (miễn là bạn tuyên truyền phụ thuộc số ngầm định):

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

scala> List(1,2,3,4).avg 
res13: Double = 2.5 
+2

lớp 'ngầm IterebleWithAvg [T: Numeric] (dữ liệu: Iterable [T]) {def AVG = trung bình (dữ liệu)}' nên được ưa thích kể từ hôm nay. –

+0

Chúng ta cũng nên xử lý không lặp lại độ dài 0, ném một lỗi. – Marboni

13

Đây là cách tôi định nghĩa nó trong mã của tôi.

Thay vì sử dụng Numeric, tôi sử dụng Fractional, vì Fractional xác định thao tác chia (Numeric không nhất thiết phải có bộ phận). Điều này có nghĩa là khi bạn gọi số .avg, bạn sẽ lấy lại cùng loại mà bạn đã nhập, thay vì luôn nhận được Double.

Tôi cũng xác định nó trên tất cả các bộ sưu tập GenTraversableOnce để nó hoạt động trên, ví dụ: 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) 

Thông báo như thế nào nếu chúng ta cung cấp cho nó một bộ sưu tập của Double, chúng tôi nhận lại Double và nếu chúng ta cung cấp cho nó BigDecimal, chúng tôi nhận lại BigDecimal. Chúng tôi thậm chí có thể xác định loại số Fractional của riêng mình (đôi khi tôi thực hiện) và nó sẽ hoạt động cho điều đó.

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 

Tuy nhiên, Int không phải là một loại Fractional, có nghĩa là nó không có ý nghĩa để có được một Int và kết quả của trung bình Int s, vì vậy chúng tôi cần phải có một trường hợp đặc biệt cho Int có thể chuyển đổi để a 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) 

Vì vậy, trung bình Int s cho chúng ta một Double:

scala> Iterator(1, 2, 3, 4, 5).avg 
res2: Double = 3 
Các vấn đề liên quan