How to count the number of objects created in Ruby - ruby

Is it possible to count the total number of objects created in a Ruby application? If so, how can I do it?
I know how to count the number of instances of a given class I create, as of in this post, but is there a way to get the number of objects created of any class in an application (including internal ones)?

You should use
ObjectSpace.count_objects
For example, this is what it outputs on a fresh IRB session:
{
:TOTAL => 30161,
:FREE => 378,
:T_OBJECT => 152,
:T_CLASS => 884,
:T_MODULE => 30,
:T_FLOAT => 4,
:T_STRING => 11517,
:T_REGEXP => 165,
:T_ARRAY => 3395,
:T_HASH => 180,
:T_STRUCT => 2,
:T_BIGNUM => 2,
:T_FILE => 15,
:T_DATA => 1680,
:T_MATCH => 99,
:T_COMPLEX => 1,
:T_NODE => 11620,
:T_ICLASS => 37
}

Related

Ruby 2.7: How to merge a hash of arrays of hashes and eliminate the duplicates based on one key:value

I'm trying to complete a project-based assessment for a job interview, and they only offer it in Ruby on Rails, which I know little to nothing about. I'm trying to take one hash that contains two or more hashes of arrays and combine the arrays into one array of hashes, while eliminating duplicate hashes based on an "id":value pair.
So I'm trying to take this:
h = {
'first' =>
[
{ 'authorId' => 12, 'id' => 2, 'likes' => 469 },
{ 'authorId' => 5, 'id' => 8, 'likes' => 735 },
{ 'authorId' => 8, 'id' => 10, 'likes' => 853 }
],
'second' =>
[
{ 'authorId' => 9, 'id' => 1, 'likes' => 960 },
{ 'authorId' => 12, 'id' => 2, 'likes' => 469 },
{ 'authorId' => 8, 'id' => 4, 'likes' => 728 }
]
}
And turn it into this:
[
{ 'authorId' => 12, 'id' => 2, 'likes' => 469 },
{ 'authorId' => 5, 'id' => 8, 'likes' => 735 },
{ 'authorId' => 8, 'id' => 10, 'likes' => 853 },
{ 'authorId' => 9, 'id' => 1, 'likes' => 960 },
{ 'authorId' => 8, 'id' => 4, 'likes' => 728 }
]
Ruby has many ways to achieve this.
My first instinct is to group them by id it and pick only first item from the array.
h.values.flatten.group_by{|x| x["id"]}.map{|k,v| v[0]}
Much cleaner approach is to pick the distinct item based on id after flattening the array of hash which is what Cary Swoveland suggested in the comments
h.values.flatten.uniq { |h| h['id'] }
TL;DR
The simplest solution to the problem that fits the data you posted is h.values.flatten.uniq. You can stop reading here unless you want to understand why you don't need to care about duplicate IDs with this particular data set, or when you might need to care and why that's often less straightforward than it seems.
Near the end I also mention some features of Rails that address edge cases that you don't need for this specific data. However, they might help with other use cases.
Skip ID-Specific Deduplication; Focus on Removing Duplicate Hashes Instead
First of all, you have no duplicate id keys that aren't also part of duplicate Hash objects. Despite the fact that Ruby implementations preserve entry order of Hash objects, a Hash is conceptually unordered. Pragmatically, that means two Hash objects with the same keys and values (even if they are in a different insertion order) are still considered equal. So, perhaps unintuitively:
{'authorId' => 12, 'id' => 2, 'likes' => 469} ==
{'id' => 2, 'likes' => 469, 'authorId' => 12}
#=> true
Given your example input, you don't actually have to worry about unique IDs for this exercise. You just need to eliminate duplicate Hash objects from your merged Array, and you have only one of those.
duplicate_ids =
h.values.flatten.group_by { _1['id'] }
.reject { _2.one? }.keys
#=> [2]
unique_hashes_with_duplicate_ids =
h.values.flatten.group_by { _1['id'] }
.reject { _2.uniq.one? }.count
#=> 0
As you can see, 'id' => 2 is the only ID found in both Hash values, albeit in identical Hash objects. Since you have only one duplicate Hash, the problem has been reduced to flattening the Array of Hash values stored in h so that you can remove any duplicate Hash elements (not duplicate IDs) from the combined Array.
Solution to the Posted Problem
There might be uses cases where you need to handle the uniqueness of Hash keys, but this is not one of them. Unless you want to sort your result by some key, all you really need is:
h.values.flatten.uniq
Since you aren't being asked to sort the Hash objects in your consolidated Array, you can avoid the need for another method call that (in this case, anyway) is a no-op.
"Uniqueness" Can Be Tricky Absent Additional Context
The only reason to look at your id keys at all would be if you had duplicate IDs in multiple unique Hash objects, and if that were the case you'd then have to worry about which Hash was the correct one to keep. For example, given:
[ {'id' => 1, 'authorId' => 9, 'likes' => 1_920},
{'id' => 1, 'authorId' => 9, 'likes' => 960} ]
which one of these records is the "duplicate" one? Without other data such as a timestamp, simply chaining uniq { h['id' } or merging the Hash objects will either net you the first or last record respectively. Consider:
[
{'id' => 1, 'authorId' => 9, 'likes' => 1_920},
{'id' => 1, 'authorId' => 9, 'likes' => 960}
].uniq { _1['id'] }
#=> [{"id"=>1, "authorId"=>9, "likes"=>1920}]
[
{'id' => 1, 'authorId' => 9, 'likes' => 1_920},
{'id' => 1, 'authorId' => 9, 'likes' => 960}
].reduce({}, :merge)
#=> {"id"=>1, "authorId"=>9, "likes"=>960}
Leveraging Context Like Rails-Specific Timestamp Features
While the uniqueness problem described above may seem out of scope for the question you're currently being asked, understanding the limitations of any kind of data transformation is useful. In addition, knowing that Ruby on Rails supports ActiveRecord::Timestamp and the creation and management of timestamp-related columns within database migrations may be highly relevant in a broader sense.
You don't need to know these things to answer the original question. However, knowing when a given solution fits a specific use case and when it doesn't is important too.

Laravel Collection with groupby, count and sum

I'm struggling to get a groupby on a collection to work - I'm not getting the concept just yet.
I'm pulling a collection of results from a table for a player the eloquent collection will have data like this:
['player_id'=>1, 'opposition_id'=>10, 'result'=>'won', 'points'=>2],
['player_id'=>1, 'opposition_id'=>11, 'result'=>'lost', 'points'=>0],
['player_id'=>1, 'opposition_id'=>12, 'result'=>'lost', 'points'=>0],
['player_id'=>1, 'opposition_id'=>10, 'result'=>'won', 'points'=>2],
['player_id'=>1, 'opposition_id'=>11, 'result'=>'lost', 'points'=>0],
['player_id'=>1, 'opposition_id'=>10, 'result'=>'lost', 'points'=>0],
['player_id'=>1, 'opposition_id'=>12, 'result'=>'won', 'points'=>2],
I want to be able to groupBy('opposition_id') and then give me a count of results in total, total won, total lost and sum of points to end up with a collection like this:
['opposition_id'=>10, 'results'=>3, 'won'=>2, 'lost'=>1, 'points'=>4],
['opposition_id'=>11, 'results'=>2, 'won'=>0, 'lost'=>2, 'points'=>0],
['opposition_id'=>10, 'results'=>2, 'won'=>1, 'lost'=>1, 'points'=>2]
I'm trying to avoid going back to the database to do this as I already have the results from previous activity.
How can I do this using Laravel collection methods, So far all I have is:
$stats = $results->groupBy('opposition_id');
I've looked at map() but do not yet understand that method to work through a solution
Can anyone point me in the right direction please.
Happy to go back to the database if needed but assumed I could do this with the collection I already have rather than create another query. Solutions I've found on here all appear to be providing a solution in the query
Thank you
Take a look here, working code with explanation in comments.
// make a collection
$c = collect(
[
['player_id' => 1, 'opposition_id' => 10, 'result' => 'won', 'points' => 2],
['player_id' => 1, 'opposition_id' => 11, 'result' => 'lost', 'points' => 0],
['player_id' => 1, 'opposition_id' => 12, 'result' => 'lost', 'points' => 0],
['player_id' => 1, 'opposition_id' => 10, 'result' => 'won', 'points' => 2],
['player_id' => 1, 'opposition_id' => 11, 'result' => 'lost', 'points' => 0],
['player_id' => 1, 'opposition_id' => 10, 'result' => 'lost', 'points' => 0],
['player_id' => 1, 'opposition_id' => 12, 'result' => 'won', 'points' => 2]
]
);
// this only splits the rows into groups without any thing else.
// $groups will be a collection, it's keys are 'opposition_id' and it's values collections of rows with the same opposition_id.
$groups = $c->groupBy('opposition_id');
// we will use map to cumulate each group of rows into single row.
// $group is a collection of rows that has the same opposition_id.
$groupwithcount = $groups->map(function ($group) {
return [
'opposition_id' => $group->first()['opposition_id'], // opposition_id is constant inside the same group, so just take the first or whatever.
'points' => $group->sum('points'),
'won' => $group->where('result', 'won')->count(),
'lost' => $group->where('result', 'lost')->count(),
];
});
// if you don't like to take the first opposition_id you can use mapWithKeys:
$groupwithcount = $groups->mapWithKeys(function ($group, $key) {
return [
$key =>
[
'opposition_id' => $key, // $key is what we grouped by, it'll be constant by each group of rows
'points' => $group->sum('points'),
'won' => $group->where('result', 'won')->count(),
'lost' => $group->where('result', 'lost')->count(),
]
];
});
// here $groupwithcount will give you objects/arrays keyed by opposition_id:
[
10 => ["opposition_id" => 10,"points" => 4,"won" => 2,"lost" => 1]
11 => ["opposition_id" => 11,"points" => 0,"won" => 0,"lost" => 2]
12 => ["opposition_id" => 12,"points" => 2,"won" => 1,"lost" => 1]
]
// if you use $groupwithcount->values() it'll reset the keys to 0 based sequence as usual:
[
0 => ["opposition_id" => 10,"points" => 4,"won" => 2,"lost" => 1]
1 => ["opposition_id" => 11,"points" => 0,"won" => 0,"lost" => 2]
2 => ["opposition_id" => 12,"points" => 2,"won" => 1,"lost" => 1]
]

Ruby count hash key

I have a hash like this:
{'yes' => 23,
'b' => 'travel',
'yesterday' => 34,
5 => '234',
:yesss => :fg,
try: 30,
key: 'some value',
'yesterday1' => 34,
'yesteryear' => 2014}
How can I count all keys which includes yes?
I suppose you meant:
your_hash.count { |k, _| k.to_s.include?('yes') }
#=> 5

ANSI escape code with html tags in Ruby?

Interestingly there are built-in ansi escape code in Ruby.
There is also a more powerful version from a gem.
Unfortunately, these logs output to the console. My text is shown in the page so I need HTML tags to wrap around my text.
Would you guys have any idea how to go about it?
I guess what you want is to transform from escape characters to HTML.
I did it once by assuming the following code/colour hash for escape characters:
{ :reset => 0,
:bright => 1,
:dark => 2,
:underline => 4,
:blink => 5,
:negative => 7,
:black => 30,
:red => 31,
:green => 32,
:yellow => 33,
:blue => 34,
:magenta => 35,
:cyan => 36,
:white => 37,
:back_black => 40,
:back_red => 41,
:back_green => 42,
:back_yellow => 43,
:back_blue => 44,
:back_magenta => 45,
:back_cyan => 46,
:back_white => 47}
What I did was the following conversion (far away from being anyhow optimized):
def escape_to_html(data)
{ 1 => :nothing,
2 => :nothing,
4 => :nothing,
5 => :nothing,
7 => :nothing,
30 => :black,
31 => :red,
32 => :green,
33 => :yellow,
34 => :blue,
35 => :magenta,
36 => :cyan,
37 => :white,
40 => :nothing,
41 => :nothing,
43 => :nothing,
44 => :nothing,
45 => :nothing,
46 => :nothing,
47 => :nothing,
}.each do |key, value|
if value != :nothing
data.gsub!(/\e\[#{key}m/,"<span style=\"color:#{value}\">")
else
data.gsub!(/\e\[#{key}m/,"<span>")
end
end
data.gsub!(/\e\[0m/,'</span>')
return data
end
Well, you will need fill the gaps of the colours I am not considering or backgrounds. But I guess you can get the idea.
Hope it helps
Thank you for the link to a cool gem I had not seen. I think what you are looking for, however, is termed Cascading Style Sheets (CSS). Because that google search will bring up about every other page cached on the internet, here are a few links for you that should get you started:
http://www.w3schools.com/css/default.asp - start with the most basic stuff.
http://guides.rubyonrails.org/layouts_and_rendering.html
http://rorrocket.com/ - generalized tutorials
http://nubyonrails.com/articles/dynamic-css - this may or may not be useful but a brief glance might provide some more rails like info on css, including SASS*.
*SASS is a ruby-ized abstraction to CSS used very frequently with ruby/rails

Ruby Array group and average by hour

We get our data from a sensor which records and stores data like hashes.
At any time it measures a few stuff like that:
{:temperature => 30, :pression => 100, :recorded_at => 14:34:23}
{:temperature => 30, :pression => 101, :recorded_at => 14:34:53}
{:temperature => 31, :pression => 102, :recorded_at => 14:34:24}
{:temperature => 30, :pression => 101, :recorded_at => 14:34:55}
{:temperature => 30, :pression => 102, :recorded_at => 14:34:25}
{:temperature => 31, :pression => 101, :recorded_at => 14:34:56}
We need to be able to export that data on a JSON format, but we have way too much data (the sensor records about every 30 seconds) and we need to remove some of the data. Ideally we'd want to export 1 measure per hour in the last 24 hours so we have something like
{0 => {:temperature => 30, :pression => 100}, 1 => {:temperature => 30, :pression => 100}, 2 => {:temperature => 30, :pression => 100}, 3 => {:temperature => 30, :pression => 100}, 4 => {:temperature => 30, :pression => 100}}
For each hour, the temperature is the average of all temperatures measured within that hour.
Also, if for any reason some data is missing for 1hour, I'd like to to extrapolate it by being the mean between the previous and next hour. Anybody can help?
More functional version (with simple interpolation of missing values)
probs = [{:temperature => .. }] # array of measurings
def average(list, key)
list.reduce(0){|acc,el| acc+el[key]} / list.length unless list.empty
end
prob_groups = probs.group_by{|prob| prob[:recorded_at][0,2].to_i}
average_groups = prob_groups.map do |hour,prob_group|
{ hour => {
:temperature => average(prob_group, :temperature),
:pression => average(prob_group, :pression)
}}
end.reduce{|acc,el| acc.merge(el)}
def interpolate(p, n, key)
(p[key] + n[key])/2 unless p.nil? || n.nil? || p[key].nil? || n[key].nil?
end
resuls = (1..24).map do |hour|
if average_groups[hour]
{ hour => average_groups[hour] }
else
{ hour => {
:temperature => interpolate(average_groups[hour-1], average_groups[hour+1], :temperature),
:pression => interpolate(average_groups[hour-1], average_groups[hour+1], :pression)
}}
end
end.reduce{|acc,el| acc.merge(el)}
Hope it works
something like this
t = [....] - array of measurings
result = {}
(1..24).each do|hour|
# measurings of given hour
measurings = t.select{|measuring| measuring[:recorded_at][0, 2].to_i == hour}
# average temperature of hour
sum = measurings.inject(0){|sum, measuring| sum + measuring[:temperature].to_i}
average_temperature = (measurings.length == 0)? nil: sum/measurings.length.to_f
result[hour] = average_temperature
end
If you are not interested on the history but only on an approximation of actual value(s), consider to use a "moving metric" (http://en.wikipedia.org/wiki/Moving_average).

Resources