Removing hashes that have identical values for particular keys - ruby

I have an Array of Hashes with the same keys, storing people's data.
I want to remove the hashes that have the same values for the keys :name and :surname. The rest of the values can differ, so calling uniq! on array won't work.
Is there a simple solution for this?

You can pass a block to uniq or uniq!, the value returned by the block is used to compare two entries for equality:
irb> people = [{name: 'foo', surname: 'bar', age: 10},
{name: 'foo', surname: 'bar' age: 11}]
irb> people.uniq { |p| [p[:name], p[:surname]] }
=> [{:name=>"foo", :surname=>"bar", :age=>10}]

arr=[{name: 'john', surname: 'smith', phone:123456789},
{name: 'thomas', surname: 'hardy', phone: 671234992},
{name: 'john', surname: 'smith', phone: 666777888}]
# [{:name=>"john", :surname=>"smith", :phone=>123456789},
# {:name=>"thomas", :surname=>"hardy", :phone=>671234992},
# {:name=>"john", :surname=>"smith", :phone=>666777888}]
arr.uniq {|h| [h[:name], h[:surname]]}
# [{:name=>"john", :surname=>"smith", :phone=>123456789},
# {:name=>"thomas", :surname=>"hardy", :phone=>671234992}]

unique_people = {}
person_array.each do |person|
unique_people["#{person[:name]} #{person[:surname]}"] = person
end
array_of_unique_people = unique_people.values
This should do the trick.

a.delete_if do |h|
a.select{|i| i[:name] == h[:name] and i[:surname] == h[:surname] }.count > 1
end

Related

Using new Ruby pattern matching to check if a hash has certain keys

I want to use the new Ruby 3 feature in this very simple case. I know it must be possible but I have not figured it out from the documentation.
Given a hash, I want to check that it has certain keys. I don't mind if it has others in addition. And I want to do this with pattern matching (or know that it is impossible.) I also don't want to use a case statement which seems overkill.
{name: "John", salary: 12000, email: "john#email.com" }
Raise an error if the hash does not have name, and email as strings and salary as a number.
Use the contruct in an if or other conditional?
And what if the hash has strings as keys (which is what I get from JSON.parse) ?
{"name" => "John", "salary" => 12000, "email" => "john#email.com" }
You're looking for the => operator:
h = {name: "John", salary: 12000, email: "john#email.com" }
h => {name: String, salary: Numeric, email: String} # => nil
With an additional pair (test: 0):
h[:test] = 0
h => {name: String, salary: Numeric, email: String} # => nil
Without the :name key:
h.delete :name
h => {name: String, salary: Numeric, email: String} # key not found: :name (NoMatchingPatternKeyError)
With the :name key but the class of its value shouldn't match:
h[:name] = 1
h => {name: String, salary: Numeric, email: String} # String === 1 does not return true (NoMatchingPatternKeyError)
A strict match:
h[:name] = "John"
h => {name: String, salary: Numeric, email: String} # => rest of {:test=>0} is not empty
The in operator returns a boolean value instead of raising an exception:
h = {name: "John", salary: 12000, email: "john#email.com" }
h in {name: String, salary: Numeric, email: String} # => true
h[:name] = 1
h in {name: String, salary: Numeric, email: String} # => false
"I also don't want to use a case statement which seems overkill." case is just the syntax for pattern matching. AFAIK it is not the same as a case when, it's a case in.
h = {name: "John", salary: 12000, email: "john#email.com", other_stuff: [1] }
case h
in {name: String, salary: Integer, email: String}
puts "matched"
else
raise "#{h} not matched"
end

Passing sort_by an array of sort fields

If I have a array of hashes
collection = [
{ first_name: 'john', last_name: 'smith', middle: 'c'},
{ first_name: 'john', last_name: 'foo', middle: 'a'}
]
And an array of keys I want to sort by:
sort_keys = ['first_name', 'last_name']
How can I pass these keys to sort_by given that the keys will always match the keys in the collection?
I've tried
collection.sort_by { |v| sort_keys.map(&:v) }
but this doesn't work. I believe I'll need to use a proc but I'm not sure how to implement it. Would appreciate any help!
Using Ruby 2.2.1
If you change your sort_keys to contain symbols:
sort_keys = [:first_name, :last_name]
You can use values_at to retrieve the values:
collection.sort_by { |h| h.values_at(*sort_keys) }
#=> [{:first_name=>"john", :last_name=>"foo", :middle=>"a"}, {:first_name=>"john", :last_name=>"smith", :middle=>"c"}]
The array that is used to sort the hashes looks like this:
collection.map { |h| h.values_at(*sort_keys) }
#=> [["john", "smith"], ["john", "foo"]]

sort a hash by a property of the value

I have a hash that has "Song" objects as values. These song objects have a property 'updated_at' that I want to sort them by. I tried something like:
#data.values.updated_at.sort
but no luck. Any ideas?
You want to check the documentation of Enumerable#sort_by
e.g.
[{name: 'ryan', number: 3},
{name: 'john', number: 1},
{name: 'june', number: 2}].sort_by{|item| item[:number]}
Hopefully that is enough to get you going.
It's simply:
#data.values.sort_by(&:updated_at)
For example:
class A
attr_reader :updated_at
def initialize(updated_at)
#updated_at = updated_at
end
end
#data = { "Bob"=>A.new(3), "Trixi"=>A.new(1), "Bubba"=>A.new(2) }
#=> {"Bob"=>#<A:0x007ffa191935a0 #updated_at=3>,
# "Trixi"=>#<A:0x007ffa19193550 #updated_at=1>,
# "Bubba"=>#<A:0x007ffa19193528 #updated_at=2>}
#data.values.sort_by(&:updated_at)
#=> [#<A:0x007ffa19193550 #updated_at=1>,
# #<A:0x007ffa19193528 #updated_at=2>,
# #<A:0x007ffa191935a0 #updated_at=3>]

Check if any of array hash objects has needed value

I have an array like this:
arr = [{id: 1, name: 'John' }, {id: 2, name: 'Sam' }, {id: 3, name: 'Bob' }]
I need to check if any of arr objects have name Sam. What is the most elegant way? I can only think of cycling with each.
I need to check if any of arr objects have name Sam
Enumerable#any? is a good way to go.
arr = [ {id: 1, name: 'John' }, {id: 2, name: 'Sam' }, {id: 3, name: 'Bob' }]
arr.any? {|h| h[:name] == "Sam"}
# => true
Now if you also want to see which Array object has the value Sam in it,you can use Enumerable#find for the same:
arr.find {|h| h[:name] == "Sam"}
# => {:id=>2, :name=>"Sam"}
You can also choose select or count methods
Enumberable#select
> arr = [{id: 1, name: 'John' }, {id: 2, name: 'Sam' }, {id: 3, name: 'Bob' }]
> arr.select { | h | h[:name] == 'Sam' }
# => [{:id=>2, :name=>"Sam"}]
Enumberable#count
> arr.count { | h | h[:name] == 'Sam' }
# => 1
You can use Enumberable#find_all to return all object that match the constrain
arr = [{:id=>1,:first_name=>'sam'},{:id=>2,:first_name=>'sam'},{:id=>3,:first_name=>'samanderson'},{:id=>4,:first_name=>'samuel'}]
arr.find_all{|obj| obj.first_name == 'sam'}
# => [{:id=>1,:first_name=>'sam'},{:id=>2,:first_name=>'sam'}]

Convert Array of objects to Hash with a field as the key

I have an Array of objects:
[
#<User id: 1, name: "Kostas">,
#<User id: 2, name: "Moufa">,
...
]
And I want to convert this into an Hash with the id as the keys and the objects as the values. Right now I do it like so but I know there is a better way:
users = User.all.reduce({}) do |hash, user|
hash[user.id] = user
hash
end
The expected output:
{
1 => #<User id: 1, name: "Kostas">,
2 => #<User id: 2, name: "Moufa">,
...
}
users_by_id = User.all.map { |user| [user.id, user] }.to_h
If you are using Rails, ActiveSupport provides Enumerable#index_by:
users_by_id = User.all.index_by(&:id)
You'll get a slightly better code by using each_with_object instead of reduce.
users = User.all.each_with_object({}) do |user, hash|
hash[user.id] = user
end
You can simply do (using the Ruby 3 syntax of _1)
users_by_id = User.all.to_h { [_1.id, _1] }

Resources