11

Does anyone have a Scala implementation of Kadane's algorithm done in a functional style?

Edit Note: The definition on the link has changed in a way that invalidated answers to this question -- which goes to show why questions (and answers) should be self-contained instead of relying on external links. Here's the original definition:

In computer science, the maximum subarray problem is the task of finding the contiguous subarray within a one-dimensional array of numbers (containing at least one positive number) which has the largest sum. For example, for the sequence of values −2, 1, −3, 4, −1, 2, 1, −5, 4; the contiguous subarray with the largest sum is 4, −1, 2, 1, with sum 6.

Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
S0rin
  • 1,283
  • 1
  • 10
  • 22

3 Answers3

19

What about this, if an empty subarray is allowed or the input array cannot be all negative:

numbers.scanLeft(0)((acc, n) => math.max(0, acc + n)).max

Or, failing the conditions above this (which assumes the input is non-empty):

numbers.tail.scanLeft(numbers.head)((acc, n) => (acc + n).max(n)).max
Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
Heiko Seeberger
  • 3,702
  • 21
  • 20
  • 6
    `xs.tail.scanLeft(xs.head)((acc, x) => (acc+x).max(x)).max` if all can possibly be negative. :D – lcn Dec 12 '13 at 19:39
  • There are two formulations of the max sub-array problem one of which allows an empty subarray with implicit sum of zero to be returned. By the way the indices of the max subarray must be returned as well. – Nader Ghanbari Jun 15 '19 at 18:04
  • Fails when [-1], the expected result is -1 but giving 0. – Suryaprakash Pisay Jan 01 '21 at 03:52
  • @SuryaprakashPisay _If_ empty subarrays are not allowed, and all negative are allowed, yes. See first comment for an alternative implementation for that case, though I'll edit the answer to include it. – Daniel C. Sobral Jan 04 '21 at 17:39
  • It turns out I changed your answer instead of mine. I hope you don't mind -- it's the accepted one anyway. – Daniel C. Sobral Jan 04 '21 at 17:54
6

I prefer the folding solution to the scan solution -- though there's certainly elegance to the latter. Anyway,

numbers.foldLeft(0 -> 0) {
  case ((maxUpToHere, maxSoFar), n) =>
    val maxEndingHere = 0 max maxUpToHere + n
    maxEndingHere -> (maxEndingHere max maxSoFar)
}._2
Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
  • This solution will not work if the array contains negative values – Moustafa Mahmoud Nov 19 '19 at 08:03
  • @MoustafaMahmoud Uh, sure it will. I just tested it with the example on the wikipedia link of the question, and it gives the expected solution. What problem do you envision? – Daniel C. Sobral Dec 02 '19 at 18:40
  • Fails when [-1], the expected result is -1 but giving 0. – Suryaprakash Pisay Jan 01 '21 at 03:52
  • @SuryaprakashPisay Ah, I just noticed I replied to your comment on the wrong answer! At the time this question was posed the linked Wikipedia article specified that the input array would be "containing at least one positive number" , so `[-1]` is an invalid input. See [March 3, 2012](https://en.wikipedia.org/w/index.php?title=Maximum_subarray_problem&direction=next&oldid=465609597). – Daniel C. Sobral Jan 04 '21 at 17:48
1

The following code returns the start and end index as well as the sum:


import scala.math.Numeric.Implicits.infixNumericOps
import scala.math.Ordering.Implicits.infixOrderingOps

case class Sub[T: Numeric](start: Index, end: Index, sum: T)

def maxSubSeq[T](arr: collection.IndexedSeq[T])(implicit n: Numeric[T]) =
  arr
    .view
    .zipWithIndex
    .scanLeft(Sub(-1, -1, n.zero)) {
      case (p, (x, i)) if p.sum > n.zero => Sub(p.start, i, p.sum + x)
      case (_, (x, i))                   => Sub(i, i, x)
    }
    .drop(1)
    .maxByOption(_.sum)
Nader Ghanbari
  • 4,120
  • 1
  • 23
  • 26