Making a new array considered a Dynamic Assignment Ruby - ruby-on-rails

I want to make an array where to keep scores for every user. An example of the array would be ScoreArray["example#yahoo.com"] = 10. To do this I need to instantiate the Array first. So I tried something like:
ScoreArray = Array.new
#sugested.each do |gg|
nr = 0
#followees.each do |ff|
if (ff.email == gg.email) then nr = nr + 1
end
end
if(gg.following.count != 0) then
score = ( nr/#followees.count ) * ( gg.followers.count / gg.following.count)
ScoreArray[gg.email] = score
pry.bind
else score = 0
end
end
All this code is inside a method called candidates . When I try to run rails server I get the following error message on the page where I invoke this method :
home/flo/Ruby/Licenta/honk_app/app/controllers/application_controller.rb:45: dynamic constant assignment ScoreArray = Array.new ^
Any ideas how can I avoid this problem? And why is it doing this?(from what I've read is because it's inside a method and ruby doesn't like instantiating a "constant" each time a method is called. The thing is , this is not a constant ... for each user that logs in I will have a separate array).

In ruby a leading capital letter denotes a constant - if you don't want a constant then start with a lowercase letter (if a local variable isn't sufficient for your purpose, consider an instance variable)
In addition arrays can't be used as you show them
some_array[gg.email]
Will raise an exception if gg.email is a string

Try this
score_hash = Hash.new
score_hash[gg.email] = score
sorted_hash = Hash[score_hash.sort_by{|k, v| v}.reverse]

Related

Reverse a String in Ruby by Reading Backwards

I'm just practicing and learning while loops and conditionals (not yet into arrays). I'm trying to reverse any string by concatenating letters, starting from the last letter of the word to the first. eg., for cat, start at t, then a, then c to get tac.
I don't get what's wrong in the code. I'm wondering why the 6th line ( reverse += letter) gives the error message:
6: in `+': no implicit conversion of nil into String (TypeError)
What's being nil'ed?
def is_reversed(word)
i = word.length
reverse = ""
while i > word.length || i != -1
letter = word[i]
reverse += letter
i = i - 1
end
return reverse
end
puts is_reversed("cat")
There are a few issue here but the bottom line is that you're looping wrong. You can either count up to a number or down from a number and it looks like you want to count down by starting at word.length. That's fine, but let's look and see what you're actually doing.
With while i > word.length || i != -1 you're checking each iteration that i is...greater than the length of the word? How would it get that way (you're not adding to i anywhere) and why would you want to check that?
Since you chose to count down, we want to stop when there are no letters remaining. So change your condition to while i > 0. Now we will loop only while there are letters left to go through.
There's another problem though - because indices start at 0, trying to get word[i] when i == 3 will get you nil! So you actually want to move the i = i - 1 to be the first line within your loop.
After these changes, you should have:
def is_reversed(word)
i = word.length
reverse = ""
while i > 0
i = i - 1
letter = word[i]
reverse += letter
end
return reverse
end
puts is_reversed("cat")
def is_reversed(word)
i = word.length
reverse = ""
while i > word.length || i != -1
i = i - 1
letter = word[i]
reverse += letter
end
return reverse
end
puts is_reversed("cat")
// will return 'tact'
Very first time in the loop above you're trying to find the letter you're putting the index as word.length which actually doesn't exist & hence returns nil which threw the error.
To get the last letter of a string you'll have to do i = i - 1 before you do anything else inside the loop.
Second, I think your condition is flawed. If you try to find the element at -1 in an array or string in ruby it will give you the last element.
And the first condition i > word.length will never satisfy as i's value is word.length.
So you can do something like this
def is_reversed(word)
i = word.length
reverse = ""
while i > 0
i = i - 1
letter = word[i]
reverse += letter
end
return reverse
end
puts is_reversed("cat")
//returns 'tac'
I realize this has been answered, but how about something more like:
def is_reversed(w)
w.split("").each_with_object("").with_index{ |(l,a), i| a << w[(w.length-1-i)] }
end
In console:
is_reversed('this is reversed')
=> "desrever si siht"
is_reversed('and so is this')
=> "siht si os dna"
is_reversed('antidisestablishmentarianism')
=> "msinairatnemhsilbatsesiditna"
Notes:
105 characters instead of 152
3 lines instead of 10
Uses << instead of += (which is faster)
Avoids the while i > 0 bit which is, IMO, not very idiomatic Ruby
Avoids unnecessary variable assignments (i = word.length, reverse = "", i = i - 1, and letter = word[i])

Why is initializing variables so important?

Please can someone explain to me, why NOT initializing first_idx and last_idx causes the code not to run??
When I run it I get this error "undefined local variable or method last_idx". I know that the advice is to always initialize the variables, but I don't understand why. After all first_idx and last_idx will ALWAYS get a value inside the loop because the argument letter is always present in the string (in this particular problem).
I'd really appreciate some (simple) insight. Thank you!
P.S, I also know that the problem is easily solved using #index and #rindex in Ruby, but I'm not allowed to solve it using straightforward methods.
def find_for_letter(string, letter)
first_idx = nil
0.upto(string.length - 1) do |idx1|
if string[idx1] == letter
first_idx = idx1
break
end
end
last_idx = nil
(string.length - 1).downto(0) do |idx2|
if string[idx2] == letter
last_idx = idx2
break
end
end
if last_idx == first_idx
return [first_idx]
else
return [first_idx, last_idx]
end
end
def first_last_indices(word)
h = {}
word.chars.each do |char|
h[char] = find_for_letter(word, char)
end
h
end
Variables in block
From the Ruby Programming Language:
Blocks define a new variable scope: variables created within a block
exist only within that block and are undefined outside of the block.
Be cautious, however; the local variables in a method are available to
any blocks within that method. So if a block assigns a value to a
variable that is already defined outside of the block, this does not
create a new block-local variable but instead assigns a new value to
the already-existing variable.
a = 0
2.times do
a = 1
end
puts a #=> 1
b = 0
2.times do |i;b| # <- b will stay a block-local variable
b = 1
end
puts b #=> 0
2.times do |i|
c = 1
end
puts c #=> undefined local variable or method `c' for main:Object (NameError)
Refactoring your code
Iterating with chars and index
Here's a smaller method for your goal.
It keeps a hash with minmax indices for each character.
The default hash value is an empty array.
The method iterates over each character (with index).
If minmax array already contains 2 values :
it replaces the second one (max) with current index.
it adds current index to the array otherwise.
def first_last_indices(word)
minmax_hash = Hash.new { |h, k| h[k] = [] }
word.each_char.with_index do |char, index|
minmax = minmax_hash[char]
if minmax.size == 2
minmax[1] = index
else
minmax << index
end
end
minmax_hash
end
p first_last_indices('hello world')
{"h"=>[0], "e"=>[1], "l"=>[2, 9], "o"=>[4, 7], " "=>[5], "w"=>[6], "r"=>[8], "d"=>[10]}
With group_by
Here's another possibility. It uses group_by to get all the indices for each character, and minmax to get just the first and last indices :
def first_last_indices(word)
word.each_char.with_index
.group_by{ |c, _| c }.map{ |c, vs|
[c, vs.map(&:last).minmax.uniq]
}.to_h
end
p first_last_indices('hello world')
{"h"=>[0], "e"=>[1], "l"=>[2, 9], "o"=>[4, 7], " "=>[5], "w"=>[6], "r"=>[8], "d"=>[10]}
Even if you do not declare last_idx, you can still initialise it inside the loop, i.e.:
(string.length - 1).downto(0) do |idx2|
if string[idx2] == letter
last_idx = idx2 # works absolutely fine
break
end
end
However notice where you declared the variable. Its a local variable and hence its tied to the block you are in. Now when you try to access that variable outside the block, you get the error:
undefined local variable or method last_idx
To make the variable available outside the block, you have to declare it outside. That is what you are doing when you declare last_idx = nil before the block where its assigned a value.
UPDATE:
Though by using instance variables you can avoid declaration, the best practices suggests it should be used in cases where information that these variables have is relevant to all or almost all of the class. On the other hand, if the information is very much limited to this particular method use local variables.
This is just the way that local variables work.
If you use instance variables, Ruby will assume that they have been initialised inside the conditional block, but will not for local variables.
def find_for_letter(string, letter)
0.upto(string.length - 1) do |idx1|
if string[idx1] == letter
#first_idx = idx1
break
end
end
(string.length - 1).downto(0) do |idx2|
if string[idx2] == letter
#last_idx = idx2
break
end
end
if #last_idx == #first_idx
return [#first_idx]
else
return [#first_idx, #last_idx]
end
end
This works fine.

Getting the number of same elements in both arrays in Ruby

I need to count the number of values that both arrays have.
def process_2arrays(arr1, arr2)
length1 = arr1.count
length2 = arr2.count
arr3 = []
i = 0
while length1 >= i do
ci = arr1[i]
if arr2.include?(ci)
arr3 << ci
damn = arr3.count
i = i + 1
end
return [(damn), (2), (3), (4)]
end
end
When I pass the values to the function it returns [nil, 2, 3, 4]
Whats the problem here?
To find elements that exist in both arrays, use the set intersection method &.
http://ruby-doc.org/core-2.2.0/Array.html#method-i-26
def count_same_elements(a1,a2)
(array1 & array2).length
end
Example
count_same_elements([1,2,3,4],[2,3,4,5])
=> 3
damn is initialized within a do .. end block, specifically the while block. Therefore, its value will live within that scope, and when you call the variable outside the block its value is nil.
If you want to preserve the value, you must initialize the variable to nil outside the block.
i = 0
damn = nil
...
As a side note, your code is lacking the most basic Ruby standards. In Ruby you generally use an iterator, not the while. Moreover, you don't use the return at the end of a method.
This is how you would write your method in Ruby using the iterators and taking advantage of some methods from the core library.
def process_2arrays(arr1, arr2)
arr3 = arr1.select { |e| arr2.include?(e) }
[arr3.size, 2, 3, 4]
end
Changing completely approach, you can use
def process_2arrays(arr1, arr2)
(arr1 & arr2).size
end

call function until (with call limit)

I have a function that generates random output (string).
I need to call that function until I get 3 different outputs (strings).
What is the most elegant way to generate array with 3 unique strings by calling the function, with the limit how many times the function can be called if the output is not generated in specified number of attempts?
Here's what I currently have:
output = []
limit_calls = 5
limit_calls.times do |i|
str = generate_output_function
output.push str
break if output.uniq.size > 2
end
Can this be beautified / shortened to 1 line? I'm pretty sure in ruby.. :)
Thanks
Using a set makes it (a bit) easier:
require 'set'
output = Set.new
limit_calls = 5
call_count = 0
while output.size < 3 and call_count < limit_calls
output << generate_output_function
call_count += 1
end
output
or with an array
output = []
limit_calls = 5
while output.size < limit_calls and output.uniq.size < 3
output << generate_output_function
end
output.uniq
UPDATE with the call limit. Seems like the Array version wins! Thanks Iain!
Will also ponder a version using inject.
UPDATE 2 - with inject:
5.times.inject([]) { |a, el| a.uniq.size < 3 ? a << generate_output_function : a }
there is your oneliner. I am not sure I prefer it cause it is a bit hard to follow.....
Froderik's answer missed out the call_limit requirement. What about a function like...
def unique_string_array(call_limit)
output = []
calls = 0
until (output.size == 3 || calls == call_limit) do
(output << generate_output_function).uniq! && calls+=1
end
output
end
It isn't a one-liner but it is readable... with this implementation, you may end up with arrays less than size 3. The most important thing is that you have a test that asserts the behaviour you want! (in order to test this thoroughly you'll have to stub out the call to generate_output_function)

ruby looping question

I want to make a loop on a variable that can be altered inside of the loop.
first_var.sort.each do |first_id, first_value|
second_var.sort.each do |second_id, second_value_value|
difference = first_value - second_value
if difference >= 0
second_var.delete(second_id)
else
second_var[second_id] += first_value
if second_var[second_id] == 0
second_var.delete(second_id)
end
first_var.delete(first_id)
end
end
end
The idea behind this code is that I want to use it for calculating how much money a certain user is going to give some other user. Both of the variables contain hashes. The first_var is containing the users that will get money, and the second_var is containing the users that are going to pay. The loop is supposed to "fill up" a user that should get money, and when a user gets full, or a user is out of money, to just take it out of the loop, and continue filling up the rest of the users.
How do I do this, because this doesn't work?
Okay. What it looks like you have is two hashes, hence the "id, value" split.
If you are looping through arrays and you want to use the index of the array, you would want to use Array.each_index.
If you are looping through an Array of objects, and 'id' and 'value' are attributes, you only need to call some arbitrary block variable, not two.
Lets assume these are two hashes, H1 and H2, of equal length, with common keys. You want to do the following: if H1[key]value is > than H2[key]:value, remove key from H2, else, sum H1:value to H2:value and put the result in H2[key].
H1.each_key do |k|
if H1[k] > H2[k] then
H2.delete(k)
else
H2[k] = H2[k]+H1[k]
end
end
Assume you are looping through two arrays, and you want to sort them by value, and then if the value in A1[x] is greater than the value in A2[x], remove A2[x]. Else, sum A1[x] with A2[x].
b = a2.sort
a1.sort.each_index do |k|
if a1[k] > b[k]
b[k] = nil
else
b[k] = a1[k] + b[k]
end
end
a2 = b.compact
Based on the new info: you have a hash for payees and a hash for payers. Lets call them ees and ers just for convenience. The difficult part of this is that as you modify the ers hash, you might confuse the loop. One way to do this--poorly--is as follows.
e_keys = ees.keys
r_keys = ers.keys
e = 0
r = 0
until e == e_keys.length or r == r_keys.length
ees[e_keys[e]] = ees[e_keys[e]] + ers[r_keys[r]]
x = max_value - ees[e_keys[e]]
ers[r_keys[r]] = x >= 0 ? 0 : x.abs
ees[e_keys[e]] = [ees[e_keys[e]], max_value].min
if ers[r_keys[r]] == 0 then r+= 1 end
if ees[e_keys[e]] == max_value then e+=1 end
end
The reason I say that this is not a great solution is that I think there is a more "ruby" way to do this, but I'm not sure what it is. This does avoid any problems that modifying the hash you are iterating through might cause, however.
Do you mean?
some_value = 5
arrarr = [[],[1,2,5],[5,3],[2,5,7],[5,6,2,5]]
arrarr.each do |a|
a.delete(some_value)
end
arrarr now has the value [[], [1, 2], [3], [2, 7], [6, 2]]
I think you can sort of alter a variable inside such a loop but I would highly recommend against it. I'm guessing it's undefined behaviour.
here is what happened when I tried it
a.each do |x|
p x
a = []
end
prints
1
2
3
4
5
and a is [] at the end
while
a.each do |x|
p x
a = []
end
prints nothing
and a is [] at the end
If you can I'd try using
each/map/filter/select.ect. otherwise make a new array and looping through list a normally.
Or loop over numbers from x to y
1.upto(5).each do |n|
do_stuff_with(arr[n])
end
Assuming:
some_var = [1,2,3,4]
delete_if sounds like a viable candidate for this:
some_var.delete_if { |a| a == 1 }
p some_var
=> [2,3,4]

Resources