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"}
Related
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}]
I am using ActiveRecord. It has a handy method called group_by. When I use it with my activerecord objects, i get the below hash:
{["junior"]=>[#<Lead id: 1, created_at: "2015-02-13 02:34:39", updated_at: "2015-02-13 02:35:27", case_enabled: true>, #<Lead id: 2, created_at: "2015-02-13 20:48:19", updated_at: "2015-02-13 20:48:19", case_enabled: nil>, ["senior"]=>[#<Lead id: 3, created_at: "2015-02-13 20:48:19", updated_at: "2015-02-13 20:48:19", case_enabled: nil>, #<Lead id: 4, created_at: "2015-02-13 20:49:16", updated_at: "2015-02-13 20:49:16", case_enabled: nil>]}
However, I want a hash with subhashes that contain the collection as an ActiveRecord::Relation and column data. So this is what I come up with:
i = 0
r = group.reduce({}) do |acc, (k,v)|
h = {}
active_record_relation = where(id: v.map(&:id))
h["#{k.first}_collection"] = active_record_relation
h["#{k.first}_columns"] = Classification.where(code: k.first).first.default_fields
acc[i] = h
i += 1
acc
end
And it gives me the results I want:
{0=>{"junior_collection"=>#<ActiveRecord::Relation [# ... ]>, "junior_columns"=>[ ... ]}, 1=>{"senior_collection"=>#<ActiveRecord::Relation [# ... ]>, "senior_columns"=>[ ... ]}}
The fact that I had to add the i variable makes me feel like this is not the ruby way to do this. But I looked at the docs and I didn't find a way to add an index to reduce, since I am already passing a hash into reduce. Is there another way?
Your way is probably good enough but you can avoid separately tracking the index by doing .each.with_index.reduce(...) { |acc, ((k,v),i)| ... }, like so:
h = {'a' => 'b', 'c' => 'd', 'e' => 'f'}
h.each.with_index.reduce('OK') do |acc, ((k, v), i)|
puts "acc=#{acc}, k=#{k}, v=#{v}, i=#{i}"
acc
end
# acc=OK, k=a, v=b, i=0
# acc=OK, k=c, v=d, i=1
# acc=OK, k=e, v=f, i=2
# => "OK"
Not sure if it's more Rubyish than your way =\
I have an array of hashes:
array = [
{
id: 1,
name: "A",
points: 20,
victories: 4,
goals: 5,
},
{
id: 1,
name: "B",
points: 20,
victories: 4,
goals: 8,
},
{
id: 1,
name: "C",
points: 21,
victories: 5,
goals: 8,
}
]
To sort them using two keys I do:
array = array.group_by do |key|
[key[:points], key[:goals]]
end.sort_by(&:first).map(&:last)
But in my program, the sort criterias are stored in a database and I can get them and store in a array for example: ["goals","victories"] or ["name","goals"].
How can I sort the array using dinamic keys?
I tried many ways with no success like this:
criterias_block = []
criterias.each do |criteria|
criterias_block << "key[:#{criteria}]"
end
array = array.group_by do |key|
criterias_block
end.sort_by(&:first).map(&:last)
Array#sort can do this
criteria = [:points, :goals]
array.sort_by { |entry|
criteria.map { |c| entry[c] }
}
#=> [{:id=>1, :name=>"A", :points=>20, :victories=>4, :goals=>5},
# {:id=>1, :name=>"B", :points=>20, :victories=>4, :goals=>8},
# {:id=>1, :name=>"C", :points=>21, :victories=>5, :goals=>8}]
This works because if you sort an array [[1,2], [1,1], [2,3]], it sorts by the first elements, using any next elements to break ties
You can use values_at:
criteria = ["goals", "victories"]
criteria = criteria.map(&:to_sym)
array = array.group_by do |key|
key.values_at(*criteria)
end.sort_by(&:first).map(&:last)
# => [[{:id=>1, :name=>"A", :points=>20, :victories=>4, :goals=>5},
# {:id=>1, :name=>"B", :points=>20, :victories=>4, :goals=>8},
# {:id=>1, :name=>"C", :points=>21, :victories=>5, :goals=>8}]]
values_at returns an array of all the keys requested:
array[0].values_at(*criteria)
# => [4, 5]
I suggest doing it like this.
Code
def sort_it(array,*keys)
array.map { |h| [h.values_at(*keys), h] }.sort_by(&:first).map(&:last)
end
Examples
For array as given by you:
sort_it(array, :goals, :victories)
#=> [{:id=>1, :name=>"A", :points=>20, :victories=>4, :goals=>5},
# {:id=>1, :name=>"B", :points=>20, :victories=>4, :goals=>8},
# {:id=>1, :name=>"C", :points=>21, :victories=>5, :goals=>8}]
sort_it(array, :name, :goals)
#=> [{:id=>1, :name=>"A", :points=>20, :victories=>4, :goals=>5},
# {:id=>1, :name=>"B", :points=>20, :victories=>4, :goals=>8},
# {:id=>1, :name=>"C", :points=>21, :victories=>5, :goals=>8}]
For the first of these examples, you could of course write:
sort_it(array, *["goals", "victories"].map(&:to_sym))
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.
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'}]