How do I iterate through an array of nils? - ruby

I'm trying to set up an Array with all nil values so that someone can iterate the sequence for each value until it reaches the end, then displays the changed array.
class Big
def ben
x = [nil,1,nil,2]
y = 0
x[y] == nil ? "good": "bad"
y += 1
puts x
end
end
I know this can be simplified. Is there a way to overwrite each value in the array?

Here are some things that might help, based on what I see in the sample code.
This is a simple way to create an array if you want it to be a certain size filled with nil values:
foo = [nil] * 5
=> [nil, nil, nil, nil, nil]
If you want to interweave two arrays, such as an array of nils and another one with values:
TOTAL_ELEMENTS = 5
([nil] * TOTAL_ELEMENTS).zip((1..TOTAL_ELEMENTS).to_a).flatten
=> [nil, 1, nil, 2, nil, 3, nil, 4, nil, 5]
Based on the OPs comment below, that this is for a tic-tac-toe game, here are some ways to create x:
Array.new(9)
[nil] * 9
Both of which return:
=> [nil, nil, nil, nil, nil, nil, nil, nil, nil]
That is useful if you receive the cell coordinate as an offset from 0.
For a tic-tac-toe grid it might be more useful to have three rows of three columns if you get your cell coordinates as an row/column pair:
Array.new(3) { Array.new(3) }
[[nil] * 3] * 3
Returning:
=> [[nil, nil, nil], [nil, nil, nil], [nil, nil, nil]]
And some things to meditate on:
ROWS = COLUMNS = 3
x = Array.new( ROWS * COLUMNS ) # for offsets
x = Array.new(ROWS) { Array.new(COLUMNS) } # for rows and columns
If you get your position as an offset but want to convert it to a row/column,
use divmod. Your offset will be 0..8, being converted to fit into a 3x3 grid,
i.e. [0..2][0..2]. Converting back is easy too:
def row_col_to_offset(x,y)
x * ROW + y
end
>> row_col_to_offset(0,0) # => 0
>> row_col_to_offset(0,1) # => 1
>> row_col_to_offset(1,1) # => 4
>> row_col_to_offset(2,2) # => 8
def offset_to_row_col(o)
o.divmod(ROW)
end
>> offset_to_row_col(0) # => [0, 0]
>> offset_to_row_col(1) # => [0, 1]
>> offset_to_row_col(4) # => [1, 1]
>> offset_to_row_col(8) # => [2, 2]
Now you need to learn about Ruby's # instance variables, and the proper use of the initialize method.

If I'm understanding you right you could do this:
class Big
def ben
x = [nil,1,nil,2]
# These lines don't do much
# y = 0
# x[y] == nil ? "good": "bad"
# y += 1
puts x
end
end
And yes to your second question: fill. For example:
[1, 2, 3].fill(0) # => [0, 0, 0]

Related

error when searching through 2d array ruby

I have the following grids (connect four)
grid1 = [
[nil, nil, nil],
[1, nil, nil],
[1, nil, nil],
[1, nil, nil]
]
grid2 = [
[nil, nil, nil],
[nil, nil, 1],
[nil, nil, 1],
[nil, nil, 1]
]
grid3 = [
[nil, nil, nil],
[nil, nil, nil],
[nil, nil, nil],
[1, 1, 1]
]
and this is the method I created to find three 1's in a vertical row and return the next available slot above
def searchArray(array)
array.each_with_index do |y, yi|
y.each_with_index do |x, xi|
if array[yi][xi] != nil && array[yi][xi] == array[yi+1][xi] && array[yi][xi] == array[yi+2][xi]
return v = [yi-1, xi]
end
end
end
end
searchArray(grid2)
When I call the method on grid1, and grid 2 it works great but when I call it on Grid 3 the grid where the 1's are placed on the bottom row I get this error
undefined method `[]' for nil:NilClass
(repl):28:in `block (2 levels) in searchArray'
(repl):27:in `each'
(repl):27:in `each_with_index'
(repl):27:in `block in searchArray'
(repl):26:in `each'
(repl):26:in `each_with_index'
(repl):26:in `searchArray'
(repl):36:in `<main>'
Not sure what's going on
Thanks
You can solve a lot of problems here by simplifying this code using dig:
def search_array(array)
array.each_with_index do |y, yi|
y.each_with_index do |x, xi|
stack = (0..2).map { |o| array.dig(yi + o, xi) }
if (stack == [ 1, 1, 1 ])
return [ yi - 1, xi ]
end
end
end
end
Where dig can poke around and not cause exceptions if it misses the end of the array. Here map is used to quickly pull out an N high stack. You can do 1..2 or 0..4 or whatever is necessary.
Let's take a look at your code, simplified slightly1:
def search_array(array)
array.each_with_index do |y, yi|
y.each_with_index do |x, xi|
return [yi-1, xi] if x != nil && x == array[yi+1][xi] && x == array[yi+2][xi]
end
end
end
You go one row at a time, then for each element in that row, check if that element is not nil and if so, determine whether the two elements below it have the same non-nil value. If you reach the penultimate (next-to-last) row, yi = array.size - 2, you will compare x with array[yi+2][xi], which equals array[array.size][xi], which in turn equals nil[xi]. However, nil has no method [] so an undefined method exception is raised. Pay close attention to those error messages; often, as here, they guide you to the error.
Another problem is that if you found 1's in the first three rows of a column j you would return the index [-1, j], -1 being 0-1. You don't want that either.
I understand that you also wish to determine if dropping a coin in a column results in four-in-a-row horizontally. You could check both vertically and horizontally as follows.
def search_array(arr)
arr.first.each_index do |j|
r = arr.each_index.find { |i| arr[i][j] == 1 }
next if r == 0
r = r.nil? ? arr.size-1 : r-1
return [r,j] if below?(arr,r,j) || left?(arr,r,j) || right?(arr,r,j)
end
nil
end
def below?(arr,r,j)
r < arr.size-3 && (1..3).all? { |i| arr[r+i][j] == 1 }
end
def right?(arr,r,j)
j < arr.first.size-3 && (1..3).all? { |i| arr[r][j+i] == 1 }
end
def left?(arr,r,j)
j >= 3 && (1..3).all? { |i| arr[r][j-i] == 1 }
end
grid4 = [
[nil, nil, nil, nil, nil],
[nil, nil, nil, nil, nil],
[nil, nil, 1, nil, nil],
[nil, nil, 1, 1, 1],
[ 1, 1, 1, nil, 1]
]
grid5 = [
[nil, nil, nil, nil, nil],
[nil, nil, nil, nil, nil],
[nil, nil, 1, nil, nil],
[nil, 1, 1, nil, nil],
[nil, 1, 1, nil, 1]
]
search_array grid1 #=> [0, 0] (vertical)
search_array grid2 #=> [0, 2] (vertical)
search_array grid3 #=> nil
search_array grid4 #=> [3, 1] (horizontal)
search_array grid5 #=> [1, 2] (vertical)
Note that if you wish to also check for four-in-a-row diagonnal you could change:
return [r,j] if below?(arr,r,j) || left?(arr,r,j) || right?(arr,r,j)
to
return [r,j] if below?(arr,r,j) || left?(arr,r,j) || right?(arr,r,j) ||
top_left_to_bottom_right?(arr,r,j) || bottom_left_to_top_right?(arr,r,j)
and add the additional methods top_left_to_bottom_right? and bottom_left_to_top_right?.
1. I changed the name of your method to search_array because Ruby has a convention to use snake case for the naming of variables and methods. You don't have to adopt that convention but 99%+ of Rubiests do.
I could suggest a slight different approach, this is not a complete solution, just a start. It should also help to catch the four.
First map the not nil indexes of the grid, let's consider grid3:
mapping = grid3.flat_map.with_index{ |y, yi| y.map.with_index { |x, xi| [xi, yi] if x }.compact }
#=> [[0, 3], [1, 3], [2, 3]]
Then group by first and second element to get the columns and rows:
cols = mapping.group_by(&:first) #=> {0=>[[0, 3]], 1=>[[1, 3]], 2=>[[2, 3]]}
rows = mapping.group_by(&:last) #=> {3=>[[0, 3], [1, 3], [2, 3]]}
Now, if you want to look for three elements in a row or in a column:
cols.keep_if { |_,v| v.size == 3 } #=> {}
rows.keep_if { |_,v| v.size == 3 } #=> {3=>[[0, 3], [1, 3], [2, 3]]}
The first line says there are no columns with three element aligned.
The second line says that row with index 3 has three elements aligned and indexes are [[0, 3], [1, 3], [2, 3]].
Next step it to check that there are no gaps amongst elements. For example in a 4x4 grid you could get also [[0, 3], [1, 3], [3, 3]] which are three elements, but there is a gap in [2, 3],

Ruby Iterating through 2D arrays and Populating with random data

I have to generate a dynamically sized 2D array with a predetermined value in the first index of each sub array, three random values in each of the three following indices (each falling in a different range), and finally, a calculated total of the three random indices. Here is what I have so far.
Sample code
print("Please enter the number of athletes competing in the triathalon: ")
field=gets.to_i
count=1
athlete = Array.new(5)
triathalon = Array.new(field){athlete}
triathalon.each do
athlete.each do
athlete.insert(0,count)
athlete.insert(1,rand(30..89))
athlete.insert(2,rand(90..119))
athlete.insert(3,rand(120..360))
#calculate total time per athlete
athlete.insert(4,athlete[1]+athlete[2]+athlete[3])
count+=1
end
end
One possible option is using Range and mapping the range using Enumerable#map.
For example given n = 3 athletes, basic example:
(1..n).map { |n| [n] } #=> [[1], [2], [3]]
So, adding some of your specifications to the basic example:
n = 3
res = (1..n).map do |n|
r1 = rand(30..89)
r2 = rand(90..119)
r3 = rand(120..360)
score = r1 + r2 + r3
[n, r1, r2, r3, score]
end
#=> [[1, 38, 93, 318, 449], [2, 64, 93, 259, 416], [3, 83, 93, 343, 519]]
An alternative way of pushing the sum of element into the array is using Object#tap:
[5,10,15].tap{ |a| a << a.sum } #=> [5, 10, 15, 30]
So you could write:
[rand(30..89), rand(90..119), rand(120..360)].tap{ |a| a << a.sum }
This allows to write a one liner (using Array#unshift):
(1..n).map { |n| [rand(30..89), rand(90..119), rand(120..360)].tap{ |a| a << a.sum }.unshift n }
Fixing your code
Visualise the setup:
field = 3 # no user input for example
p athlete = Array.new(5) #=> [nil, nil, nil, nil, nil]
p triathalon = Array.new(field){athlete.dup} #=> [[nil, nil, nil, nil, nil], [nil, nil, nil, nil, nil], [nil, nil, nil, nil, nil]]
NOTE athlete.dup to avoid reference to the same object.
Once you see your objects (athlete and triathalon), you can realize that it is not required to iterate over the nested array, just access by index:
count=1
triathalon.each do |athlete|
athlete[0] = count
athlete[1] = rand(30..89)
athlete[2] = rand(90..119)
athlete[3] = rand(120..360)
athlete[4] = athlete[1] + athlete[2] + athlete[3]
count+=1
end
Improvement: to get rid of the counter use Enumerable#each_with_index.

Ruby check array, return the indexes, which data is exist

How can I check if there's a data that not nil in an array, and then return the index of that data?
Example:
myary = [nil, nil, 300, nil, nil] # <= index 2 is 300
now is there a method to get the value 2? As we know the index 2 is 300 and not nil.
I need to get the index not the value. And moreover there probably will ot only one element that is not nil, perhaps the array could be like this
myotherary = [nil, nil, 300, 400, nil] # <= index 2,3 = 300,400
now for this I need to get 2 and 3 value, is this posibble?
Okay thank you very much, I appreciate all answer.
P.S : Please no flaming, if you don't want to help then just leave, I have spent some time to solve this matter and not succeed. I'm not going to ask here if I can solve it by myself. I had enough of them who not helping, instead asking "what method have you tried?" or write something else that actually not helping but harrasing.
You can use map.with_index:
myary.map.with_index { |v, i| i if v }.compact
# => [2]
myotherary.map.with_index { |v, i| i if v }.compact
# => [2, 3]
I would be inclined to use Enumerable#select in part because it reads well; the word "select" describes what you want to do.
Code
For just the indices:
def indices_only(arr)
arr.size.times.select { |i| arr[i] }
end
If it would be more useful to return both non-nil values and corresponding indices:
def values_and_indices(arr)
arr.each_with_index.select(&:first)
end
Examples
arr1 = [nil, nil, 300, nil, nil]
arr2 = [nil, nil, 300, 400, nil]
indices_only(arr1) #=> [2]
indices_only(arr2) #=> [2, 3]
values_and_indices(arr1) #=> [[300, 2]]
values_and_indices(arr2) #=> [[300, 2], [400, 3]]

Making map! enumerator do what I want

Having an array
a = 1, 2, 3, 4
And an enumerator:
e = a.map!
Then, by calling e.next repeatedly, array a gets nicely destroyed:
e.next #=> 1
a #=> [1, 2, 3, 4]
e.next #=> 2
a #=> [nil, 2, 3, 4]
e.next #=> 3
a #=> [nil, nil, 3, 4]
That's so hilarious! But when I try
e.next { |x| 2 * x } # => 4
I get
a #=> [nil, nil, nil, 4]
instead of desired
a #=> [nil, nil, nil, 8]
What am I misunderstanding? How to make a.map! do what I want with the elements?
My problem is, that I do not fully understand enumerators. With the previous code in place, for example, enumerator e constitutes a backdoor to a:
e.each { 42 }
a #=> [42, 42, 42, 42]
I would like to know, how to do this gradually, with values other than nil. (I can gradually fill it with nils using e.rewind and e.next several times, as I shown before.
To make map! behave as you want, you need the Enumerator#feed method, consider this
ary = *1..4
enum = ary.map!
# the `loop` method handles `StopIteration` for us
loop do
x = enum.next
enum.feed(x * 2)
end
ary
# => [2, 4, 6, 8]
From reference it seems that Enumerator#next doesn't accept a block, so that doesn't have effect of your next call. If you just want to in-place double the last element while clearing all other, do something like, consider straight approach (like a = a[0..-2].map!{|x| nil} + [a.last*2], maybe more elegant). Anyway, could you please provide us with a more detailed usecase to make sure you are doing what you really need?
a.map! accepts a block, but returns an enumerator if no block is supplied. Enumerator#next does not accept a block.
You want to use this to accomplish your goal:
a.map! {|x| x * 2}
if you want to multiply all elements in the array by 2.
For info on next, check out http://ruby-doc.org/core-2.0/Enumerator.html#method-i-next
If you want the output to be exactly [nil, nil, nil, 8] you could do something like:
func = lambda { |x|
unless x == 4
nil
else
x * 2
end
}
a.map!(&func) #> [nil, nil, nil, 8]

Locate a string in an array of strings

I would like to find the location in an array of all strings of any given word.
phrase = "I am happy to see you happy."
t = phrase.split
location = t.index("happy") # => 2 (returns only first happy)
t.map { |x| x.index("happy") } # => returns [nil, nil, 0, nil, nil, nil, 0]
Here's a way
phrase = "I am happy to see you happy."
t = phrase.split(/[\s\.]/) # split on dot as well, so that we get "happy", not "happy."
happies = t.map.with_index{|s, i| i if s == 'happy'} # => [nil, nil, 2, nil, nil, nil, 6]
happies.compact # => [2, 6]
phrase = "I am happy to see you happy."
phrase.split(/[\W]+/).each_with_index.each_with_object([]) do |obj,res|
res << obj.last if obj.first == "happy"
end
#=> [2, 6]

Resources