Add element to an array if it's not there already - ruby

I have a Ruby class
class MyClass
attr_writer :item1, :item2
end
my_array = get_array_of_my_class() #my_array is an array of MyClass
unique_array_of_item1 = []
I want to push MyClass#item1 to unique_array_of_item1, but only if unique_array_of_item1 doesn't contain that item1 yet. There is a simple solution I know: just iterate through my_array and check if unique_array_of_item1 already contains the current item1 or not.
Is there any more efficient solution?

#Coorasse has a good answer, though it should be:
my_array | [item]
And to update my_array in place:
my_array |= [item]

You can use Set instead of Array.

You don't need to iterate through my_array by hand.
my_array.push(item1) unless my_array.include?(item1)
Edit:
As Tombart points out in his comment, using Array#include? is not very efficient. I'd say the performance impact is negligible for small Arrays, but you might want to go with Set for bigger ones.

You can convert item1 to array and join them:
my_array | [item1]

Important to keep in mind that the Set class and the | method (also called "Set Union") will yield an array of unique elements, which is great if you want no duplicates but which will be an unpleasant surprise if you have non-unique elements in your original array by design.
If you have at least one duplicate element in your original array that you don't want to lose, iterating through the array with an early return is worst-case O(n), which isn't too bad in the grand scheme of things.
class Array
def add_if_unique element
return self if include? element
push element
end
end

I'm not sure if it's perfect solution, but worked for me:
host_group = Array.new if not host_group.kind_of?(Array)
host_group.push(host)

Related

Ruby: add a simple function to Mylist < Array

I'm struggling with some database migration stuff, and this is what I want to do:
I created a Class Mylist < Array with a method each_hash added, this method needs to act just like the Array's each, that means the two calls are equal:
list = Mylist.new
list.each |d|
...
end
list.each_hash |d|
...
end
I have tried here and there but it does not work, any help would be appreciated!
Thanks #Charles Caldwell, works excellent
If you want the each_hash to be the exact same thing as each, you could do alias :each_hash :each. It would make a call to each_hash to actually be a call to each. Would that be an option in your case? – Charles Caldwell

Cannot understand what the following code does

Can somebody explain to me what the below code is doing. before and after are hashes.
def differences(before, after)
before.diff(after).keys.sort.inject([]) do |diffs, k|
diff = { :attribute => k, :before => before[k], :after => after[k] }
diffs << diff; diffs
end
end
It is from the papertrail differ gem.
It's dense code, no question. So, as you say before and after are hash(-like?) objects that are handed into the method as parameters. Calling before.diff(after) returns another hash, which then immediately has .keys called on it. That returns all the keys in the hash that diff returned. The keys are returned as an array, which is then immediately sorted.
Then we get to the most complex/dense bit. Using inject on that sorted array of keys, the method builds up an array (called diffs inside the inject block) which will be the return value of the inject method.
That array is made up of records of differences. Each record is a hash - built up by taking one key from the sorted array of keys from the before.diff(after) return value. These hashes store the attribute that's being diffed, what it looked like in the before hash and what it looks like in the after hash.
So, in a nutshell, the method gets a bunch of differences between two hashes and collects them up in an array of hashes. That array of hashes is the final return value of the method.
Note: inject can be, and often is, much, much simpler than this. Usually it's used to simply reduce a collection of values to one result, by applying one operation over and over again and storing the results in an accumlator. You may know inject as reduce from other languages; reduce is an alias for inject in Ruby. Here's a much simpler example of inject:
[1,2,3,4].inject(0) do |sum, number|
sum + number
end
# => 10
0 is the accumulator - the initial value. In the pair |sum, number|, sum will be the accumulator and number will be each number in the array, one after the other. What inject does is add 1 to 0, store the result in sum for the next round, add 2 to sum, store the result in sum again and so on. The single final value of the accumulator sum will be the return value. Here 10. The added complexity in your example is that the accumulator is different in kind from the values inside the block. That's less common, but not bad or unidiomatic. (Edit: Andrew Marshall makes the good point that maybe it is bad. See his comment on the original question. And #tokland points out that the inject here is just a very over-complex alternative for map. It is bad.) See the article I linked to in the comments to your question for more examples of inject.
Edit: As #tokland points out in a few comments, the code seems to need just a straightforward map. It would read much easier then.
def differences(before, after)
before.diff(after).keys.sort.map do |k|
{ :attribute => k, :before => before[k], :after => after[k] }
end
end
I was too focused on explaining what the code was doing. I didn't even think of how to simplify it.
It finds the entries in before and after that differ according to the underlying objects, then builds up a list of those differences in a more convenient format.
before.diff(after) finds the entries that differ.
keys.sort gives you the keys (of the map of differences) in sorted order
inject([]) is like map, but starts with diffs initialized to an empty array.
The block creates a diff line (a hash) for each of these differences, and then appends it to diffs.

How many times does Ruby evaluate a loop's enumerated collection?

In Ruby, if I were to loop over a collection, how many times would Ruby evaluate the enumerated collection?
Specifically, I'd like to sort a collection, and loop over the sorted collection. Since I have no need for keeping a copy of the sorted collection around, I figured I'd just write the loop as:
for item in #items.sort{ |a,b| b.created_at <=> a.created_at } do
#do some stuff
end
However, after crafting that lovely bit of code I began to wonder how many times I might be actually calling sort.
Would the above line indeed only sort the collection once? Or will Ruby end up sorting it N times for each item in the collection?
You're calling sort once.
Except for scoping differences,
for x in xs do
some_stuff
end
is the same as
xs.each do |x|
some_stuff
end
And of course when you do foo.bar(baz), foo is evaluated exactly once, no matter what bar does.
That's equivalent to sorting the whole collection once, and then iterating it once.
Equivalent to:
#items.sort{ |a,b| b.created_at <=> a.created_at }.each do |item|
# do some stuff
end
Even cleaner and much faster:
#items.sort_by {|a| a.created_at}.reverse
You should pretty much always use sort_by instead of sort if you can (and you almost always can), because it evaluates the sort-key function only once per item. And it lets you write half as much comparison code!

How do I modify an array while I am iterating over it in Ruby?

I'm just learning Ruby so apologies if this is too newbie for around here, but I can't work this out from the pickaxe book (probably just not reading carefully enough).
Anyway, if I have an array like so:
arr = [1,2,3,4,5]
...and I want to, say, multiply each value in the array by 3, I have worked out that doing the following:
arr.each {|item| item *= 3}
...will not get me what I want (and I understand why, I'm not modifying the array itself).
What I don't get is how to modify the original array from inside the code block after the iterator. I'm sure this is very easy.
Use map to create a new array from the old one:
arr2 = arr.map {|item| item * 3}
Use map! to modify the array in place:
arr.map! {|item| item * 3}
See it working online: ideone
To directly modify the array, use arr.map! {|item| item*3}. To create a new array based on the original (which is often preferable), use arr.map {|item| item*3}. In fact, I always think twice before using each, because usually there's a higher-order function like map, select or inject that does what I want.
arr.collect! {|item| item * 3}
Others have already mentioned that array.map is the more elegant solution here, but you can simply add a "!" to the end of array.each and you can still modify the array. Adding "!" to the end of #map, #each, #collect, etc. will modify the existing array.

How to merge sub-arrays within an array in Ruby?

I have an array which for arguments sake looks something like this:
a = [[1,100], [2,200], [3,300], [2,300]]
Of those four sub-arrays, I would like to merge any where the first element is a duplicate. So in the example above I would like to merge the 2nd and the 4th sub-arrays. However, the caveat is that where the second element in the matching sub-arrays is different, I would like to maintain the higher value.
So, I would like to see this result:
a = [[1,100], [3,300], [2,300]]
This little conundrum is a little above my Ruby skills so am turning to the community for help. Any guidance with how to tackle this is much appreciated.
Thanks
# Get a hash that maps the first entry of each subarray to the subarray
# requires 1.8.7+ or active_support (or facets, I think)
hash = a.group_by { |first, second| first }
# Take each entry in the hash and select the biggest entry for each unique key
hash.map {|k,v| v.max }

Resources