合并scala列表中的元素

时间:2010-03-11 15:31:26

标签: algorithm scala

我正在尝试将以下Java代码段移植到Scala。它需要一个MyColor个对象列表,并合并彼此增量内的所有对象。这似乎是一个可以使用Scala的一些功能部分优雅地解决的问题。有什么提示吗?

List<MyColor> mergedColors = ...;
MyColor lastColor = null;
for(Color aColor : lotsOfColors) {
  if(lastColor != null) {
    if(lastColor.diff(aColor) < delta) {
      lastColor.merge(aColor);
      continue;
    }
  }
  lastColor = aColor;
  mergedColors.add(aColor);
}

4 个答案:

答案 0 :(得分:7)

这是另一个递归解决方案,它具有尾递归的优点(因此没有堆栈溢出的可能性)但是在缺点上会进行大量的列表操作,因此可能会浪费。最后反向调用是将输出颜色放回输入顺序,但如果您不关心顺序则不需要。

def processColors(colors: List[Color], delta: Double): List[Color] = {
  def process(in: List[Color], accum: List[Color]): List[Color] = in match {
      case x :: y :: ys if x.diff(y) < delta => process( x.merge(y) :: ys, accum )
      case x :: xs                           => process( xs, x :: accum )
      case Nil                               => accum
    }

  process(colors, Nil).reverse
}

答案 1 :(得分:4)

我假设您以某种方式将颜色排列在列表中,使得颜色空间中的颜色“接近”(即具有低diff值)在列表中相邻。然后我会使用折叠:

val unmergedColors: List[MyColor] = ...
val mergedColors = (Nil:List[MyColor] /: unmergedColors)( (list,c) => {
  list match {
    case oldc :: rest if (oldc.diff(c) < delta) => oldc.merge(c) :: rest
    case _ => c :: list
  }
}).reverse

在这里,我假设merge被改变为返回一个新颜色,即前两个合并的颜色(这样你的颜色是不可变的);否则,你在第一种情况下oldc.merge(c) ; list

让我们看看这里发生了什么。

我们从新颜色的空列表开始。然后,对于未合并列表中的每种颜色,我们有两种情况:

  • 合并列表有一个头部,头部的颜色在我们测试的颜色的三角形内。在这种情况下,合并头部和新颜色,并使用新头传递保存的列表。
  • 否则,将新颜色添加到成长列表的前面并传递它。

最后,由于我们将这些作为堆栈操作使用,我们通过反转列表来完成。

答案 2 :(得分:3)

在我看来,这个问题可能导致各种问题,无论问题是什么。例如:

  • 解决方案是否应在列表的初始排序中保持不变
  • 你应该对合并的未合并的值进行差异处理

但这里有一些使用递归的乐趣(虽然它不是尾递归当然可以这样做),例如:

type C = MyColor
type Cs = list[C]

def merge(val delta: Double, diff: (C, C) => Double, colors: Cs) : Cs = {

   def _mergeHeadAndGTDiff(head: C, tail: Cs) : Cs = { 
      val (toMerge, rest) = tail.span(diff(head, _) < delta) 
      val newHead = (head :: toMerge).reduceLeft(_ merge _)
      newHead :: (rest match {
           case Nil     => Nil
           case x :: xs => _mergeHeadAndGTDiff(newHead, xs) 
        })          
   }

   colors match {
      case Nil     => Nil
      case x :: xs => _mergeHeadAndGTDiff(x, xs)
   }
}

解决方案如下:

  1. 抓住头脑
  2. 获取尾部的所有元素,这些元素可以与头部合并,然后将它们合并(可以使用折叠)成新的头部
  3. 将新头部放在一个尾部上,该尾部通过在步骤#2中取出所有无法合并的东西然后在步骤#1中将它们重新插入(在尾部为空的情况下使用强制终止子句)
  4. 我认为这更适合Stream。请注意,我假设列表最初由diff 排序,因为我使用的是span。如果将其替换为partition,则无需这样做。

答案 3 :(得分:1)

我试着折叠:

def merge(lotsOfColor: List[MyColor], delta: Double): List[MyColor] =
  lotsOfColor.tail.foldLeft((List(lotsOfColor.head), lotsOfColor.head)) {
    case ((mergedColors, lastColor), aColor) if (lastColor diff aColor) < delta =>
      (mergedColors, lastColor merge aColor)
    case ((mergedColors, _), aColor) => (aColor :: mergedColors, aColor)
  }._1.reverse

或者,略有不同,

def merge(lotsOfColor: List[MyColor], delta: Double): List[MyColor] =
  lotsOfColor.tail.foldLeft((List(lotsOfColor.head), lotsOfColor.head)) {
    case ((mergedColors, lastColor), aColor) =>
      if ((lastColor diff aColor) < delta)
        (mergedColors, lastColor merge aColor)
      else
        (aColor :: mergedColors, aColor)
  }._1.reverse

在Scala中使用ListBuffer还有另一个很酷的技巧,以避免在最后反向:

import scala.collection.mutable.ListBuffer
def merge(lotsOfColor: List[MyColor], delta: Double): List[MyColor] =
  lotsOfColor.tail.foldLeft((ListBuffer(lotsOfColor.head), lotsOfColor.head)) {
    case ((mergedColors, lastColor), aColor) if (lastColor diff aColor) < delta =>
      (mergedColors, lastColor merge aColor)
    case ((mergedColors, _), aColor) => 
      mergedColors += aColor
      (mergedColors, aColor)
  }._1.toList