Ruby Array of hash, and map function - ruby

I have an array of hashes, like this:
my_array = [{foo:1,bar:"hello",baz:3},{foo:2,bar:"hello2",baz:495,foo_baz:"some_string"},...]
#there can be arbitrary many in this list.
#There can also be arbitrary many keys on the hashes.
I want to create a new array that is a copy of the last array, except that I remove any :bar entries.
my_array2 = [{foo:1,baz:3},{foo:2,baz:495,foo_baz:"some_string"},...]
I can get the my_array2 by doing this:
my_array2 = my_array.map{|h| h.delete(:bar)}
However, this changes the original my_array, which I want to stay the same.
Is there a way of doing this without having to duplicate my_array first?

one of many ways to accomplish this:
my_array2 = my_array.map{|h| h.reject{|k,v| k == :bar}}

my_array.map {|h| h.select{|k, _| k != :bar} }
# => [{:foo=>1, :baz=>3}, {:foo=>2, :baz=>495, :foo_baz=>"some_string"}]

Related

How to remove duplicate pair values from given array in Ruby?

I want to remove a pair of 'duplicates' from an array of strings, where each element has the form R1,R2, with varying numbers. In my case, a duplicate would be R2,R1 because it has the same elements of R1,R2 but inverted.
Given:
a = ['R1,R2', 'R3,R4', 'R2,R1', 'R5,R6']
The resulting array should be like so:
a = ['R1,R2', 'R3,R4', 'R5,R6']
How could I remove the duplicates so I would have the following?
A solution with Set
require 'set'
a.uniq { |item| Set.new(item.split(",")) } # => ["R1,R2", "R3,R4", "R5,R6"]
Here is a working example :
array = ['R1,R2', 'R3,R4', 'R2,R1', 'R5,R6']
array.uniq { |a| a.split(',').sort }
try this,
def unique(array)
pure = Array.new
for i in array
flag = false
for j in pure
flag = true if (j.split(",").sort == i.split(",").sort)
end
pure << i unless flag
end
return pure
end
reference: https://www.rosettacode.org/wiki/Remove_duplicate_elements#Ruby
If the elements of your array are "pairs", they should maybe be actual pairs and not strings, like this:
pairs = [['R1', 'R2'], ['R3', 'R4'], ['R2', 'R1'], ['R5', 'R6']]
And, in fact, since order doesn't seem to matter, it looks like they really should be sets:
require 'set'
sets = [Set['R1', 'R2'], Set['R3', 'R4'], Set['R2', 'R1'], Set['R5', 'R6']]
If that is the case, then Array#uniq will simply work as expected:
sets.uniq
#=> [#<Set: {"R1", "R2"}>, #<Set: {"R3", "R4"}>, #<Set: {"R5", "R6"}>]
So, the best way would be to change the code that produces this value to return an array of two-element sets.
If that is not possible, then you should transform the value at your system boundary when it enters the system, something like this:
sets = a.map {|el| el.split(',') }.map(&Set.method(:new))

Ruby Nokogiri parsing omit duplicates

I'm parsing XML files and wanting to omit duplicate values from being added to my Array. As it stands, the XML will looks like this:
<vulnerable-software-list>
<product>cpe:/a:octopus:octopus_deploy:3.0.0</product>
<product>cpe:/a:octopus:octopus_deploy:3.0.1</product>
<product>cpe:/a:octopus:octopus_deploy:3.0.2</product>
<product>cpe:/a:octopus:octopus_deploy:3.0.3</product>
<product>cpe:/a:octopus:octopus_deploy:3.0.4</product>
<product>cpe:/a:octopus:octopus_deploy:3.0.5</product>
<product>cpe:/a:octopus:octopus_deploy:3.0.6</product>
</vulnerable-software-list>
document.xpath("//entry[
number(substring(translate(last-modified-datetime,'-.T:',''), 1, 12)) > #{last_imported_at} and
cvss/base_metrics/access-vector = 'NETWORK'
]").each do |entry|
product = entry.xpath('vulnerable-software-list/product').map { |product| product.content.split(':')[-2] }
effected_versions = entry.xpath('vulnerable-software-list/product').map { |product| product.content.split(':').last }
puts product
end
However, because of the XML input, that's parsing quite a bit of duplicates, so I end up with an array like ['Redhat','Redhat','Redhat','Fedora']
I already have the effected_versions taken care of, since those values don't duplicate.
Is there a method of .map to only add unique values?
If you need to get an array of unique values, then just call uniq method to get the unique values:
product =
entry.xpath('vulnerable-software-list/product').map do |product|
product.content.split(':')[-2]
end.uniq
There are many ways to do this:
input = ['Redhat','Redhat','Redhat','Fedora']
# approach 1
# self explanatory
result = input.uniq
# approach 2
# iterate through vals, and build a hash with the vals as keys
# since hashes cannot have duplicate keys, it provides a 'unique' check
result = input.each_with_object({}) { |val, memo| memo[val] = true }.keys
# approach 3
# Similar to the previous, we iterate through vals and add them to a Set.
# Adding a duplicate value to a set has no effect, and we can convert it to array
result = input.each_with_object.(Set.new) { |val, memo| memo.add(val) }.to_a
If you're not familiar with each_with_object, it's very similar to reduce
Regarding performance, you can find some info if you search for it, for example What is the fastest way to make a uniq array?
From a quick test, I see these performing in increasing time. uniq is 5 times faster than each_with_object, which is 25% slower than the Set.new approach. Probably because sort is implemetned using C. I only tested with only an arbitrary input though, so it might not be true for all cases.

Delete one of many from Array

I have an Array that contains some elements multiple times. Now I want to create a new Array with one of those elements deleted.
Example:
a = [1,1,1,2]
delete_index = a.find_index(1)
result = a.clone
result.delete_at(delete_index)
# result is now [1,1,2]
This code looks really ugly for such an easy task. I had a look at the methods that Array provides, but couldn't find a better way of doing this.
a.delete_at(a.index(1) || a.length)
a.length handles the case where your element isn't found; because it's out of range, nothing will be deleted and your return value wil be nil.
If part of your question was to do this to a copy of the array, just call it on a clone:
a2 = a.clone ; a2.delete_at(...)
If you want to do this for each duplicated element, you can chain it to a block that selects the duplicated elements:
a.select { |e| array.count(e) > 1 }.each { |dup| a.delete_at a.index(dup) }
You could monkey patch Array:
class Array
def delete_first_occurrence(o)
delete_at(find_index(o) || length)
self
end
end
a = [1,1,1,2]
result = a.clone.delete_first_occurrence(1)
=> [1, 1, 2]
I can't quite tell, but it sounds like you're just trying to remove duplicates from the array. If that's the case, it's as easy as array.uniq, which will return a new array with all duplicates removed. If you'd like to modify the original array in place, you can use array.uniq! instead.
If that's not what you're trying to accomplish, please update your question with some example input and output of what you're trying to accomplish.

Testing if a hash has any of a number of keys

I was wondering if there was a better way to test if a hash has any keys from an array. I want to use it something like this:
keys = %w[k1 k2 k5 k6]
none = true if hash.key?(keys)
Or am I going to have to loop this?
No need to loop:
(hash.keys & keys).any? # => true
Explanation:
.keys returns all keys in a hash as an array. & intersects two arrays, returning any objects that exists in both arrays. Finally, .any? checks if the array intersect has any values.
keys.any? { |i| hash.has_key? i }

Is this the best way to grab common elements from a Hash of arrays?

I'm trying to get a common element from a group of arrays in Ruby. Normally, you can use the
& operator to compare two arrays, which returns elements that are present or common in both arrays. This is all good, except when you're trying to get common elements from more than two arrays. However, I want to get common elements from an unknown, dynamic number of arrays, which are stored in a hash.
I had to resort to using the eval() method in ruby, which executes a string as actual code. Here's the function I wrote:
def get_common_elements_for_hash_of_arrays(hash) # get an array of common elements contained in a hash of arrays, for every array in the hash.
# ["1","2","3"] & ["2","4","5"] & ["2","5","6"] # => ["2"]
# eval("[\"1\",\"2\",\"3\"] & [\"2\",\"4\",\"5\"] & [\"2\",\"5\",\"6\"]") # => ["2"]
eval_string_array = Array.new # an array to store strings of Arrays, ie: "[\"2\",\"5\",\"6\"]", which we will join with & to get all common elements
hash.each do |key, array|
eval_string_array << array.inspect
end
eval_string = eval_string_array.join(" & ") # create eval string delimited with a & so we can get common values
return eval(eval_string)
end
example_hash = {:item_0 => ["1","2","3"], :item_1 => ["2","4","5"], :item_2 => ["2","5","6"] }
puts get_common_elements_for_hash_of_arrays(example_hash) # => 2
This works and is great, but I'm wondering...eval, really? Is this the best way to do it? Are there even any other ways to accomplish this(besides a recursive function, of course). If anyone has any suggestions, I'm all ears.
Otherwise, Feel free to use this code if you need to grab a common item or element from a group or hash of arrays, this code can also easily be adapted to search an array of arrays.
Behold the power of inject! ;)
[[1,2,3],[1,3,5],[1,5,6]].inject(&:&)
=> [1]
As Jordan mentioned, if your version of Ruby lacks support for &-notation, just use
inject{|acc,elem| acc & elem}
Can't you just do a comparison of the first two, take the result and compare it to the next one etc? That seems to meet your criteria.
Why not do this:
def get_common_elements_for_hash_of_arrays(hash)
ret = nil
hash.each do |key, array|
if ret.nil? then
ret = array
else
ret = array & ret
end
end
ret = Array.new if ret.nil? # give back empty array if passed empty hash
return ret
end

Resources