Using Enumerable#zip on an Array of Arrays - ruby

I am trying to use Enumerable#zip on an array of arrays in order to group the elements of the first nested array with the corresponding elements of each subsequent nested array. This is my array:
roster = [["Number", "Name", "Position", "Points per Game"],
["12","Joe Schmo","Center",[14, 32, 7, 0, 23] ],
["9", "Ms. Buckets ", "Point Guard", [19, 0, 11, 22, 0] ],
["31", "Harvey Kay", "Shooting Guard", [0, 30, 16, 0, 25] ],
["7", "Sally Talls", "Power Forward", [18, 29, 26, 31, 19] ],
["22", "MK DiBoux", "Small Forward", [11, 0, 23, 17, 0] ]]
I want to group "Number" with "12", "9", "31", "7", and "22", and then do the same for "Name", "Position", etc. using zip. The following gives me the output I want:
roster[0].zip(roster[1], roster[2], roster[3], roster[4], roster[5])
How can I reformat this so that if I added players to my roster, they would be automatically included in the zip without me having to manually type in roster[6], roster[7], etc. I've tried using ranges in a number of ways but nothing seems to have worked yet.

First extract the head and tail of the list (header and rows, respectively) using a splat, then zip them together:
header, *rows = roster
header.zip(*rows)
This is the same as using transpose on the original roster:
header, *rows = roster
zipped = header.zip(*rows)
roster.transpose == zipped #=> true

:zip.to_proc[*roster]
a bit more flexible than transpose:
:zip.to_proc[*[(0..2), [:a, :b, :c]]] #=> [[0, :a], [1, :b], [2, :c]]

p roster.transpose()
.......................

roster[0].zip(*(roster[1..-1]))
Doesn't matter how many are in the roster array.

Related

Ruby inbuilt method to get the position of letter in the alphabet series?

Input: str = "stackoverflow"
Output: [19 20 1 3 11 15 22 5 18 6 12 15 23]
Do we have any method to get the position of the letters in ruby?
So that I can use something like str.chars.map { |al| al.some_method }.
str.chars = ["s", "t", "a", "c", "k", "o", "v", "e", "r", "f", "l", "o", "w"]
You can do this. I'd use String#chars which returns the ASCII numbers of each character in the string.
'abcdggg'.bytes
# => [97, 98, 99, 100, 103, 103, 103]
As you can see, the alphabet is sequential, each letter is one higher than the previous one. You can get it's position in the alphabet by taking 96 from the number.
Note that the upper-case letter is in a different position, but we can fix this using String#downcase.
To get all the alphabetical positions in a string (if it only has letters) we can write this method.
def alphabet_positions(string)
string.downcase.bytes.map{|b| b - 96}
end
This will work unexpectedly if any characters aren't letters, tho.
You can build a hash with position of a letter in an alphabet and then query this hash:
indexes = ('a'..'z').each_with_index.map{|l,i| [l, i+1]}.to_h
"stackoverflow".chars.map{|l| indexes[l]}
# => [19, 20, 1, 3, 11, 15, 22, 5, 18, 6, 12, 15, 23]
You can do that :
def position(letter)
letter.upcase.ord - 'A'.ord + 1
end
And then :
chars = ["s", "t", "a", "c", "k", "o", "v", "e", "r", "f", "l", "o", "w"]
chars.map do |char| position(char) end
=> [19, 20, 1, 3, 11, 15, 22, 5, 18, 6, 12, 15, 23]
See ord method for more information or this question
Below will give you the result you want.
str = "stackoverflow"
def conversion(str)
arr = []
str.upcase.gsub(/[A-Z]/){|m| arr << m.ord-64}
return arr
end
It is better to use each_char than chars because the latter creates an array that is immediately thrown out.
str.each_char.map{|al| al.ord - ?a.ord + 1}
# => [19, 20, 1, 3, 11, 15, 22, 5, 18, 6, 12, 15, 23]

Delete duplicate entries in Ruby

What is the command for deleting duplicate elements in an array? This is my best try:
my_array.reject.with_string{s.clone}
If you want an array of unique values of my_array = [1, 2, 3, 3, 4], then do this:
my_array.uniq
# => [1, 2, 3, 4]
If your array contains objects with some field that you want to be unique, for example, :fname in:
my_array = [
{fname: "amanze", age: 28},
{fname: "ben", age: 13},
{fname: "ben", age: 4}
]
then you need to do this:
my_array.uniq { |obj| obj[:fname] }
# =>
# [
# {fname: "amanze", age: 28},
# {fname: "ben", age: 13}
# ]
Array#uniq is the best way to find out the uniq records, but as an alternate, you can use Array#&, which returns a new array containing the elements common to the two arrays, excluding any duplicates.
a = [1, 2, 3, 4, 5, 2, 2, 3, 4]
b = a & a
b #=> [1, 2, 3, 4, 5]

Recursive solution doesn't iterate correctly

I'm working through a toy problem in Ruby: how to produce all possible 10-digit phone numbers where each successive number is adjacent to the last on the keypad. I've represented the adjacent relationships between numbers, and have a recursive function, but my method isn't iterating through the whole solution space. It's just finding the first solution and returning.
Here's my code:
adjacencies = { 1 => [2, 4],
2 => [1, 3, 5],
3 => [2, 6],
4 => [1, 5, 7],
5 => [2, 4, 6, 8],
6 => [3, 5, 9],
7 => [4, 8],
8 => [5, 7, 9, 0],
9 => [6, 8],
0 => [8]
}
def append_number(partial_phone_number, to_append, adjacencies)
phone_length = 10
partial_phone_number = partial_phone_number + to_append.to_s
if (partial_phone_number.length == phone_length)
return partial_phone_number
else
adjacencies[to_append].each do |a|
return append_number(partial_phone_number, a, adjacencies)
end
end
end
(0..9).each do |n|
puts append_number("", n, adjacencies)
end
And here is the output when I run it:
0852121212
1212121212
2121212121
3212121212
4121212121
5212121212
6321212121
7412121212
8521212121
9632121212
The first time you enter the adjacencies[to_append].each, you immediately return from the method, therefore the loop will never be executed more than once.
You need to
return a list of phone numbers instead of just a single phone number
build that list somehow in your recursive call
Here is a modification of your recursive method. FIRST_DIGIT is an array of possible first digits of an n-digit phone number, n being the first argument of the method recurse. You wish to determine recurse(10).
ADJ = { 1 => [2, 4],
2 => [1, 3, 5],
3 => [2, 6],
4 => [1, 5, 7],
5 => [2, 4, 6, 8],
6 => [3, 5, 9],
7 => [4, 8],
8 => [5, 7, 9, 0],
9 => [6, 8],
0 => [8]
}
FIRST_DIGIT = (1..9).to_a
#=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
def recurse(n, nxt=FIRST_DIGIT)
nxt.each_with_object([]) do |i,a|
is = i.to_s
if n==1
a << is
else
recurse(n-1, ADJ[i]).each { |s| a << is + s }
end
end
end
recurse 1
#=> ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
recurse 2
#=> ["12", "14", "21", "23", "25", "32", "36", "41", "45",
# "47", "52", "54", "56", "58", "63", "65", "69",
# "74", "78", "85", "87", "89", "80", "96", "98"]
recurse 3
#=> ["121", "123", "125", "141", "145", "147",
# "212", "214", "232", "236", "252", "254", "256", "258",
# "321", "323", "325", "363", "365", "369",
# "412", "414", "452", "454", "456", "458", "474", "478",
# "521", "523", "525", "541", "545", "547", "563", "565",
# "569", "585", "587", "589", "580",
# "632", "636", "652", "654", "656", "658", "696", "698",
# "741", "745", "747", "785", "787", "789", "780",
# "852", "854", "856", "858", "874", "878", "896", "898", "808",
# "963", "965", "969", "985", "987", "989", "980"]
recurse(10).size
#=> 117529
[Edit: the OP has asked about the possibility of modifying the code to avoid loops. This would not be difficult. The same modification could be used to enforce other rules as well (e.g, no 666), all of which would reduce the numbers of combinations to be considered. We could do this by adding an argument so_far to recurse that is an array (or it could be a string) of all digits selected so far:
def recurse(n, so_far=[], nxt=FIRST_DIGIT)
nxt.each_with_object([]) do |i,a|
is = i.to_s
if n==1
a << is
else
< construct array 'permitted' from ADJ[i] and other rules >
recurse(n-1, so_far+[i], permitted).each { |s| a << is + s }
end
end
end
Note that having two arguments with defaults is not a problem, as recurse will initially be called with only the first argument and thereafter will be called with all three arguments.
The return statement in the each iterator bails out of the recursive call on the first iteration. Do not use return there. One possible solution is to append the result to a list (which you pass by argument) when you get to the recursive base case.

Finding similar objects located in same index position of arrays in Ruby

I have the following hash:
hash = {"1"=>[ 5, 13, "B", 4, 10],
"2"=>[27, 19, "B", 18, 20],
"3"=>[45, 41, "B", 44, 31],
"4"=>[48, 51, "B", 58, 52],
"5"=>[70, 69, "B", 74, 73]}
Here is my code:
if hash.values.all? { |array| array[0] == "B" } ||
hash.values.all? { |array| array[1] == "B" } ||
hash.values.all? { |array| array[2] == "B" } ||
hash.values.all? { |array| array[3] == "B" } ||
hash.values.all? { |array| array[4] == "B" }
puts "Hello World"
What my code does is iterates through an array such that if the same element appears in the same index position of each array, it will output the string "Hello World" (Since "B" is in the [2] position of each array, it will puts the string. Is there a way to condense my current code without having a bunch of or's connecting each index of the array?
Assuming all arrays are always of the same length, the following gives you the column indexes where all values are equal:
hash.values.transpose.each_with_index.map do |column, index|
index if column.all? {|x| x == column[0] }
end.compact
The result is [2] for your hash. So you know that for all arrays the index 2 has the same values.
You can print "Hello World" if the resulting array has at least one element.
How does it work?
hash.values.transpose gives you all the arrays, but with transposed (all rows are now columns) values:
hash.values.transpose
=> [[5, 27, 45, 48, 70],
[13, 19, 41, 51, 69],
["B", "B", "B", "B", "B"],
[4, 18, 44, 58, 74],
[10, 20, 31, 52, 73]]
.each_with_index.map goes over every row of the transposed array while providing an inner array and its index.
We look at every inner array, yielding the column index only if all elements are equal using all?.
hash.values.transpose.each_with_index.map {|column, index| index if column.all? {|x| x == column[0] }
=> [nil, nil, 2, nil, nil]
Finally, we compact the result to get rid of the nil values.
Edit: First, I used reduce to find the column with identical elements. #Nimir pointed out, that I re-implemented all?. So I edited my anwer to use all?.
From #tessi brilliant answer i though of this way:
hash.values.transpose.each_with_index do |column, index|
puts "Index:#{index} Repeated value:#{column.first}" if column.all? {|x| x == column[0]}
end
#> Index:2 Repeated value:B
How?
Well, the transpose already solves the problem:
hash.values.transpose
=> [[5, 27, 45, 48, 70],
[13, 19, 41, 51, 69],
["B", "B", "B", "B", "B"],
[4, 18, 44, 58, 74],
[10, 20, 31, 52, 73]
]
We can do:
column.all? {|x| x == column[0]}
To find column with identical items
Assuming that all the values of the hash will be arrays of the same size, how about something like:
hash
=> {"1"=>[5, 13, "B", 4, 10], "2"=>[27, 19, "B", 18, 20], "3"=>[45, 41, "B", 44, 31], "4"=>[48, 51, "B", 58, 52], "5"=>[70, 69, "B", 74, 73]}
arr_of_arrs = hash.values
=> [[5, 13, "B", 4, 10], [27, 19, "B", 18, 20], [45, 41, "B", 44, 31], [48, 51, "B", 58, 52], [70, 69, "B", 74, 73]]
first_array = arr_of_arrs.shift
=> [5, 13, "B", 4, 10]
first_array.each_with_index do |element, index|
arr_of_arrs.map {|arr| arr[index] == element }.all?
end.any?
=> true
This is not really different from what you have now, as far as performance - in fact, it may be a bit slower. However, it allows for a dynamic number of incoming key/value pairs.
I ended up using the following:
fivebs = ["B","B","B","B","B"]
if hash.values.transpose.any? {|array| array == fivebs}
puts "Hello World"
If efficiency, rather than readability, is most important, I expect this decidedly un-Ruby-like and uninteresting solution probably would do well:
arr = hash.values
arr.first.size.times.any? { |i| arr.all? { |e| e[i] == ?B } }
#=> true
Only one intermediate array (arr) is constructed (e.g, no transposed array), and it quits if and when a match is found.
More Ruby-like is the solution I mentioned in a comment on your question:
hash.values.transpose.any? { |arr| arr.all? { |e| e == ?B } }
As you asked for an explanation of #Phrogz's solution to the earlier question, which is similar to this one, let me explain the above line of code, by stepping through it:
a = hash.values
#=> [[ 5, 13, "B", 4, 10],
# [27, 19, "B", 18, 20],
# [45, 41, "B", 44, 31],
# [48, 51, "B", 58, 52],
# [70, 69, "B", 74, 73]]
b = a.transpose
#=> [[ 5, 27, 45, 48, 70],
# [ 13, 19, 41, 51, 69],
# ["B", "B", "B", "B", "B"],
# [ 4, 18, 44, 58, 74],
# [ 10, 20, 31, 52, 73]]
In the last step:
b.any? { |arr| arr.all? { |e| e == ?B } }
#=> true
(where ?B is shorthand for the one-character string "B") an enumerator is created:
c = b.to_enum(:any?)
#=> #<Enumerator: [[ 5, 27, 45, 48, 70],
# [ 13, 19, 41, 51, 69],
# ["B", "B", "B", "B", "B"],
# [ 4, 18, 44, 58, 74],
# [ 10, 20, 31, 52, 73]]:any?>
When the enumerator (any enumerator) is acting on an array, the elements of the enumerator are passed into the block (and assigned to the block variable, here arr) by Array#each. The first element passed into the block is:
arr = [5, 27, 45, 48, 70]
and the following is executed:
arr.all? { |e| e == ?B }
#=> [5, 27, 45, 48, 70].all? { |e| e == ?B }
#=> false
Notice that false is returned to each right after:
5 == ?B
#=> false
is evaluated. Since false is returned, we move on to the second element of the enumerator:
[13, 19, 41, 51, 69].all? { |e| e == ?B }
#=> false
so we continue. But
["B", "B", "B", "B", "B"].all? { |e| e == ?B }
#=> true
so when true is returned to each, the latter returns true and we are finished.

How do I replace an array's element?

How can I substitue an element in an array?
a = [1,2,3,4,5]
I need to replace 5 with [11,22,33,44].flatten!
so that a now becomes
a = [1,2,3,4,11,22,33,44]
Not sure if you're looking to substitute a particular value or not, but this works:
a = [1, 2, 3, 4, 5]
b = [11, 22, 33, 44]
a.map! { |x| x == 5 ? b : x }.flatten!
This iterates over the values of a, and when it finds a value of 5, it replaces that value with array b, then flattens the arrays into one array.
Perhaps you mean:
a[4] = [11,22,33,44] # or a[-1] = ...
a.flatten!
A functional solution might be nicer, how about just:
a[0..-2] + [11, 22, 33, 44]
which yields...
=> [1, 2, 3, 4, 11, 22, 33, 44]
The version of bta using a.index(5) is the fastest one:
a[a.index(5)] = b if a.index(5) # 5.133327 sec/10^6
At least 10% faster than Ryan McGeary's one:
a.map!{ |x| x == 5 ? b : x } # 5.647182 sec/10^6
However, note that a.index(5) only return the first index where 5 is found.
So, given an array where 5 appears more than once, results will be different:
a = [1, 2, 3, 4, 5, 5]
b = [11,22,33,44]
a[a.index(5)] = b if a.index(5)
a.flatten # => [1, 2, 3, 4, 11, 22, 33, 44, 5]
a.map!{ |x| x == 5 ? b : x }
a.flatten # => [1, 2, 3, 4, 11, 22, 33, 44, 11, 22, 33, 44]
Array#delete will return the item or nil. You may use this to know whether or not to push your new values
a.push 11,22,33,44 if a.delete 5
You really don't have to flatten if you just concatenate. So trim the last element off the first array and concatenate them:
a = [ 1, 2, 3, 4, 5 ] #=> [1, 2, 3, 4, 5]
t = [11, 22, 33, 44] #=> [11, 22, 33, 44]
result = a[0..-2] + t #=> [1, 2, 3, 4, 11, 22, 33, 44]
a[0..-2] is a slice operation that takes all but the last element of the array.
Hope it helps!
This variant will find the 5 no matter where in the array it is.
a = [1, 2, 3, 4, 5]
a[a.index(5)]=[11, 22, 33, 44] if a.index(5)
a.flatten!
Here is another simple way to replace the value 5 in the array:
a[-1, 1] = [11, 22, 33, 44]
This uses the Array#[]= method. I'm not exactly sure why it works though.
gweg, not sure what you're trying to do here, but are you looking for something like this?
a = [1, 2, 3, 4, 5]
a.delete_at(4)
a = a.concat([11,22,33,44])
There are a number of ways of doing this -- I don't think the code above is especially nice looking. It all depends on the significance of '5' in your original array.

Resources