Trying to understand Ruby arrays [duplicate] - ruby

This question already has answers here:
Array slicing in Ruby: explanation for illogical behaviour (taken from Rubykoans.com)
(10 answers)
Closed 9 years ago.
array = [:peanut, :butter, :and, :jelly]
Why does array[4,0] return [] and array[5,0] returns nil?

According to Array#[] documentation:
an empty array is returned when the starting index for an element
range is at the end of the array.
Returns nil if the index (or starting index) are out of range.
a = [ "a", "b", "c", "d", "e" ]
a[2] + a[0] + a[1] #=> "cab"
a[6] #=> nil
a[1, 2] #=> [ "b", "c" ]
a[1..3] #=> [ "b", "c", "d" ]
a[4..7] #=> [ "e" ]
a[6..10] #=> nil
a[-3, 3] #=> [ "c", "d", "e" ]
# special cases
a[5] #=> nil
a[6, 1] #=> nil
a[5, 1] #=> []
a[5..10] #=> []

Related

How to break down create a substring method

Can someone help me break down this problem? I have the answer but I'm not really sure how my teacher got to it.
class String
# Write a method, String#substrings, that takes in a optional length argument
# The method should return an array of the substrings that have the given length.
# If no length is given, return all substrings.
#
# Examples:
#
# "cats".substrings # => ["c", "ca", "cat", "cats", "a", "at", "ats", "t", "ts", "s"]
# "cats".substrings(2) # => ["ca", "at", "ts"]
def substrings(length = nil)
subs = []
(0...self.length).each do |start_idx|
(start_idx...self.length) do |end_idx|
sub = self[start_idx..end_idx]
subs << sub
end
end
if length.nil?
subs
else
subs.select { |str| str.length == length}
end
end
end
The start_idx and the end_idx are really confusing, if the start_idx is "ca" for example is the end_idx "ca" as well? Please help..
So think of start_idx and end_idx as a constantly changing variable.
def substrings(length = nil)
subs = []
# in the case of 'cats' the length is 4
# so this is making an array UP TO BUT NOT INCLUDING 4
# [0,1,2,3].each do ...
# let's take 0
(0...self.length).each do |start_idx|
# start_idx = 0 the first time through this each
# Array from start_idx UP TO BUT NOT INCLUDING 4
# so the FIRST time through this is 0, second time through is 1, ...
#[0,1,2,3].each do ...
(start_idx...self.length) do |end_idx|
# end_idx = 0
# slice of the string from the 0th to the 0th value (first letter)
sub = self[start_idx..end_idx]
subs << sub
end
end
if length.nil?
subs
else
subs.select { |str| str.length == length}
end
end
So think of this as a bunch of nested loops using numbers that are reassigned during each pass of the loop.
Does that help?
The following would be a more Ruby-like way of writing that.
class String
def all_substrings
(1..size).flat_map { |n| all_substrings_by_length(n) }
end
def all_substrings_by_length(length)
each_char.each_cons(length).with_object([]) { |a,arr| arr << a.join }
end
end
"cats".all_substrings_by_length(1)
#=> ["c", "a", "t", "s"]
"cats".all_substrings_by_length(2)
#=> ["ca", "at", "ts"]
"cats".all_substrings_by_length(3)
#=> ["cat", "ats"]
"cats".all_substrings
#=> ["c", "a", "t", "s", "ca", "at", "ts", "cat", "ats", "cats"]
Note that 1..size is the same as 1..self.size, all_substrings_by_length(n) is the same as self.all_substrings_by_length(n) and each_char is the same as self.each_char, as self is implied when a method has no explicit receiver.
See Enumerable#flat_map, String#each_char, Enumerable#each_cons and Emumerator#with_object.
Let's break down
each_char.each_cons(length).with_object([]) { |a,arr| arr << a.join }
when length = 2 and self = "cats".
length = 2
e0 = "cats".each_char
#=> #<Enumerator: "cats":each_char>
We can see the elements that will be generated by this enumerator by converting it to an array.
e0.to_a
#=> ["c", "a", "t", "s"]
Continuing,
e1 = e0.each_cons(length)
#=> #<Enumerator: #<Enumerator: "cats":each_char>:each_cons(2)>
e1.to_a
#=> [["c", "a"], ["a", "t"], ["t", "s"]]
e2 = e1.with_object([])
#=> #<Enumerator: #<Enumerator: #<Enumerator:
# "cats":each_char>:each_cons(2)>:with_object([])>
e2.to_a
#=> [[["c", "a"], []], [["a", "t"], []], [["t", "s"], []]]
By examining the return values for the creation of e1 and e2 one can see that they could be thought of as compound enumerators, though Ruby has no formal concept of such. Also, as will be seen, the empty arrays in the last return value will be built up as the calculations progress.
Lastly,
e2.each { |a,arr| arr << a.join }
#=> ["ca", "at", "ts"]
which is our desired result. Now examine this last calculation in more detail. each directs e2 to generate an element and then sets the block variables equal to it.
First, observe the following.
e2
#=> #<Enumerator: #<Enumerator: #<Enumerator: "cats":each_char>:
# each_cons(2)>:=with_object(["ca", "at", "ts"])>
This shows us that we need to return e2 to its initial state in order to reproduce the calculations.
e2 = e1.with_object([])
#=> #<Enumerator: #<Enumerator: #<Enumerator:
# "cats":each_char>:each_cons(2)>:with_object([])>
Then:
a, arr = e2.next
#=> [["c", "a"], []]
Array decomposition breaks this array into parts for a and arr:
a #=> ["c", "a"]
arr
#=> []
We now perform the block calculation:
arr << a.join
#=> ["ca"]
each then commands e2 to generate the next element, assigns values to the block variables and performs the block calculation.
a, arr = e2.next
#=> [["a", "t"], ["ca"]]
a #=> ["a", "t"]
arr
#=> ["ca"]
arr << a.join
#=> ["ca", "at"]
This is repeated once more.
a, arr = e2.next
#=> [["t", "s"], ["ca", "at"]]
arr << a.join
#=> ["ca", "at", "ts"]
Lastly, the exception
a, arr = e2.next
#=> StopIteration (iteration reached an end)
causes each to return
arr
#=> ["ca", "at", "ts"]
from the block, which, being the last calculation, is returned by the method.

Ruby slicing not nil [duplicate]

This question already has answers here:
Array slicing in Ruby: explanation for illogical behaviour (taken from Rubykoans.com)
(10 answers)
Closed 7 years ago.
For a four-element array, [4] returns nil, but [4, 0] returns an empty array.
array = [:peanut, :butter, :and, :jelly]
array[4] # => nil
array[4, 0] # => []
array[5] # => nil
array[5, 0] # => nil
Why would [4, 0] not return nil just like [5, 0]?
Answer from Array slicing in Ruby: looking for explanation for illogical behaviour (taken from Rubykoans.com) =>
Graphical Explination of WHats Happening
It's a special case. From the official docs (the italics are mine):
For start and range cases the starting index is just
before an element. Additionally, an empty array is returned when the
starting index for an element range is at the end of the array.
a = [ "a", "b", "c", "d", "e" ]
# ...
# special cases
a[5] #=> nil
a[6, 1] #=> nil
a[5, 1] #=> []

array[array.size..-1] doesn't return nil

I noticed a strange behavior when Range are used as Array subscript. (At least it's strange for me.)
a = [1,2,3]
=> [1, 2, 3]
a[3]
=> nil
a[3..-1]
=> []
a[4]
=> nil
a[4..-1]
=> nil
I thought a[3..-1] returns nil, but somehow it returns []. a[-3..-4] also returns [].
Could anyone explain why it returns [], when I use marginal values of range?
Because when range.begin == array.length, it always returns []. This is noted as a "special case" in the Ruby documentation:
a = [ "a", "b", "c", "d", "e" ]
# special cases
a[5] #=> nil
a[6, 1] #=> nil
a[5, 1] #=> []
a[5..10] #=> []

slice an array by specific way

There is a good slice method in Python like
my_array[3:]
I'm aware there are slice methods in Ruby as well, but there is no method which does exactly the same as Python's my_array[3:] (in case if don't know the size of the array). Is not it?
class Array
def sub_array(pos, len = -1)
if len == -1
then # the rest of the array starting at pos
len = self.size - pos
end
self.slice(pos, len)
end
end
my_array = %w[a b c d e f]
p my_array.sub_array(3) #=> ["d", "e", "f"]
p my_array.sub_array(5) #=> ["f"]
p my_array.sub_array(9) #=> nil
p my_array.sub_array(3, 2) #=> ["d", "e"]
p my_array.sub_array(3, 9) #=> ["d", "e", "f"]
Actually this was originally a substring method for String.
Please have a look at the ruby slice methods here. and as #Blender suggested you can pass a range like:
my_array[3..-1]
EDIT:
range example
array = ["a", "b", "c", "d", "e"]
array[3..-1]
will result in ["d", "e"] as d's index is 3 and e is the last element.
more examples
a = [ "a", "b", "c", "d", "e" ]
a[2] + a[0] + a[1] #=> "cab"
a[6] #=> nil
a[1, 2] #=> [ "b", "c" ]
a[1..3] #=> [ "b", "c", "d" ]
a[4..7] #=> [ "e" ]
a[6..10] #=> nil
a[-3, 3] #=> [ "c", "d", "e" ]
# special cases
a[5] #=> nil
a[5, 1] #=> []
a[5..10] #=> []

How does ruby handle array range accessing?

ruby-1.8.7-p174 > [0,1][2..3]
=> []
ruby-1.8.7-p174 > [0,1][3..4]
=> nil
In a 0-index setting where index 2, 3, and 4 are all in fact out of bounds of the 2-item array, why would these return different values?
This is a known ugly odd corner. Take a look at the examples in rdoc for Array#slice.
This specific issue is listed as a "special case"
a = [ "a", "b", "c", "d", "e" ]
a[2] + a[0] + a[1] #=> "cab"
a[6] #=> nil
a[1, 2] #=> [ "b", "c" ]
a[1..3] #=> [ "b", "c", "d" ]
a[4..7] #=> [ "e" ]
a[6..10] #=> nil
a[-3, 3] #=> [ "c", "d", "e" ]
# special cases
a[5] #=> nil
a[5, 1] #=> []
a[5..10] #=> []
If the start is exactly one item beyond the end of the array, then it will return [], an empty array. If the start is beyond that, nil. It's documented, though I'm not sure of the reason for it.

Resources