Ed de Almeida Ed de Almeida - 11 months ago 41
Ruby Question

Adding unique elements to an array without auxiliary variable in Ruby

I have an array of hashes like

@data_records = [
{"user": "user1", "key1": "v1k1", ... , "keyN": "v1kN"},
{"user": "user2", "key1": "v2k1", ... , "keyN": "v2kN"},
{"user": "user3", "key1": "v3k1", ... , "keyN": "v3kN"},
{"user": "user1", "key1": "v4k1", ... , "keyN": "v4kN"},
{"user": "user1", "key1": "v5k1", ... , "keyN": "v5kN"},
{"user": "user4", "key1": "v6k1", ... , "keyN": "v6kN"},

As you may see, I may have many 'records' for the same user. In the example above,
has three records, for instance.

Now I need, based on this array of hashes, to generate an array with a single entry for every user in it. I mean, I need

[ "user1", "user2", "user3", "user4" ]

but not

[ "user1", "user2", "user3", "user1", "user1", "user4" ].

I wrote the following piece of code, which does the job:

def users_array
arr = Array.new
@data_records.each { |item| arr.push(item["user"]) if not arr.include?(item["user"])}

But it bothers me the fact I must use the auxiliary variable
for this to work. I'm sure there is a shorter way to to this with
method. Since
returns an array, it could be something like

def users_array
@data_records.map { |item| item["user"] if ... }

The problem is I don't know how to mention the array I'm creating with
inside the block. I believe it could be something like

def users_array
@data_records.map { |item| item["user"] if not this.include?(item["user"]) }

but it doesn't work, of course.

Can someone tell if there is a way to do this?


Yes, I could use
to do this. But then I rephrase the question: Is there a way to refer to the implicit array created by
inside the map's block?

mwp mwp
Answer Source

Aetherus is the closest to answering your rephrased question and he should get all the credit for pointing out #each_with_object to get at the "implicit array." But here's something a little closer to what you're asking:

@data_records.each_with_object([]) do |item, this|
  this << item[:user] unless this.include?(item[:user])

I think using a Set:

Set.new(@data_records.map { |item| item[:user] })

or #uniq:

@data_records.map { |item| item[:user] }.uniq

will probably be faster and scale to a large number of items better, but I haven't benchmarked it.