Efficient way to update values to array of hashes in ruby? - arrays

I have an array of hashes like below:
items = [ {"id" => 1, "cost" => '2.00'},
{"id" => 2, "cost" => '6.00'},
{"id" => 1, "cost" => '2.00'},
{"id" => 1, "cost" => '2.00'},
{"id" => 1, "cost" => '2.00'} ]
I would like to update the cost to '8.00' where the id = 1. I have tried with the each method like below which does work but I would like to know if there is another more efficient way of updating the values?
items.each { |h| h["cost"] = "8.00" if h["id"] == 1 }

You could just use the same object:
item_1 = {'id' => 1, 'cost' => '2.00'}
item_2 = {'id' => 2, 'cost' => '6.00'}
items = [item_1, item_2, item_1, item_1, item_1]
#=> [{"id"=>1, "cost"=>"2.00"}, {"id"=>2, "cost"=>"6.00"},
# {"id"=>1, "cost"=>"2.00"}, {"id"=>1, "cost"=>"2.00"},
# {"id"=>1, "cost"=>"2.00"}]
This makes updates trivial:
item_1['cost'] = '8.00'
items
#=> [{"id"=>1, "cost"=>"8.00"}, {"id"=>2, "cost"=>"6.00"},
# {"id"=>1, "cost"=>"8.00"}, {"id"=>1, "cost"=>"8.00"},
# {"id"=>1, "cost"=>"8.00"}]

You might consider changing your data structure from:
items = [{"id" => 1, "cost" => '2.00'}, {"id" => 2, "cost" => '6.00'},
{"id" => 1, "cost" => '2.00'}, {"id" => 1, "cost" => '2.00'},
{"id" => 1, "cost" => '2.00'}]
To a hash like this:
items = { 1 => '2.00', 2 => '6.00' }
To updating the record with id = 1 to 8.00 call:
items[1] = '8.00'
Or if you need to know the number of items, you might want to conside a structure like this:
items = { 1 => ['2.00', 4], 2 => ['6.00', 1] }
Than update like this:
items[1][0] = '8.00'

You can achieve this by using each on array
items.each{|v| v["cost"] = "8.00" if v["id"] == 1 }
Cheers!

Related

how to add additional value to array

i have an array, where all data is calculated by records from matches table:
Illuminate\Support\Collection {#1342 ▼
#items: array:4 [▼
"First team" => & array:6 [▼
"points" => 3
"scoredGoals" => 6
"goalsConceded" => 6
"wins" => 0
"loses" => 0
"draws" => 3
]
"Second team" => array:6 [▶]
"third team" => array:6 [▶]
"fourth team" => & array:6 [▶]
]
}
i need add to array image of each team (from teams table, where column image)
how can i do that?
here is my code from controller, where all data is calculated from matches table:
there is my code which i need edit:
$standings = [];
$blank = [
'points' => 0,
'scoredGoals' => 0,
'goalsConceded' => 0,
'wins' => 0,
'loses' => 0,
'draws' => 0,
];
$matches = Match::with('score', 'homeTeam', 'awayTeam')
->whereHas('score', function($query){
$query->whereNotNull('home_team_score')
->whereNotNull('away_team_score');
})
->where('league_id', '=', $league->id)
->get();
foreach ($matches as $match) {
$homeTeamScore = $match->score->home_team_score;
$awayTeamScore = $match->score->away_team_score;
if (! isset($standings[$match->homeTeam->name])) {
$standings[$match->homeTeam->name] = $blank;
}
if (! isset($standings[$match->awayTeam->name])) {
$standings[$match->awayTeam->name] = $blank;
}
$home = &$standings[$match->homeTeam->name];
$away = &$standings[$match->awayTeam->name];
$away['scoredGoals'] += $awayTeamScore;
$home['scoredGoals'] += $homeTeamScore;
$away['goalsConceded'] += $homeTeamScore;
$home['goalsConceded'] += $awayTeamScore;
switch ($homeTeamScore <=> $awayTeamScore) {
case -1:
// home lost
// swap home and away and let it fall through
$tmpHome = &$home;
$home = &$away;
$away = &$tmpHome;
case 1:
// home won
$home['points'] += 3;
$home['wins']++;
$away['loses']++;
break;
default:
// draw
$home['points']++;
$away['points']++;
$home['draws']++;
$away['draws']++;
}
}
$standings = collect($standings)->sort(function ($one, $other) {
if ($one['points'] !== $other['points']) {
return $other['points'] - $one['points']; // similar to desc
}
$oneDelta = $one['scoredGoals'] - $one['goalsConceded'];
$otherDelta = $other['scoredGoals'] - $other['goalsConceded'];
return $otherDelta - $oneDelta; // similar to desc
});
return view('admin.leagues.standings')->with([
'standings' => $standings,
]);
Going with the key of each element in your collection is the name of your team and is stored in the name column of your teams table, you can map over your collection and add in your image.
For example:
$images = [
'First team' => 'first-team.jpg',
'Second team' => 'second-team.jpg',
'Third team' => 'third-team.jpg'
];
$teamsWithImages =
collect([
"First team" => [
"points" => 3,
"scoredGoals" => 6,
"goalsConceded" => 6,
"wins" => 0,
"loses" => 0,
"draws" => 3,
],
"Second team" => [
"points" => 3,
"scoredGoals" => 6,
"goalsConceded" => 6,
"wins" => 0,
"loses" => 0,
"draws" => 3,
],
"Third team" => [
"points" => 3,
"scoredGoals" => 6,
"goalsConceded" => 6,
"wins" => 0,
"loses" => 0,
"draws" => 3,
]
])->map(function ($item, $key) use ($images) {
// You would uncomment this line to retrieve the image
// from your teams table
// You also wouldn't need the use ($images) either
//$item['image'] = Teams::where('name', $key)->first()->image;
$item['image'] = $images[$key];
return $item;
})->all();
dump($teamsWithImages);
Update
Based on the code you've added, you won't need to map you can just add the image in your foreach:
if (! isset($standings[$match->homeTeam->name])) {
$standings[$match->homeTeam->name] = $blank;
$standing[$match->homeTeam->name]['image'] = $match->homeTeam->image;
}
if (! isset($standings[$match->awayTeam->name])) {
$standings[$match->awayTeam->name] = $blank;
$standing[$match->awayTeam->name]['image'] = $match->awayTeam->image;
}
Alternatively you could still use map once you have the standings sorted, but you may as well just add the image in with everything else.
$standingsWithImages = $standings
->map(function ($item, $key) {
$item['image'] = Team::where('name', $key)->first()->image;
return $item;
})->all();

Hash with array of hashes inside hash

I have a hash like this:
document = {
"results" => {[
{"ip" => 10, "host" => 12},
{"ip" => 13, "host" => 17}
]}
}
It's a one item hash with an array of hashes inside the hash. I specified a value of ip = 10.
If the ip is more than 10, I want to print both keys with values. This hash is very complicated and I don't know how to access these values. Can you help me?
Edit:
What if I had hash like this
document = { "results" => [{"ip" => 10, "host" => 12, "temp" => yes},{"ip" => 13, "host" => 17, "temp" => yes}] } and wanted print only ip and host after matching ip with 10?
document["results"].each do |result|
if result["ip"] > 10
puts result # will print ip and host
end
end
I would use select:
document = { "results" => [{"ip" => 10, "host" => 12},{"ip" => 13, "host" => 17}] }
puts document['results'].select { |hash| hash['ip'] > 10 }
#=> {"ip"=>13, "host"=>17}
Explanation:
document['results']
returns the array of hashes:
[{"ip" => 10, "host" => 12},{"ip" => 13, "host" => 17}]
In the next step select is called on that returned array:
document['results'].select { |hash| hash['ip'] > 10 }
This returns all sub-hashes with an value > 10 assigned to the key 'ip'.
puts just prints the result to STDOUT
I have another problem today. Here is my code:
require 'rubygems'
require 'json'
document = JSON.load File.new("hosts.txt")
file = JSON.load File.new("admins.txt")
first_table = document["results"]
second_table = file["admins"]
new_one = first_table | second_table
First hash looks like this:
document = { "results" => [{"ip" => 10, "host" => 12},{"ip" => 13, "host" => 17}] }
The second hash is
file = { "admins" => [{"host" => 12, "name" => 12},{"host" => 17, "name" => 17}] }
I want merge these two hashes matching them by host by the same value to get
{ "new_one" => [{"ip" => 10, "host" => 12, "name" => 12}, {"ip" => 13, "host" => 17}, "name" => 17]
When I try new_one = first_table | second_tableit says test.rb:24:in <main>': undefined method|' for #Hash:0x00000002ca8be8 (NoMethodError) and when I try new_one = first_table.merge(second_table)its says test.rb:26:in <main>': undefined methodmerge' for #Array:0x00000002ce88b0(NoMethodError). So what is wrong with these hashes? One time they are hashes and the second time they are arrays? How to mach these hashes? The keys and values of host the same in both hashes.

Iterate into an array of hash

I'm trying to loop into an array of hash :
response = [
{
"element" => A,
"group" => {"created" => 13, "code" => "Paris.rb", :"rsvp_limit" => 40},
"name" => "CODELAB",
"venue" => {"id" => 17485302, "place" => "la cordée", "visibility" => "public"}
},
{
"element" => B,
"group" => {"created" => 13, "code" => "Paris.rb", :"rsvp_limit" => 40},
"name" => "PARISRB",
"venue" => {"id" => 17485302, "place" => "la cordée", "visibility" => "public"}
}
]
When I run
response[0]["name"], it returns "CODELAB"
response[1]["name"] returns "PARISRB"
How can I make a loop to have the name of each element of this array of hash ?
I tried :
response.each_with_index do |resp, index|
puts array[index]["name"]
end
This is the error I get in my console :
NoMethodError: undefined method `[]' for nil:NilClass
from (pry):58:in `block in __pry__'
The array here seems to be a typo. You meant:
response.each_with_index do |resp, index|
puts resp["name"]
end
The index is not needed because the resp variable is correctly initialized to contain the hash at each iteration.
So it can be simplified to:
response.each do |resp|
puts resp["name"]
end
A little bit shorter:
puts response.map{|hash| hash['name']}
# CODELAB
# PARISRB

Return Array of Objects/Hashes sorted by attributes

I have an array of objects, in this example represented as hashes:
[
{ "mid" => 123, "updated" => "2015-05-05 05:05:05"}
{ "mid" => 456, "updated" => "2015-05-05 05:06:05"}
{ "mid" => 789, "updated" => "2015-05-05 05:05:05"}
{ "mid" => 123, "updated" => "2015-05-05 05:05:07"}
{ "mid" => 456, "updated" => "2015-05-05 05:05:05"}
]
And I need to get back only elements with the unique mid, in a process of selecting unique objects, timestamp needs to be taken to consideration, where higher datetime is the one to return. So my examples outcome would be:
[
{ "mid" => 456, "updated" => "2015-05-05 05:06:05"}
{ "mid" => 789, "updated" => "2015-05-05 05:05:05"}
{ "mid" => 123, "updated" => "2015-05-05 05:05:07"}
]
I tried couple of different approaches, but I have a problem with my logic, I always get 20 objects back instead of 3. Here is my code:
res = []
queue.length.times do
a = queue.pop
queue.each do |job|
if a.mid == job.mid
if DateTime.parse(a.updated) >= DateTime.parse(a.updated)
res << a
end
else
res << a
end
end
queue << a
end
Any idea?
Assuming you have your input in arr:
arr.group_by do |h|
h['mid'] # group same mids
end.values.map do |all|
all.max_by do |h|
Date.parse h['updated'] # pick latest by date
end
end
#⇒ [
# {"mid"=>123, "updated"=>"2015-05-05 05:05:07"},
# {"mid"=>456, "updated"=>"2015-05-05 05:06:05"},
# {"mid"=>789, "updated"=>"2015-05-05 05:05:05"}
# ]
Please avoid writing phpish code using ruby syntax.
UPD Credits to Cary Swoveland, sort_by+last reduced to max_by.
You can use something like:
queue.sort_by { |r| DateTime.parse(r[:updated]) }.reverse.uniq { |r| r[:mid] }
# => [{:mid=>456, :updated=>"2015-05-05 05:06:05"}, {:mid=>123, :updated=>"2015-05-05 05:05:07"}, {:mid=>789, :updated=>"2015-05-05 05:05:05"}]
where queue is your input array.

Random item by weight [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 3 years ago.
Improve this question
For example I have this data:
headings = {
:heading1 => { :weight => 60, :show_count => 0}
:heading2 => { :weight => 10, :show_count => 0}
:heading3 => { :weight => 20, :show_count => 0}
:heading4 => { :weight => 10, :show_count => 0}
}
total_views = 0
Now I want to serve each heading based on their weightages. For instance, for first 10 requests/iterations, heading1, heading3, heading2 and heading4 would be served 6, 2, 1, and 1 times respectively in order (by weight).
For every iteration show_count of served heading will increment by one and total_views will also increment globally.
Could you please suggest an algorithm or some ruby code to handle this.
You can use pickup gem
It accepts hash like this:
require 'pickup'
headings = {
heading1: 60,
heading2: 10,
heading3: 20,
heading4: 10
}
pickup = Pickup.new(headings)
pickup.pick
#=> heading1
pickup.pick
#=> heading1
pickup.pick
#=> heading3
pickup.pick
#=> heading1
pickup.pick
#=> heading4
So you can do something like this:
require 'pickup'
headings = {
heading1: { :weight => 60, :show_count => 0},
heading2: { :weight => 10, :show_count => 0},
heading3: { :weight => 20, :show_count => 0},
heading4: { :weight => 10, :show_count => 0}
}
pickup_headings = headings.inject({}){ |h, (k,v)| h[k] = v[:weight]; h}
pickup = Pickup.new(pickup_headings)
# let's fire it 1000 times
1000.times do
server = pickup.pick
headings[server][:show_count] += 1
end
puts headings
#=> {
#=> :heading1=>{:weight=>60, :show_count=>601},
#=> :heading2=>{:weight=>10, :show_count=>116},
#=> :heading3=>{:weight=>20, :show_count=>176},
#=> :heading4=>{:weight=>10, :show_count=>107}
#=> }
This should work for your basic case and can be modified according to the details of what you need:
class Heading
attr_reader :heading, :weight, :show_count
def initialize(heading,weight=1)
#heading=heading
#weight=weight
#show_count=0
end
def serve
puts "Served #{#heading}! "
#show_count += 1
end
end
class HeadingServer
attr_reader :headings
def initialize(headings_hash)
#headings=headings_hash.map {|h, data| Heading.new(h,data[:weight])}
#total_weight=#headings.inject(0) {|s,h| s+= h.weight}
end
def serve(num_to_serve=#total_weight)
#headings.sort {|a,b| b.weight <=> a.weight}.each do |h|
n = (h.weight * num_to_serve) / #total_weight #possibility of rounding errors
n.times { h.serve }
end
end
def total_views
#headings.inject(0) {|s,h| s += h.show_count}
end
end
headings = {
:heading1 => { :weight => 60, :show_count => 0},
:heading2 => { :weight => 10, :show_count => 0},
:heading3 => { :weight => 20, :show_count => 0},
:heading4 => { :weight => 10, :show_count => 0}
}
# Example Usage:
hs = HeadingServer.new(headings)
hs.serve(10)
hs.headings.each {|h| puts "#{h.heading} : served #{h.show_count} times"}
puts "Total Views: #{hs.total_views}"

Resources