A tiny Ruby gem to provide the #scan_left
operation on any Ruby
Enumerable
.
Imagine a series of numbers which you want to sum. You accomplish this by processing all elements, adding each to the previous sum, returning the final result.
Now imagine that, rather than just the final sum at the end of the series, you want a series of the partial sums after processing each element. This is called a "Prefix Sum".
In functional programming (FP), the sum is generalized as the fold operation, in which an initial state and a binary operation are combined to "fold" a series of values into a single result. The closely related prefix sum, which produces a series of intermediate results, is generalized as the scan operation. Adding "left" to these operation names indicates that calculation is to proceed left-to-right.
Ruby's standard library operation Enumerable#inject
implements the
FP fold operation. It also implements the simpler reduce operation,
which is a fold whose initial state is the first element of the
series.
The key differences between #inject
and #scan_left
are:
-
Incremental results:
#scan_left
returns a series of results after processing each element of the input series.#inject
returns a single value, which equals the final result in the series returned by#scan_left
. -
Laziness:
#scan_left
can preserve the laziness of the input series. As each incremental result is read from the output series, the actual calculation is lazily performed against the input.#inject
cannot be a lazy operation in general, as its single result reflects a calculation across every element of the input series.
require "scan_left"
# For comparison, results from #inject are shown as well:
ScanLeft.new([]).scan_left(0) { |s, x| s + x } == [0]
[].inject(0) { |s, x| s + x } == 0
ScanLeft.new([1]).scan_left(0) { |s, x| s + x } == [0, 1]
[1].inject(0) { |s, x| s + x } == 1
ScanLeft.new([1, 2, 3]).scan_left(0) { |s, x| s + x } == [0, 1, 3, 6]
[1, 2, 3].inject(0) { |s, x| s + x } == 6
# OPTIONAL: To avoid explicitly using the `ScanLeft` class, you may
# choose to use the provided refinement on Enumerable.
#
# This refinement adds a `#scan_left` method directly to Enumerable
# for a more concise syntax.
using EnumerableWithScanleft
[].scan_left(0) { |s, x| s + x } => [0]
[1].scan_left(0) { |s, x| s + x } => [0, 1]
[1, 2, 3].scan_left(0) { |s, x| s + x } => [0, 1, 3, 6]