3
def removal(arr)
    letters ="i"
    p arr  
    new_array = arr.map do |c_word|
        c_word.each_char.with_index do |char, index|
            if letters.include?(char)
                c_word[index] = "*"
            end    
        end
    end     
    p arr #the original array is getting edited? why?
    p new_array     

end

removal(["hiiiiiigh","git", "training"])

In this code, the original array (arr) in the map method keeps getting edited. I thought that map does not edit the original array. If I needed to edit the original, then I would use .map!

I believe it has something to do with the nested enumerator or a variable reference that I am not seeing. Instead of each_char.with_index, I used a while loop and map would still edit the original array. Why is the original array being edited?

fr_awd
  • 33
  • 4

4 Answers4

7

You are actually wrong in (at least) two places:

  • map is not editing the original array
  • in fact, the original array is not being edited at all

If you look closely, the array hasn't changed, only the strings inside the array have changed. And it is not map that is doing this, it is String#[]=, which you are calling here:

c_word[index] = "*"

So, you are calling a method that edits strings, and you should not be surprised that your strings are edited!

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
6

Think of using:

  • map as saying "I want to create new data based on existing data"
  • each as saying "I either want to not change any data, or change existing data"

Having this in mind, what you are doing is using map with array to create new array based on existing one, and then using each to modify characters in existing strings. This is why the strings in the original array end up modified.

To fix this use map twice, first to "create new array based on existing array", and then the second time to "create new string based on existing string". This way the original strings won't get modified.

def removal(arr)
  letters ="i"
  p arr
  new_array = arr.map do |word|
    word.chars.map do |char|
      letters.include?(char) ? '*' : char
    end.join
  end
  p arr
  p new_array
end

removal(["hiiiiiigh","git", "training"])  #=> ["hiiiiiigh", "git", "training"]
                                          #   ["hiiiiiigh", "git", "training"]
                                          #   ["h******gh", "g*t", "tra*n*ng"]

More practical solution to this problem would be something like this:

def censor(strings, forbidden_chars_string, censor_char = '*')
  re = Regexp.union(forbidden_chars_string.chars)
  strings.map {|str| str.gsub(re, censor_char) }
end

p ["hiiiiiigh","git", "training"]                     #=> ["hiiiiiigh", "git", "training"]
p censor(["hiiiiiigh","git", "training"], "i")        #=> ["h******gh", "g*t", "tra*n*ng"]
p censor(["hiiiiiigh","git", "training"], "gn", '_')  #=> ["hiiiiii_h", "_it", "trai_i__"]
TeWu
  • 5,928
  • 2
  • 22
  • 36
1

This is happening because inside the map block you are doing some processing on each word of arr and not on each word new_array. If you want to copy the words of arr and change it in new_array then create a copy, change it and return the word.

Checkout these 2 codes and you will get my point

Code 1

def removal(arr)
    letters ="i"
    p arr
    new_array = arr.map do |c_word|
        c_word.each_char.with_index do |char, index|
            if letters.include?(char)
                c_word[index] = "*"
            end
        end
        c_word
    end
    p arr
    p new_array

end

removal(["hiiiiiigh","git", "training"])

Here you are changing words of arr and copying it to new_array

Code 2

def removal(arr)
    letters ="i"
    p arr
    new_array = arr.map do |c_word|
        n_word = c_word.dup
        n_word.each_char.with_index do |char, index|
            if letters.include?(char)
                n_word[index] = "*"
            end
        end
        n_word
    end
    p arr
    p new_array

end

removal(["hiiiiiigh","git", "training"])

Here you are copying words of arr, changing it and adding them to new_array

1

If you don't want to change the array's elements, you should not change them. Your problem is in this line:

c_word[index] = "*"

So just use methods that do not affect the recipient, e.g.:

def removal(array)
  letter = 'i'
  array.map { |word| word.gsub(letter, '*') }
end
taras
  • 1,239
  • 1
  • 10
  • 16