Scala - 计算交错数组总和的惯用方法?

时间:2010-07-17 17:15:19

标签: scala functional-programming

我正在尝试计算Scala中图像的平均颜色,其中“average”被定义为redSum / numpixels,greenSum / numpixels,blueSum / numpixels。

以下是我用来计算图像矩形区域(栅格)的平均颜色的代码。

// A raster is an abstraction of a piece of an image and the underlying
// pixel data.
// For instance, we can get a raster than is of the upper left twenty
// pixel square of an image
def calculateColorFromRaster(raster:Raster): Color = {
  var redSum = 0
  var greenSum = 0
  var blueSum = 0

  val minX = raster.getMinX()
  val minY = raster.getMinY()

  val height = raster.getHeight()
  val width = raster.getWidth()
  val numPixels = height * width

  val numChannels = raster.getNumBands() 

  val pixelBuffer = new Array[Int](width*height*numChannels)
  val pixels = raster.getPixels(minX,minY,width,height,pixelBuffer)

  // pixelBuffer now filled with r1,g1,b1,r2,g2,b2,...
  // If there's an alpha channel, it will be r1,g1,b1,a1,r2,... but we skip the alpha
  for (i <- 0 until numPixels) {
    val redOffset = numChannels * i
    val red = pixels(redOffset)
    val green = pixels(redOffset+1)
    val blue = pixels(redOffset+2)

    redSum+=red
    greenSum+=green
    blueSum+=blue
  }
  new Color(redSum / numPixels, greenSum / numPixels, blueSum / numPixels)
}

是否有更惯用的Scala方法可以总结不同的交错数组?某种方法可以在数组上进行投影,迭代每个第4个元素?我对Stack Overflow社区可以提供的任何专业知识感兴趣。

3 个答案:

答案 0 :(得分:10)

pixels.grouped(3)将返回Iterator[Array[Int]]个3元素数组。所以

val pixelRGBs = pixels.grouped(3)

val (redSum, greenSum, blueSum) = 
  pixelRGBs.foldLeft((0, 0, 0)) {case ((rSum, gSum, bSum), Array(r, g, b)) => (rSum + r, gSum + g, bSum + b)}

new Color(redSum / numPixels, greenSum / numPixels, blueSum / numPixels)

更新:要处理3和4通道,我会写

pixels.grouped(numChannels).foldLeft((0, 0, 0)) {case ((rSum, gSum, bSum), Array(r, g, b, _*)) => (rSum + r, gSum + g, bSum + b)}

_*这里基本上意味着“0或更多元素”。请参阅http://programming-scala.labs.oreilly.com/ch03.html

中的“序列匹配”

答案 1 :(得分:6)

对于这个问题,这是疯狂的过度杀伤,但是我对数据集进行了大量的分区缩减,并为它构建了一些实用功能。其中最常见的是reduceBy,它采用集合(实际上是Traversable),分区函数,映射函数和简化函数,并生成从分区到缩减/映射值的映射。

  def reduceBy[A, B, C](t: Traversable[A], f: A => B, g: A => C, reducer: (C, C) => C): Map[B, C] = {
    def reduceInto(map: Map[B, C], key: B, value: C): Map[B, C] =
      if (map.contains(key)) {
        map + (key -> reducer(map(key), value))
      }
      else {
        map + (key -> value)
      }
    t.foldLeft(Map.empty[B, C])((m, x) => reduceInto(m, f(x), g(x)))
  }

鉴于机器很重,你的问题就变成了

val sumByColor:Map[Int, Int] = reduceBy(1 until numPixels, (i => i%numChannels), (i=>pixel(i)), (_+_))
return Color(sumByColor(0)/numPixels, sumByColor(1)/numPixels, sumByColor(2)/numPixels)

在高阶编程的强大功能之前静音。

答案 2 :(得分:2)

这是一个很好的问题,因为我认为您提供的解决方案是惯用的解决方案!命令式模型非常适合这个问题。我试图找到一个读得很好的简单功能解决方案,但我无法做到。

我认为带有pixels.grouped(3)的那个相当不错,但我不确定它比你拥有的更好。

我自己的“非命令性”解决方案涉及使用+运算符/方法定义案例类:

import java.awt.image.Raster
import java.awt.Color

def calculateColorFromRaster(raster:Raster): Color = {
  val minX = raster.getMinX()
  val minY = raster.getMinY()

  val height = raster.getHeight()
  val width = raster.getWidth()
  val numPixels = height * width

  val numChannels = raster.getNumBands()

  val pixelBuffer = new Array[Int](width*height*numChannels)
  val pixels = raster.getPixels(minX,minY,width,height,pixelBuffer)

  // pixelBuffer now filled with r1,g1,b1,r2,g2,b2,...
  // If there's an alpha channel, it will be r1,g1,b1,a1,r2,... but we skip the alpha

  // This case class is only used to sum the pixels, a real waste of CPU!
  case class MyPixelSum(r: Int, g: Int, b: Int){
    def +(sum: MyPixelSum) = MyPixelSum(sum.r +r, sum.g + g, sum.b + b)
  }

  val pixSumSeq= 0 until numPixels map((i: Int) => {
    val redOffset = numChannels * i
    MyPixelSum(pixels(redOffset), pixels(redOffset+1),pixels(redOffset+2))
  })
  val s = pixSumSeq.reduceLeft(_ + _)

  new Color(s.r / numPixels, s.g / numPixels, s.b / numPixels)
}