Search code examples
rubydictionaryreferenceenumerate

Ruby Map Method edits the original array?


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?


Solution

  • 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__"]