【问题标题】:Scala: Problem with foldLeft with negative numbers in listScala:列表中带有负数的 foldLeft 问题
【发布时间】:2019-02-19 12:59:49
【问题描述】:

我正在编写一个 Scala 函数,它返回列表中偶数元素的总和,减去列表中奇数元素的总和。我的解决方案不能使用可变、递归或 for/while 循环。下面的代码通过了 2/3 次测试,但我似乎无法弄清楚为什么它不能正确计算最后一次测试。

def sumOfEvenMinusOdd(l: List[Int]) : Int = {
    if (l.length == 0) return 0
    val evens = l.filter(_%2==0)
    val odds = l.filter(_%2==1)
    val evenSum = evens.foldLeft(0)(_+_)
    val oddSum = odds.foldLeft(0)(_+_)
    evenSum-oddSum
  }

//BEGIN TESTS
val i1 = sumOfEvenMinusOdd(List(1,3,5,4,5,2,1,0)) //answer: -9
val i2 = sumOfEvenMinusOdd(List(2,4,5,6,7,8,10)) //answer: 18
val i3 = sumOfEvenMinusOdd(List(109, 19, 12, 1, -5, -120, -15, 30,-33,-13, 12, 19, 3, 18, 1, -1)) //answer -133

我的代码输出如下:

defined function sumOfEvenMinusOdd
i1: Int = -9
i2: Int = 18
i3: Int = -200

我非常困惑为什么这些负数会影响我的其余代码。我看到一篇文章解释了使用 foldLeft foldRight 的操作顺序,但即使更改为 foldRight 仍然会产生 i3: Int = -200。有没有我遗漏的细节?任何指导/帮助将不胜感激。

【问题讨论】:

  • 所以问题是-3 % 2 == -1 这意味着您的两个过滤器语句实际上都只会抛出所有负奇数。我也认为你可以用一个 foldLeft/foldRight 来做到这一点

标签: scala filter pattern-matching fold


【解决方案1】:

问题不在于foldLeftfoldRight,问题在于您过滤掉奇数的方式:

val odds = l.filter(_ % 2 == 1)

应该是:

val odds = l.filter(_ % 2 != 0)

谓词_ % 2 == 1 只会对正元素产生真值。例如,表达式-15 % 2 等于-1,而不是1

作为旁注,我们还可以提高效率:

def sumOfEvenMinusOdd(l: List[Int]): Int = {
  val (evenSum, oddSum) = l.foldLeft((0, 0)) {
    case ((even, odd), element) =>
      if (element % 2 == 0) (even + element, odd) else (even, odd + element)
  }
  evenSum - oddSum
}

或者仅通过累积差异甚至更好:

def sumOfEvenMinusOdd(l: List[Int]): Int = {
  l.foldLeft(0) {
    case (diff, element) =>
      diff + element * (if (element % 2 == 0) 1 else -1)
  }
}

【讨论】:

  • 你是一个真正的救生员,你不知道我被难住了多久!谢谢!
  • 积累差异比在最后做更有效。您也不需要 isEmpty 测试,即使是空列表也可以正常工作。
  • @Tim 添加了一个带有运行差异的附加示例 :)
【解决方案2】:

问题在于您在列表中应用的过滤条件以查找奇数。 您不适用于负奇数的奇数条件,因为 mod 2 对于这种数字返回 -1。

number % 2 == 0 if number is even
number % 2 != 0 if number is odd

因此,如果您更改过滤条件,一切都会按预期工作。

另一个建议: 既然可以直接使用 sum 函数,为什么还要使用 foldleft 函数进行简单的 sum 运算?

  test("Test sum Of even minus odd") {
    def sumOfEvenMinusOdd(l: List[Int]) : Int = {
      val evensSum = l.filter(_%2 == 0).sum
      val oddsSum = l.filter(_%2 != 0).sum
      evensSum-oddsSum
    }

    assert(sumOfEvenMinusOdd(List.empty[Int]) == 0)
    assert(sumOfEvenMinusOdd(List(1,3,5,4,5,2,1,0)) == -9) //answer: -9
    assert(sumOfEvenMinusOdd(List(2,4,5,6,7,8,10)) == 18) //answer: 18
    assert(sumOfEvenMinusOdd(List(109, 19, 12, 1, -5, -120, -15, 30,-33,-13, 12, 19, 3, 18, 1, -1)) == -133)
  }

使用此解决方案,您的功能更加清晰,您可以删除功能上的 if

【讨论】:

  • foldLeft 解决方案效率更高,但如果您想构建每个列表,请使用一次调用 partition 而不是两次调用 filter
  • 谢谢!感谢您的反馈,这可能是我使用 Scala 编码的第 20 小时,所以我还没有完全掌握提示和技巧!
  • @MeganByers 你将迎来一段美妙的旅程,但途中会有一些令人讨厌的惊喜 :)
  • @Tim 你是对的。但是我从来没有在我的代码上提到过性能优化,只是基于起始代码的更清晰的解决方案。
猜你喜欢
  • 1970-01-01
  • 2017-11-04
  • 1970-01-01
  • 2012-04-28
  • 1970-01-01
  • 2016-01-31
  • 1970-01-01
  • 2022-01-01
  • 2020-03-17
相关资源
最近更新 更多