When writing iterative code with mutation in ruby, I often find myself following this pattern:
def build_x some_data
x = [] # or x = {}
some_data.each do |data|
x.some_in_place_update! (... data ...)
end
x
end
(x
often does not have the same shape as some_data
, so a simple map
will not do.)
Is there a more idiomatic or better way to write code that follows this pattern?
[edit] A real example:
def to_hierarchy stuff
h = {}
stuff.each do |thing|
path = thing.uri.split("/").drop(4)
sub_h = h
path.each do |segment|
sub_h[segment] ||= {}
sub_h = sub_h[segment]
end
sub_h.merge!(
data: thing.data,
)
end
h
end
This begins with a flat list of thing
s, which have related but distinct uri
s. It transforms this flat list into a hierarchy, grouping related thing
s that share the same segment
s of a uri
. This follows the pattern I described: initialize h
, loop over some data and mutate h
along the way, and then spit out h
at the end.
[edit2] Another related example
def count_data obj
i = if obj[:data] then 1 else 0
obj.each do |k, v|
i += count_statements v unless :data == k
end
i
end
Your to_hierarchy
example could be done with each_with_object
:
def to_hierarchy stuff
stuff.each_with_object({}) do |thing, h|
#...
end
end
each_with_object
passes the extra object to the block and returns that object when the iteration is done.
If you're more of a traditionalist, you could use inject
:
def to_hierarchy stuff
stuff.inject({}) do |h, thing|
#...
h
end
end
Note the block argument order change and that the block has to return h
so that inject
can feed it back into the next block invocation.
Your general example could be written as:
def build_x some_data
some_data.each_with_object([]) do |data, x|
x.some_in_place_update! (... data ...)
end
end
or:
def build_x some_data
some_data.inject({}) do |x, data|
x.some_in_place_update! (... data ...)
x
end
end