Remove/squash entries in a vertical hash - ruby

I have a grid that represents an X, Y matrix, stored as a hash here.
Some points on the X Y matrix may have values (as type string), and some may not.
A typical grid could look like this:
{[9, 5]=>"Alaina", [10, 3]=>"Courtney", [11, 1]=>"Gladys", [8, 7]=>"Alford", [14, 11]=>"Lesley", [17, 2]=>"Lawson", [0, 5]=>"Katrine", [2, 1]=>"Tyra", [3, 3]=>"Fredy", [1, 7]=>"Magnus", [6, 9]=>"Nels", [7, 11]=>"Kylie", [11, 0]=>"Kellen", [10, 2]=>"Johan", [14, 10]=>"Justice", [0, 4]=>"Barton", [2, 0]=>"Charley", [3, 2]=>"Magnolia", [1, 6]=>"Maximo", [7, 10]=>"Olga", [19, 5]=>"Isadore", [16, 3]=>"Delfina", [17, 1]=>"Noe", [20, 11]=>"Francis", [10, 5]=>"Creola", [9, 3]=>"Bulah", [8, 1]=>"Lempi", [11, 7]=>"Raquel", [13, 11]=>"Jace", [1, 5]=>"Garth", [3, 1]=>"Ernest", [2, 3]=>"Malcolm", [0, 7]=>"Alejandrin", [7, 9]=>"Marina", [6, 11]=>"Otilia", [16, 2]=>"Hailey", [20, 10]=>"Brandt", [8, 0]=>"Madeline", [9, 2]=>"Leanne", [13, 10]=>"Jenifer", [1, 4]=>"Humberto", [3, 0]=>"Nicholaus", [2, 2]=>"Nadia", [0, 6]=>"Abigail", [6, 10]=>"Zola", [20, 5]=>"Clementina", [23, 3]=>"Alvah", [19, 11]=>"Wallace", [11, 5]=>"Tracey", [8, 3]=>"Hulda", [9, 1]=>"Jedidiah", [10, 7]=>"Annetta", [12, 11]=>"Nicole", [2, 5]=>"Alison", [0, 1]=>"Wilma", [1, 3]=>"Shana", [3, 7]=>"Judd", [4, 9]=>"Lucio", [5, 11]=>"Hardy", [19, 10]=>"Immanuel", [9, 0]=>"Uriel", [8, 2]=>"Milton", [12, 10]=>"Elody", [5, 10]=>"Alexanne", [1, 2]=>"Lauretta", [0, 0]=>"Louvenia", [2, 4]=>"Adelia", [21, 5]=>"Erling", [18, 11]=>"Corene", [22, 3]=>"Haskell", [11, 11]=>"Leta", [10, 9]=>"Terrence", [14, 1]=>"Giuseppe", [15, 3]=>"Silas", [12, 5]=>"Johnnie", [4, 11]=>"Aurelie", [5, 9]=>"Meggie", [2, 7]=>"Phoebe", [0, 3]=>"Sister", [1, 1]=>"Violet", [3, 5]=>"Lilian", [18, 10]=>"Eusebio", [11, 10]=>"Emma", [15, 2]=>"Theodore", [14, 0]=>"Cassidy", [4, 10]=>"Edmund", [2, 6]=>"Claire", [0, 2]=>"Madisen", [1, 0]=>"Kasey", [3, 4]=>"Elijah", [17, 11]=>"Susana", [20, 1]=>"Nicklaus", [21, 3]=>"Kelsie", [10, 11]=>"Garnett", [11, 9]=>"Emanuel", [15, 1]=>"Louvenia", [14, 3]=>"Otho", [13, 5]=>"Vincenza", [3, 11]=>"Tate", [2, 9]=>"Beau", [5, 7]=>"Jason", [6, 1]=>"Jayde", [7, 3]=>"Lamont", [4, 5]=>"Curt", [17, 10]=>"Mack", [21, 2]=>"Lilyan", [10, 10]=>"Ruthe", [14, 2]=>"Georgianna", [4, 4]=>"Nyasia", [6, 0]=>"Sadie", [16, 11]=>"Emil", [21, 1]=>"Melba", [20, 3]=>"Delia", [3, 10]=>"Rosalee", [2, 8]=>"Myrtle", [7, 2]=>"Rigoberto", [14, 5]=>"Jedidiah", [13, 3]=>"Flavie", [12, 1]=>"Evie", [8, 9]=>"Olaf", [9, 11]=>"Stan", [20, 2]=>"Judge", [5, 5]=>"Cassie", [7, 1]=>"Gracie", [6, 3]=>"Armando", [4, 7]=>"Delia", [3, 9]=>"Marley", [16, 10]=>"Robyn", [2, 11]=>"Richie", [12, 0]=>"Gilberto", [13, 2]=>"Dedrick", [9, 10]=>"Liam", [5, 4]=>"Jabari", [7, 0]=>"Enola", [6, 2]=>"Lela", [3, 8]=>"Jade", [2, 10]=>"Johnson", [15, 5]=>"Willow", [12, 3]=>"Fredrick", [13, 1]=>"Beau", [9, 9]=>"Carlie", [8, 11]=>"Daisha", [6, 5]=>"Declan", [4, 1]=>"Carolina", [5, 3]=>"Cruz", [7, 7]=>"Jaime", [0, 9]=>"Anthony", [1, 11]=>"Esta", [13, 0]=>"Shaina", [12, 2]=>"Alec", [8, 10]=>"Lora", [6, 4]=>"Emely", [4, 0]=>"Rodger", [5, 2]=>"Cedrick", [0, 8]=>"Collin", [1, 10]=>"Armani", [16, 5]=>"Brooks", [19, 3]=>"Eleanora", [18, 1]=>"Alva", [7, 5]=>"Melissa", [5, 1]=>"Tabitha", [4, 3]=>"Aniya", [6, 7]=>"Marc", [1, 9]=>"Marjorie", [0, 11]=>"Arvilla", [19, 2]=>"Adela", [7, 4]=>"Zakary", [5, 0]=>"Emely", [4, 2]=>"Alison", [1, 8]=>"Lorenz", [0, 10]=>"Lisandro", [17, 5]=>"Aylin", [18, 3]=>"Giles", [19, 1]=>"Kyleigh", [8, 5]=>"Mary", [11, 3]=>"Claire", [10, 1]=>"Avis", [9, 7]=>"Manuela", [15, 11]=>"Chesley", [18, 2]=>"Kristopher", [24, 3]=>"Zola", [8, 4]=>"Pietro", [10, 0]=>"Delores", [11, 2]=>"Timmy", [15, 10]=>"Khalil", [18, 5]=>"Trudie", [17, 3]=>"Rafael", [16, 1]=>"Anthony"}
What I need to do though, is basically remove all the empty entries.
Let's say [17,3] => Raphael does not have an element in front of if (let's say - no [16,3] exists) then [17,3] should become [16,3] etc.
So basically all empty items will be popped off the vertical (row) structure of the hash.
Are there functions I should have a look at or is there an easy squash-like method that would just remove blanks and adjust and move other items?
Thanks in advance for your help.

Here's my shot at it (probably not the fastest possible):
data.group_by{|k,v| k[1]}.inject({}){|a,(k,v)|
v.sort_by{|i| i[0][0]}.each_with_index{|elem,i|
a[[i,elem[0][1]]] = elem[1]
}
a
}
And here's input subset suitable for testing:
{[9, 5]=>"Alaina", [10, 3]=>"Courtney", [11, 5]=>"Gladys", [8, 5]=>"Alford"}
which should result with:
{[0, 5]=>"Alford", [1, 5]=>"Alaina", [2, 5]=>"Gladys", [0, 3]=>"Courtney"}
Step-by-step explanation (you can experiment in irb):
We need group-by because we should process each row separately, so first we group records by row, making a hash of arrays (let's call each of these arrays r)
data.group_by{|k,v| k[1]}
# => {5=>[[[9, 5], "Alaina"], [[11, 5], "Gladys"], [[8, 5], "Alford"]], 3=>[[[10, 3], "Courtney"]]}
inject is there so we can create output hash, we start with the empty one and we'll add "transformed" element at a time. Transformation is simply replacing the column indexes with "compacted" indexes, so the idea is to somehow map array [8, 9, 11] to [0, 1, 2] (when processing row 5). That's why we need to sort array r (let's call the sorted array rs):
r = [[[9, 5], "Alaina"], [[11, 5], "Gladys"], [[8, 5], "Alford"]]
rs = r.sort_by{|i| i[0][0]}
#=> [[[8, 5], "Alford"], [[9, 5], "Alaina"], [[11, 5], "Gladys"]]
And then assign each element in rs an index, starting from 0. We use each_with_index for that, so that in elem, i we get pairs such as
[[8, 5], "Alford"], 0
[[9, 5], "Alaina"], 1
[[11, 5], "Gladys"], 2
and now we have all needed numbers to fill in the resulting hash a:
a[[i,elem[0][1]]] = elem[1]
so
a[[0,5]] = 'Alford'
a[[1,5]] = 'Alaina'
etc.
We need to return a (accumulator) from a block for inject to work properly.
That should be it. :)

Ok... I think i get what you want it to do; Here is how I would do it:
data = # the hash in the format above
rows = [] # a new array
# then place each row in the entry of rows corresponding to it's y value at
# the index of it's x value
data.each_pair {|key, value| rows[key[1]] ||= []; rows[key[1]][key[0]] = value }
# compact the x values (if there is an adjacent lower x available move to it)
# this is what i believe you meant in your example
rows.map(&:compact!)
# clear out the old data
data = {}
# fill the hash back up using the new compacted data
rows.each_with_index { |xs, y| xs.each_with_index { |entry, x| data[[x,y]] = entry } }
tested it on your data (along with adding some more difficult x values) and it seems to work :)
Roja

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.

Given a set of crossword clues, generate a compatible crossword grid

I have a number of crossword clues in two arrays for across and down clues (each clue is a pair - [clue_number, clue_length]. I am trying to find a compatible grid for this crossword, (assuming all answer words are made of the same letter, i.e, don't bother about the actual answers - just use the length and number to find a compatible grid).
I assume that the grid is 13 x 13 (this test example, I know, has a 13 x 13 solution). So far, I am computing all the combinations for positioning the 'across' words amidst black squares. The thought was to then check each option and if it fits the down clues as well, accept it. However, this already has ~10^17 combinations (with grid dimension of 13 x 13 and the above test dataset). I am wondering if there is a more efficient approach to solve this in a reasonable time. The code I have so far is shown below:
ACROSS_CLUES = [
[1, 5], [6, 5], [9, 7], [10, 5], [11, 5], [12, 5], [13, 7], [15, 3], [17, 4],[18, 6],[19, 5], [20, 6], [22, 4], [24, 3], [25, 7], [26, 5], [27, 5], [28, 5], [29, 7], [30, 5], [31, 5]]
DOWN_CLUES = [
[2, 6], [3, 6], [4, 3], [5, 5], [6, 7], [7, 4], [8, 6], [12, 5], [13, 5], [14, 5], [15, 5], [16, 5], [18, 5], [19, 7], [21, 6], [22, 6], [23, 6], [25, 5], [26, 4], [28, 3]
]
ACROSS_DIRECTION = :across
DOWN_DIRECTION = :down
# merge the across and down clues into a single array sorted by the clue number
CLUES = ACROSS_CLUES.map{|ac|ac + [:across]} + DOWN_CLUES.map{|dc| dc + [:down]}.sort_by{|c| c.first}
UNINITIALIZED = -2
BLACK = -1
BLANK = 0
SIDE = 13
SIZE = SIDE ** 2
# a fragment is a string of clue_length characters (number followed by blanks), which is rendered in the grid
def get_fragment(clue)
clue_number = clue[0]
clue_length = clue[1]
fragment = [clue_number] + Array.new(clue_length - 1, BLANK)
end
# returns the first compatible grid found
def get_grid
fragments = []
ACROSS_CLUES.each do |clue|
fragments << get_fragment(clue)
end
across_clues_length = fragments.flatten.count
grid = Array.new(SIZE - across_clues_length + fragments.length, UNINITIALIZED)
grid_length = grid.length
(0..(grid_length - 1)).to_a.combination(fragments.length).each do |num_locations|
fragments.each_with_index do |fragment, fragment_index|
num_location = num_locations[fragment_index]
grid[num_location] = fragment
end
grid.flatten!
# TODO: check if grid is compatible, return if valid; reject if invalid
grid = Array.new(SIZE - across_clues_length + fragments.length, UNINITIALIZED)
end
grid
end
grid = get_grid

How can I combine elements of an array that have a common element?

I want to take an array of number pairs:
[[2, 3], [2, 15], [3, 15], [7, 8], [8, 7], [11, 15]]
and if two of these number pairs have a number in common, combine them and display the unique values, ultimately creating this array:
[[2, 3, 15, 11], [7, 8]]
Order of the numbers in the output array is not important. What would be the best solution?
The answer was corrected following Howard's comment.
[[2, 3], [2, 15], [3, 2], [3, 15], [7, 8], [8, 7], [11, 15], [15, 2], [15, 3], [15, 11]]
.each_with_object([]) do |e1, a|
a.select{|e2| (e1 & e2).any?}.each{|e2| e1.concat(a.delete(e2))}
e1.uniq!
a.push(e1)
end
# => [[8, 7], [15, 11, 3, 2]]
It's really just another mapping and sorting problem,:
array = [[2, 3], [2, 15], [3, 2], [3, 15], [7, 8], [8, 7], [11, 15], [15, 2], [15, 3], [15, 11]]
array.map{|a| array.each{|x| a |= x if (x & a).any?}; a.sort}.uniq
#=> [[2, 3, 11, 15], [7, 8]]
Not as elegant as #sawa's solution - but works for all the cases:
def includes? item, groups
groups.each{|sub|
return true if sub.include?(item)
}
false
end
def add groups, match, item
groups.each{|sub|
if sub.include?(match)
sub << item
sub.uniq!
return
end
}
end
def iterate arr
groups = []
grp = []
arr.each do |a,b|
grp = [a,b] if grp.empty?
if includes? a, groups
add groups, a, b
elsif includes? b, groups
add groups, b, a
else
groups << [a,b]
end
end
groups
end
arr = [[2, 3], [2, 15], [3, 2], [3, 15], [7, 8], [8, 7], [11, 15], [15, 2], [15, 3], [15, 11]]
p iterate(arr)
OUTPUT
[[2, 3, 15, 11], [7, 8]]
A recursive solution:
def doit(a,b)
return a unless b
h = b.group_by {|e| (a[-1] & e).any?}
h[true] ? a[-1] |= h[true].flatten : a << h[false].pop
doit(a, h[false])
end
arr = [[2, 3], [2, 15], [3, 2], [3, 15], [16, 21], [7, 8], [8, 7], \
[21, 44], [11, 15], [15, 2], [15, 3], [15, 11], [8, 9]]
doit([arr.pop], arr) # => [[8, 9, 7], [15, 11, 2, 3], [21, 44, 16]]

Iterate over array of array

I have an array of arrays like the following:
=> [[1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15]]
I want to rearrange it by order of elements in the inner array, e.g.:
=> [[1,6,11],[2,7,12],[3,8,13],[4,9,14],[5,10,15]]
How can I achieve this?
I know I can iterate an array of arrays like
array1.each do |bla,blo|
#do anything
end
But the side of inner arrays isn't fixed.
p [[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]]
use transpose method on Array
a = [[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]]
Note that this only works if the arrays are of all the same length.
If you want to handle transposing arrays that have different lengths to each other, something like this should do it
class Array
def safe_transpose
max_size = self.map(&:size).max
self.dup.map{|r| r << nil while r.size < max_size; r}.transpose
end
end
and will yield the following
a = [[1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15,16]]
a.safe_transpose
#=> [[1, 6, 11], [2, 7, 12], [3, 8, 13], [4, 9, 14], [5, 10, 15], [nil, nil, 16]]

Resources