Ruby game to find cells with water [closed] - ruby

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 months ago.
Improve this question
How do I solve this question:
A rectangular grid is divided into regions called "aquariums" stacked
together in a Tetris manner. Each aquarium can be filled with water up
to a certain level or left empty. The water level in each aquarium is
the same across its full width. There can't be air bubble inside the
water.
Each region is defined by a letter.
Each cell is defined by a unique ID, from left to right, then from top
to bottom.
You have at your disposal the description of the grid as well as a
list of cell ids we know for sure that there is water in. According to
the following rules, implement the method find filled cells count that
returns the number of filled cells.
Rule N°1: Same water level Within a region, cells that are located in the same row are either all filled or all empty.
Rule N°2: No bubbles When, in a region, a row is filled, within the same region all cells below that row must be filled too.
INPUT:
gridRegions, an array of strings, the grid definition (regions) row
per row
filledCellIds, an array of integers, the cell ids where there is
water
OUTPUT:
an integer: the number of filled cells
CONSTRAINTS:
1 ≤ grid width ≤ 100
1 ≤ grid height ≤ 100
EXAMPLE:
Input
AAAABB, ACBABD, CCBBBD,
CCCBDD, CEFBDD, EEFBBB,
6, 16
Output
10
Write a below function:
def find_filled_cells_count(grid_regions, filled_cell_ids)
end
I've followed with the advice to use pen and paper to better understanding the algorithm, but nothing happens. I've started with the process the region data input and assign height to each cell. Then I look up which regions have any water in them and count their underwater cells. That's the theory, but how to code this? I doubted all my capabilities.

There are many ways to structure the data. But since the input is operating on absolute indices, I would create a Cell class with 3 attributes: aquarium (A-F), row (0-5) and a filled state (true or false).
class Cell
attr_accessor :aquarium
attr_accessor :row
attr_accessor :filled
def initialize(aquarium, row)
#aquarium = aquarium
#row = row
#filled = false
end
end
I would then convert the input grid definition into an array of 36 separate Cell instances:
grid = %w[AAAABB ACBABD CCBBBD CCCBDD CEFBDD EEFBBB]
cells = []
grid.each_with_index do |string, row|
string.each_char do |aquarium|
cells << Cell.new(aquarium, row)
end
end
Since the cells array was populated by appending new cells row-by-row and letter-by-letter, the indices correspond to the IDs in the description (3rd image), e.g.
cells[6] #=> #<Cell:0x00007fabea0dd7a0 #aquarium="A", #row=1, #filled=false>
cells[16] #=> #<Cell:0x00007fabea0dd458 #aquarium="B", #row=2, #filled=false>
Note that I didn't model the aquariums as separate objects or retained the grid as a 2D array. The cells array is just a flat sequence of 36 cells with 3 attributes each. But it's all that's needed.
Now, for each filled cell index (6 and 16) I would set the corresponding cell's filled state to true, traverse all other cells and for those with identical aquarium and an equal or greater row, also set their filled state to true:
filled_cell_indices = 6, 16
filled_cell_indices.each do |index|
filled_cell = cells[index]
filled_cell.filled = true
cells.each do |cell|
next unless cell.aquarium == filled_cell.aquarium
next unless cell.row >= filled_cell.row
cell.filled = true
end
end
Finally, I would count the cells with a filled state:
cells.count(&:filled) #=> 10

Here's one way that prioritizes speed and minimal memory usage:
def find_filled_cells_count(grid_regions, filled_cell_ids)
width = grid_regions.first.length
# Set minimums which map aquarium to minimum filled_cell_id
minimums = {}
filled_cell_ids.each do |cell_id|
row_idx, col_idx = cell_id.divmod width
char = grid_regions[row_idx][col_idx]
minimums[char] = cell_id if !minimums[char] || cell_id < minimums[char]
end
# Count filled cells based on minimums
minimums.to_a.inject(0) do |sum,(char,cell_id)|
row_idx = cell_id / width
sum + grid_regions[row_idx..-1].inject(0) do |count,row|
n = row.count(char)
break count if n.zero? # Assumes aquariums are connected
count + n
end
end
end
Basic idea is that for each cell_id in filled_cell_ids you first find its letter char and then simply count all occurrences of it in grid_regions starting at the row it is in.
However to avoid double-counting cells in case multiple ids are given for the same aquarium, use a hash minimums which only keeps the minimum filled_cell_id given for each aquarium. Then this can be iterated over without double-counting cells.
If we can assume aquariums are always connected, then this can be sped up by exiting the 2nd inject loop as soon as row.count(char) returns 0.

Related

Finding group sizes in matrices

So i was wondering, is there an easy way to detect the sizes of adjacent same values in a matrix? For example, when looking at the matrix of values between 0 and 12 below:
The size of the group at [0,4] is 14 because there are 14 5's connected to each other. But the 1 and 4 are not connected.
I think you can use a breath first search (well kind of, try to visualize the matrix as a tree)
Here's a pseudo python implementation. that does this. Would this work for you? Did you have a complexity in mind?
Code
visited_nodes = set()
def find_adjacent_vals(target_val, cell_row, cell_column):
if inside_matrix(cell_row, cell_column)
cell = matrix(cell_row, cell_column)
if cell not in visited_nodes:
visited_nodes.add(cell)
if cell.value == target_val:
return (1 +
find_adjacent_vals(target_val, cell_row + 1, cell_column) # below
+find_adjacent_vals(target_val, cell_row - 1, cell_column) # above
+find_adjacent_vals(target_val, cell_row, cell_column -1) # left
+find_adjacent_vals(target_val, cell_row, cell_column +1) # right
))
print "Adjacent values count: " + str(find_adjacent_vals(target_val, target_row, target_column))
Explanation
Let's say you start at a node, you start branching out visiting nodes you haven't visited before. You do this till you encounter no new cells of the same value. And each node is guaranteed to have only 1 parent node thanks to the set logic. Therefore no cell is double counted.

MATLAB - Permutations of random indices in specific areas of a grid

I have a problem in which I have 4 objects (1s) on a 100x100 grid of zeros that is split up into 16 even squares of 25x25.
I need to create a (16^4 * 4) table where entries listing all the possible positions of each of these 4 objects across the 16 submatrices. The objects can be anywhere within the submatrices so long as they aren't overlapping one another. This is clearly a permutation problem, but there is added complexity because of the indexing and the fact that the positions ned to be random but not overlapping within a 16th square. Would love any pointers!
What I tried to do was create a function called "top_left_corner(position)" that returns the subscript of the top left corner of the sub-matrix you are in. E.g. top_left_corner(1) = (1,1), top_left_corner(2) = (26,1), etc. Then I have:
pos = randsample(24,2);
I = pos(1)+top_left_corner(position,1);
J = pos(2)+top_left_corner(position,2);
The problem is how to generate and store permutations of this in a table as linear indices.
First using ndgrid cartesian product generated in the form of a [4 , 16^4] matrix perm. Then in the while loop random numbers generated and added to perm. If any column of perm contains duplicated random numbers ,random number generation repeated for those columns until no column has duplicated elements.Normally no more than 2-3 iterations needed. Since the [100 ,100] array divided into 16 blocks, using kron an index pattern like the 16 blocks generated and with the sort function indexes of sorted elements extracted. Then generated random numbers form indexes of the pattern( 16 blocks).
C = cell(1,4);
[C{:}]=ndgrid(0:15,0:15,0:15,0:15);
perm = reshape([C{:}],16^4,4).';
perm_rnd = zeros(size(perm));
c = 1:size(perm,2);
while true
perm_rnd(:,c) = perm(:,c) * 625 +randi(625,4,numel(c));
[~ ,c0] = find(diff(sort(perm_rnd(:,c),1),1,1)==0);
if isempty(c0)
break;
end
%c = c(unique(c0));
c = c([true ; diff(c0)~=0]);
end
pattern = kron(reshape(1:16,4,4),ones(25));
[~,idx] = sort(pattern(:));
result = idx(perm_rnd).';

Find the optimal ordering of elements

I have a list of logos (up to 4 color) that need to be printed. Each logo requires a setup time to mix paints needed for that logo. If I can sort the data so that two logos that use the same colors are back to back, then we will not have to mix as many colors saving money and time. Paints have a limited life span once mixed.
I am looking at a dataset like this...
Red | (Other Color)
Red | Black
(Other Color) | Black
It needs to end up in that order. That is the only order that will allow for 1 red to me made and 1 black. I've tried a few things like assigning a value to each common color, but no matter what, I can't seem to get it ordered correctly.
I used the following SQL procedure that someone wrote based on the TSP problem. (http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=172154)
Using the following test data I received the correct output
delete from routes
delete from cities
insert into cities values ('Black|Red')
insert into cities values ('Red')
insert into cities values ('Blue')
insert into cities values ('Black')
insert into cities values ('Blue|Red')
-- Numeric Value is Colors not Matching
insert into routes values ('Black|Red', 'Red', 3)
insert into routes values ('Black|Red', 'Black', 3)
insert into routes values ('Red', 'Black', 4)
insert into routes values ('Blue|Red', 'Red', 3)
insert into routes values ('Blue|Red', 'Black', 4)
insert into routes values ('Blue', 'Red', 4)
insert into routes values ('Blue', 'Black|Red', 4)
insert into routes values ('Blue', 'Black', 4)
insert into routes values ('Blue', 'Blue|Red', 3)
exec getTSPRoute 'Black'
Results:
Black->Black|Red->Red->Blue|Red->Blue->Black
The only issue is running back to the original "city" (Black returned for both the start and the end) and I have to select a "start city." If the wrong one is selected I don't end up with the most optimized route.
It looks like the travelling salesman problem (TSP). Let me explain.
First, consider an example where you have a map with four cities A, B, C and D. (I use 4 in the example but it has nothing to do with the number of colors). You want to find a route between the cities so you (1) visit each city only once and (2) the route is the shortest possible. [D,C,A,B] might be shorter that [B,A,D,C] and you want the shortest one.
Now, instead of the cities you have four logos. You want to find such an ordering of the logos, that yields a minimum cost in terms of color mixing. If you imagine that each of your logos is a point (city), and the distance between the logos is the "cost" of switching between one color set to the other, then you need to find the shortest "route" between the points. Once you have this shortest route, it tells you how you should order the logos. The "distance" between two logos L1 and L2 can be defined, for example, as a number of colors in L2 that are not in L1.
TSP it is a well known algorithmic problem. And it is hard (actually, NP-hard).
If your input is small you can find the best solution. In case of 4 logos, you have 24 posible combinations. For 10 logos, you have 3.6 million combinations and for 20 logos you get 2432902008176640000 combinations (how to read that?). So for inputs larger than 10-15 you need to use some heuristic that finds an approximate solution, which I am sure is enough for you.
What I would do is that I would create a graph of costs of color mixing and feed it to some TSP solver
EDIT:
Clarification. Not each logo is a separate point, but each set of colours in a logo is a point. That is: if you have two logos that have the same set of colours, you consider them as a single point because they will be printed together. Logos with red, blue, black are on point and logos with red, green are another point.
It's rather Hamiltonian path problem than TSP (you don't need to end with the same color set as at the beginning), but it doesn't change much
If there might be no matches in your logos, then first split your logos into disjoint groups that have no matches between them and later consider each group separately. If there are no matches between any of your logos, then you cannot do much :)
Practically, I would use python and maybe networkx library to model your problem as graph, and later I would pass it to some TSP solver. Just format the input and make some other program do all the dirty work.
For a reasonable amount of logos and colors, an easy way would be a brute-force approach in which you go through all the combinations and increase a counter each time mixing is required. After that, you sort combinations by that counter and choose the one with the lowest value.
Pseudocode
foreach combination
foreach print
foreeach color
if not previous_print.contains(color)
cost++
order combination by cost (ascending)
You didn't mention if you are using (or are about to) any kind of tool (spreadsheet, programming language, ...) in which you intended perform this sort.
Edit:
Here's a quick implementation in VB.NET. Note that the code is intentionally left long as to make it easier to read and understand.
Private Sub GoGoGo()
' Adds some logos
' This is where you add them from the database or text file or wherever
Dim logos() =
{
New String() {"Black", "Magenta", "Orange"},
New String() {"Red", "Green", "Blue"},
New String() {"Orange", "Violet", "Pink"},
New String() {"Blue", "Yellow", "Pink"}
}
' Used to store the best combination
Dim minimumPermutation
Dim minimumCost = Integer.MaxValue
' Calculate all permutations of the logos
Dim permutations = GetPermutations(logos)
' For each permutation
For i As Integer = 0 To permutations.Count() - 1
Dim permutation = permutations(i)
Dim cost = 0
' For each logo in permutation
For j As Integer = 0 To permutation.Count() - 1
Dim logo = permutation(j)
' Check whether the previous logo contains one or more colors of this logo
For Each color In logo
If (j > 0) Then
If Not permutation(j - 1).Contains(color) Then
cost += 1
End If
Else
cost += 1
End If
Next
Next
' Save the best permutation
If (i = 0 Or cost < minimumCost) Then
minimumCost = cost
minimumPermutation = permutation.Clone()
End If
Next
' Output the best permutation
For Each logo In minimumPermutation
Console.Write(logo(0) + " " + logo(1) + " " + logo(2))
Next
End Sub
Public Shared Iterator Function GetPermutations(Of T)(values As T(), Optional fromInd As Integer = 0) As IEnumerable(Of T())
If fromInd + 1 = values.Length Then
Yield values
Else
For Each v In GetPermutations(values, fromInd + 1)
Yield v
Next
For i = fromInd + 1 To values.Length - 1
SwapValues(values, fromInd, i)
For Each v In GetPermutations(values, fromInd + 1)
Yield v
Next
SwapValues(values, fromInd, i)
Next
End If
End Function
Private Shared Sub SwapValues(Of T)(values As T(), pos1 As Integer, pos2 As Integer)
If pos1 <> pos2 Then
Dim tmp As T = values(pos1)
values(pos1) = values(pos2)
values(pos2) = tmp
End If
End Sub
I suspect a Genetic Algorithm would be good for this. If you have a lot of logos, a brute force solution could take quite a while, and greedy is unlikely to produce good results.
http://en.wikipedia.org/wiki/Genetic_algorithm

How to balance the number of items across multiple columns

I need to find out a method to determine how many items should appear per column in a multiple column list to achieve the most visual balance. Here are my criteria:
The list should only be split into multiple columns if the item count is greater than 10.
If multiple columns are required, they should contain no less than 5 (except for the last column in case of a remainder) and no more than 10 items.
If all columns cannot contain an equal number of items
All but the last column should be equal in number.
The number of items in each column should be optimized to achieve the smallest difference between the last column and the other column(s).
Well, your requirements and your examples appear a bit contradictory. For instance, your second example could be divided into two columns with 11 items in each, and satisfy your criteria. Let's assume that for rule #2 you meant that there should be <= 10 items / column.
In addition, I think you need to add another rule to make the requirements sensible:
The number of columns must not be greater than what is required to accomodate overflow.
Otherwise, you will often end up with degenerate solutions where you have far more columns than you need. For example, in the case of 26 items you probably don't want 13 columns of 2 items each.
If that's case, here's a simple calculation that should work well and is easy to understand:
int numberOfColumns = CEILING(numberOfItems / 10);
int numberOfItemsPerColumn = CEILING(numberOfItems / numberOfColumns);
Now you'll create N-1 columns of items (having `numberOfItemsPerColumn each) and the overflow will go in the last column. By this definition, the overflow should be minimized in the last column.
If you want to automatically determine the appropriate number of columns, and have no restrictions on its limits, I would suggest the following:
Calculate the square root of the total number of items. That would make an squared layout.
Divide that number by 1.618, and assign that to the total number of rows.
Multiply that same number by 1.618, and assign that to the total number of columns.
All columns but the right most one will have the same number of items.
By the way, the constant 1.618 is the Golden Ratio. That will achieve a more pleasant layout than a squared one.
Divide and multiply the other way round for vertical displays.
Hope this algorithm helps anyone with a similar problem.
Here's what you're trying to solve:
minimize y - z where n = xy + z and 5 <= y <= 10 and 0 <= z <= y
where you have n items split into x full columns of y items and one remainder column of z items.
There is almost certainly a smart way of doing this, but given these constraints a brute force implementation exploring all 6 + 7 + 8 + 9 + 10 = 40 possible combinations for y and z would take no time at all (only assignments where (n - z) mod y = 0 are solutions).
I think a brute force solution is easy, given the constraint on the number of items per columns: let v be the number of items per column (except the last one), then v belongs to [5,10] and can thus take a whooping 6 different values.
Evaluating 6 values is easy enough. Python one-liner (or not so far) to prove it:
# compute the difference between the number of items for the normal columns
# and for the last column, lesser is better
def helper(n,v):
modulo = n % v
if modulo == 0: return 0
else: return v - modulo
# values can only be in [5,10]
# we compute the difference with the last column for each
# build a list of tuples (difference, - number of items)
# (because the greater the value the better, it means less columns)
# extract the min automatically (in case of equality, less is privileged)
# and then pick the number of items from the tuple and re-inverse it
def compute(n): return - min([(helper(n,v), -v) for v in [5,6,7,8,9,10]])[1]
For 77 this yields: 7 meaning 7 items per columns
For 22 this yields: 8 meaning 8 items per columns

Weighted Item Algorithm

I would like to learn how to select weighted items. For example : I want to fetch questions from a pool but if some one can't give right answer to a question, that causes this question to double its weight and increase the probability of being selected again later on.
Have a class which keeps the item:weight pairs (key=item:value=weight) in a hash table.
The class should also maintain a total_weight variable, which is the sum of all the weights in the hash table. The class' methods to add_item, remove_item, and update_weight for an item should keep the total_weight updated. This avoids having to recalculate the total for every choice.
To choose an item:
Use a random number such that 1<=random_number<=total_weight.
Iterate over the item:weight pairs in the hash table, summing the weights until the random number is <= that running sum. When that happens, the key of the pair you're on is the chosen item.
This is like rolling an imaginary die whose size is the sum of all the weights. For every roll, each item has its own range of numbers on the die, with the size of each range equal to its item's weight. If the roll result falls within an item's range, that item is the chosen one.
Editing to add the following sample code after the request in the comment below. Tested this with Python 2.5.2:
from random import randint # Import randint function from random module.
class WeightedCollection(object):
def __init__(self):
self.total_weight = 0
self.items = {} # This is a python dictionary == a hash table
def add_item(self, item, weight):
self.items[item] = weight
self.total_weight += weight
def remove_item(self, item):
self.total_weight -= self.items[item] # Subtracts the weight.
del(self.items[item])
def update_weight(self, item, new_weight):
self.total_weight += (new_weight - self.items[item])
self.items[item] = new_weight
def get_random_item(self):
''' Returns random selection but weighted by item weights. '''
# Result of call below is 1 <= random_number <= self.total_weight...
random_number = randint(1, self.total_weight)
sum_so_far = 0
# For every item and its weight...
for item, weight in self.items.iteritems():
sum_so_far += weight
if random_number <= sum_so_far:
return item
# Usage demo...
questions = WeightedCollection()
questions.add_item('What is your name?', 1)
questions.add_item('What is your favorite color?', 50)
questions.add_item('What is the meaning to life?', 100)
print 'Here is what the dictionary looks like:'
print questions.items
print ''
print "Total weight:", questions.total_weight
print ''
print 'Some sample random picks...'
for i in range(5):
print questions.get_random_item()
And here is the output:
Here is what the dictionary looks like:
{'What is the meaning to life?': 100, 'What is your name?': 1, 'What is your favorite color?': 50}
Total weight: 151
Some sample random picks...
What is your favorite color?
What is the meaning to life?
What is the meaning to life?
What is your favorite color?
What is the meaning to life?
Keep around an array of the candidate items. If one item has weight 2, put it in the array twice, generally if one has weight n put it in there n times. Then select a random element from the array. Ta-daaa.
I like #André Hoffmann's idea of using a binary tree, in which every leaf node corresponds to a question, and every intermediate node stores the sum of the weight of its child nodes. But he says the tree needs to be re-created every time a weight changes.
Actually, this need not be the case! When you change the weight of a given leaf, you only need to update the weights of those nodes between it and the root of the tree. But...you also need some way to find the node within the tree, if you want to modify it.
So my suggestion is to use a self-balancing binary tree (e.g. a red-black tree, AVL tree, etc), which is ordered by the question ID. Operations on the tree need to maintain the property that the weight of any node is equal to the sum of the weights of its children.
With this data structure, the root node's weight W is equal to the sum of the weights of all the questions. You can retrieve a question either by question ID, or by a random weight (between zero and W). This operation, as well as insertions, deletions, or updating the weight of a question are all O(log n).
Have a look at this this(scroll down for the code).
EDIT for the critics:)
The code on this thread I linked shows how to implement a binary tree approach that actually works with weights and doesn't store masses of elements in an array to achieve a weighted probability. Then again, it's pretty inefficient when the weights change very often as the binary tree has to be re-created every time a weight changes.
EDIT2:
See Todd Owen's post about using self-balancing trees. The tree obviously does not have to be re-created every time a weight changes. That part just isn't included in the implementation I linked and needs to be added if your weights change a lot.

Resources