How do I make a hash out of many arrays? - ruby

I have an array that looks like this:
[[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15]]
How would I turn that into a hash that looks similar to this:
{1=>[6, 11], 2=>[7, 12], 3=>[8, 13], 4=>[9, 14], 5=>[10, 15]]
Any help would be appreciated! Trying to do this in Ruby.

foo = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15]]
foo.transpose.map { |x, *y| [x, y] }.to_h

That's a really strange way of mapping things, but with a clever method signature it's not too hard:
def pivot(keys, *values)
keys.each_with_index.map do |key, i|
[ key, values.map { |v| v[i] } ]
end.to_h
end
Then you'd call it with a splat:
a = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15]]
pivot(*a)
# => {1=>[6, 11], 2=>[7, 12], 3=>[8, 13], 4=>[9, 14], 5=>[10, 15]}

I kind of like zip:
a = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15]]
a[0].zip(a[1].zip(a[2])).to_h
The downside is that it's hardwired for three subarrays.
This can be generalized with a splat, so
a = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], [16, 17, 18, 19, 20]]
a[0].zip(a[1].zip(*a.drop(2))).to_h
yields
{1=>[6, 11, 16], 2=>[7, 12, 17], 3=>[8, 13, 18], 4=>[9, 14, 19], 5=>[10, 15, 20]}
without any additional levels of zipping required.

Let's say your array is stored under the variable name array I would go about it like this:
hash = {}
array[0].each.with_index do |value, i|
hash[value] = [array[1][i], array[2][i]]
end

A kind of mixture of pjs and ndn's answers:
arr.first.zip(arr[1..-1].transpose).to_h
Also very similarly (posted by CarySwoveland) in comments:
arr.first.zip(arr.drop(1).transpose).to_h

Related

Combining arrays in Ruby

The following array contains two arrays each having 5 integer values:
[[1,2,3,4,5],[6,7,8,9,10]]
I want to combine these in such a way that it generates five different arrays by combining values of both arrays at index 0,1.. upto 4.
The output should be like this:
[[1,6],[2,7],[3,8],[4,9],[5,10]]
Is there any simplest way to do this?
What about transpose method?
a = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
#=> [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
a.transpose
#=> [[1, 6], [2, 7], [3, 8], [4, 9], [5, 10]]
this method also can help you in future, as example:
a = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15]]
#=> [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15]]
a.transpose
#=> [[1, 6, 11], [2, 7, 12], [3, 8, 13], [4, 9, 14], [5, 10, 15]]
a = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
a.first.zip(a.last)
If you're sure your sub arrays have the same length, you can use Array#transpose :
[[1,2,3,4,5],[6,7,8,9,10]].transpose
#=> [[1, 6], [2, 7], [3, 8], [4, 9], [5, 10]]
As a bonus, it works fine with more than 2 arrays :
[[1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15]].transpose
#=> [[1, 6, 11], [2, 7, 12], [3, 8, 13], [4, 9, 14], [5, 10, 15]]
If you're not sure your sub arrays have the same length :
[[1,2,3,4,5],[6,7,8,9], [10,11]].reduce(&:zip).map(&:flatten)
#=> [[1, 6, 10], [2, 7, 11], [3, 8, nil], [4, 9, nil], [5, nil, nil]]
Using transpose in this example would throw an IndexError.
Using parallel assignment:
a, b = [[1, 2, 3, 4, 5],[6, 7, 8, 9, 10]]
a.zip b #=> [[1, 6], [2, 7], [3, 8], [4, 9], [5, 10]]

method that generate numbers unless remaining of quotient is 0 ruby

I want to create a little method that generates 2 random numbers (num1,num2) (let's say from 1 to 100) but these numbers need to be divisable together
put in more mathematics terms, I would like to generate 2 random numbers where the remaining of the quotient is 0
I did this:
def operation
operators = [:/, :+, :-, :*]
#operation = operators.sample
end
def result(num1, num2)
if #operation == :/
unless num1 % num2 != 0
numbers
else
#result = #num1.send(#operation, #num2)
end
else
#result = #num1.send(#operation, #num2)
end
end
def numbers
#num1 = rand(1..100)
#num2 = rand(1..100)
end
numbers
result(#num1,#num2)
the idea is that unless the remaining of the two numbers is 0 it returns the number method again. I believe there is some iteration problem over here, as I receive an error '`result': nil is not a symbol nor a string (TypeError)'
thx
You're not calling the method operation, so #operation will be nil. So it is invalid to use #operation as the method name when you call send.\
I suggest you refactor things so that you aren't storing a bunch of state in instance variables. Instead, pass any data into functions using function arguments.
To ensure that there is no bias in the sampling, you must first construct the universe of all pairs that satisfy the specified conditions. You can do that as follows.
The numbers can range from 1 to a specified maximum, say
mx = 50
For this value of mx the list of valid pairs is constructed as follows.
pairs = (1..mx/2).flat_map { |n| (1..mx/n).map { |m| [n, n*m] } }
#=> [[1, 1], [1, 2],..., [1, 50],
# [2, 2], [2, 4],..., [2, 50],
# [3, 3], [3, 6],..., [3, 48],
# [4, 4], [4, 8],..., [4, 48],
# [5, 5], [5, 10],..., [5, 50],
# [6, 6], [6, 12],..., [6, 48],
# [7, 7], [7, 14],..., [7, 49],
# [8, 8], [8, 16],..., [8, 48],
# [9, 9], [9, 18],..., [9, 45],
# [10, 10], [10, 20],..., [10, 50],
# [11, 11], [11, 22],..., [11, 44],
# [12, 12], [12, 24],..., [12, 48],
# [13, 13], [13, 26], [13, 39],
# [14, 14], [14, 28], [14, 42],
# [15, 15], [15, 30], [15, 45],
# [16, 16], [16, 32], [16, 48],
# [17, 17], [17, 34],
# [18, 18], [18, 36],
# [19, 19], [19, 38],
# [20, 20], [20, 40],
# [21, 21], [21, 42],
# [22, 22], [22, 44],
# [23, 23], [23, 46],
# [24, 24], [24, 48],
# [25, 25], [25, 50]]
pairs_size = pairs.size
#=> 182
Now you can draw random samples of, say, size
sample_size = 10
from this population, either with replacement:
sample_size.times.map { pairs[rand pairs_size] }
#=> [[3, 6], [2, 10], [7, 35], [2, 40], [18, 36],
# [1, 45], [11, 22], [2, 40], [1, 6], [9, 36]]
or without replacement:
pairs.sample(sample_size)
#=> [[22, 22], [6, 42], [1, 28], [1, 42], [1, 20],
# [1, 36], [23, 46], [9, 18], [4, 36], [16, 16]]
If arrays [n, n] (e.g, [2, 2]) are not to be included in pairs, change the block above to
{ |n| (2..mx/n).map { |m| [n, n*m] } }
in which case pairs.size #=> 157.

What is the fastest way to calculate a rolling average of an array with Ruby?

What is the fastest way to calculate a x second rolling average of an array in ruby?
I have two arrays of data from a bicycle ride. The time is when the corresponding speed value was read during the ride. You'll notice that the readings were not taken every second. For this reason I don't believe I can just increment the rolling array by one.
speed = [0, 15, 17, 19, 18, 22, 24, 28, 22, 17, 16, 14, 15, 15, 15, 0, 15, 19, 21, 25, 26, 24, 24]
time = [0, 1, 2, 3, 5, 6, 7, 8, 10, 11, 12, 13, 15, 16, 17, 18, 20, 21, 22, 23, 25, 26, 27]
I have tried something like the following (calculates a rolling 5 second average and puts it into an array), but it's pretty slow for large arrays and multiple intervals (takes 8 minutes to calculate all the intervals from a 1 hour bike ride, 1..3600):
duration = time.max
interval_average = []
time_hash = Hash[time.map.with_index.to_a]
roll_start = 0
roll_stop = 5
for i in 1..(duration+1) do
start = time_hash[roll_start]
stop = time_hash[roll_stop]
rolling_array = speed[start..stop]
avg_value = mean(rolling_array)
interval_average.push(avg_value)
roll_start += 1
roll_stop += 1
end
In my own code I'm ignoring the exceptions and pushing 0 instead, as I'm really just interested in finding the max of the x second averages in the end.
I'm not sure about its performance, but here's another approach that you can test for finding the maximum of rolling averages over some fixed length of time.
speed = [0, 15, 17, 19, 18, 22, 24, 28, 22, 17, 16, 14, 15, 15, 15, 0, 15, 19, 21, 25, 26, 24, 24]
time = [0, 1, 2, 3, 5, 6, 7, 8, 10, 11, 12, 13, 15, 16, 17, 18, 20, 21, 22, 23, 25, 26, 27]
interval_length = 5 # seconds
speed.zip(time) # 1
.each_cons(interval_length) # 2
.select { |i| i.last.last - i.first.last == interval_length} # 3
.map { |i| i.map(&:first).reduce(:+) / interval_length.to_f } # 4
.max # 5
Breaking it down into components with intermediate results:
1) Pair each speed reading with the time it was taken.
# => [[0, 0], [15, 1], [17, 2], [19, 3], [18, 5], [22, 6], [24, 7], [28, 8], [22, 10], [17, 11], [16, 12], [14, 13], [15, 15], [15, 16], [15, 17], [0, 18], [15, 20], [19, 21], [21, 22], [25, 23], [26, 25], [24, 26], [24, 27]]
2) Section off the above into consecutive runs of interval_length, in this case 5. This will give you an Enumerator object, but using to_a we can see the intermediate result looks like this:
# => [[15, 1], [17, 2], [19, 3], [18, 5], [22, 6]], [[17, 2], [19, 3], [18, 5], [22, 6], [24, 7]], [[19, 3], [18, 5], [22, 6], [24, 7], [28, 8]], [[18, 5], [22, 6], [24, 7], [28, 8], [22, 10]], [[22, 6], [24, 7], [28, 8], [22, 10], [17, 11]], [[24, 7], [28, 8], [22, 10], [17, 11], [16, 12]], [[28, 8], [22, 10], [17, 11], [16, 12], [14, 13]], [[22, 10], [17, 11], [16, 12], [14, 13], [15, 15]], [[17, 11], [16, 12], [14, 13], [15, 15], [15, 16]], [[16, 12], [14, 13], [15, 15], [15, 16], [15, 17]], [[14, 13], [15, 15], [15, 16], [15, 17], [0, 18]], [[15, 15], [15, 16], [15, 17], [0, 18], [15, 20]], [[15, 16], [15, 17], [0, 18], [15, 20], [19, 21]], [[15, 17], [0, 18], [15, 20], [19, 21], [21, 22]], [[0, 18], [15, 20], [19, 21], [21, 22], [25, 23]], [[15, 20], [19, 21], [21, 22], [25, 23], [26, 25]], [[19, 21], [21, 22], [25, 23], [26, 25], [24, 26]], [[21, 22], [25, 23], [26, 25], [24, 26], [24, 27]
3) Since you don't have information for every second, some of each chunk of speed values may be over time intervals that are not really interval_length seconds long. So, let's restrict our calculations only to those. For 5 seconds, it happens that no data needs to be dropped and the intermediate result is the same as step 2.
4) Now we can take the rolling average:
# => [13.8, 18.2, 20.0, 22.2, 22.8, 22.6, 21.4, 19.4, 16.8, 15.4, 15.0, 11.8, 12.0, 12.8, 14.0, 16.0, 21.2, 23.0, 24.0]
5) And the maximum thereof:
# => 24.0
Again, I'm not sure how this will fare on a really large data set, but it might be worth a try.

When I make a second instance of a class in Ruby it changes the first instance of that class

I have a class that randomly creates a two dimensional array. I am testing this program in irb. When I create the first instance of a class by doing thing1 = CatanBoard.new everything works fine. When I create a second instance by doing thing2 = CatanBoard.new I run into problems. This causes thing1.board to be identical to thing2.board and it adds elements to the arrays in thing1.
Correct output of thing1.board after the initialization of thing1 looks like
[[2, 8, "wheat"], [4, 8, "forest"], [15, 6, "forest"], [12, 6, "stone"], [19, 12, "sheep"], [9, 11, "forest"], [17, 11, "stone"], [6, 10, "wheat"], [14, 10, "stone"], [18, 9, "wheat"], [11, 9, "sheep"], [10, 5, "forest"], [16, 5, "brick"], [1, 4, "sheep"], [13, 4, "brick"], [5, 3, "sheep"], [8, 3, "wheat"], [7, 2, "brick"], [3, nil, "desert"]]
Then when I initialize thing2 by doing thing2 = CatanBoard.new I get this as the value of thing2.board:
[[2, 8, "wheat", 8, 8, "wheat"], [4, 8, "forest", 10, 8, "stone"], [15, 6, "forest", 17, 6, "forest"], [12, 6, "stone", 19, 6, "brick"], [19, 12, "sheep", 14, 12, "brick"], [9, 11, "forest", 11, 11, "sheep"], [17, 11, "stone", 18, 11, "stone"], [6, 10, "wheat", 16, 10, "wheat"], [14, 10, "stone", 12, 10, "brick"], [18, 9, "wheat", 13, 9, "forest"], [11, 9, "sheep", 5, 9, "wheat"], [10, 5, "forest", 2, 5, "sheep"], [16, 5, "brick", 1, 5, "sheep"], [1, 4, "sheep", 6, 4, "stone"], [13, 4, "brick", 9, 4, "wheat"], [5, 3, "sheep", 15, 3, "forest"], [8, 3, "wheat", 4, 3, "forest"], [7, 2, "brick", 3, 2, "sheep"], [3, nil, "desert", 7, nil, "desert"]]
Then I check the value of thing1.board and it is now the same as thing2.board:
[[2, 8, "wheat", 8, 8, "wheat"], [4, 8, "forest", 10, 8, "stone"], [15, 6, "forest", 17, 6, "forest"], [12, 6, "stone", 19, 6, "brick"], [19, 12, "sheep", 14, 12, "brick"], [9, 11, "forest", 11, 11, "sheep"], [17, 11, "stone", 18, 11, "stone"], [6, 10, "wheat", 16, 10, "wheat"], [14, 10, "stone", 12, 10, "brick"], [18, 9, "wheat", 13, 9, "forest"], [11, 9, "sheep", 5, 9, "wheat"], [10, 5, "forest", 2, 5, "sheep"], [16, 5, "brick", 1, 5, "sheep"], [1, 4, "sheep", 6, 4, "stone"], [13, 4, "brick", 9, 4, "wheat"], [5, 3, "sheep", 15, 3, "forest"], [8, 3, "wheat", 4, 3, "forest"], [7, 2, "brick", 3, 2, "sheep"], [3, nil, "desert", 7, nil, "desert"]]
This post had a similar problem, but since I'm using Array.new I don't think it's the same problem, since I am creating deep copies. What do you think the problem is with my code?
Here is my code:
# The class CatanBoard represents a Catan Board with no expansions
# A Catan board has 19 hexagons. Each hexagon has a roll and a resource on it.
# Rolls that are 6's and 8's cannot be adjacent to other 6's and 8's
# The 'desert' square has no roll on it
RESOURCES = ['forest', 'forest', 'forest', 'forest', 'brick', 'brick', 'brick', 'wheat', 'wheat', 'wheat', 'wheat', 'sheep', 'sheep', 'sheep', 'sheep', 'stone', 'stone', 'stone'] # desert is left out because it isn't a resource and needs to be specially added
# note - there are 19 tiles, yet 18 rolls. This is because the desert tile does not get a roll.
SPECIAL_ROLLS = [6, 6, 8, 8]
PLAIN_ROLLS = [2, 3, 3, 4, 4, 5, 5, 9, 9, 10, 10, 11, 11, 12]
TILES = (1..19).to_a
EMPTY_BOARD = Array.new(19) {Array.new(0) {[]}}
HARBORS = ['brick', 'generic', 'generic', 'generic', 'generic', 'sheep', 'stone', 'wheat', 'wood']
# this function returns the adjacent tiles on the catan board.
# See the picture in "catan overview.odg" for details
def neighbors(loc)
case loc
when 1
return [2, 4, 5]
when 2
return [1, 3, 5, 6]
when 3
return [2, 6, 7]
when 4
return [1, 5, 8, 9]
when 5
return [1, 2, 4, 6, 9, 10]
when 6
return [2 , 3, 5, 7, 10, 11]
when 7
return [3, 6, 11, 12]
when 8
return [4, 9, 13]
when 9
return [4, 5, 8, 10, 13, 14]
when 10
return [5, 6, 9, 11, 14, 15]
when 11
return [6, 7, 10, 12, 15, 16]
when 12
return [7, 11, 16]
when 13
return [9, 14, 17]
when 14
return [9, 10, 13, 15, 17, 18]
when 15
return [10, 11, 14, 16, 18, 19]
when 16
return [11, 12, 15, 19]
when 17
return [13, 14, 18]
when 18
return [14, 15, 17, 19]
when 19
return [15, 16, 18]
else
return "error"
end
end
class CatanBoard
def initialize()
# #board and #harbors are tha arrays that represent the games
# The other variables are used to set up the board
#board = Array.new(EMPTY_BOARD)
#harbors = Array.new(HARBORS) # #harbors[0] corresponds with A, while #harbors[8] corresponds with I in the harbor diagram in "catan overview.odg"
#resources = Array.new(RESOURCES)
#special_rolls = Array.new(SPECIAL_ROLLS)
#plain_rolls = Array.new(PLAIN_ROLLS)
#tiles = Array.new(TILES)
# RANDOMIZE THE HARBORS #
#harbors = #harbors.shuffle
# PLACE THE SPECIAL ROLLS #
temp_tiles = #tiles
for i in (0..#special_rolls.length-1)
loc = temp_tiles.delete_at(rand(temp_tiles.length)) # chooses a random tile as the location and saves it
#temp_tiles.delete(loc) # I think this line isn't needed
temp_tiles = temp_tiles - neighbors(loc)
# puts the tile and the roll onto the board
#board[i] << loc
#board[i] << #special_rolls.pop
#tiles.delete(loc)
end
# THEN PLACE THE REST OF THE ROLLS #
for i in (0..#tiles.length-1)
loc = #tiles.delete_at(rand(#tiles.length))
#board[i+4] << loc # +4 because loctions 0 through 3 are filled
#board[i+4] << #plain_rolls.pop
end
# THEN PLACE THE RESOURCES #
#board[#board.length-1] << 'desert' # matches the desert tile to the nil roll
for r in (0..#resources.length-1)
# removes a random resource and pairs it with a location and a roll
# resources must be removed randomly otherwise the 6's and 8's are all on stone and sheep
#board[r] << #resources.delete_at(rand(#resources.length))
end
end
def board()
return #board
end
def harbors()
return #harbors
end
# This determines if the board is set up according to the game's rules
def is_legal?()
#TEST SPECIAL ROLLS#
# special rolls are in first four locations
for loc in (0..3)
for other_loc in (0..3)
if neighbors(#board[loc][0]).include?(!#board[other_loc][0])
return false
end
end
end
#TEST TOTAL ROLLS#
all_rolls = Array.new(SPECIAL_ROLLS + PLAIN_ROLLS)
# extracts the rolls from board
temp_rolls = []
# -2 because the desert square doesn't have a roll and it is last in the array
for i in (0..#board.length-2)
temp_rolls << #board[i][1]
end
temp_rolls = temp_rolls.sort
all_rolls = all_rolls.sort
if temp_rolls != all_rolls
temp_resources
return false
end
#TEST RESOURCE AMOUNT#
all_resources = Array.new(RESOURCES)
temp_resources = []
# -2 because the desert square isn't a resource and it is last in the array
for i in (0..#board.length-2)
temp_resources << #board[i][2]
end
temp_resources = temp_resources.sort
all_resources = all_resources.sort
if temp_resources != all_resources
return false
end
#TEST HARBORS#
temp_harbors = Array.new(HARBORS)
sorted_harbors = #harbors.sort
if temp_harbors != sorted_harbors
return false
end
return true
end
end
Array.new does not create a deep copy, so your boards are shared, since EMPTY_BOARD is two levels deep.
In addition, keeping EMPTY_BOARD outside of your CatanBoard class probably isn't a good idea anyway from a cohesion point of view. If you introduce a private method
def empty_board
Array.new(19) {Array.new(0) {[]}}
end
private :empty_board
then you can just reference this method during initialization:
#board = empty_board
Instead Array.new(EMPTY_BOARD) do a deep copy. Here is an example how you can do it since there is no standard way.
def deep_copy(obj)
Marshal.load(Marshal.dump(obj))
end
#board = deep_copy(EMPTY_BOARD)

Remove duplicates from nested array

I have an Array of Arrays that contains numbers in a particular order. I want to remove the duplicates out of the nested arrays, but there is a hierarchy: If a number occurs in a lower-index of the array, remove all duplicates down the Array chain.
Example:
nums = [[10, 6, 14], [6], [10, 6, 9], [10, 13, 6], [10, 13, 6, 9, 16], [10, 13]]
nums[0] contains [10,6,14] so any subsequent mention of 10,6,14 should be removed from the other arrays in the chain, meaning nums[2] should have 10,6 removed and only 9 should remain.
I'm having trouble doing this with nested loops, can any Ruby wizards help please?
This should do it:
input = [[10, 6, 14], [6], [10, 6, 9], [10, 13, 6], [10, 13, 6, 9, 16], [10, 13]]
seen = []
output = input.map do |numbers|
new = numbers.uniq - seen
seen += new
new
end
# => output is [[10, 6, 14], [], [9], [13], [16], []]
If you want to remove the empty lists in the output, simply
output.reject!(&:empty?)
require 'set'
nums = [[10, 6, 14], [6], [10, 6, 9], [10, 13, 6], [10, 13, 6, 9, 16], [10, 13]]
found = Set.new
new_nums = []
for subarray in nums do
sub_new = []
for i in subarray do
if not found.member? i
sub_new << i
end
found << i
end
new_nums << sub_new
end
puts(nums.inspect)
puts(new_nums.inspect)
Yet another way. It keeps original order of elements in arrays:
require 'set'
nums = [[10, 6, 14], [6], [10, 6, 9], [10, 13, 6], [10, 13, 6, 9, 16], [10, 13]]
nums2 = nums.inject([[], Set.new]) do |(output, seen), ary|
[output << ary.reject { |a| seen.include?(a) }, seen.union(ary)]
end[0]
p nums2
# [[10, 6, 14], [], [9], [13], [16], []]
Is the following incorrect? Should the [6] be removed or not?
nums = [[10, 6, 14], [6], [10, 6, 9], [10, 13, 6], [10, 13, 6, 9, 16], [10, 13]]
def remove_duplicate_numbers( array )
seen = []
array.map{ |sub_array|
result = sub_array - seen
seen += sub_array
result
}
end
p remove_duplicate_numbers( nums )
#=> [[10, 6, 14], [], [9], [13], [16], []]
If this is not what you want, please post the actual output you expect for your array.

Resources