Skip to content

Instantly share code, notes, and snippets.

@RoUS
Created September 6, 2012 14:16
Show Gist options
  • Save RoUS/3656728 to your computer and use it in GitHub Desktop.
Save RoUS/3656728 to your computer and use it in GitHub Desktop.
[Ruby 1.8.7] Reduce consecutive elements in an array to ranges
class Array
#
# Return an array with all occurrences of 2 or more logically
# successive elements replaced with ranges, and disjoint elements
# as-is.
#
# @param [Boolean] sorted
# If this parameter can be evaluated as a Boolean 'true' value, the
# current array will be copied and the copy sorted before
# processing. If {#enrange} is passed a block, it will be passed
# along to the {Array#sort} method.
# @raise [ArgumentError]
# if the <tt>sorted</tt> parameter is true and the array cannot be
# sorted.
# @yieldparam [Object] a
# Element for comparison by block passed to {Array#sort}.
# @yieldparam [Object] b
# Element for comparison by block passed to {Array#sort}.
# @example
# ary = [1, 2, 3, 4, 5]
# ary.enrange => [1..5]
# @example
# ary = [5, 4, 3, 2, 1]
# ary.enrange => [5, 4, 3, 2, 1]
# @example
# ary = [5, 4, 3, 2, 1]
# ary.enrange(true) => [1..5]
# @example
# ary = [1, 2, 3, 4, 5, "a", "b", "c", "d", "e"]
# ary.enrange => ArgumentError raised
# @example
# ary = [1, 2, 3, 4, 5, "a", "b", "c", "d", "e"]
# ary.enrange { |a,b| a.to_s <=> b.to_s } => [1..5, "a".."e"]
# @example
# ary = [1, "a", 2, "b", 3, "c", 4, "d", 5, "e"]
# ary.enrange { |a,b| a.to_s <=> b.to_s } => [1, "a", 2, "b", 3, "c", 4, "d", 5, "e"]
# @example
# ary = [1, "a", 2, "b", 3, "c", 4, "d", 5, "e"]
# ary.enrange(true) { |a,b| a.to_s <=> b.to_s } => [1..5, "a".."e"]
# @note
# The example block may produce results other than expected if
# integer values have differing numbers of digits -- because "1"
# will be compared with "10" rather than 1 with 10.
#
def enrange(sorted=false, &block)
ary = sorted ? self.dup.sort(&block) : self
results = ary.inject([]) do |memo,elt|
unless (memo.empty? || (elt != [ *memo[-1] ].last.succ))
#
# If there's already something in the results array, check the
# last element to see if this value can logically be appended
# to it (_i.e._, is a successor to the current endpoint
# value).
#
memo[-1] = [ *memo.last ][0] .. elt
else
#
# Not a successor, or maybe the results array is empty -- just
# append this value as-is. It may or may not form the seed of
# a new range.
#
memo << elt
end
memo
end
return results
end
end
@RoUS
Copy link
Author

RoUS commented Sep 11, 2012

Pass 2. More direct, though perhaps slightly less clear -- and no internal comments. :-/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment