How does ruby handle array range accessing? - ruby

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.

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.

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] #=> []

Convert array into a hash

I try to learn map and group_by but it's difficult...
My array of arrays :
a = [ [1, 0, "a", "b"], [1, 1, "c", "d"], [2, 0, "e", "f"], [3, 1, "g", "h"] ]
Expected result :
b= {
1=> {0=>["a", "b"], 1=>["c", "d"]} ,
2=> {0=>["e", "f"]} ,
3=> {1=>["g", "h"]}
}
Group by the first value, the second value can just be 0 or 1.
A starting :
a.group_by{ |e| e.shift}.map { |k, v| {k=>v.group_by{ |e| e.shift}} }
=> [{1=>{0=>[["a", "b"]], 1=>[["c", "d"]]}},
{2=>{0=>[["e", "f"]]}}, {3=>{1=>[["g", "h"]]}}]
I want to get "a" and "b" with the 2 first values, it's the only solution that I've found... (using a hash of hash)
Not sure if group_by is the simplest solution here:
a = [ [1, 0, "a", "b"], [1, 1, "c", "d"], [2, 0, "e", "f"], [3, 1, "g", "h"] ]
result = a.inject({}) do |acc,(a,b,c,d)|
acc[a] ||= {}
acc[a][b] = [c,d]
acc
end
puts result.inspect
Will print:
{1=>{0=>["a", "b"], 1=>["c", "d"]}, 2=>{0=>["e", "f"]}, 3=>{1=>["g", "h"]}}
Also, avoid changing the items you're operating on directly (the shift calls), the collections you could be receiving in your code might not be yours to change.
If you want a somewhat custom group_by I tend do just do it manually. group_by creates an Array of grouped values, so it creates [["a", "b"]] instead of ["a", "b"]. In addition your code is destructive, i.e. it manipulates the value of a. That is only a bad thing if you plan on re using a later on in its original form, but important to note.
As I mentioned though, you might as well just loop through a once and build the desired structure instead of doing multiple group_bys.
b = {}
a.each do |aa|
(b[aa[0]] ||= {})[aa[1]] = aa[2..3]
end
b # => {1=>{0=>["a", "b"], 1=>["c", "d"]}, 2=>{0=>["e", "f"]}, 3=>{1=>["g", "h"]}}
With (b[aa[0]] ||= {}) we check for the existence of the key aa[0] in the Hash b. If it does not exist, we assign an empty Hash ({}) to that key. Following that, we insert the last two elements of aa (= aa[2..3]) into that Hash, with aa[1] as key.
Note that this does not account for duplicate primary + secondary keys. That is, if you have another entry [1, 1, "x", "y"] it will overwrite the entry of [1, 1, "c", "d"] because they both have keys 1 and 1. You can fix that by storing the values in an Array, but then you might as well just do a double group_by. For example, with destructive behavior on a, handling "duplicates":
# Added [1, 1, "x", "y"], removed some others
a = [ [1, 0, "a", "b"], [1, 1, "c", "d"], [1, 1, "x", "y"] ]
b = Hash[a.group_by(&:shift).map { |k, v| [k, v.group_by(&:shift) ] }]
#=> {1=>{0=>[["a", "b"]], 1=>[["c", "d"], ["x", "y"]]}}
[[1, 0, "a", "b"], [1, 1, "c", "d"], [2, 0, "e", "f"], [3, 1, "g", "h"]].
group_by{ |e| e.shift }.
map{ |k, v| [k, v.inject({}) { |h, v| h[v.shift] = v; h }] }.
to_h
#=> {1=>{0=>["a", "b"], 1=>["c", "d"]}, 2=>{0=>["e", "f"]}, 3=>{1=>["g", "h"]}}
Here's how you can do it (nondestructively) with two Enumerable#group_by's and an Object#tap. The elements of a (arrays) could could vary in size and the size of each could be two or greater.
Code
def convert(arr)
h = arr.group_by(&:first)
h.keys.each { |k| h[k] = h[k].group_by { |a| a[1] }
.tap { |g| g.keys.each { |j|
g[j] = g[j].first[2..-1] } } }
h
end
Example
a = [ [1, 0, "a", "b"], [1, 1, "c", "d"], [2, 0, "e", "f"], [3, 1, "g", "h"] ]
convert(a)
#=> {1=>{0=>["a", "b"], 1=>["c", "d"]}, 2=>{0=>["e", "f"]}, 3=>{1=>["g", "h"]}}
Explanation
h = a.group_by(&:first)
#=> {1=>[[1, 0, "a", "b"], [1, 1, "c", "d"]],
# 2=>[[2, 0, "e", "f"]],
# 3=>[[3, 1, "g", "h"]]}
keys = h.keys
#=> [1, 2, 3]
The first value of keys passed into the block assigns the value 1 to the block variable k. We will set h[1] to a hash f, computed as follows.
f = h[k].group_by { |a| a[1] }
#=> [[1, 0, "a", "b"], [1, 1, "c", "d"]].group_by { |a| a[1] }
#=> {0=>[[1, 0, "a", "b"]], 1=>[[1, 1, "c", "d"]]}
We need to do further processing of this hash, so we capture it with tap and assign it to tap's block variable g (i.e., g will initially equal f above). g will be returned by the block after modification.
We have
g.keys #=> [0, 1]
so 0 is the first value passed into each's block and assigned to the block variable j. We then compute:
g[j] = g[j].first[2..-1]
#=> g[0] = [[1, 0, "a", "b"]].first[2..-1]
#=> ["a", "b"]
Similarly, when g's second key (1) is passed into the block,
g[j] = g[j].first[2..-1]
#=> g[1] = [[1, 1, "c", "d"]].first[2..-1]
#=> ["c", "d"]
Ergo,
h[1] = g
#=> {0=>["a", "b"], 1=>["c", "d"]}
h[2] and h[3] are computed similarly, giving us the desired result.

Trying to understand Ruby arrays [duplicate]

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] #=> []

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] #=> []

Resources