I'm learning Ruby, and I'm having a problem while making a program.
I have a class "LineAnalyzer" that has 4 parameters (2 provided and 2 calculated). Both calculated params are: #high_wf_count (integer) and #high_wf_words (array).
Then, I have this one:
class Solution < LineAnalyzer
attr_reader :analyzers,
:highest_count_across_lines,
:highest_count_words_across_lines
def initialize
#analyzers = []
end
def analyze_file
File.foreach('test.txt') do |line|
#analyzers << LineAnalyzer.new(line.chomp,#analyzers.length+1)
end
end
def calculate_line_with_highest_frequency
#highest_count_words_across_lines = []
#highest_count_across_lines = #analyzers.max_by do
|a| a.instance_variable_get(:#highest_wf_count)
end .instance_variable_get(:#highest_wf_count)
#highest_count_words_across_lines << #analyzers.each do
|a| a.instance_variable_get(:#highest_wf_count) == #highest_count_across_lines
end .instance_variable_get(:#highest_wf_words)
end
end
The problem is that I cannot append the array #highest_wf_count to #highest_count_words_across_lines in the way I've done (it returns nil). But, I've previously taken the integer #highest_wf_count in the same way perfectly.
Can anyone tell me where's the problem?
Thanks in advance!
It seems that your problem is in this bit of code:
#highest_count_words_across_lines << #analyzers.each do
|a| a.instance_variable_get(:#highest_wf_count) == #highest_count_across_lines
end .instance_variable_get(:#highest_wf_words)
Preferably formatted as:
#highest_count_words_across_lines << #analyzers.each do |analyzer|
analyzer.instance_variable_get(:#highest_wf_count) == #highest_count_across_lines
end.instance_variable_get(:#highest_wf_words)
The problem here is that you are calling .instance_variable_get(:#highest_wf_words) on the result of the :each method.
A few lines above, you are doing something similar, where you call .instance_variable_get(:#highest_wf_count) on the result of the :max_by method, and it is working.
The difference between :max_by and :each is that :max_by returns a single analyzer, whereas :each returns the array of #analyzers over which it is iterating.
When you call :instance_variable_get(:#highest_wf_words) on that array, it's returning nil because an array will not have an instance variable named :#highest_wf_words
That is where your problem exists.
Sidenote:
It is generally not good practice to ever use :instance_variable_get. I would recommend adding to your analyzer class attr_reader :highest_wf_words, :highest_wf_count
Then, instead of calling analyzer.instance_variable_get(:#highest_wf_words), you can just call analyzer.highest_wf_words
There's a lot going on here and most of the code results from going against the grain when writing Ruby. Using instance_variable_get should be an absolute last resort. It's considered highly rude to just reach into an object and pull out a variable. It creates ugly and undesirable inter-dependencies. If that other object wanted to give you that value it would have a method to access it.
The way I see it what you're trying to do boils down to something like this:
def highest_frequency
#analyzers.map do |a|
a.highest_wf_count
end.sort.last
end
Let Analyzer implement highest_wf_count as a method, even if it's just an attr_reader. This gives you the flexibility to change how and when that value is computed. Maybe you don't need to do it when the object is initialized. Maybe it's done in another thread, or it's evaluated lazily.
Whenever possible try and structure your code as a series of straight-forward transformations. Try not to create convoluted, branching, ugly comparisons. Lean on Enumerable whenever possible, it's usually got a method that does exactly what you want, or two that in conjunction do the job perfectly.
This is way more complex than it needs to be (or should be).
Why does Solution subclass LineAnalyzer? And why are you using instance_variable_get? You should define getter methods using attr_reader on the LineAnalyzer class so you can call methods instead of using instance_variable_get, which is a brute force approach that should only be used as a last resort.
I think you should fix this before proceeding.
When you have instance methods created with attr_reader, calculating the max becomes very simple:
highest_count_across_lines = #analyzers.map(&:highest_wf_count).max
I think your error probably is caused by these lines:
#highest_count_words_across_lines << #analyzers.each do
|a| a.instance_variable_get(:#highest_wf_count) == #highest_count_across_lines
end .instance_variable_get(:#highest_wf_words)
I suggest simplifying this code, and the error will probably present itself to you. Did you really mean to append the value returned by each to #highest_count_words_across_lines? This will be an Array of analyzers. The Array class, of course, does not have a variable named :#highest_wf_words.
Again, I think you really need to simplify this code.
Related
I've been working on Ruby Koans and I've gotten to this part that I don't understand too well. Specifically, it's the about_dice_project, where they ask us to define a class to pass several pre-set tests. Originally, my class looked like
class DiceSet
attr_accessor :values
def initialize
#values = []
end
def roll(num)
num.times { |i| #values[i] = rand(1..6) }
return #values
end
end
But this failed the test below
dice = DiceSet.new
dice.roll(5)
first_time = dice.values
dice.roll(5)
second_time = dice.values
assert_not_equal first_time, second_time,
"Two rolls should not be equal"
And I realized after some testing that both first_time and second_time have the same object_id. So I looked on stackoverflow for some help and found this answer, which doesn't answer my question directly, but I did find a major difference in our code; namely, that instead of using the "num.times" way of rolling the dice, goes instead with
#values = Array.new(num) { rand(1..6) }
and this does pass the above test.
After finding this, I was trying to figure out why my original code didn't work, and so I thought that maybe Ruby was passing by reference, and that's why when the dice-rolling-method created a new array instead of modifying the existing array, that was a new object id created. But then I found this question, which says that Ruby is pass-by-value, and if that was the case, then I would have expected that my original way would work.
So now I'm confused again, and I'd like to ask - where did my line of thought go wrong? Is Ruby pass-by-value only in certain cases? Have I misunderstood what pass-by-value/reference even means? Is my issue not related to pass-by-value/reference at all?
You can see object in ruby as a reference to data.
So even if Ruby is pass by value, the thing got copy is object, a.k.a a handle to underlining data.
Even from the answer you link to, had said:
Variables are always references to objects. In order to get an object that won't change out from under you, you need to dup or clone the object you're passed, thus giving an object that nobody else has a reference to. (Even this isn't bulletproof, though — both of the standard cloning methods do a shallow copy, so the instance variables of the clone still point to the same objects that the originals did. If the objects referenced by the ivars mutate, that will still show up in the copy, since it's referencing the same objects.)
I have the following:
article_results.keys do |key|
article_results[key].map! do |a|
a[:filename] = c.filename
a[:sitename] = c.site_name
a
end
end
As I want to add to each element of each array within the hash dynamically, but for some reason a[:filename] and a[:sitename] are blank when they are used.
So I want to know if I should be using .each instead.
Also I guess I'd like to know what's the main difference since they both can be used for side-effects.
I'm adding this as an extra fyi, I'm using ruby 1.8.7 so it would be nice to know how it differs between versions (1.8.7 - 1.9+) as well.
P.s. I know what the difference between .each and .map is, I'm asking specifically about .map!.
#map has a bit different semantics for hashes than it has for arrays (and i think it's not very consistent between versions of ruby). in general if you are looking for an array as a result of some operation - #map is your friend, however if you want hash as a result of some operation - you're better off with #reduce:
article_results.reduce({}) do |hash, (key, value)|
hash.merge(key => value.merge(filename: c.filename,
sitename: c.sitename))
end
alternatively if you don't care how "functional" your code is, you can use #each:
article_results.each do |key, value|
article_results[key].merge!(filename: c.filename,
sitename: c.sitename)
end
I am using Ruby, and I am relearning arrays and trying to better understand them. I know what they are but have never fully utilized them. I have an array, odds, and wanted to double every number in it. I came up with the below solution; however, I wanted to see if there was a more elegant/simple solution to my problem.
odds = [1,3,5,7,9]
odds.each do |x|
odds[odds.index(x)]=x*2
end
end result is odds = [2,6,10,14,18]
You can use the map! enumerator to modify every item in an array:
odds.map!{ |x| x*2}
If you really want to modify in place, and you really want to use each, I guess your approach is as good as any. It doesn't feel idiomatic, but it does meet your stated constraints.
Here are some more common approaches:
Array.map
Mapping the array with map won't modify the original array (it's not in-place), which is often a good thing, but it might not be what you're looking for:
odds.map { |x| x*2 }
Array.map!
If you really do want to modify the original array, you can use map! to map in-place:
odds.map! { |x| x*2 }
Array.each_index
You did ask specifically about each, so if you want to use an each and you want to modify the original array, each_index might be your best bet:
odds.each_index { |i| odds[i] *= 2 }
I'm trying to put all unique combinations of a number's individual digits into an array. I'm passing in an integer and then using the permutation method. But to do so I believe I need to convert the number argument into a string. However when I iterate through the array, it is not joining the strings such that I can convert to an integer. Also I realize I should be using each to do the loop and not for but not sure how to do this in the block.
def number_shuffle(number)
combo = []
combo = number.to_s.split("").permutation.to_a
for i in combo
i.join.to_i
end
return combo
end
Is there something wrong with my loop? When I test one item in the array using combo[0].join.to_i I get an integer as output. But for some reason my loop is leaving these as strings.
In your example attempt, you failed to modify the contents of the array. I've replaced your for loop with a call to Array#map, which has a return value of a new Array containing the results of each iteration in order. Here is a verbose version of the method you want (for clarity).
def number_shuffle(number)
permutations = number.to_s.split('').permutation
numbers = permutations.map do |digits|
digits.join.to_i
end
return numbers
end
number_shuffle 123 # => [123, 132, 213, 231, 312, 321]
If you prefer a one-liner (replacing split with chars, per #timbetimbe below):
def number_shuffle(n)
n.to_s.chars.permutation.map {|x| x.join.to_i }
end
The reason why your for loop does not mutate combo is because it does nothing to it. The interpreter just evaluates i.join.to_i but does not edit the value in place.
Secondly, you do not need to use for, return, split('') or to_a on the return from permutation as they are not really idiomatic approaches. A more concise approach would be:
def number_shuffle(number)
number.to_s.chars.permutation.map { |combo| combo.join.to_i }
end
Reasoning:
favor each over for as it's preferred in the community.
use map from permutation as it includes Enumerable (same as Array) so you can just jump right into map
returns are implicit, embrace that :)
Linters help tremendously when getting started: Rubocop
This style guide is enforced by: bbatsov's Style Guide
Even faster and more efficient (to not have to copy anything in memory):
def number_shuffle(number)
number.to_s.split('').permutation.to_a.map do |combination|
combination.join.to_i
end
end
I am playing around with OOP in MATLAB, and I have the following constructor:
function obj = Squadron(num_fighters, num_targets, time_steps)
if nargin == 0
num_targets = 100;
time_steps = 100;
num_fighters = 10;
end
obj.num_shooters = num_fighters;
for iShooter = 1:obj.num_shooters
a(iShooter) = Shooter(num_targets, time_steps);
end
obj.ShooterArray = a;
obj.current_detections = zeros(num_fighters, num_targets);
end
That temporary variable 'a' smells terrible. Is there a better way to initialize an array of objects, I wish there was a push/pop method. I am sure there is a better way to do this.
Looks like you are trying to create an array of handle objects (Shooters) and store it inside the property of another handle object (a Squardron). I have had a very similar problem discussion that might help you.
In short: What you are doing might not be pretty - but might be pretty good already.
When creating an array in Matlab it is usually a good Idea to do some pre-allocation to reserve memory which speeds up performance significantly.
In a normal case something like this:
a=zeros(1,1000);
for n=1:1000
a(n)=n;
end
(here a=1:1000; would be even better)
For objects the pre-allocation works by assigning one of the objects to the very last field in the array. Matlab then fills the other fields before that with objects (handles) that it creates by calling the constructor of that object with no arguments (see Matlab help). Hence a pre-allocation for objects could look like this:
a(1,1000)=ObjectConstructor();
for n=1:1000
a(n)=ObjectConstructor();
end
or simply
for n=1000:-1:1
a(n)=ObjectConstructor();
end
Making sure Shooter can be called with no arguments you should be able to do something like:
for iShooter = obj.num_shooters:-1:1
obj.ShooterArray(iShooter) = Shooter(num_targets, time_steps);
end
However, it turns out that for some reason this direct storing of an array of objects in another object's property creates very bad performance. (Probably the array pre-allocation does not work well in this case). Hence using an auxiliary variable and allocating the full array at once to the property is in this case is a good idea to increase performance.
I would try:
for iShooter = obj.num_shooters:-1:1
a(iShooter) = Shooter(num_targets, time_steps);
end
obj.ShooterArray = a;
Again - for more detail see this discussion
There are a couple of ways to handle this situation...
Building object arrays in the constructor:
You could modify your Shooter class such that when you pass arrays of values it creates an array of objects. Then you could initialize ShooterArray like so:
obj.ShooterArray = Shooter(repmat(num_targets,1,num_fighters),...
repmat(time_steps,1,num_fighters));
Replicating instances of a value class:
If Shooter is a value class, and each object is going to be exactly the same (i.e. you don't initialize any of its default properties to random values), then you can create just one object and replicate it using REPMAT:
obj.ShooterArray = repmat(Shooter(num_targets,time_steps),1,num_fighters);
Unfortunately, if Shooter is a subclass of the handle class, you can't just replicate it as you can with a value class. You would actually be replicating references to just one object, when you really need a number of separate objects each with their own unique reference. In such a case, your current code is likely the best solution.