This question presents a version of Haskell's scanl
in Python, but is there a Ruby version of this function?
Asked
Active
Viewed 435 times
3

Community
- 1
- 1

Alex Moore-Niemi
- 2,913
- 2
- 24
- 22
-
Cool. And what is your question so far? – Aleksei Matiushkin Jun 29 '16 at 15:36
-
i can rephrase, if that helps? i wondered if there was a way to do it without writing my own function, or if there is a best way to write the function – Alex Moore-Niemi Jun 29 '16 at 15:39
3 Answers
1
You can use reduce()
and implement it by yourself.
def scanl(op, init, range)
op = op.to_proc unless op.is_a?(Proc)
range.reduce([init]) { |a, e| a.push(op.call(a.last,e)) }
end
p scanl(lambda { |a, b| a + b }, 0, 1..10)
#=> [0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
p scanl(:+, 0, 1..10)
#=> [0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
Alternatively you can use map()
and put the initial element in front of the array.
def scanl(op, init, range)
op = op.to_proc unless op.is_a?(Proc)
acc = init
range.map { |e| acc = op.call(acc, e) }.unshift(init)
end

sschmeck
- 7,233
- 4
- 40
- 67
-
nice i had a feeling i could piggy-back on reduce somehow. do you have to do the `is_a?(Proc)` check? i'd figure it'd be a no-op if it was already a proc and thus safe – Alex Moore-Niemi Jun 29 '16 at 15:56
-
1yeah i mean that you can just run `.to_proc` always, if it is a proc, no-op, if it isn't, then you made it a proc, no? – Alex Moore-Niemi Jun 29 '16 at 15:59
-
although it is less concise, i think i prefer my version for the reason @SergioTulentsev points out. though the `to_proc` bit is helpful – Alex Moore-Niemi Jun 29 '16 at 16:06
-
@SergioTulentsev I updated my answer and replaced `Array#+` by `Array#push`. – sschmeck Jun 29 '16 at 20:29
-
@AlexMoore-Niemi You are rigth with `to_proc` call. The condition don't changes the behavoir. The condition makes the intention clearer, maybe. ;-) – sschmeck Jun 29 '16 at 20:33
-
"nice i had a feeling i could piggy-back on reduce somehow" – That's a pretty boring feeling, since `reduce` is general ;-) IOW: it can do *everything* a loop can do, and so you can *always* piggy-back on it. There's a proof sketch in Haskell on the Wikipedia page for `Fold`, if you're interested. The gist is this: a collection can be either empty or not. The two arguments to `reduce` (the initial and the block) tell it what to do with an empty collection and what to do with a not-empty collection. Since you have all cases covered, there is nothing `reduce` can't do. I once re-wrote all of … – Jörg W Mittag Jun 29 '16 at 23:59
-
1
0
In Ruby 2.0, where the Enumerator
class exists, we can build a nicer implementation that works properly with infinite ranges:
def scanl(elem, &op)
Enumerator.new do |yielder|
acc = elem
loop do
yielder << acc
acc = op.call(acc)
end
end.lazy
end
And use it like so:
scanl(1, &:next).take(10)
#=> #<Enumerator::Lazy: ...>
scanl(1, &:next).take(10).force
#=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
fib = scanl([0,1]) {|x, y| [y, x + y]}.map(&:first)
fib.take(10).force
#=> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Qqwy
- 5,214
- 5
- 42
- 83
-1
Probably a nicer version could be written, but this is what I came up with:
require 'spec_helper'
class Array
def scanl accumulator, &block
results = [accumulator] + self
results.each_with_index do |e, i|
results[i] = block.call(accumulator, e)
accumulator = results[i]
end
results
end
end
describe "#scanl" do
it 'is similar to foldl, but returns a list of successive reduced values from the left' do
# example from http://learnyouahaskell.com/higher-order-functions
expect([3, 5, 2, 1].scanl(0, &:+)).to eq([0,3,8,10,11])
end
end
I considered changing scanl
to take just a method name like :+
instead of a block to be more like reduce
. Thoughts?

Alex Moore-Niemi
- 2,913
- 2
- 24
- 22
-
Come chat here http://chat.stackexchange.com/rooms/8595/the-2nd-monitor . I think we can work out something – Marc-Andre Jun 29 '16 at 15:45
-
-
1Just as a FYI, deleting questions to preserve reputation isn't the best solution. Instead, fix the question so it's a better one. Think of it this way, questions are owned by the SO community once you ask them. We'll delete them if necessary. – the Tin Man Jun 29 '16 at 16:40
-
@SergioTulentsev ah, i thought it was more correct to put my potential solution in the Answer spot than the Question. i'll do better next time. – Alex Moore-Niemi Jun 19 '19 at 18:41