How to get index of value in anonymous array inside of iteration - ruby

I would like to be able to take an anonymous array, iterate through it and inside of the iterator block find out what the index is of the current element.
For instance, I am trying to output only every third element.
["foo", "bar", "baz", "bang", "bamph", "foobar", "Hello, Sailor!"].each do |elem|
if index_of(elem) % 3 == 0 then
puts elem
end
end
(where index_of is a nonexistent method being used as a placeholder here to demonstrate what I'm trying to do)
In theory the output should be:
foo
bang
Hello, Sailor!
This is pretty straightforward when I'm naming the array. But when it is anonymous, I can't very well refer to the array by name. I've tried using self.find_index(elem) as well as self.index(elem) but both fail with the error: NoMethodError: undefined method '(find_)index' for main:Object
What is the proper way to do this?

Use each_with_index:
arr = ["foo", "bar", "baz", "bang", "bamph", "foobar", "Hello, Sailor!"]
arr.each_with_index do |elem, index|
puts elem if index % 3 == 0
end

Another way:
arr = ["foo", "bar", "baz", "bang", "bamph", "foobar", "Hello, Sailor!"]
arr.each_slice(3) { |a| puts a.first }
#=> foo
# bang
# Hello, Sailor!

Related

Use [].replace to make a copy of an array

I have a class where I was using the Array#shift instance method on an instance variable. I thought I made a "copy" of my instance variable but in fact I hadn't and shift was actually changing the instance variables.
For example, before I would have expected to get ["foo", "bar", "baz"] both times given the following:
class Foo
attr_reader :arr
def initialize arr
#arr = arr
end
def some_method
foo = arr
foo.shift
end
end
foo = Foo.new %w(foo bar baz)
p foo.arr #=> ["foo", "bar", "baz"]
foo.some_method
p foo.arr #=> ["bar", "baz"]
result:
["foo", "bar", "baz"]
["bar", "baz"]
But as shown my "copy" wasn't really a copy at all. Now, I'm not sure if I should be calling what I want a "copy", "clone", "dup", "deep clone", "deep dup", "frozen clone", etc...
I was really confused about what to search for and found a bunch of crazy attempts to do what seems like "making a copy of an array".
Then I found another answer with literally one line that solved my problem:
class Foo
attr_reader :arr
def initialize arr
#arr = arr
end
def some_method
foo = [].replace arr
foo.shift
end
end
foo = Foo.new %w(foo bar baz)
p foo.arr #=> ["foo", "bar", "baz"]
foo.some_method
p foo.arr #=> ["foo", "bar", "baz"]
output:
["foo", "bar", "baz"]
["foo", "bar", "baz"]
I understand that Array#replace is an instance method being called on an instance of Array that happens to be an empty array (so for example foo = ["cats", "and", "dogs"].replace arr will still work) and it makes sense that I get a "copy" of the instance variable #arr.
But how is that different than:
foo = arr
foo = arr.clone
foo = arr.dup
foo = arr.deep_clone
Marshal.load # something something
# etc...
Or any of the other crazy combinations of dup and map and inject that I'm seeing on SO?
This is the tricky concept of mutability in ruby. In terms of core objects, this usually comes up with arrays and hashes. Strings are mutable as well, but this can be disabled with a flag at the top of the script. See What does the comment "frozen_string_literal: true" do?.
In this case, you can call dup, deep_dup, clone easily to the same effect as replace:
['some', 'array'].dup
['some', 'array'].deep_dup
['some', 'array'].clone
Marshal.load Marshal::dump(['some', 'array'])
In terms of differences, dup and clone are the same except for some nuanced details - see What's the difference between Ruby's dup and clone methods?
The difference between these and deep_dup is that deep_dup works recursively. For example if you dup a nested array, the inner array will not be cloned:
a = [[1]]
b = a.clone
b[0][0] = 2
a # => [[2]]
The same thing happens with hashes.
Marshal.load Marshal::dump <object> is a general approach to deep cloning objects, which, unlike deep_dup, is in ruby core. Marshal::dump returns a string so it can be handy in serializing objects to file.
If you want to avoid unexpected errors like this, keep a mental index of which methods have side-effects and only call those when it makes sense to. An explanation point at the end of a method name indicates that it has side effects, but others include unshift, push, concat, delete, and pop. A big part of fuctional programming is avoiding side effects. You can see https://www.sitepoint.com/functional-programming-techniques-with-ruby-part-i/
The preferred method is dup
use array.dup whenever you need to copy an array
use array.map(&:dup) whenever you need to copy a 2D array
Don't use the marshalling trick unless you really want to deep copy an entire object graph. Usually you want to copy the arrays only but not the contained elements.

Why does .map produce a row of nils when used to enumerate over hashes?

test =
{:content=>"type_name", :content_length=>9, :array_index=>0},
{:content=>"product_id", :content_length=>10, :array_index=>1},
{:content=>"First Item", :content_length=>10, :array_index=>0},
{:content=>"1111", :content_length=>4, :array_index=>1}
pp test.map {|x| puts x} #=>
{:content=>"type_name", :content_length=>9, :array_index=>0}
{:content=>"product_id", :content_length=>10, :array_index=>1}
{:content=>"First Item", :content_length=>10, :array_index=>0}
{:content=>"1111", :content_length=>4, :array_index=>1}
[nil, nil, nil, nil]
What is the cause of that array of nils? The map works perfectly, but then it causes these nils!
The trouble is that #map is designed to transform an array into a different array. Generally, the block of #map will not have side effects. Here's a use of #map to double all the numbers in an array:
[1, 2, 3].map { |n| n * 2} # => [2, 4, 6]
If the purpose of your loop is solely to have side effects (such as printing the elements), you want #each instead:
[1, 2, 3].each { |n| puts n }
# => 1
# => 2
# => 3
In this case, we don't care about the return value of #each. All we care about is that each number gets printed.
Argh what a stupid error!
This fixes it:
test.map {|x| puts x}
I was pretty printing the puts statement, and irb, trying to be helpful, returned nil four times!

Ruby what's the lazy way to get input in loop

Say I want to get 10 inputs in loop and store it in an array. The input will be either string or line or json string.
I'm aware of Ruby's upto and gets.chomp but I'm looking for a simple and lazy technique like:
n=10
arr = []
loop(n) { arr.push getline } #Just an example to share my thought. Will not work
Don't know if this is "simple and lazy" enough:
irb> 3.times.collect { gets.chomp }
foo
bar
baz
# => ["foo", "bar", baz"]
Array.new.
Array.new(3){gets.chomp}
(1..3).map {gets.strip!}
This works nice, and is clean for noise before and after the entries.
Valid in 1.9 and 2.0.
>> (1..3).map {gets.strip!}
Hello
1
2
=> ["Hello", "1", "2"]

Why does "puts" return a blank line, but not "puts []"?

I'm going through a Ruby tutorial, and learned that the code
puts 'start'
puts
puts 'end'
will output three lines, but the following code
puts 'start'
puts []
puts 'end'
will only output two. The stated reason is that [] isn't an object (edit: "doesn't point to anything"), so puts can't do anything with it, but why is that not also true in the first case?
I tried to find an official page about puts to figure this out, and this one was no help.
The stated reason is that [] isn't an object
Stated where?
puts has a special handling for arrays. When you pass it an array, it prints each element on a new line. You pass it an array with zero elements, it prints zero lines.
puts with an array will print one line per element. No element, no lines.
EDIT: What I just said is documented in your link:
If called with an array argument, writes each element on a new line.
The link your shared, states:
If called with an array argument, writes each element on a new line.
puts []
means, you are calling puts with empty array. i.e. no elements to print. and that's what happened.
puts arr
is like
arr.each { |e| puts e }
You can do something like this by yourself:
def p(s)
if s.respond_to? 'each'
s.each { |e| p e }
else
puts s
end
end
p 'hello' # prints line with 'hello'
p [] # prints nothing
p [1, 2] # prints 2 lines with 1 and 2
Puts with no arguments has special behaviour - i.e. print new line. In all other cases, it treats all arguments as an array, and maps these arguments to strings using #to_s, and outputs each string on a new line. That's why you get no output when calling puts []. If you want to have a new line in the output, you can either call puts with no arguments (it's obvjous), or use splat operator with empty array, like this: puts *[].
You can write your own implementation of puts in order to understand things better.
def my_puts(*args)
STDOUT.write("args is #{args.inspect}\n")
if args.empty?
STDOUT.write("\n")
else
args.each { |arg| STDOUT.write("#{arg.to_s}\n") }
end
end
1.9.3p194 :039 > my_puts
args is []
=> 1
1.9.3p194 :040 > my_puts []
args is [[]]
[]
=> [[]]
1.9.3p194 :041 > my_puts *[]
args is []
=> 1
1.9.3p194 :042 > my_puts 1,2,3
args is [1, 2, 3]
1
2
3
=> [1, 2, 3]
1.9.3p194 :043 > my_puts [1,2,3]
args is [[1, 2, 3]]
[1, 2, 3]
=> [[1, 2, 3]]
1.9.3p194 :044 > my_puts *[1,2,3]
args is [1, 2, 3]
1
2
3
=> [1, 2, 3]

Strip in collect on splitted array in Ruby

The following code:
str = "1, hello,2"
puts str
arr = str.split(",")
puts arr.inspect
arr.collect { |x| x.strip! }
puts arr.inspect
produces the following result:
1, hello,2
["1", " hello", "2"]
["1", "hello", "2"]
This is as expected. The following code:
str = "1, hello,2"
puts str
arr = (str.split(",")).collect { |x| x.strip! }
puts arr.inspect
Does however produce the following output:
1, hello,2
[nil, "hello", nil]
Why do I get these "nil"? Why can't I do the .collect immediately on the splitted-array?
Thanks for the help!
The #collect method will return an array of the values returned by each block's call. In your first example, you're modifying the actual array contents with #strip! and use those, while you neglect the return value of #collect.
In the second case, you use the #collect result. Your problem is that #strip! will either return a string or nil, depending on its result – especially, it'll return nil if the string wasn't modified.
Therefore, use #strip (without the exclamation mark):
1.9.3-p194 :005 > (str.split(",")).collect { |x| x.strip }
=> ["1", "hello", "2"]
Because #strip! returns nil if the string was not altered.
In your early examples you were not using the result of #collect, just modifying the strings with #strip!. Using #each in that case would have made the non-functional imperative loop a bit more clear. One normally uses #map / #collect only when using the resulting new array.
You last approach looks good, you wrote a functional map but you left the #strip! in ... just take out the !.

Resources