问题
This is a follow-up to my previous question.
Suppose I want to find a pair of integers, which sum to a given number x
, in a given sorted array. The well-known "one pass" solution looks like that:
def pair(a: Array[Int], target: Int): Option[(Int, Int)] = {
var l = 0
var r = a.length - 1
var result: Option[(Int, Int)] = None
while (l < r && result.isEmpty) {
(a(l), a(r)) match {
case (x, y) if x + y == target => result = Some(x, y)
case (x, y) if x + y < target => l = l + 1
case (x, y) if x + y > target => r = r - 1
}
}
result
}
How would you suggest write functionally without any mutable state ?
I guess I can write a recursive version with Stream
(lazy list in Scala)
Could you suggest a non-recursive version ?
回答1:
Here's a fairly straightforward version. It creates a Stream
of Vectors
that removes the first or last element on each iteration. Then we limit the size of the otherwise infinite Stream
(-1 so you can't add a number with itself), then map
it into the output format and check for the target condition.
def findSum(a: Vector[Int], target: Int): Option[(Int, Int)] = {
def stream = Stream.iterate(a){
xs => if (xs.head + xs.last > target) xs.init else xs.tail
}
stream.take (a.size - 1)
.map {xs => (xs.head, xs.last)}
.find {case (x,y) => x + y == target}
}
There are a lot of gems hidden in the companion objects of Scala's collections, like Stream.iterate
. I highly recommend checking them out. Knowing about them can greatly simplify a problem like this.
回答2:
Here's a version that doesn't use indices (which I try to avoid unless there's an important computation with the value of them):
def findPair2(x: Int, a: Array[Int]): Option[(Int, Int)] = {
def findPairLoop(x: Int, l: Array[Int], r: Array[Int]): Option[(Int, Int)] = {
if (l.head >= r.last) None
else if (l.head + r.last == x) Some((l.head, r.last))
else if (l.head + r.last > x) findPairLoop(x, l, r.init)
else findPairLoop(x, l.tail, r)
}
findPairLoop(x, a, a)
}
It's recursive, but doesn't need Stream. tail
and init
are O(N) for Array but if we use Lists and reverse the r
collection to avoid init
and last
an O(N) version can be done
def findPairInOrderN(x: Int, a: Array[Int]): Option[(Int, Int)] = {
def findPairLoop(x: Int, l: List[Int], r: List[Int]): Option[(Int, Int)] = {
if (l.head >= r.head) None
else if (l.head + r.head == x) Some((l.head, r.head))
else if (l.head + r.head > x) findPairLoop(x, l, r.tail)
else findPairLoop(x, l.tail, r)
}
val l = a.toList
findPairLoop(x, l, l.reverse)
}
If we don't care about one-pass (or efficiency generally :)) it's a one liner
(for (m <-a ; n <- a if m + n == x) yield (m,n)).headOption
unwrapping that into flatmap/map and then using collectFirst gives us this, which is fairly neat and more optimal (but still not O(n)) - it stops at the first correct pair but does more work than necessary to get there.
a.collectFirst{case m => a.collectFirst{case n if n+m == x => (m,n)}}.get
回答3:
Without recursion and without a mutable state it can get pretty ugly. Here's my attempt:
def f(l: List[Int], x: Int): Option[(Int, Int)] = {
l.foldLeft(l.reverse) {
(list, first) =>
list.headOption.map {
last =>
first + last match {
case `x` => return Some(first, last)
case sum if sum < x => list
case sum if sum > x =>
val innerList = list.dropWhile(_ + first > x)
innerList.headOption.collect {
case r if r + first == x => return Some(first, r)
}.getOrElse {
innerList
}
}
}.getOrElse {
return None
}
}
None
}
Examples:
scala> f(List(1, 2, 3, 4, 5), 3)
res33: Option[(Int, Int)] = Some((1,2))
scala> f(List(1, 2, 3, 4, 5), 9)
res34: Option[(Int, Int)] = Some((4,5))
scala> f(List(1, 2, 3, 4, 5), 12)
res36: Option[(Int, Int)] = None
The .reverse
in the beginning plus the foldLeft
with a return when the result is found makes this O(2n)
.
回答4:
This is a one liner
[ (x, y) | x <- array, y <- array, x+y == n ]
It even works on unsorted lists.
But if you want to take advantage of the sorting, just do a binary search for (n-x)
for every x
in the array instead of going through the array.
来源:https://stackoverflow.com/questions/42306911/functional-way-to-find-a-pair-of-integers-which-sum-to-x-in-a-sorted-array