Ed de Almeida Ed de Almeida - 3 months ago 12
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,
user1
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"])}
arr
end


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

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


The problem is I don't know how to mention the array I'm creating with
Array#map
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"]) }
end


but it doesn't work, of course.

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

EDIT

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

mwp mwp
Answer

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])
end

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.

Comments