Prepare the Bunnies Escape - Foobar - algorithm
I've been at this for a while and for the life of me I cannot figure out why I cannot pass test cases 4 and 5. My code is below, including my own custom test cases that all execute and pass in under 5ms.
Basically I added a third dimension to each node's position that represents whether a wall has already been traversed or not. When analyzing each current node's neighbor, if it's a wall and the current node has a zero for its third coordinate, then moving to the wall and to a 1 on the third coordinate becomes an option. On paper, it works great. In my own IDE, it works great.
I'm starting to wonder if there's something in here that's Python 3 and not working correctly in foobar or something. I'd appreciate any help.
class Node():
def __init__(self, position):
self.position = position
self.gCost = 1
self.hCost = 0
self.fCost = 0
def __eq__(self, other):
return self.position == other.position
def solution(map):
startNode = Node((0, 0, 0))
endNode = Node((len(map[0]) - 1, len(map) - 1, 0))
openList = [startNode]
closedList = []
while openList:
currentNode = openList[0]
currentIndex = 0
for i in range(len(openList)):
if openList[i].fCost < currentNode.fCost:
currentNode = openList[i]
currentIndex = i
openList.pop(currentIndex)
closedList.append(currentNode)
if currentNode.position[0] == endNode.position[0] and currentNode.position[1] == endNode.position[1]:
return currentNode.gCost
for offset in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
neighborPosition = (currentNode.position[0] + offset[0], currentNode.position[1] + offset[1], currentNode.position[2])
if neighborPosition[0] < 0 or neighborPosition[0] >= len(map[0]) or neighborPosition[1] < 0 or neighborPosition[1] >= len(map):
continue
if map[neighborPosition[0]][neighborPosition[1]] == 1:
if currentNode.position[2] == 1:
continue
neighborPosition = (neighborPosition[0], neighborPosition[1], 1)
neighbor = Node(neighborPosition)
if neighbor in closedList:
continue
if neighbor in openList:
openNodeIndex = openList.index(neighbor)
if openList[openNodeIndex].gCost < currentNode.gCost + 1:
continue
openList.pop(openNodeIndex)
openList.append(neighbor)
else:
openList.append(neighbor)
neighbor.gCost = currentNode.gCost + 1
neighbor.hCost = endNode.position[0] - neighbor.position[0] + endNode.position[1] - neighbor.position[1]
neighbor.fCost = neighbor.gCost + neighbor.hCost
return -1
import time
start = time.time()
map1 = [[0, 0, 0, 1], [1, 0, 0, 1], [1, 0, 0, 0], [0, 0, 0, 0]]
sol1 = solution(map1)
print("Result: ", sol1, "Expected: ", 7)
map2 = [[0,1,0,0,0], [0,1,0,1,0], [0,1,0,1,0], [0,1,0,1,0], [0,0,0,1,0]]
sol2 = solution(map2)
print("Result: ", sol2, "Expected: ", 9)
map3 = [[0,0,0,0,0,0,1,0,0,0], [0,0,0,0,1,0,1,0,1,0], [0,0,0,0,1,0,1,0,1,0], [0,0,0,0,1,0,1,0,1,0], [0,0,0,0,1,0,1,0,1,0], [0,0,0,0,1,0,1,0,1,0], [0,0,0,0,1,0,1,0,1,0], [0,0,0,0,1,0,1,0,1,0], [0,0,0,0,1,0,1,0,1,0], [0,0,0,0,1,0,0,0,1,0]]
sol3 = solution(map3)
print("Result: ", sol3, "Expected: ", 19)
map4 = [[0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 0], [0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1], [0, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0]]
sol4 = solution(map4)
print("Result: ", sol4, "Expected: ", 11)
map5 = [[0, 1, 1, 0], [0, 0, 0, 1], [1, 1, 0, 0], [1, 1, 1, 0]]
sol5 = solution(map5)
print("Result: ", sol5, "Expected: ", 7)
map6 = [[0,1,0], [0,1,0], [0,1,0]]
sol6 = solution(map6)
print("Result: ", sol6, "Expected: ", 5)
map7 = [[0,1,1,0,0,0,0,1,0,1,0,0,0,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,0,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,1,1,0]]
sol7 = solution(map7)
print("Result: ", sol7, "Expected: ", 123)
map8 = [[0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0],[0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0],[0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0],[0,1,0,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1],[0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0],[1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1],[0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0],[0,1,0,1,0,1,1,1,0,1,1,0,1,1,1,1,1,1,0,1],[0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0],[0,1,0,1,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1],[0,1,0,1,0,1,0,1,0,1,0,0,0,1,0,0,0,0,0,0],[0,1,0,1,0,1,0,1,0,0,0,1,1,1,0,1,1,1,1,1],[0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,0,0,0,0,0],[1,1,0,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,1],[0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,0,0,1,0,0],[0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,0],[0,1,0,1,0,1,0,0,0,1,0,1,0,1,0,0,0,1,0,0],[0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,0,1,1,1,1],[0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,0,1,0,1,0,1,0,0,1,1,0,0]]
sol8 = solution(map8)
print("Result: ", sol8, "Expected: ", 89)
end = time.time()
print("Time: ", end - start)
Edit: Quick update - converted the closedList to a set and now it solves my test cases in under 1ms, still fails Google's test cases 4 and 5 though.
So I figured it out. The line
if map[neighborPosition[0]][neighborPosition[1]] == 1:
had the x and y coordinates backwards. It should have been
if map[neighborPosition[1]][neighborPosition[0]] == 1:
In cases where the map was not square it was going out of bounds. Just needed to add a test case that wasn't square and figured it out pretty quick from there.
Related
How to solve flow game using Google OR tools?
I tried to make solver for flow game using google-OR tools. I made a few rules for the corner to only contains corner pipes, but other than that, i can not figure out how to make the pipe connected to each other nor how to tell the model to make a pipe that is connecting to each other. A few snippet pipe_types = { 0: " ", 1: "-", 2: "|", 3: "┗" , 4: "┛" , 5: "┓", 6: "┏", 7: "●" } model = cp_model.CpModel() filled_map = [[0,0,0,0], [0,0,7,0], [0,0,0,0], [0,7,0,0]] mesh_size = int(np.sqrt(len(np.array(filled_map).flatten()))) target_map = [[model.NewIntVar(1, 6, 'column: %i' % i) for i in range(mesh_size)] for j in range(mesh_size)] flow_map = init_map(model, target_map, filled_map) for i in range(len(flow_map)): for j in range(len(flow_map[0])): # check if top or bottom side if (i == 0) or (i == len(flow_map)-1): model.Add(flow_map[i][j] != 2) # check if left or right side if (j == 0) or (j == len(flow_map[0])-1): model.Add(flow_map[i][j] != 1) # left up corner if (i == 0) & (j == 0): model.Add(flow_map[i][j] != 3) model.Add(flow_map[i][j] != 4) model.Add(flow_map[i][j] != 5) # right up corner if (i == 0) & (j == len(flow_map[0])-1): model.Add(flow_map[i][j] != 3) model.Add(flow_map[i][j] != 4) model.Add(flow_map[i][j] != 6) # left bottom corner if (i == len(flow_map)-1) & (j == 0): model.Add(flow_map[i][j] != 4) model.Add(flow_map[i][j] != 5) model.Add(flow_map[i][j] != 6) # right bottom corner if (i == len(flow_map)-1) & (j == len(flow_map[0])-1): model.Add(flow_map[i][j] != 3) model.Add(flow_map[i][j] != 5) model.Add(flow_map[i][j] != 6) # Solving status = solver.Solve(model) res = [] if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: for i in range(len(flow_map)): for j in range(len(flow_map[0])): res.append(solver.Value(flow_map[i][j])) print(solver.Value(flow_map[i][j]), end=" ") print() This would results horizontal pipes on the center of the mesh. Later on, i would have to figure out how to add color and such on this too. Is there any pointer on how to make this on OR tools? Edit 1: Based on David Eisenstat's answer, I can find solution. Visualizing this solution based on JohanC's answer, I get this result. Can I get the pathing made from google-OR tools? Edit 2: Using hamilton path from "Hamiltonian" path using Python I could generate somewhat correct pathing. But it feels so weird since OR tools already calculate the pathing, and I have to recalculate the path. The path generated from "Hamiltonian" path using Python doesn't show all possible combinations. If I can take the path from OR tools, I think that would be my best interest.
As I don't have experience with OR-tools, here is an approach with Z3. The initial board is represented by numbers for the end points, one number for each color. The idea is a bit similar to how Sudoku is represented. Each other cell on the board will get either a value for zero, or a number. This number should be equal to exactly two of its neighbors. The initial endpoints should have exactly one neighbor with its color. from z3 import Solver, Sum, Int, If, And, Or, sat def plot_solution(S): import matplotlib.pyplot as plt ax = plt.gca() colors = plt.cm.tab10.colors for i in range(M): for j in range(N): if board[i][j] != 0: ax.scatter(j, i, s=500, color=colors[board[i][j]]) if S[i][j] != 0: for k in range(M): for l in range(N): if abs(k - i) + abs(l - j) == 1 and S[i][j] == S[k][l]: ax.plot([j, l], [i, k], color=colors[S[i][j]], lw=15) ax.set_ylim(M - 0.5, -0.5) ax.set_xlim(-0.5, N - 0.5) ax.set_aspect('equal') ax.set_facecolor('black') ax.set_yticks([i + 0.5 for i in range(M - 1)], minor=True) ax.set_xticks([j + 0.5 for j in range(N - 1)], minor=True) ax.grid(b=True, which='minor', color='white') ax.set_xticks([]) ax.set_yticks([]) ax.tick_params(axis='both', which='both', length=0) plt.show() board = [[1, 0, 0, 2, 3], [0, 0, 0, 4, 0], [0, 0, 4, 0, 0], [0, 2, 3, 0, 5], [0, 1, 5, 0, 0]] M = len(board) N = len(board[0]) B = [[Int(f'B_{i}_{j}') for j in range(N)] for i in range(M)] s = Solver() s.add(([If(board[i][j] != 0, B[i][j] == board[i][j], And(B[i][j] >= 0, B[i][j] < 10)) for j in range(N) for i in range(M)])) for i in range(M): for j in range(N): same_neighs_ij = Sum([If(B[i][j] == B[k][l], 1, 0) for k in range(M) for l in range(N) if abs(k - i) + abs(l - j) == 1]) if board[i][j] != 0: s.add(same_neighs_ij == 1) else: s.add(Or(same_neighs_ij == 2, B[i][j] == 0)) if s.check() == sat: m = s.model() S = [[m[B[i][j]].as_long() for j in range(N)] for i in range(M)] print(S) plot_solution(S) Solution: [[1, 2, 2, 2, 3], [1, 2, 4, 4, 3], [1, 2, 4, 3, 3], [1, 2, 3, 3, 5], [1, 1, 5, 5, 5]] As mentioned in the comments, a possible requirement is that all cells would need to be colored. This would need a more complicated approach. Here is an example of such a configuration for which the above code could create a solution that connects all end points without touching all cells: board = [[0, 1, 2, 0, 0, 0, 0], [1, 3, 4, 0, 3, 5, 0], [0, 0, 0, 0, 0, 0, 0], [0, 2, 0, 4, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 5, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0]]
The best way is probably with AddCircuit. This constraint takes a directed graph where each arc is labeled with a literal and requires that the arcs labeled true form a subgraph where each node has in- and out-degree 1, and further that there is at most one cycle that is not a self-loop. By forcing an arc from the end to the beginning, we can use this constraint type to require that there is a single path from the beginning to the end. The documentation is somewhat poor, so here's a working code sample. I'll leave the drawing part to you. import collections from ortools.sat.python import cp_model def validate_board_and_count_colors(board): assert isinstance(board, list) assert all(isinstance(row, list) for row in board) assert len(set(map(len, board))) == 1 colors = collections.Counter(square for row in board for square in row) del colors[0] assert all(count == 2 for count in colors.values()) num_colors = len(colors) assert set(colors.keys()) == set(range(1, num_colors + 1)) return num_colors def main(board): num_colors = validate_board_and_count_colors(board) model = cp_model.CpModel() solution = [ [square or model.NewIntVar(1, num_colors, "") for (j, square) in enumerate(row)] for (i, row) in enumerate(board) ] true = model.NewBoolVar("") model.AddBoolOr([true]) for color in range(1, num_colors + 1): endpoints = [] arcs = [] for i, row in enumerate(board): for j, square in enumerate(row): if square == color: endpoints.append((i, j)) else: arcs.append(((i, j), (i, j))) if i < len(board) - 1: arcs.append(((i, j), (i + 1, j))) if j < len(row) - 1: arcs.append(((i, j), (i, j + 1))) (i1, j1), (i2, j2) = endpoints k1 = i1 * len(row) + j1 k2 = i2 * len(row) + j2 arc_variables = [(k2, k1, true)] for (i1, j1), (i2, j2) in arcs: k1 = i1 * len(row) + j1 k2 = i2 * len(row) + j2 edge = model.NewBoolVar("") if k1 == k2: model.Add(solution[i1][j1] != color).OnlyEnforceIf(edge) arc_variables.append((k1, k1, edge)) else: model.Add(solution[i1][j1] == color).OnlyEnforceIf(edge) model.Add(solution[i2][j2] == color).OnlyEnforceIf(edge) forward = model.NewBoolVar("") backward = model.NewBoolVar("") model.AddBoolOr([edge, forward.Not()]) model.AddBoolOr([edge, backward.Not()]) model.AddBoolOr([edge.Not(), forward, backward]) model.AddBoolOr([forward.Not(), backward.Not()]) arc_variables.append((k1, k2, forward)) arc_variables.append((k2, k1, backward)) model.AddCircuit(arc_variables) solver = cp_model.CpSolver() status = solver.Solve(model) if status == cp_model.OPTIMAL: for row in solution: print("".join(str(solver.Value(x)) for x in row)) if __name__ == "__main__": main( [ [1, 0, 0, 2, 3], [0, 0, 0, 4, 0], [0, 0, 4, 0, 0], [0, 2, 3, 0, 5], [0, 1, 5, 0, 0], ] )
Why is only one value in my hash being changed?
I'm making a simple RPG as a learning project, and am having an issue with part of the character creator. This code should determine what skill string is assigned to player[:caste][:skill] and player[:sub][:skill], then increase each respective skill's value in player[:skills] by 2. This code should work regardless of what string is assigned to player[:caste][:skill] and player[:sub][:skill], as long as it is equal to player[:skills].to_s. Currently, it is only applying the change to player[:skills][:endurance] but not player[:skills][:athletics]. player = { caste: {skill: "athletics"}, sub: {skill: "endurance"}, skills: {acrobatics: 0, athletics: 0, engineering: 0, endurance: 0, heal: 0, history: 0, influence: 0, insight: 0, magicka: 0, perception: 0, riding: 0, stealth: 0, streetwise: 0, thievery: 0}, } player[:skills] = player[:skills].map do |skill, mod| [skill, (mod += 2 if skill.to_s == player[:caste][:skill])] [skill, (mod += 2 if skill.to_s == player[:sub][:skill])] end.to_h In other words, my code is returning the following player[:skills] hash: skills: {acrobatics: 0, athletics: 0, engineering: 0, endurance: 2, heal: 0, history: 0, influence: 0, insight: 0, magicka: 0, perception: 0, riding: 0, stealth: 0, streetwise: 0, thievery: 0} but I want it to return: skills: {acrobatics: 0, athletics: 2, engineering: 0, endurance: 2, heal: 0, history: 0, influence: 0, insight: 0, magicka: 0, perception: 0, riding: 0, stealth: 0, streetwise: 0, thievery: 0} Please let me know if there is a simpler way to do this. I've also tried the following: player[:skills] = player[:skills].map do |skill, mod| [skill, (mod += 2 if skill.to_s == (player[:caste][:skill] || player[:sub][:skill]))] end.to_h which only affects the skill found in player[:caste][:skill].
When I run your code I get this as the result. {:acrobatics=>nil, :athletics=>nil, :engineering=>nil, :endurance=>2, :heal=>nil, :history=>nil, :influence=>nil, :insight=>nil, :magicka=>nil, :perception=>nil, :riding=>nil, :stealth=>nil, :streetwise=>nil, :thievery=>nil} That's because map returns last statement executed. In addition you actually only set a value for skill when it's matches the sub skill otherwise, it is set to nil. So whats happening in your code is that each iteration is returning the following which is the result of the last statement in the block passed into map. [:acrobatics, nil] [:athletics, nil] [:engineering, nil] [:endurance, 2] [:heal, nil] [:history, nil] [:influence, nil] [:insight, nil] [:magicka, nil] [:perception, nil] [:riding, nil] [:stealth, nil] [:streetwise, nil] [:thievery, nil] The final result being an array that looks like this. [[:acrobatics, nil], [:athletics, nil], [:engineering, nil], [:endurance, 2], [:heal, nil], [:history, nil], [:influence, nil], [:insight, nil], [:magicka, nil], [:perception, nil], [:riding, nil], [:stealth, nil], [:streetwise, nil], [:thievery, nil]] Which is finally mapped to a new hash {:acrobatics=>nil, :athletics=>nil, :engineering=>nil, :endurance=>2, :heal=>nil, :history=>nil, :influence=>nil, :insight=>nil, :magicka=>nil, :perception=>nil, :riding=>nil, :stealth=>nil, :streetwise=>nil, :thievery=>nil} The reason you get all those nil's is because in your statements the result of the case were the if statement is not true is nil. For example: [skill (mod += 2 if skill.to_s == player[:caste][:skill])] will return [the_skill, nil] for the cases were skill.to_s == player[:caste][:skill] is not true To see what's happening try this in irb. x = 0 => 0 x += 1 if false => nil x += 1 if true => 1 You could get past that using something like this. [skill, skill.to_s == player[:caste][:skill] ? mod + 2 : mod ] or using the above example: x = 0 => 0 x = false ? x + 1 : x => 0 x = true ? x + 1 : x => 1 The following modified version of your code should work. player[:skills] = player[:skills].map do |skill, mod| [skill, skill.to_s == player[:caste][:skill] || skill.to_s == player[:sub][:skill] ? mod + 2 : mod ] end.to_h However, here is a slightly more verbose, but hopefully much easier to follow way to accomplish what you want to do and allows for added modifications in the future with out the code getting too confusing. player = { caste: {skill: "athletics"}, sub: {skill: "endurance"}, skills: {acrobatics: 0, athletics: 0, engineering: 0, endurance: 0, heal: 0, history: 0, influence: 0, insight: 0, magicka: 0, perception: 0, riding: 0, stealth: 0, streetwise: 0, thievery: 0}, } player_caste_skill = player[:caste][:skill] player_sub_skill = player[:sub][:skill] current_skills = player[:skills] updated_skills = {} current_skills.each_pair do |skill, prev_value| new_value = prev_value case skill.to_s when player_caste_skill, player_sub_skill new_value = prev_value + 2 when "some_other_skill" new_value = prev_value + 3 end updated_skills[skill] = new_value end puts current_skills puts updated_skills
I'd set a default value (Hash#default) to the player[:skill] hash, just to avoid errors in case of missing key (it adds the key!!), allowing to add also a new key without the need to initialise to 0 each skill. player[:skills].default = 0 Then scan the keys you need to increment in just one liner: [:caste, :sub].each { |key| player.dig(key, :skill).to_sym.then { |skill| player[:skills][skill] += 2 } } Thanks to the initialisation, your player can also be player = { caste: {skill: "athletics"}, sub: {skill: "endurance"}, skills: {} } Returning a result like: player #=> {:caste=>{:skill=>"athletics"}, :sub=>{:skill=>"endurance"}, :skills=>{:athletics=>2, :endurance=>2}} Where: player[:skills][:whatever] #=> 0
I would iterate through defined skills rather than through skill values. player. map { |_, h| h[:skill] }. compact. map(&:to_sym). each { |skill| player[:skills][skill] += 2 } Now player is updated accordingly, as you might check by exaimning player with p player or like.
Change the code to be something like this: player = { caste: {skill: "athletics"}, sub: {skill: "endurance"}, skills: {acrobatics: 0, athletics: 0, engineering: 0, endurance: 0, heal: 0, history: 0, influence: 0, insight: 0, magicka: 0, perception: 0, riding: 0, stealth: 0, streetwise: 0, thievery: 0}, } player[:skills] = player[:skills].map do |skill, mod| [skill, (mod += 2 if [player[:caste][:skill], player[:sub][:skill]].include? (skill.to_s))] end.to_h The reason your code didn't work because map return the last line as a result for the current iteration, So in the athletics case the last line which is [skill, (mod += 2 if skill.to_s == player[:sub][:skill])] will be false which will be nil that's why only endurance case works. hope it helps.
Linearly reading a multi-dimensional array obeying dimensional sub-sectioning
I have an API for reading multi-dimensional arrays, requiring to pass a vector of ranges to read sub-rectangles (or hypercubes) from the backing array. I want to read this array "linearly", all elements in some given order with arbitrary chunk sizes. Thus, the task is with an off and a len, to translate the elements covered by this range into the smallest possible set of hyper-cubes, i.e. the smallest number of read commands issued in the API. For example, we can calculate index vectors for the set of dimensions giving a linear index: def calcIndices(off: Int, shape: Vector[Int]): Vector[Int] = { val modsDivs = shape zip shape.scanRight(1)(_ * _).tail modsDivs.map { case (mod, div) => (off / div) % mod } } Let's say the shape is this, representing an array with rank 4 and 120 elements in total: val sz = Vector(2, 3, 4, 5) val num = sz.product // 120 A utility to print these index vectors for a range of linear offsets: def printIndices(off: Int, len: Int): Unit = (off until (off + len)).map(calcIndices(_, sz)) .map(_.mkString("[", ", ", "]")).foreach(println) We can generate all those vectors: printIndices(0, num) [0, 0, 0, 0] [0, 0, 0, 1] [0, 0, 0, 2] [0, 0, 0, 3] [0, 0, 0, 4] [0, 0, 1, 0] [0, 0, 1, 1] [0, 0, 1, 2] [0, 0, 1, 3] [0, 0, 1, 4] [0, 0, 2, 0] [0, 0, 2, 1] [0, 0, 2, 2] [0, 0, 2, 3] [0, 0, 2, 4] [0, 0, 3, 0] [0, 0, 3, 1] [0, 0, 3, 2] [0, 0, 3, 3] [0, 0, 3, 4] [0, 1, 0, 0] ... [1, 2, 1, 4] [1, 2, 2, 0] [1, 2, 2, 1] [1, 2, 2, 2] [1, 2, 2, 3] [1, 2, 2, 4] [1, 2, 3, 0] [1, 2, 3, 1] [1, 2, 3, 2] [1, 2, 3, 3] [1, 2, 3, 4] Let's look at an example chunk that should be read, the first six elements: val off1 = 0 val len1 = 6 printIndices(off1, len1) I will already partition the output by hand into hypercubes: // first hypercube or read [0, 0, 0, 0] [0, 0, 0, 1] [0, 0, 0, 2] [0, 0, 0, 3] [0, 0, 0, 4] // second hypercube or read [0, 0, 1, 0] So the task is to define a method def partition(shape: Vector[Int], off: Int, len: Int): List[Vector[Range]] which outputs the correct list and uses the smallest possible list size. So for off1 and len1, we have the expected result: val res1 = List( Vector(0 to 0, 0 to 0, 0 to 0, 0 to 4), Vector(0 to 0, 0 to 0, 1 to 1, 0 to 0) ) assert(res1.map(_.map(_.size).product).sum == len1) A second example, elements at indices 6 until 22, with manual partitioning giving three hypercubes or read commands: val off2 = 6 val len2 = 16 printIndices(off2, len2) // first hypercube or read [0, 0, 1, 1] [0, 0, 1, 2] [0, 0, 1, 3] [0, 0, 1, 4] // second hypercube or read [0, 0, 2, 0] [0, 0, 2, 1] [0, 0, 2, 2] [0, 0, 2, 3] [0, 0, 2, 4] [0, 0, 3, 0] [0, 0, 3, 1] [0, 0, 3, 2] [0, 0, 3, 3] [0, 0, 3, 4] // third hypercube or read [0, 1, 0, 0] [0, 1, 0, 1] expected result: val res2 = List( Vector(0 to 0, 0 to 0, 1 to 1, 1 to 4), Vector(0 to 0, 0 to 0, 2 to 3, 0 to 4), Vector(0 to 0, 1 to 1, 0 to 0, 0 to 1) ) assert(res2.map(_.map(_.size).product).sum == len2) Note that for val off3 = 6; val len3 = 21, we would need four readings.
The idea of the following algorithm is as follows: a point-of-interest (poi) is the left-most position at which two index representations differ (for example for [0, 0, 0, 1] and [0, 1, 0, 0] the poi is 1) we recursively sub-divide the original (start, stop) linear index range we use motions in two directions, first by keeping the start constant and decreasing the stop through a special "ceil" operation on the start, later by keeping the stop constant and increasing the start through a special "floor" operation on the stop for each sub range, we calculate the poi of the boundaries, and we calculate "trunc" which is ceil or floor operation described above if this trunc value is identical to its input, we add the entire region and return otherwise we recurse the special "ceil" operation takes the previous start value and increases the element at the poi index and zeroes the subsequent elements; e.g. for [0, 0, 1, 1] and poi = 2, the ceil would be [0, 0, 2, 0] the special "floor" operation takes the previous stop value and zeroes the elements after the poi index; e.g. for [0, 0, 1, 1], and poi = 2, the floor would be [0, 0, 1, 0] Here is my implementation. First, a few utility functions: def calcIndices(off: Int, shape: Vector[Int]): Vector[Int] = { val modsDivs = (shape, shape.scanRight(1)(_ * _).tail, shape.indices).zipped modsDivs.map { case (mod, div, idx) => val x = off / div if (idx == 0) x else x % mod } } def calcPOI(a: Vector[Int], b: Vector[Int], min: Int): Int = { val res = (a.drop(min) zip b.drop(min)).indexWhere { case (ai,bi) => ai != bi } if (res < 0) a.size else res + min } def zipToRange(a: Vector[Int], b: Vector[Int]): Vector[Range] = (a, b).zipped.map { (ai, bi) => require (ai <= bi) ai to bi } def calcOff(a: Vector[Int], shape: Vector[Int]): Int = { val divs = shape.scanRight(1)(_ * _).tail (a, divs).zipped.map(_ * _).sum } def indexTrunc(a: Vector[Int], poi: Int, inc: Boolean): Vector[Int] = a.zipWithIndex.map { case (ai, i) => if (i < poi) ai else if (i > poi) 0 else if (inc) ai + 1 else ai } Then the actual algorithm: def partition(shape: Vector[Int], off: Int, len: Int): List[Vector[Range]] = { val rankM = shape.size - 1 def loop(start: Int, stop: Int, poiMin: Int, dir: Boolean, res0: List[Vector[Range]]): List[Vector[Range]] = if (start == stop) res0 else { val last = stop - 1 val s0 = calcIndices(start, shape) val s1 = calcIndices(stop , shape) val s1m = calcIndices(last , shape) val poi = calcPOI(s0, s1m, poiMin) val ti = if (dir) s0 else s1 val to = if (dir) s1 else s0 val st = if (poi >= rankM) to else indexTrunc(ti, poi, inc = dir) val trunc = calcOff(st, shape) val split = trunc != (if (dir) stop else start) if (split) { if (dir) { val res1 = loop(start, trunc, poiMin = poi+1, dir = true , res0 = res0) loop (trunc, stop , poiMin = 0 , dir = false, res0 = res1) } else { val s1tm = calcIndices(trunc - 1, shape) val res1 = zipToRange(s0, s1tm) :: res0 loop (trunc, stop , poiMin = poi+1, dir = false, res0 = res1) } } else { zipToRange(s0, s1m) :: res0 } } loop(off, off + len, poiMin = 0, dir = true, res0 = Nil).reverse } Examples: val sz = Vector(2, 3, 4, 5) partition(sz, 0, 6) // result: List( Vector(0 to 0, 0 to 0, 0 to 0, 0 to 4), // first hypercube Vector(0 to 0, 0 to 0, 1 to 1, 0 to 0) // second hypercube ) partition(sz, 6, 21) // result: List( Vector(0 to 0, 0 to 0, 1 to 1, 1 to 4), // first read Vector(0 to 0, 0 to 0, 2 to 3, 0 to 4), // second read Vector(0 to 0, 1 to 1, 0 to 0, 0 to 4), // third read Vector(0 to 0, 1 to 1, 1 to 1, 0 to 1) // fourth read ) The maximum number of reads, if I'm not mistaken, would be 2 * rank.
All Possible Tic Tac Toe Winning Combinations
I had an interview were I was asked a seemingly simple algorithm question: "Write an algorithm to return me all possible winning combinations for tic tac toe." I still can't figure out an efficient way to handle this. Is there a standard algorithm or common that should be applied to similar questions like this that I'm not aware of?
This is one of those problems that's actually simple enough for brute force and, while you could use combinatorics, graph theory, or many other complex tools to solve it, I'd actually be impressed by applicants that recognise the fact there's an easier way (at least for this problem). There are only 39, or 19,683 possible combinations of placing x, o or <blank> in the grid, and not all of those are valid. First, a valid game position is one where the difference between x and o counts is no more than one, since they have to alternate moves. In addition, it's impossible to have a state where both sides have three in a row, so they can be discounted as well. If both have three in a row, then one of them would have won in the previous move. There's actually another limitation in that it's impossible for one side to have won in two different ways without a common cell (again, they would have won in a previous move), meaning that: XXX OOO XXX cannot be achieved, while: XXX OOX OOX can be. But we can actually ignore that since there's no way to win two ways without a common cell without having already violated the "maximum difference of one" rule, since you need six cells for that, with the opponent only having three. So I would simply use brute force and, for each position where the difference is zero or one between the counts, check the eight winning possibilities for both sides. Assuming only one of them has a win, that's a legal, winning game. Below is a proof of concept in Python, but first the output of time when run on the process sending output to /dev/null to show how fast it is: real 0m0.169s user 0m0.109s sys 0m0.030s The code: def won(c, n): if c[0] == n and c[1] == n and c[2] == n: return 1 if c[3] == n and c[4] == n and c[5] == n: return 1 if c[6] == n and c[7] == n and c[8] == n: return 1 if c[0] == n and c[3] == n and c[6] == n: return 1 if c[1] == n and c[4] == n and c[7] == n: return 1 if c[2] == n and c[5] == n and c[8] == n: return 1 if c[0] == n and c[4] == n and c[8] == n: return 1 if c[2] == n and c[4] == n and c[6] == n: return 1 return 0 pc = [' ', 'x', 'o'] c = [0] * 9 for c[0] in range (3): for c[1] in range (3): for c[2] in range (3): for c[3] in range (3): for c[4] in range (3): for c[5] in range (3): for c[6] in range (3): for c[7] in range (3): for c[8] in range (3): countx = sum([1 for x in c if x == 1]) county = sum([1 for x in c if x == 2]) if abs(countx-county) < 2: if won(c,1) + won(c,2) == 1: print " %s | %s | %s" % (pc[c[0]],pc[c[1]],pc[c[2]]) print "---+---+---" print " %s | %s | %s" % (pc[c[3]],pc[c[4]],pc[c[5]]) print "---+---+---" print " %s | %s | %s" % (pc[c[6]],pc[c[7]],pc[c[8]]) print As one commenter has pointed out, there is one more restriction. The winner for a given board cannot have less cells than the loser since that means the loser just moved, despite the fact the winner had already won on the last move. I won't change the code to take that into account but it would be a simple matter of checking who has the most cells (the last person that moved) and ensuring the winning line belonged to them.
Another way could be to start with each of the eight winning positions, xxx --- --- xxx --- --- ... etc., and recursively fill in all legal combinations (start with inserting 2 o's, then add an x for each o ; avoid o winning positions): xxx xxx xxx oo- oox oox --- o-- oox ... etc.,
Today I had an interview with Apple and I had the same question. I couldn't think well at that moment. Later one on, before going to a meeting I wrote the function for the combinations in 15 minutes, and when I came back from the meeting I wrote the validation function again in 15 minutes. I get nervous at interviews, Apple not trusts my resume, they only trust what they see in the interview, I don't blame them, many companies are the same, I just say that something in this hiring process doesn't look quite smart. Anyways, here is my solution in Swift 4, there are 8 lines of code for the combinations function and 17 lines of code to check a valid board. Cheers!!! // Not used yet: 0 // Used with x : 1 // Used with 0 : 2 // 8 lines code to get the next combination func increment ( _ list: inout [Int], _ base: Int ) -> Bool { for digit in 0..<list.count { list[digit] += 1 if list[digit] < base { return true } list[digit] = 0 } return false } let incrementTicTacToe = { increment(&$0, 3) } let win0_ = [0,1,2] // [1,1,1,0,0,0,0,0,0] let win1_ = [3,4,5] // [0,0,0,1,1,1,0,0,0] let win2_ = [6,7,8] // [0,0,0,0,0,0,1,1,1] let win_0 = [0,3,6] // [1,0,0,1,0,0,1,0,0] let win_1 = [1,4,7] // [0,1,0,0,1,0,0,1,0] let win_2 = [2,5,8] // [0,0,1,0,0,1,0,0,1] let win00 = [0,4,8] // [1,0,0,0,1,0,0,0,1] let win11 = [2,4,6] // [0,0,1,0,1,0,1,0,0] let winList = [ win0_, win1_, win2_, win_0, win_1, win_2, win00, win11] // 16 lines to check a valid board, wihtout countin lines of comment. func winCombination (_ tictactoe: [Int]) -> Bool { var count = 0 for win in winList { if tictactoe[win[0]] == tictactoe[win[1]], tictactoe[win[1]] == tictactoe[win[2]], tictactoe[win[2]] != 0 { // If the combination exist increment count by 1. count += 1 } if count == 2 { return false } } var indexes = Array(repeating:0, count:3) for num in tictactoe { indexes[num] += 1 } // '0' and 'X' must be used the same times or with a diference of one. // Must one and only one valid combination return abs(indexes[1] - indexes[2]) <= 1 && count == 1 } // Test var listToIncrement = Array(repeating:0, count:9) var combinationsCount = 1 var winCount = 0 while incrementTicTacToe(&listToIncrement) { if winCombination(listToIncrement) == true { winCount += 1 } combinationsCount += 1 } print("There is \(combinationsCount) combinations including possible and impossible ones.") print("There is \(winCount) combinations for wining positions.") /* There are 19683 combinations including possible and impossible ones. There are 2032 combinations for winning positions. */ listToIncrement = Array(repeating:0, count:9) var listOfIncremented = "" for _ in 0..<1000 { // Win combinations for the first 1000 combinations _ = incrementTicTacToe(&listToIncrement) if winCombination(listToIncrement) == true { listOfIncremented += ", \(listToIncrement)" } } print("List of combinations: \(listOfIncremented)") /* List of combinations: , [2, 2, 2, 1, 1, 0, 0, 0, 0], [1, 1, 1, 2, 2, 0, 0, 0, 0], [2, 2, 2, 1, 0, 1, 0, 0, 0], [2, 2, 2, 0, 1, 1, 0, 0, 0], [2, 2, 0, 1, 1, 1, 0, 0, 0], [2, 0, 2, 1, 1, 1, 0, 0, 0], [0, 2, 2, 1, 1, 1, 0, 0, 0], [1, 1, 1, 2, 0, 2, 0, 0, 0], [1, 1, 1, 0, 2, 2, 0, 0, 0], [1, 1, 0, 2, 2, 2, 0, 0, 0], [1, 0, 1, 2, 2, 2, 0, 0, 0], [0, 1, 1, 2, 2, 2, 0, 0, 0], [1, 2, 2, 1, 0, 0, 1, 0, 0], [2, 2, 2, 1, 0, 0, 1, 0, 0], [2, 2, 1, 0, 1, 0, 1, 0, 0], [2, 2, 2, 0, 1, 0, 1, 0, 0], [2, 2, 2, 1, 1, 0, 1, 0, 0], [2, 0, 1, 2, 1, 0, 1, 0, 0], [0, 2, 1, 2, 1, 0, 1, 0, 0], [2, 2, 1, 2, 1, 0, 1, 0, 0], [1, 2, 0, 1, 2, 0, 1, 0, 0], [1, 0, 2, 1, 2, 0, 1, 0, 0], [1, 2, 2, 1, 2, 0, 1, 0, 0], [2, 2, 2, 0, 0, 1, 1, 0, 0] */
This is a java equivalent code sample package testit; public class TicTacToe { public static void main(String[] args) { // TODO Auto-generated method stub // 0 1 2 // 3 4 5 // 6 7 8 char[] pc = {' ' ,'o', 'x' }; char[] c = new char[9]; // initialize c for (int i = 0; i < 9; i++) c[i] = pc[0]; for (int i = 0; i < 3; i++) { c[0] = pc[i]; for (int j = 0; j < 3; j++) { c[1] = pc[j]; for (int k = 0; k < 3; k++) { c[2] = pc[k]; for (int l = 0; l < 3; l++) { c[3] = pc[l]; for (int m = 0; m < 3; m++) { c[4] = pc[m]; for (int n = 0; n < 3; n++) { c[5] = pc[n]; for (int o = 0; o < 3; o++) { c[6] = pc[o]; for (int p = 0; p < 3; p++) { c[7] = pc[p]; for (int q = 0; q < 3; q++) { c[8] = pc[q]; int countx = 0; int county = 0; for(int r = 0 ; r<9 ; r++){ if(c[r] == 'x'){ countx = countx + 1; } else if(c[r] == 'o'){ county = county + 1; } } if(Math.abs(countx - county) < 2){ if(won(c, pc[2])+won(c, pc[1]) == 1 ){ System.out.println(c[0] + " " + c[1] + " " + c[2]); System.out.println(c[3] + " " + c[4] + " " + c[5]); System.out.println(c[6] + " " + c[7] + " " + c[8]); System.out.println("*******************************************"); } } } } } } } } } } } } public static int won(char[] c, char n) { if ((c[0] == n) && (c[1] == n) && (c[2] == n)) return 1; else if ((c[3] == n) && (c[4] == n) && (c[5] == n)) return 1; else if ((c[6] == n) && (c[7] == n) && (c[8] == n)) return 1; else if ((c[0] == n) && (c[3] == n) && (c[6] == n)) return 1; else if ((c[1] == n) && (c[4] == n) && (c[7] == n)) return 1; else if ((c[2] == n) && (c[5] == n) && (c[8] == n)) return 1; else if ((c[0] == n) && (c[4] == n) && (c[8] == n)) return 1; else if ((c[2] == n) && (c[4] == n) && (c[6] == n)) return 1; else return 0; } } `
Below Solution generates all possible combinations using recursion It has eliminated impossible combinations and returned 888 Combinations Below is a working code Possible winning combinations of the TIC TAC TOE game const players = ['X', 'O']; let gameBoard = Array.from({ length: 9 }); const winningCombination = [ [ 0, 1, 2 ], [ 3, 4, 5 ], [ 6, 7, 8 ], [ 0, 3, 6 ], [ 1, 4, 7 ], [ 2, 5, 8 ], [ 0, 4, 8 ], [ 2, 4, 6 ], ]; const isWinningCombination = (board)=> { if((Math.abs(board.filter(a => a === players[0]).length - board.filter(a => a === players[1]).length)) > 1) { return false } let winningComb = 0; players.forEach( player => { winningCombination.forEach( combinations => { if (combinations.every(combination => board[combination] === player )) { winningComb++; } }); }); return winningComb === 1; } const getCombinations = (board) => { let currentBoard = [...board]; const firstEmptySquare = board.indexOf(undefined) if (firstEmptySquare === -1) { return isWinningCombination(board) ? [board] : []; } else { return [...players, ''].reduce((prev, next) => { currentBoard[firstEmptySquare] = next; if(next !== '' && board.filter(a => a === next).length > (gameBoard.length / players.length)) { return [...prev] } return [board, ...prev, ...getCombinations(currentBoard)] }, []) } } const startApp = () => { let combination = getCombinations(gameBoard).filter(board => board.every(item => !(item === undefined)) && isWinningCombination(board) ) printCombination(combination) } const printCombination = (combination)=> { const ulElement = document.querySelector('.combinations'); combination.forEach(comb => { let node = document.createElement("li"); let nodePre = document.createElement("pre"); let textnode = document.createTextNode(JSON.stringify(comb)); nodePre.appendChild(textnode); node.appendChild(nodePre); ulElement.appendChild(node); }) } startApp();
This discovers all possible combinations for tic tac toe (255,168) -- written in JavaScript using recursion. It is not optimized, but gets you what you need. const [EMPTY, O, X] = [0, 4, 1] let count = 0 let coordinate = [ EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY ] function reducer(arr, sumOne, sumTwo = null) { let func = arr.reduce((sum, a) => sum + a, 0) if((func === sumOne) || (func === sumTwo)) return true } function checkResult() { let [a1, a2, a3, b1, b2, b3, c1, c2, c3] = coordinate if(reducer([a1,a2,a3], 3, 12)) return true if(reducer([a1,b2,c3], 3, 12)) return true if(reducer([b1,b2,b3], 3, 12)) return true if(reducer([c1,c2,c3], 3, 12)) return true if(reducer([a3,b2,c1], 3, 12)) return true if(reducer([a1,b1,c1], 3, 12)) return true if(reducer([a2,b2,c2], 3, 12)) return true if(reducer([a3,b3,c3], 3, 12)) return true if(reducer([a1,a2,a3,b1,b2,b3,c1,c2,c3], 21)) return true return false } function nextPiece() { let [countX, countO] = [0, 0] for(let i = 0; i < coordinate.length; i++) { if(coordinate[i] === X) countX++ if(coordinate[i] === O) countO++ } return countX === countO ? X : O } function countGames() { if (checkResult()) { count++ }else { for (let i = 0; i < 9; i++) { if (coordinate[i] === EMPTY) { coordinate[i] = nextPiece() countGames() coordinate[i] = EMPTY } } } } countGames() console.log(count) I separated out the checkResult returns in case you want to output various win conditions.
Could be solved with brute force but keep in mind the corner cases like player2 can't move when player1 has won and vice versa. Also remember Difference between moves of player1 and player can't be greater than 1 and less than 0. I have written code for validating whether provided combination is valid or not, might soon post on github.
Use Ruby to Truncate duplicate patterns in an Array
SITE ADMIN: WOULD YOU PLEASE REMOVE THIS POST? For example, I have tt = [0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0] and I would like to slim it down to tt_out = [0, 1, 1, 2, 2, 1, 1, 0, 0] also I'd like to know when does the repetition begins and ends, hence I'd like to have the following tip tip = '0','1.','.5','6.','.11','12.','.15','16.','.20'
tt = [0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0] tip = [] tt_out = tt.map.with_index{|t, i| start_range = (i==0 || tt[i-1] != tt[i]) end_range = (tt[i+1] != tt[i]) if start_range && end_range tip << "#{i}" elsif start_range tip << "#{i}." elsif end_range tip << ".#{i}" end t if start_range || end_range }.compact tip => ["0", "1.", ".5", "6.", ".11", "12.", ".15", "16.", ".20"] tt_out => [0, 1, 1, 2, 2, 1, 1, 0, 0] P.S: You've got an error in your example, the last element of tip should be '.20'