Find & Replace elements in array of hashes - Ruby [duplicate] - ruby

This question already has an answer here:
Find and replace specific hash and it's values within array
(1 answer)
Closed 4 years ago.
I have an array of hashes as below
status_arr = [{id: 5, status: false},
{id: 7, status: false},
{id: 3, status: false},
{id: 9, status: false} ]
I would like to update the hash with status: true if it has ids 5, 7
update_ids = [5, 9]
I am trying the following and has no idea to proceed
status_arr.select{ |arr| update_ids.include?(arr[:id]) arr[:status] = true}
Expected output:
status_arr = [{id: 5, status: true},
{id: 7, status: false},
{id: 3, status: false},
{id: 9, status: true} ]

require 'set'
update_ids = Set.new([5,3])
status_arr.map{ |s| s[:status] = update_ids.include?(s[:id]); s }
#=> [{:id=>5, :status=>true}, {:id=>7, :status=>false}, {:id=>3, :status=>true}, {:id=>9, :status=>false}]
instead of Set you can use just a Hash
update_ids = {5 => true, 3=> true}
status_arr.map{ |s| s[:status] = update_ids.include?(s[:id]); s }
#=> [{:id=>5, :status=>true}, {:id=>7, :status=>false}, {:id=>3, :status=>true}, {:id=>9, :status=>false}]
Or an array, but it will have some performance issues for big arrays
update_ids = [5,3]
status_arr.map{ |s| s[:status] = update_ids.include?(s[:id]); s }
#=> [{:id=>5, :status=>true}, {:id=>7, :status=>false}, {:id=>3, :status=>true}, {:id=>9, :status=>false}]

Related

Append increment number to duplicate item in array of objects [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
This post was edited and submitted for review 12 days ago.
Improve this question
There are duplicate items in the array. I want to append an incremental number to such items. For example:
Input:
[
{id: 1, name: "john"},
{id: 2, name: "Man"},
{id: 3, name: "Man"},
{id: 4, name: "john"},
{id: 5, name: "kind"},
{id: 6, name: "nova"},
{id: 7, name: "kind"},
{id: 8, name: "fred"},
{id: 9, name: "fred"},
{id: 10, name: "john"}
]
Expected output:
[
{id: 1, name: "john-1"},
{id: 2, name: "Man-1"},
{id: 3, name: "Man-2"},
{id: 4, name: "john-2"},
{id: 5, name: "kind-1"},
{id: 6, name: "nova"},
{id: 7, name: "kind-2"},
{id: 8, name: "fred"},
{id: 9, name: "monk"},
{id: 10, name: "john-3"}
]
the input data can be huge, is there any way to mutate the same object and find only duplicate to reduce the latency and memory
Something like this would work:
input
.group_by { |item| item[:name] }
.reject { |_name, entries| entries.count == 1 }
.each do |_name, entries|
entries.each.with_index(1) do |entry, index|
entry[:name] << "-#{index}"
end
end
It groups the items by their :name key and rejects those with only 1 entry. It then traverses the remaining groups (i.e. with 2 or more entries) and appends the 1-bases index to each entry's name.
Afterwards, input will be:
[{:id=>1, :name=>"john-1"},
{:id=>2, :name=>"Man-1"},
{:id=>3, :name=>"Man-2"},
{:id=>4, :name=>"john-2"},
{:id=>5, :name=>"kind-1"},
{:id=>6, :name=>"nova"},
{:id=>7, :name=>"kind-2"},
{:id=>8, :name=>"fred-1"},
{:id=>9, :name=>"fred-2"},
{:id=>10, :name=>"john-3"}]
This is some quick example with given input:
input
.group_by { |value| value[:name] }
.values
.select { |x| x.map.with_index(1) { |y, i| p y[:name] = "#{y[:name]}-#{i}" } }
.flatten
And thats the output:
[{:id=>1, :name=>"john-1"},
{:id=>4, :name=>"john-2"},
{:id=>10, :name=>"john-3"},
{:id=>2, :name=>"Man-1"},
{:id=>3, :name=>"Man-2"},
{:id=>5, :name=>"kind-1"},
{:id=>7, :name=>"kind-2"},
{:id=>6, :name=>"nova-1"},
{:id=>8, :name=>"fred-1"},
{:id=>9, :name=>"fred-2"}]
Group by will group input by duplicated names. I don't know if you tried anything but this is something that you can start work with and improve.
Tried to mutate the same input and incremented the values in name string
orders = [{:id=>2, :name=>"Man"}, {:id=>3, :name=>"Man"}, {:id=>8, :name=>"fred"}, {:id=>9, :name=>"fred"},{:id=>5, :name=>"kindaa"}, {:id=>1, :name=>"john"}, {:id=>4, :name=>"john"}, {:id=>10, :name=>"john"}, {:id=>5, :name=>"kinda"}, {:id=>5, :name=>"kind"}, {:id=>7, :name=>"kind"}, {:id=>6, :name=>"nova"}, {:id=>11, :name=>"nova"}]
counter = 1
orders.each_with_index do |order, index|
if index < orders.length - 1
if order[:name] == orders[index + 1][:name]
orders[index][:name] = orders[index][:name].to_s + "-#{counter}"
counter = counter + 1
else
orders[index][:name] = orders[index][:name].to_s + "-#{counter}" if counter > 1
counter = 1
end
else
orders[index][:name] = orders[index][:name].to_s + "-#{counter}" if counter > 1
end
end
puts orders
Output:
{:id=>2, :name=>"Man-1"}
{:id=>3, :name=>"Man-2"}
{:id=>8, :name=>"fred-1"}
{:id=>9, :name=>"fred-2"}
{:id=>5, :name=>"kindaa"}
{:id=>1, :name=>"john-1"}
{:id=>4, :name=>"john-2"}
{:id=>10, :name=>"john-3"}
{:id=>5, :name=>"kinda"}
{:id=>5, :name=>"kind-1"}
{:id=>7, :name=>"kind-2"}
{:id=>6, :name=>"nova-1"}
{:id=>11, :name=>"nova-2"}

Skip certain indices while iterating each and maintaining group size

I have piece of code that currently takes an array of elements (in this case blog posts), sorts their numerical ID in descending order, puts them into groupings of 3, and iterates through each and every one of them.
I need to add a condition where if blog.published is false for any blog, do not display it and use the next blog.
Whenever a blog that has the published value set to false, the grouping will set the position in the grouping to nil, rather than filling it with the next blog entry, leaving some groupings with only 2 or 1 blogs on the view. I have attempted increasing the index by replacing each with each_with_index and iterating the index value when the condition is met, but this also yields the same issue. Here is my current code.
<% #blogs.order('created_at DESC').in_groups_of(3, false).each do |group| %>
<div class="row pt-3">
<% group.each do |blog| %>
<% next if blog.published == false && current_page?(root_path) %>
<%# Do View Stuff For Each Blog Here %>
<% end #each %>
</div>
<% end #grouping %>
Each grouping should always have 3 blogs and should skip over any blog that has the publication value set to false.
#blogs = Blog.where.not(published: false).order('created_at desc').in_groups_of(3, false)
PS. If you're doing #blogs = Blog.all in your controller, try not to build AR queries inside your view. Even queries aren't executed until you call it somehow, they should be fully-built in your controller method.
If I get the point, this is a possible solution. I'm using pure Ruby and a collection of hashes as example, but it should be easy to transpose it to Rails.
So, given the collection:
posts = [ {id: 1, pub: true}, {id: 2, pub: false}, {id: 3, pub: true}, {id: 4, pub: true}, {id: 5, pub: true}, {id: 6, pub: false}, {id: 7, pub: true}, {id: 8, pub: true}, {id: 9, pub: true}, {id: 10, pub: true}, {id: 11, pub: true}, {id: 12, pub: true}, {id: 13, pub: false}, {id: 14, pub: true} ]
Using Enumerable#each_slice and Array#reject:
groups_of_three = posts.each_slice(3).map { |group| group.reject { |h| h[:pub] == false } }
Printing out tho show the result:
groups_of_three.each { |group| p group }
# [{:id=>1, :pub=>true}, {:id=>3, :pub=>true}]
# [{:id=>4, :pub=>true}, {:id=>5, :pub=>true}]
# [{:id=>7, :pub=>true}, {:id=>8, :pub=>true}, {:id=>9, :pub=>true}]
# [{:id=>10, :pub=>true}, {:id=>11, :pub=>true}, {:id=>12, :pub=>true}]
# [{:id=>14, :pub=>true}]

Sort array with custom order

I have an array of ids order say
order = [5,2,8,6]
and another array of hash
[{id: 2,name: name2},{id: 5,name: name5}, {id: 6,name: name6}, {id: 8,name: name8}]
I want it sorted as
[{id: 5,name: name5},{id: 2,name: name2}, {id: 8,name: name8}, {id: 6,name: name6}]
What could be best way to implement this? I can implement this with iterating both and pushing it to new array but looking for better solution.
Try this
arr = [
{:id=>2, :name=>"name2"}, {:id=>5, :name=>"name5"},
{:id=>6, :name=>"name6"}, {:id=>8, :name=>"name8"}
]
order = [5,2,8,6]
arr.sort_by { |a| order.index(a[:id]) }
# => [{:id=>5, :name=>"name5"}, {:id=>2, :name=>"name2"},
#{:id=>8, :name=>"name8"}, {:id=>6, :name=>"name6"}]
Enumerable#in_order_of (Rails 7+)
Starting from Rails 7, there is a new method Enumerable#in_order_of.
A quote right from the official Rails docs:
in_order_of(key, series)
Returns a new Array where the order has been set to that provided in the series, based on the key of the objects in the original enumerable.
[ Person.find(5), Person.find(3), Person.find(1) ].in_order_of(:id, [ 1, 5, 3 ])
=> [ Person.find(1), Person.find(5), Person.find(3) ]
If the series include keys that have no corresponding element in the Enumerable, these are ignored. If the Enumerable has additional elements that aren't named in the series, these are not included in the result.
It is not perfect in a case of hashes, but you can consider something like:
require 'ostruct'
items = [{ id: 2, name: 'name2' }, { id: 5, name: 'name5' }, { id: 6, name: 'name6' }, { id: 8, name: 'name8' }]
items.map(&OpenStruct.method(:new)).in_order_of(:id, [5,2,8,6]).map(&:to_h)
# => [{:id=>5, :name=>"name5"}, {:id=>2, :name=>"name2"}, {:id=>8, :name=>"name8"}, {:id=>6, :name=>"name6"}]
Sources:
Official docs - Enumerable#in_order_of.
PR - Enumerable#in_order_of #41333.
Rails 7 adds Enumerable#in_order_of.

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'}]

Remove element from array

I have an array in my Rails 3.1 apps that has made by several objects:
[#<Hardware id: 10, brand_id: 5, model: "B4200", description: "Stampante OKI B4200", typology_id: 3, sub_typology_id: 10, created_at: nil, updated_at: nil>, #<Hardware id: 19, brand_id: 9, model: "JetLab", description: "JetLab - 600 ", typology_id: 5, sub_typology_id: nil, created_at: nil, updated_at: nil>]
and I want remove one object from this array. Using Rails console, I've tried to do something such as (try to remove first object):
array.pop=#<Hardware id: 10, brand_id: 5, model: "B4200", description: "Stampante OKI B4200", typology_id: 3, sub_typology_id: 10, created_at: nil, updated_at: nil>
but it doesn't work. How can I do this?
UPDATED: My goal isn't to pop last element on array, but a generic object (everywhere inside array) that I should find using mysql search query.
my_array = [ 1, 2, 3 ]
item = my_array.pop
puts item
# => 3
puts my_array
# => [ 1, 2 ]
You probably want to use the Array#delete function
an_array = [1,3,4]
an_array.delete(3)
# => 3
puts an_array
# => [1,4]
Check it out in the Ruby documentation:
http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-delete

Resources