42

How do I get the following to happen: I want to change the value of an array element which is being referenced between pipe characters in a .each loop.

Here is an example of what I want to do, but is not currently working:

x = %w(hello there world)
x.each { |element|
   if(element == "hello") {
       element = "hi" # change "hello" to "hi"
   }
}
puts x # output: [hi there world]

It's hard to look up something so general.

tronerta
  • 432
  • 6
  • 12
Derek
  • 747
  • 3
  • 8
  • 13
  • 1
    There's a series of blog posts called [Enumerating enumerable](http://www.globalnerdy.com/tag/enumerating-enumerable/) that you may find useful. – Andrew Grimm Apr 13 '11 at 22:30

7 Answers7

39

You can get the result you want using collect! or map! to modify the array in-place:

x = %w(hello there world)
x.collect! { |element|
  (element == "hello") ? "hi" : element
}
puts x

At each iteration, the element is replaced into the array by the value returned by the block.

SirDarius
  • 41,440
  • 8
  • 86
  • 100
  • In case anybody wonders. Aside from the name, `collect!` and `map!` are identical functions. See [Difference between map and collect in Ruby?](https://stackoverflow.com/questions/5254732). – jox Feb 05 '21 at 11:41
35

The each method never changes the object it works on.

You should use map! method instead:

x = %w(hello there world)
x.map! { |element|
   if(element == "hello")
       "hi" # change "hello" to "hi"
   else
       element
   end
}
puts x # output: [hi there world]
Yossi
  • 11,778
  • 2
  • 53
  • 66
  • 1
    Why are you assigning `"hi"` to `element`? `element` is a block-local variable that goes immediately out of scope anyway. This simply doesn't make sense. – Jörg W Mittag Apr 13 '11 at 10:34
10

Map is probably the best way, but you can change the string in-place too.

> a = "hello"
> puts a
=> hello

> a.replace("hi")
> puts a
=> hi

changes the internal value of the string. For example, your code could become :

x = %w(hello there world)
x.each { |e| if (e == "hello"); e.replace("hi") end; }

but this is much nicer :

x = %w(hello there world)
x.map! { |e| e == "hello" ? "hi" : e }
lucas1000001
  • 2,740
  • 1
  • 25
  • 22
  • +1, dead handy way of doing things. While not specifically answering this question, you can replace via index too, i.e. a[0].replace('HI'). That's what I was looking for, and your answer got me there. Cheers! – SRack Dec 07 '15 at 12:17
3
x = %w(hello there world)
x[index] = "hi" if index = x.index("hello")
x[index] = "hi" if index

or

x = %w(hello there world)
index = x.index("hello") and x[index] = "hi"

But one notice: it will replace only first match. Otherwise use map! as @SirDarlus suggested

Also you can use each_with_index

x.each_with_index do |element, index|
  x[index] = "hi" if element == "hello" # or x[index].replace("hi") if element == "hello"
end

But I still prefer using map! :)

Community
  • 1
  • 1
fl00r
  • 82,987
  • 33
  • 217
  • 237
1

This is a way has less lines of code:

  x = %w(hello there world)
  x = x.join(",").gsub("hello","hi").split(",")
  puts x
kevyau
  • 77
  • 1
  • 6
1

How about simply:

x = %w(hello there world).map! { |e| e == "hello" ? "hi" : e }
EladG
  • 794
  • 9
  • 21
0

Its very simple you can do the same -

x = %w(hello there world)
x = x.collect { |element|
 if element == "hello"
   element = "hi" # change "hello" to "hi"
 end
 element
}
puts x # output: [hi there world]
Snm Maurya
  • 1,085
  • 10
  • 12