How to track and look up coordinates in a 2d array - ruby

I am working on a problem from adventofcode.com. I need to track the movement on an undetermined grid. I created a move function that uses the case method like this.
def move(direction)
case move
when ">"
x += 1
when "<"
x -= 1
when "^"
y += 1
when "v"
y -= 1
end
end
I have to keep track of coordinates visited and I thought of creating a 2d array that keeps track and pushes to the back of it when we visit a new location. What I dont know is how to do keep track of the unique visited locations with an if statement.

If you only need to keep track of visited coordinates and their order doesn't matter, then you should use a Set instead:
visited = Set.new
visited << [0,0]
visited << [1,0]
visited << [1,1]
visited << [1,0]
p visited
# => #<Set: {[0, 0], [1, 0], [1, 1]}>
p visited.include?([1,1])
# => true
If for some reason you can't use a Set you can accomplish the same thing with a Hash (this is basically how Set works under the covers):
visited = {}
visited[ [0,0] ] = true
visited[ [1,0] ] = true
# Or:
visited.store([0, 0], true)
p visited
# { [0, 0] => true,
# [1, 0] => true
# }
p visited[ [1,0] ] # => true
p visited[ [2,5] ] # => nil
# Or:
p visited.key?([2,5]) # => false
Here's an example of how you could use Set in a Grid class:
require 'set'
class Grid
attr_reader :visited
def initialize
#visited = Set.new
end
def visit!(x, y)
visited << [x, y]
end
def visited?(x, y)
visited.include?([x, y])
end
end
grid = Grid.new
grid.visit!(0, 0)
grid.visit!(1, 0)
grid.visit!(1, 1)
grid.visit!(1, 0)
p grid.visited
# => #<Set: {[0, 0], [1, 0], [1, 1]}>
p grid.visited?(1, 0) # => true
p grid.visited?(3, 3) # => false

Related

Simple Way to Check if the Ruby array is a matrix?

I am working on a project that involves checking if the input is a n-dimensional matrix(and find its dimensions) and raise error if not. For example
arr = [ [[1,2],[3,4]], [[5,6],[7,8]], [[9,10],[11,12]] ]
is a matrix of dimensions [3 2 2]. What would be the simplest generic way to do that ?
A recursive solution, but not pretty easy to understand.
arr1 = [[[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]]]
arr2 = [[[1, 2], [4]], [6, [7, 8]]]
def dimensions(m)
if m.any? { |e| e.is_a?(Array) }
d = m.group_by { |e| e.is_a?(Array) && dimensions(e) }.keys
[m.size] + d.first if d.size == 1 && d.first
else
[m.size]
end
end
dimensions(arr1) #=> [3, 2, 2]
dimensions(arr2) #=> nil
Explaination
The algorithm checks first for nested arrays, m.any? { |e| e.is_a?(Array) }.
If there aren't nested arrays then you have just one dimension and it returns the size of the given array via [m.size] within the else block.
dimensions([1,2,3]) #=> [3]
If there is at least one nested array then you have to ensure that all elements are arrays and the arrays have the same dimensions. This check is done via d = m.group_by { |e| e.is_a?(Array) && dimensions(e) }.keys which groups all elements by its dimensions.
[[5, 6], [7, 8]].group_by { |e| ... }.keys
#=> [[2]], all nested array dimensions are equal [2]
[[1, 2], [4]].group_by { |e| ... }.keys
#=> [[1], [2]], different dimensions
[6, [7, 8]].group_by { |e| ... }.keys
#=> [false, [2]], an element isn't an array
The algorithm takes only the valid results of the group_by with if d.size == 1 && d.first and adds the dimensions of the nested arrays to the result via [m.size] + d.first.
If there are more than one key element or only nil which means all nested arrays are invalid then it returns nil implicitly.
That's all.
Ruby actually has a Matrix class, maybe use that?
Matrix[[[1,2],[3,4]], [[5,6],[7,8]], [[9,10],[11,12]]]
#=> Matrix[[[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]]]
Matrix[[1,2], [3]]
# ExceptionForMatrix::ErrDimensionMismatch: row size differs (1 should be 2)
Look for patterns in your data
If you look at your example of
[[[1,2],[3,4]], [[5,6],[7,8]], [[9,10],[11,12]]]
and dimension [3, 2, 2] you can read the dimension element-by-element in the following way:
An array of 3 items and each of the items is ...
an array of 2 items and each of the subitems is ...
an array of 2 items.
This suggest that a dimension can be computed by calling Array#size on each level of depth.
Compute the dimension
The method above can be implemented as:
def unchecked_matrix_dimension(matrix)
dimension = []
while matrix.is_a?(Array)
dimension << matrix.size
matrix = matrix[0]
end
dimension
end
This code looks at elements in the first position only so [[1], []] is reported as having dimension of [2, 1] but it's not a valid matrix at all.
Wishful coding
Assume for a moment that we have a function matrix_dimension?(matrix, dimension) that returns true if matrix is of the specified dimension and false otherwise. We can use it to detect invalid matrices like this:
def matrix_dimension(matrix)
dimension = unchecked_matrix_dimension(matrix)
if matrix_dimension?(matrix, dimension)
dimension
else
nil
end
end
It turns out that writing matrix_dimension? is easy!
Wishes come true
We can define matrix_dimension? in a recursive fashion:
If dimension == [] then we expect a scalar value.
If dimension == [d_1] then we expect an array of d_1 submatrices of dimension [] (i.e. scalars).
If dimension == [d_1, d_2] then we expect an array of d_1 submatrices of dimension [d_2] (i.e. arrays of d_2 scalars).
In general, if dimension == [d_1, ..., d_n] then we expect an array of d_1 elements and each of these elements should be of dimension [d_2, ..., d_n. In Ruby:
def matrix_dimension?(matrix, dimension)
if dimension == []
!matrix.is_a?(Array)
else
matrix.size == dimension[0] &&
matrix.all? { |submatrix| matrix_dimension?(submatrix, dimension[1..-1]) }
end
end
With this definition of matrix_dimension? our matrix_dimension function will return the dimension, if the argument is a valid n-dimension matrix, or nil otherwise.
Complete code
def unchecked_matrix_dimension(matrix)
dimension = []
while matrix.is_a?(Array)
dimension << matrix.size
matrix = matrix[0]
end
dimension
end
def matrix_dimension(matrix)
dimension = unchecked_matrix_dimension(matrix)
if matrix_dimension?(matrix, dimension)
dimension
else
nil
end
end
def matrix_dimension?(matrix, dimension)
if dimension == []
!matrix.is_a?(Array)
else
matrix.size == dimension[0] &&
matrix.all? { |submatrix| matrix_dimension?(submatrix, dimension[1..-1]) }
end
end
Without use of Matrix class:
input = [ [[1,2],[3,4]], [[5,6],[7,8]], [[9,10],[11,12]] ]
m3 = input.map { |a| a.map(&:size) }
m2 = input.map(&:size)
m1 = input.size
checker = ->(e, memo) { raise unless e == memo; e }
[ m1, m2.reduce(&checker), m3.reduce(&checker).reduce(&checker) ]
#⇒ [3, 2, 2]
I've solved this using recursion. If the array represents an n-dimensional matrix, an array of dimensions is returned; else false is returned.
Code
def ndim_matrix(arr)
return false if arr.map(&:size).uniq != [arr.first.size]
arrays, literals = arr.partition { |e| e.is_a? Array }
return [arr.size] if arrays.empty?
return false unless literals.empty?
res = arr.map { |e| ndim_matrix(e) }.uniq
return false if res.size > 1 or res == [false]
[arr.size, *res.first]
end
Examples
arr = [1,2]
ndim_matrix(arr)
#=> [2]
arr = [ [1,2,3],[4,5,6] ]
ndim_matrix(arr)
#=> [2,3]
arr = [ [1,2,3],[4,5,6,7] ]
ndim_matrix(arr)
#=> false
arr = [ [[1,2],[3,4]], [[5,6],[7,8]], [[9,10],[11,12]] ]
ndim_matrix(arr)
#=> [3,2,2]
arr = [ [[1,2],[3,4]], [[5,6],[7,8]], [[9,10]] ]
ndim_matrix(arr)
#=> false
arr = [ [[1,2],[3,4]], [[5,6,7],[7,8]], [[9,10],[11,12]] ]
ndim_matrix(arr)
#=> false
arr = [ [[[1,2,3],[2,1,3]],[[3,4,5],[4,3,2]]],
[[[5,6,7],[6,5,7]],[[7,8,9],[8,7,6]]],
[[[9,10,11],[10,9,8]],[[11,12,13],[12,11,10]]] ]
ndim_matrix(arr)
#=> [3, 2, 2, 3]
arr = [ [[[1,2,3],[2,1,3]],[[3,4],[4,3]]],
[[[5,6,7],[6,5,7]],[[7,8,9],[8,7,6]]],
[[[9,10,11],[10,9,8]],[[11,12,13],[12,11,10]]] ]
ndim_matrix(arr)
#=> false

Checking to see if 2 numbers in array sum to 0 in Ruby

I've been going at this problem for a few hours, and I can't see why I can't get it to run properly. The end game to this method is having 2 numbers in an array equaling zero when added together. Here is my code:
def two_sums(nums)
i = 0
j = -1
while i < nums.count
num_1 = nums[i]
while j < nums.count
num_2 = nums[j]
if num_1 + num_2 == 0
return "There are 2 numbers that sum to zero & they are #{num_1} and #{num_2}."
else
return "Nothing adds to zero."
end
end
i += 1
j -= 1
end
end
The problem I'm having is unless the first and last number in the array are the positive and negative of the same number, this will always return false.
For example, if I had an array that was [1, 4, 6, -1, 10], it should come back true. I'm sure my 2 while statement is the cause of this, but I can't think of a way to fix it. If someone could point me in the right direction, that would be helpful.
You can find the first pair that adds up to 0 like this:
nums.combination(2).find { |x, y| x + y == 0 }
#=> returns the first matching pair or nil
Or if you want to select all pairs that add up to 0:
nums.combination(2).select { |x, y| x + y == 0 }
#=> returns all matching pairs or an empty array
Therefore you can implement your method like this:
def two_sums(nums)
pair = nums.combination(2).find { |x, y| x + y == 0 }
if pair
"There are 2 numbers that sum to zero & they are #{pair.first} and #{pair.last}."
else
"Nothing adds to zero."
end
end
Or if you want to find all pairs:
def two_sums(nums)
pairs = nums.combination(2).select { |x, y| x + y == 0 }
if pairs.empty?
"Nothing adds to zero."
else
"The following pairs sum to zero: #{pairs}..."
end
end
Here's another way:
Code
def sum_to_zero(arr)
arr.group_by { |e| e.abs }
.values
.select { |a| (a.size > 1 && a.first == 0) || a.uniq.size > 1 }
end
Examples
sum_to_zero [1, 4, 6, -1, 10] #=> [[1, -1]]
sum_to_zero [1, 4, 1, -2, 10] #=> []
sum_to_zero [1, 0, 4, 1, 0, -1] #=> [[1, 1, -1], [0, 0]]
This method is relatively fast. Let's try it with an array of 200,000 elements, each a random number between -500,000 and 500,000.
require 'time'
t = Time.now
arr = Array.new(200_000) { rand(1_000_001) - 500_000 }
arr.size #=> 200000
sum_to_zero(arr).size #=> 16439
Time.now - t
#=> 0.23 (seconds)
sum_to_zero(arr).first(6)
#=> [[-98747, 98747],
# [157848, -157848],
# [-459650, 459650],
# [176655, 176655, -176655],
# [282101, -282101],
# [100886, 100886, -100886]]
If you wish to group the non-negative and negative values that sum to zero:
sum_to_zero(arr).map { |a| a.partition { |e| e >= 0 } }.first(6)
#=> [[[98747], [-98747]],
# [[157848], [-157848]],
# [[459650], [-459650]],
# [[176655, 176655], [-176655]],
# [[282101], [-282101]],
# [[100886, 100886], [-100886]]]
If you only want a single value for each group (a non-negative value, say):
sum_to_zero(arr).map { |a| a.first.abs }.first(6)
#=> [98747, 157848, 459650, 176655, 282101, 100886]
I think the most Ruby way would be:
nums.combination(2).any? { |x,y| (x+y).zero? }
Here's a way that should work well for large arrays. The methods above which go through every possible combination of two numbers are perfectly fine for small cases but will be very slow and memory hungry for arrays with lots of elements.
def two_sums nums
h = Hash.new
nums.each do |n|
return true if h[-n]
h[n] = true
end
false
end
Well, given it's tagged as #ruby, here's the most "ruby way" I could think of tackling this problem:
def two_sums(arr)
numbers = arr.combination(2).select { |a| a.reduce(:+) == 0 }.flatten
if numbers.empty?
"Nothing adds to zero."
else
"There are 2 numbers that sum to zero & they are #{numbers.first} and #{numbers.last}."
end
end
array.combination(2).select{|x|x[0] + x[1] == 0}

Best way to partition a sorted array into arrays of contiguous numbers?

Is there an easy way or a method to partition an array into arrays of contiguous numbers in Ruby?
[1,2,3,5,6,8,10] => [[1,2,3],[5,6],[8],[10]]
I can make some routine for that but wonder if there's a quick way.
Sam
I like to inject:
numbers = [1, 2, 3, 5, 6, 8, 10]
contiguous_arrays = []
contiguous_arrays << numbers[1..-1].inject([numbers.first]) do |contiguous, n|
if n == contiguous.last.succ
contiguous << n
else
contiguous_arrays << contiguous
[n]
end
end
#=> [[1, 2, 3], [5, 6], [8], [10]]
A smörgåsbord of approaches, with:
arr = [1,2,3,5,6,8,10]
#1
# If subarray is empty or the current value n is not the last value + 1,
# add the subarray [n] to the collection; else append the current value
# to the last subarray that was added to the collection.
arr.each_with_object([]) { |n,a|
(a.empty? || n != a.last.last+1) ? a << [n] : a[-1] << n }
#=> [[1, 2, 3], [5, 6], [8], [10]]
#2
# Change the value of 'group' to the current value n if it is the first
# element in arr or it is not equal to the previous element in arr + 1,
# then 'chunk' on 'group' and extract the result from the resulting chunked
# array.
arr.map.with_index do |n,i|
group = n if i == 0 || n != arr[i-1] + 1
[n, group]
end.chunk(&:last)
.map { |_,c| c.map(&:first) }
#=> [[1, 2, 3], [5, 6], [8], [10]]
#3
# If n is the last element of arr, append any number other than n+1 to
# a copy of arr and convert to an enumerator. Step though the enumerator
# arr.size times, adding the current value to a subarray b, and using
# 'peek' to see if the next value of 'arr' equals the current value plus 1.
# If it does, add the subarray b to the collecton a and set b => [].
enum = (arr+[arr.last]).to_enum
a, b = [], []
arr.size.times do
curr = enum.next
b << curr
(a << b; b = []) unless curr + 1 == enum.peek
end
end
a
#=> [[1, 2, 3], [5, 6], [8], [10]]
#4
# Add elements n of arr sequentially to an array a, each time first inserting
# an arbitrary separator string SEP when n does not equal the previous value
# of arr + 1, map each element of a to a string, join(' '), split on SEP and
# convert each resulting array of strings to an array of integers.
SEP = '+'
match_val = arr.first
arr.each_with_object([]) do |n,a|
(a << SEP) unless n == match_val
a << n
match_val = n + 1
end.map(&:to_s)
.join(' ')
.split(SEP)
.map { |s| s.split(' ').map(&:to_i) }
#=> [[1, 2, 3], [5, 6], [8], [10]]
All of the above methods work when arr contains negative integers.
arr = [1,2,3,5,6,8,10]
prev = arr[0]
result = arr.slice_before { |e|
prev, prev2 = e, prev
e != prev2.succ
}.entries
p result
Not very original, lifted right out of the Ruby docs actually.
Another method with enumerator:
module Enumerable
def split_if
enum = each
result = []
tmp = [enum.peek]
loop do
v1, v2 = enum.next, enum.peek
if yield(v1, v2)
result << tmp
tmp = [enum.peek]
else
tmp << v2
end
end
result
end
end
[1,2,3,5,6,8,10].split_if {|i,j| j-i > 1}
Or:
class Array
def split_if(&block)
prev_element = nil
inject([[]]) do |results, element|
if prev_element && block.call(prev_element, element)
results << [element]
else
results.last << element
end
prev_element = element
results
end
end
end
Just do it iteratively.
x = [1,2,3,5,6,8,10]
y = []; z = []
(1..x.length - 1).each do |i|
y << x[i - 1]
if x[i] != x[i-1] + 1
z << y
y = []
end
end
y << x[x.length - 1]
z << y
z
# => [[1, 2, 3], [5, 6], [8], [10]]

Grouping an array by comparing 2 adjacent elements

I have an array of objects and I would like to group them based on the difference between the attributes of 2 adjacent elements. The array is already sorted by that attribute. For instance:
Original array:
array = [a, b, c, d, e]
and
a.attribute = 1
b.attribute = 3
c.attribute = 6
d.attribute = 9
e.attribute = 10
If I want to group the elements such that the difference between the attributes of 2 adjacent elements are less or equal than 2, the result should look like so:
END RESULT
result_array = [[a, b], [c], [d, e]]
WHAT I HAVE
def group_elements_by_difference(array, difference)
result_array = []
subgroup = []
last_element_attribute = array.first.attribute
array.each do |element|
if element.attribute <= (last_element_attribute + difference)
subgroup << element
else
#add the subgroup to the result_array
result_array << subgroup
subgroup = []
subgroup << element
end
#update last_element_attribute
last_element_attribute = element.attribute
end
result_array << subgroup
end
QUESTION
Is there a built in function in Ruby 1.9.3, such as group_by that could replace my group_elements_by_difference?
The following uses numerals directly, but the algorithm should be the same as when you do it with attributes. It assumes that all numerals are greater than 0. If not, then replace it with something that works.
array = [1, 3, 6, 9, 10]
[0, *array].each_cons(2).slice_before{|k, l| l - k > 2}.map{|a| a.map(&:last)}
# => [[1, 3], [6], [9, 10]]
With attributes, do l.attribute, etc., and replace 0 with a dummy element whose attribute is 0.
Following Jan Dvorak's suggestion, this solution uses slice_before and a hash to keep the state:
class GroupByAdjacentDifference < Struct.new(:data)
def group_by(difference)
initial = { prev: data.first }
data.slice_before(initial) do |item, state|
prev, state[:prev] = state[:prev], item
value_for(item) - value_for(prev) > difference
end.to_a
end
def value_for(elem)
elem.attribute
end
end
require 'rspec/autorun'
describe GroupByAdjacentDifference do
let(:a) { double("a", attribute: 1) }
let(:b) { double("b", attribute: 3) }
let(:c) { double("c", attribute: 6) }
let(:d) { double("d", attribute: 9) }
let(:e) { double("e", attribute: 10) }
let(:data) { [a, b, c, d, e] }
let(:service) { described_class.new(data) }
context "#group_by" do
it "groups data by calculating adjacent difference" do
expect(service.group_by(2)).to eq([[a, b], [c], [d, e]])
end
end
end
which gives
$ ruby group_by_adjacent_difference.rb
.
Finished in 0.0048 seconds
1 example, 0 failures
In alternative, local variables could also be used to keep state, although I find it a bit harder to read:
class GroupByAdjacentDifference < Struct.new(:data)
def group_by(difference)
tmp = data.first
data.slice_before do |item|
tmp, prev = item, tmp
value_for(item) - value_for(prev) > difference
end.to_a
end
def value_for(elem)
elem.attribute
end
end
array = [1, 3, 6, 9, 10]
prev = array[0]
p array.slice_before{|el| prev,el = el,prev; prev-el > 2}.to_a
# => [[1, 3], [6], [9, 10]]

How to return the index of the array items occuring under a certain threshold

In Ruby, given an array of elements, what is the easiest way to return the indices of the elements that are not identical?
array = ['a','b','a','a','a','c'] #=> [1,5]
Expanded question:
Assuming that the identity threshold is based on the most frequent element in the array.
array = ['a','c','a','a','a','d','d'] #=> [1,5,6]
For an array with two equally frequent elements, return the indices of either of the 2 elements. e.g.
array = ['a','a','a','b','b','b'] #=>[0,1,2] or #=> [3,4,5]
Answer edited after question edit:
def idx_by_th(arr)
idx = []
occur = arr.inject(Hash.new(0)) { |k,v| k[v] += 1; k }
th = arr.sort_by { |v| occur[v] }.last
arr.each_index {|i| idx << i if arr[i]!=th}
idx
end
idx_by_th ['a','b','a','a','a','c'] # => [1, 5]
idx_by_th ['a','c','a','a','a','d','d'] # => [1, 5, 6]
idx_by_th ['a','a','a','b','b','b'] # => [0, 1, 2]
These answers are valid for the first version of the question:
ruby < 1.8.7
def get_uniq_idx(arr)
test=[]; idx=[]
arr.each_index do |i|
idx << i if !(arr[i+1..arr.length-1] + test).include?(arr[i])
test << arr[i]
end
return idx
end
puts get_uniq_idx(['a','b','a','a','a','c']).inspect # => [1, 5]
ruby >= 1.8.7:
idxs=[]
array.each_index {|i| idxs<<i if !(array.count(array[i]) > 1)}
puts idxs.inspect # => [1, 5]
It's not quite clear what you're looking for, but is something like this what you want?
array = ['a','b','a','a','a','c']
array.uniq.inject([]) do |arr, elem|
if array.count(elem) == 1
arr << array.index(elem)
end
arr
end
# => [1,5]

Resources