Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
like image 753
Joana B Avatar asked Dec 24 '16 12:12

Joana B


2 Answers

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]}
like image 155
Eric Duminil Avatar answered Nov 15 '22 00:11

Eric Duminil


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.

like image 44
shivam Avatar answered Nov 15 '22 00:11

shivam