Why is variable initialization so important?

Please someone explain to me why first_idx and last_idx are NOT initialized so that the code does not run?

When I run it, I get this error "undefined local variable or last_idx method". I know that the advice is to always initialize the variables, but I don't understand why. After first_idx and last_idx will ALWAYS get the value inside the loop, because the argument lette r is always present in the string (in this particular problem).

I would really appreciate simple information. Thanks!

PS, I also know that the problem is easily solved with #index and #rindex in Ruby, but I cannot solve it with simple 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 
+6
source share
3 answers

Variables in a block

From the Ruby programming language :

Blocks define a new scope of variables: variables created in a block exist only inside this block and undefined outside the block. Be careful, however; local variables in the method are available for any blocks in this method. Therefore, if a block assigns a value to a variable that is already defined outside the block, it does not create a new local-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) 

Code Refactoring

Iteration with Symbols and Index

Here a smaller method is used for your purpose. It stores 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 the minmax array already contains 2 values:

  • it replaces the second (maximum) current index.
  • it adds the 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 is another opportunity. It uses group_by to get all the indices for each character, and minmax to get only 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]} 
+6
source

Even if you do not declare last_idx , you can still initialize it inside the loop, last_idx .:

 (string.length - 1).downto(0) do |idx2| if string[idx2] == letter last_idx = idx2 # works absolutely fine break end end 

However, note where you specified the variable. Its local variable and, therefore, its binding to the block in which you are. Now, when you try to access this variable outside the block, you get an error:

undefined local variable or last_idx method

To make a variable accessible outside the block, you must declare it outside. This is what you do when you declare last_idx = nil in front of the block where it is assigned a value.

UPDATE:

Although you can avoid the declaration by using instance variables, best practice suggests that it should be used in cases where the information that these variables have is relevant to all or almost all classes. On the other hand, if the information is very limited by this particular method, use local variables.

+5
source

This is exactly how local variables work.

If you use instance variables, Ruby will assume that they were initialized inside the conditional block, but will not be used 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 great.

+4
source

Source: https://habr.com/ru/post/1013493/


All Articles