Given a list of paths(with start and end coordinates) that are only horizontal and vertical, how can I find all the rectangles that they form?
Details:
The end points of the rectangle must be end-points of their constituent edges. i.e rectangles formed from the intersections of lines are not needed.
The x,y of start and end, represented as complex numbers of each line is given.
The output should be four lines representing each edge of the rectangle
This is my naive implementation which is too slow. Are there any other approaches?
def find_rectangles(paths):
vertical_paths = filter(lambda path: is_vertical(path), paths)
horizontal_paths = filter(lambda path: is_horizontal(path), paths)
vertical_paths.sort(key=lambda path: path.start.imag, reverse=True)
horizontal_paths.sort(key=lambda path: path.start.real)
h_pairs = []
for i in range(0, len(horizontal_paths) - 1):
for j in range(1, len(horizontal_paths)):
if horizontal_paths[i].start.real == horizontal_paths[j].start.real and horizontal_paths[i].end.real == horizontal_paths[j].end.real:
h_pairs.append((horizontal_paths[i], horizontal_paths[j]))
v_pairs = []
for i in range(0, len(vertical_paths) - 1):
for j in range(1, len(vertical_paths)):
if vertical_paths[i].start.imag == vertical_paths[j].start.imag and vertical_paths[i].end.imag == vertical_paths[j].end.imag:
v_pairs.append((vertical_paths[i], vertical_paths[j]))
rects = []
for h1, h2 in h_pairs:
for v1, v2 in v_pairs:
if h1.start == v1.start and v1.end == h2.start and h1.end == v2.start and h2.end == v2.end:
rects.append(Rect(h1.start, h1.end, h2.end, h2.start))
return rects
Edit:(Improvements from all suggestions)
Main difference is I am storing all horizontal edges' end-points in a set so look up for them is O(1):
def find_rectangles(paths):
vertical_paths = [path for path in paths if is_vertical(path)]
horizontal_paths_set = set([(path.start, path.end) for path in paths if is_horizontal(path)])
vertical_paths.sort(key=lambda pair: path.start.imag, reverse=True)
v_pairs = [pair for pair in list(itertools.combinations(vertical_paths, 2)) if pair[0].start.imag == pair[1].start.imag and pair[0].end.imag == pair[1].end.imag]
rects = []
for v1,v2 in v_pairs:
h1 = (v1.start, v2.start)
h2 = (v1.end, v2.end)
if(h1 in horizontal_paths_set and h2 in horizontal_paths_set):
rects.append(Rect(h1[0], h1[1], h2[1], h2[0 ]))
return rects
My new code runs much much faster, but it still is on the order of O(n2). Any suggestion to improve upon it is welcome.
You can drop the search for v_pairs to begin with. You only need to know if the potential rectangle (a horizontal pair) can be closed.
def find_rectangles(paths):
vertical_paths = filter(lambda path: is_vertical(path), paths)
horizontal_paths = filter(lambda path: is_horizontal(path), paths)
vertical_paths.sort(key=lambda path: path.start.imag, reverse=True)
horizontal_paths.sort(key=lambda path: path.start.real)
potential_rectangles = []
for i,h1 in enumerate(horizontal_paths[:-1]):
for h2 in horizontal_paths[i+1:]:
if ((h1.start.real == h2.start.real)
and (h1.end.real == h2.end.real)):
potential_rectangles.append((h1,h2,None,None))
rectangles = []
for v in vertical_paths:
for i,(h1,h2,v1,v2) in enumerate(potential_rectangles):
if v1 is None and v.start == h1.start and v.end == h2.start:
potential_rectangles[i][2] = v
if v2 is not None:
rectangles.append(potential_rectangles.pop(i))
break
if v2 is None and v.start == h1.end and v.end == h2.end:
potential_rectangles[i][3] = v
if v1 is not None:
rectangles.append(potential_rectangles.pop(i))
break
return rectangles
There certainly is a lot of potential to speed up the selection, dependent on the data received. For instance, sorting paths by length. Can you give more details?
After Edit
Bisect is a lot faster than 'is in', but it requires an ordered list of scalar values.
Related
first of all let me pretext this by the fact that this is for an university homework, so I only want hints and not solutions.
The problem consists of finding the path from s to t that has the smallest maximum amount of snow on one of the edges, while choosing shortest distance for tie breaking. (i. e. if multiple edges with the same snow amount are considered, we take the one with the shortest length). For any two vertices, there is at most one edge that connects them.
n - number of vertices
m - number of edges
s - source
t - target
a - list of edge beginnings
b - list of edge ends
w - list of lengths corresponding to the edges
c - list of amounts of snow corresponding to the edges
I would really appreciate the help as I've been racking my head over this for a long time.
I tried this.
import heapq
# # intersections, # roads, start list, end list, len list, snow list
def snowy_road(n, m, s, t, a, b, w, c):
# Initialize distances to all nodes as infinite except for the source node, which has distance 0
distancesc = [float("inf")] * n
distancesc[s-1] = 0
distancesw = [float("inf")] * n
distancesw[s-1] = 0
# Create a set to store the nodes that have been visited
visited = set()
# Create a priority queue to store the nodes that still need to be processed
# We will use the distance to the source node as the priority
# (i.e. the next node to process will be the one with the smallest distance to the source)
pq = []
pq.append((0, 0, s-1))
while pq:
# Pop the node with the smallest distance from the priority queue
distc, distw, u = heapq.heappop(pq)
# Skip the node if it has already been visited
if u in visited:
continue
# Mark the node as visited
visited.add(u)
# Update the distances to all adjacent nodes
for i in range(m):
if a[i] == u+1:
v = b[i]-1
elif b[i] == u+1:
v = a[i]-1
else:
continue
altc = c[i]
if distc != float("inf"):
altc = max(distc, altc)
altw = distw + w[i]
if altc < distancesc[v]:
distancesc[v] = altc
distancesw[v] = altw
heapq.heappush(pq, (altc, altw, v))
elif altc == distancesc[v] and altw < distancesw[v]:
distancesw[v] = altw
heapq.heappush(pq, (altc, altw, v))
# Return the distance to the target node
return distancesc[t-1], distancesw[t-1]
If anyone is wondering, I solved this using two consequent Dijkstras. The first one finds the value of the shortest bottleneck path, the second one takes this value into account and doest a shortest path normal Dijkstra on the graph using w, while considering only edges that have a snow coverage <= than the bottleneck we found. Thank you to everyone that responded!
I wrote a simple algorithm to generate 1000 random graphs given total nodes and associated node degree. Graph is undirected with multiple edges and no self-loops. My current algorithm is very slow:
Graph properties: no self-loops, undirected, multiple edges (i.e. many edges b/w same pair of vertices)
Pick 2 random node ids from node list using np.random
Check if node degree is full, if not connect 2 nodes and add to dictionary with connections
If a certain node has reached node degree removed it from list so it is not picked again.
randomness is graph generation is crucial as graphs form part of a statistical test
Algorithm reaches exception, where only 1 node is left out (mostly the node with large degree compared to other nodes) In such a case I take the intermediate generated graph and start breaking random edges and connecting these edges to the left out node until all nodes reach their original node degree.
This above is the bottleneck I suppose as I generate a graph and degenerate it later due to the exception.
every random graph in 1000 iterations must be different that older ones. I check this later in the code by appending to a list. If any 2 graphs are exactly same regenerate
all graphs are realisable given above conditions as I already have original graphs with these configs to start with, I am just generating 1000 random versions of these original graphs
from collections import defaultdict
for iteration in range(1000): # 1000 random graphs
all_stop = False
all_stop_2 = False
count = count +1
success_dict = defaultdict(list)
popped_dict = defaultdict(dict)
grid_dens_dict = defaultdict(set) # final graph being built
# pfxlinks = [edge1,edge2,edge3,edge4,........edgen] # just a placeholder edge list to help me identify which node connected with which node in success_dict
pfxlinks = [i for i in range(842814)] # total 842814 edges each connected to a node in grid_ids_withlinks , so if we add all node degrees in grid_ids_withlinks we get (842814 * 2)
grid_ids_withlinks = {1107415: 751065,1125583: 15256,1144686: 108969,1115625: 17038,1081048: 6749,1103814: 6476,1108340: 107431,1111992: 45946,1117451: 3594,1093803: 10860,1117452: 2126,1089226: 52518,1082859: 21211,1105613: 94587,1092862: 43891,1083786: 17073,1092899: 999,1141954: 4347,1106506: 2072,1094690: 119736,1116547: 3284,1104705: 2404,1135637: 3815,1121070:16598,1087417: 4514,1103777: 310,1114682: 4265,1091948: 5468,1093788: 2176, 1098316: 2067,1105597: 19090,1141055: 8454,1097427: 3041,1092875: 4159,1086500: 2204,1095619: 9732,1087430: 2041,1112884: 2167,1097413: 17056,1107414: 34769,1111088: 2025,1083768: 2176,1130180: 1886, 1144699: 988,1146499: 6818,1111081: 12509,1104687: 6186,1092866: 4272,1091037: 3,1121044: 39,1098333: 294,1118359: 27,1151091: 21,1107441: 10766,1141094: 3523,1102898: 53,1115634: 2199,1100140: 4347,1086515: 3029,1116505: 238,1082883: 4070,1118366:2065,1102866: 1590,1115631: 4345,1091990: 2131,1144703: 4053,1075589: 19,1081062: 2124,1097425: 11,1133804: 8,1112864: 158,1088307: 112,1138312: 112,1127446: 6245,1108356: 155,1082874: 6315,1115640: 3978,1107432: 2234,1131077: 2032,1115590: 2672,1094696: 13,1136502: 52,1094683: 20,1110183: 2,1113821: 56,1106515: 6,1120183: 11,1083765: 23,1101079: 6,1091944: 12,1085599: 10,1083783: 25,1148339: 6}
# dict with node_id : node degree (total nodes: 93)
for pfxpair in pfxlinks:
start_put = False
end_put = False
if all_stop_2 == True:
break
while True:
if all_stop == True:
all_stop_2 = True
break
try:
grid_start_id, grid_end_id = (np.random.choice(list(grid_ids_withlinks.keys()),size = 2, replace = False)) # get 2 random node ids
grid_start_id = int(grid_start_id)
grid_end_id = int(grid_end_id)
if start_put == False:
start_value = grid_dens_dict.get(grid_start_id) # if node id exists in my dict and node degree hasnt reached capacity
start_process_condition = (not start_value) or ( (start_value) and (len(grid_dens_dict[grid_start_id]) < grid_ids_withlinks[grid_start_id]) )
if start_process_condition:
grid_dens_dict[grid_start_id].add(pfxpair)
start_put = True
if len(grid_dens_dict[grid_start_id]) == grid_ids_withlinks[grid_start_id]: # node degree for node full, remove from dict
try:
#print('deleted key: ',grid_start_id, 'with size:',grid_dens_dict[grid_start_id],'Capacity:',grid_ids_withlinks[grid_start_id])
popped_dict[grid_start_id] = {'orig_capacity': grid_ids_withlinks[grid_start_id],'size':len(grid_dens_dict[grid_start_id]) }
grid_ids_withlinks.pop(grid_start_id)
except:
print('already popped')
else:
print('check')
if end_put == False:
end_value = grid_dens_dict.get(grid_end_id)
if (not end_value) or (end_value and (len(grid_dens_dict[grid_end_id]) < grid_ids_withlinks[grid_end_id])):
grid_dens_dict[grid_end_id].add(pfxpair)
end_put = True
if len(grid_dens_dict[grid_end_id]) == grid_ids_withlinks[grid_end_id]:
try:
#print('deleted key: ',grid_end_id, 'with size:',grid_dens_dict[grid_end_id],'Capacity:',grid_ids_withlinks[grid_end_id])
popped_dict[grid_end_id] = {'orig_capacity': grid_ids_withlinks[grid_end_id],'size':len(grid_dens_dict[grid_end_id]) }
grid_ids_withlinks.pop(grid_end_id)
except: # only 1 node left with large degree, start breaking edges
print('already popped')
else:
print('check')
if (start_put == False and end_put == True): # only end while when both nodes have been assigned a link
grid_dens_dict[grid_end_id].discard(pfxpair)
end_put = False
if (start_put == True and end_put == False):
grid_dens_dict[grid_start_id].discard(pfxpair)
start_put = False
if start_put == True and end_put == True:
success_dict[pfxpair].append((grid_start_id,grid_end_id))
break
except:
#print('In except block')
grid2grid = defaultdict(list)
for k,v in success_dict.items():
grid2grid[v[0][0]].append(v[0][1])
grid2grid[v[0][1]].append(v[0][0])
# ppick 2 random gridids
while True:
pop_id1, pop_id2 = (np.random.choice(list(grid_dens_dict.keys()),size = 2, replace = False)) # get 2 random node ids for popping
pop_id1 = int(pop_id1)
pop_id2 = int(pop_id2)
if (pop_id1 != list(grid_ids_withlinks.keys())[0] and pop_id2 != list(grid_ids_withlinks.keys())[0] and (pop_id1 in grid2grid[pop_id2] and pop_id2 in grid2grid[pop_id1])): ##have an assigned link
grid2grid[pop_id1].remove(pop_id2)
grid2grid[pop_id2].remove(pop_id1)
grid2grid[list(grid_ids_withlinks.keys())[0]].append(pop_id1)
grid2grid[list(grid_ids_withlinks.keys())[0]].append(pop_id2)
grid2grid[pop_id1].append(list(grid_ids_withlinks.keys())[0])
grid2grid[pop_id2].append(list(grid_ids_withlinks.keys())[0])
if len(grid2grid[list(grid_ids_withlinks.keys())[0]]) == grid_ids_withlinks[list(grid_ids_withlinks.keys())[0]]:
for k,v in grid2grid.items():
grid2grid[k] = Counter(v)
if len(all_itr_list) != 0:
# check if current grpah same as any previous
for graph in all_itr_list:
#same_counter = 0
#for a,b in graph.items():
shared_items = {k: graph[k] for k in graph if k in grid2grid and graph[k] == grid2grid[k]}
len(shared_items)
if len(shared_items) == grid_ids_withlinks: # total no of grids
#print('no of same nodes: ',len(shared_items))
break
all_itr_list.append(grid2grid)
filename = 'path'
with open(filename,'wb') as handle:
pickle.dump(grid2grid, handle)
all_stop = True
break
print('iteration no:',count)
if all_stop == False:
grid2grid = defaultdict(list)
for k,v in success_dict.items():
grid2grid[v[0][0]].append(v[0][1])
grid2grid[v[0][1]].append(v[0][0])
for k,v in grid2grid.items():
grid2grid[k] = Counter(v)
all_itr_list.append(grid2grid)
filename = 'path'
with open(filename,'wb') as handle:
pickle.dump(grid2grid, handle)
> ##from answer
import pickle
from collections import defaultdict
import numpy as np
for iteration in range(1000):
graph = defaultdict(list)
filename2 = r'path'
filename3 = r'path'
with open(filename2,'rb') as handle:
pfxlinks = pickle.load(handle,encoding ='latin-1')
with open(filename3,'rb') as handle:
grid_ids_withlinks = pickle.load(handle,encoding ='latin-1')
nodes = list(grid_ids_withlinks.keys())
degrees = list(grid_ids_withlinks.values())
while len(nodes) > 0:
# Get a random index from current nodes
node_idx = np.random.randint(0, len(nodes)-1)
# Store the node and its corresponding degree
node = nodes[node_idx]
degree = degrees[node_idx]
# Swap that node and its degree with the last node/degree and pop
# This helps us to remove them in O(1) time
# We don't need them anymore since we are going to exhaust the required edges
# for this particular node.
# This also prevents self-edges.
nodes[node_idx], nodes[-1] = nodes[-1], nodes[node_idx]
nodes.pop()
degrees[node_idx], degrees[-1] = degrees[-1], degrees[node_idx]
degrees.pop()
for _ in range(degree): # this is the amount of edges this node needs
# To make sure we don't get out of bounds.
# This could potentially happen unless
# there is a guarantee that the degrees and number of nodes
# are made such that they fit exactly
if len(nodes) == 0:
break
neighbor_idx = np.random.randint(0, len(nodes)-1)
graph[node].append(nodes[neighbor_idx])
graph[nodes[neighbor_idx]].append(node)
degrees[neighbor_idx] -= 1
if degrees[neighbor_idx] == 0:
# we need to remove the neighbor node if it has its maximum edges already
nodes[neighbor_idx], nodes[-1] = nodes[-1], nodes[neighbor_idx]
nodes.pop()
degrees[neighbor_idx], degrees[-1] = degrees[-1], degrees[neighbor_idx]
degrees.pop()
print('done')
Since you have also posted parts of the code that are unrelated to the core algorithm, it makes going through the code and finding bottlenecks relatively difficult.
Here's an algorithm that is faster from what I've seen in your code. It runs in O(n * m) for creating each graph, where n is the number of nodes, and m is the max degree that any of the nodes can have. In other words it's O(V + E) where V is the number of vertices and E the number of edges.
Create a list for the nodes, called nodes, like [1, 2, ..., n].
Create a corresponding list for degrees, called degrees, where degrees[i] is the degree of nodes[i].
Create a store for your graph however you like it. Adjacency list, matrix. Just make sure that adding edges to the graph is of O(1) complexity. Let's call this graph. A defaultdict(list) from collections in python would make a good adjacency list. For this algorithm I assume graph is a defaultdict(list).
Run a while loop on nodes. while len(nodes) > 0: and do as follows:
# Get a random index from current nodes
node_idx = random.randint(0, len(nodes)-1)
# Store the node and its corresponding degree
node = nodes[node_idx]
degree = degrees[node_idx]
# Swap that node and its degree with the last node/degree and pop
# This helps us to remove them in O(1) time
# We don't need them anymore since we are going to exhaust the required edges
# for this particular node.
# This also prevents self-edges.
nodes[node_idx], nodes[-1] = nodes[-1], nodes[node_idx]
nodes.pop()
degrees[node_idx], degrees[-1] = degrees[-1], degrees[node_idx]
degrees.pop()
for _ in degree: # this is the amount of edges this node needs
# To make sure we don't get out of bounds.
# This could potentially happen unless
# there is a guarantee that the degrees and number of nodes
# are made such that they fit exactly
if len(nodes) == 0:
break
neighbor_idx = random.randint(0, len(nodes)-1)
graph[node].append(nodes[neighbor_idx])
graph[nodes[neighbor_idx]].append(node)
degrees[neighbor_idx] -= 1
if degrees[neighbor_idx] == 0:
# we need to remove the neighbor node if it has its maximum edges already
nodes[neighbor_idx], nodes[-1] = nodes[-1], nodes[neighbor_idx]
nodes.pop()
degrees[neighbor_idx], degrees[-1] = degrees[-1], degrees[neighbor_idx]
degrees.pop()
This algorithm potentially leaves one node at the end that has not all its required edges, but this isn't a shortcoming of the algorithm but can happen if the number of edges for the nodes dosn't work out. I'm not sure how to express is mathematically though.
Also note that this algorithm could produce multiple edges between two nodes. It isn't clear to me if this is allowed or not for the particular graph you are looking for. If so, the code can be ammended such that it avoids such edges without sacrificing the time complexity. But it has the potential to leave multiple nodes with less edges than required. This wouldn't be a shortcoming of the algorithm but a result of how the degrees for particular nodes are defined.
I'm a beginner, trying to write code listing the most frequently overlapping ranges in a list of ranges.
So, input is various ranges (#1 through #7 in the example figure; https://prntscr.com/kj80xl) and I would like to find the most common range (in the example 3,000- 4,000 in 6 out of 7 - 86 %). Actually, I would like to find top 5 most frequent.
Not all ranges overlap. Ranges are always positive and given as integers with 1 distance (standard range).
What I have now is only code comparing one sequence to another and returning the overlap, but after that I'm stuck.
def range_overlap(range_x,range_y):
x = (range_x[0], (range_x[-1])+1)
y = (range_y[0], (range_y[-1])+1)
overlap = (max(x[0],y[0]),min(x[-1],(y[-1])))
if overlap[0] <= overlap[1]:
return range(overlap[0], overlap[1])
else:
return "Out of range"
I would be very grateful for any help.
Better solution
I came up with a simpler solution (at least IMHO) so here it is:
def get_abs_min(ranges):
return min([min(r) for r in ranges])
def get_abs_max(ranges):
return max([max(r) for r in ranges])
def count_appearances(i, ranges):
return sum([1 for r in ranges if i in r])
def create_histogram(ranges):
keys = [str(i) for i in range(len(ranges) + 1)]
histogram = dict.fromkeys(keys)
results = []
min = get_abs_min(range_list)
max = get_abs_max(range_list)
for i in range(min, max):
count = str(count_appearances(i, ranges))
if histogram[count] is None:
histogram[count] = dict(start=i, end=None)
elif histogram[count]['end'] is None:
histogram[count]['end'] = i
elif histogram[count]['end'] == i - 1:
histogram[count]['end'] = i
else:
start = histogram[count]['start']
end = histogram[count]['end']
results.append((range(start, end + 1), count))
histogram[count]['start'] = i
histogram[count]['end'] = None
for count, d in histogram.items():
if d is not None and d['start'] is not None and d['end'] is not None:
results.append((range(d['start'], d['end'] + 1), count))
return results
def main(ranges, top):
appearances = create_histogram(ranges)
return sorted(appearances, key=lambda t: t[1], reverse=True)[:top]
The idea here is as simple as iterating through a superposition of all the ranges and building a histogram of appearances (e.g. the number of original ranges this current i appears in)
After that just sort and slice according to the chosen size of the results.
Just call main with the ranges and the top number you want (or None if you want to see all results).
OLDER EDITS BELOW
I (almost) agree with #Kasramvd's answer.
here is my take on it:
from collections import Counter
from itertools import combinations
def range_overlap(x, y):
common_part = list(set(x) & set(y))
if common_part:
return range(common_part[0], common_part[-1] +1)
else:
return False
def get_most_common(range_list, top_frequent):
overlaps = Counter(range_overlap(i, j) for i, j in
combinations(list_of_ranges, 2))
return [(r, i) for (r, i) in overlaps.most_common(top_frequent) if r]
you need to input the range_list and the number of top_frequent you want.
EDIT
the previous answer solved this question for all 2's combinations over the range list.
This edit is tested against your input and results with the correct answer:
from collections import Counter
from itertools import combinations
def range_overlap(*args):
sets = [set(r) for r in args]
common_part = list(set(args[0]).intersection(*sets))
if common_part:
return range(common_part[0], common_part[-1] +1)
else:
return False
def get_all_possible_combinations(range_list):
all_combos = []
for i in range(2, len(range_list)):
all_combos.append(combinations(range_list, i))
all_combos = [list(combo) for combo in all_combos]
return all_combos
def get_most_common_for_combo(combo):
return list(filter(None, [range_overlap(*option) for option in combo]))
def get_most_common(range_list, top_frequent):
all_overlaps = []
combos = get_all_possible_combinations(range_list)
for combo in combos:
all_overlaps.extend(get_most_common_for_combo(combo))
return [r for (r, i) in Counter(all_overlaps).most_common(top_frequent) if r]
And to get the results just run get_most_common(range_list, top_frequent)
Tested on my machine (ubunut 16.04 with python 3.5.2) with your input range_list and top_frequent = 5 with the results:
[range(3000, 4000), range(2500, 4000), range(1500, 4000), range(3000, 6000), range(1, 4000)]
You can first change your function to return a valid range in both cases so that you can use it in a set of comparisons. Also, since Python's range objects are not already created iterables but smart objects that only get start, stop and step attributes of a range and create the range on-demand, you can do a little change on your function as well.
def range_overlap(range_x,range_y):
rng = range(max(range_x.start, range_y.start),
min(range_x.stop, range_y.stop)+1)
if rng.start < rng.stop:
return rng.start, rng.stop
Now, if you have a set of ranges and you want to compare all the pairs you can use itertools.combinations to get all the pairs and then using range_overlap and collections.Counter you can find the number of overlapped ranges.
from collections import Counter
from itertools import combinations
overlaps = Counter(range_overlap(i,j) for i, j in
combinations(list_of_ranges, 2))
I am trying to implement dijkstra's algorithm (on an undirected graph) to find the shortest path and my code is this.
Note: I am not using heap/priority queue or anything but an adjacency list, a dictionary to store weights and a bool list to avoid cycling in the loops/recursion forever. Also, the algorithm works for most test cases but fails for this particular one here: https://ideone.com/iBAT0q
Important : Graph can have multiple edges from v1 to v2 (or vice versa), you have to use the minimum weight.
import sys
sys.setrecursionlimit(10000)
def findMin(n):
for i in x[n]:
cost[n] = min(cost[n],cost[i]+w[(n,i)])
def dik(s):
for i in x[s]:
if done[i]:
findMin(i)
done[i] = False
dik(i)
return
q = int(input())
for _ in range(q):
n,e = map(int,input().split())
x = [[] for _ in range(n)]
done = [True]*n
w = {}
cost = [1000000000000000000]*n
for k in range(e):
i,j,c = map(int,input().split())
x[i-1].append(j-1)
x[j-1].append(i-1)
try: #Avoiding multiple edges
w[(i-1,j-1)] = min(c,w[(i-1,j-1)])
w[(j-1,i-1)] = w[(i-1,j-1)]
except:
try:
w[(i-1,j-1)] = min(c,w[(j-1,i-1)])
w[(j-1,i-1)] = w[(i-1,j-1)]
except:
w[(j-1,i-1)] = c
w[(i-1,j-1)] = c
src = int(input())-1
#for i in sorted(w.keys()):
# print(i,w[i])
done[src] = False
cost[src] = 0
dik(src) #First iteration assigns possible minimum to all nodes
done = [True]*n
dik(src) #Second iteration to ensure they are minimum
for val in cost:
if val == 1000000000000000000:
print(-1,end=' ')
continue
if val!=0:
print(val,end=' ')
print()
The optimum isn't always found in the second pass. If you add a third pass to your example, you get closer to the expected result and after the fourth iteration, you're there.
You could iterate until no more changes are made to the cost array:
done[src] = False
cost[src] = 0
dik(src)
while True:
ocost = list(cost) # copy for comparison
done = [True]*n
dik(src)
if cost == ocost:
break
I have a fun little problem.
I need to count the amount of 'groups' of characters in a file. Say the file is...
..##.#..#
##..####.
.........
###.###..
##...#...
The code will then count the amount of groups of #'s. For example, the above would be 3. It includes diagonals. Here is my code so far:
build = []
height = 0
with open('file.txt') as i:
build.append(i)
height += 1
length = len(build[0])
dirs = {'up':(-1, 0), 'down':(1, 0), 'left':(0, -1), 'right':(0, 1), 'upleft':(-1, -1), 'upright':(-1, 1), 'downleft':(1, -1), 'downright':(1, 1)}
def find_patches(grid, length):
queue = []
queue.append((0, 0))
patches = 0
while queue:
current = queue.pop(0)
line, cell = path[-1]
if ## This is where I am at. I was making a pathfinding system.
Here’s a naive solution I came up with. Originally I just wanted to loop through all the elements once an check for each, if I can put it into an existing group. That didn’t work however as some groups are only combined later (e.g. the first # in the second row would not belong to the big group until the second # in that row is processed). So I started working on a merge algorithm and then figured I could just do that from the beginning.
So how this works now is that I put every # into its own group. Then I keep looking at combinations of two groups and check if they are close enough to each other that they belong to the same group. If that’s the case, I merge them and restart the check. If I completely looked at all possible combinations and could not merge any more, I know that I’m done.
from itertools import combinations, product
def canMerge (g, h):
for i, j in g:
for x, y in h:
if abs(i - x) <= 1 and abs(j - y) <= 1:
return True
return False
def findGroups (field):
# initialize one-element groups
groups = [[(i, j)] for i, j in product(range(len(field)), range(len(field[0]))) if field[i][j] == '#']
# keep joining until no more joins can be executed
merged = True
while merged:
merged = False
for g, h in combinations(groups, 2):
if canMerge(g, h):
g.extend(h)
groups.remove(h)
merged = True
break
return groups
# intialize field
field = '''\
..##.#..#
##..####.
.........
###.###..
##...#...'''.splitlines()
groups = findGroups(field)
print(len(groups)) # 3
I'm not exactly sure what your code is trying to do. Your with statement opens a file, but all you do is append the file object to a list before the with ends and it gets closed (without its contents ever being read). I suspect his is not what you intend, but I'm not sure what you were aiming for.
If I understand your problem correctly, you are trying to count the connected components of a graph. In this case, the graph's vertices are the '#' characters, and the edges are wherever such characters are adjacent to each other in any direction (horizontally, vertically or diagonally).
There are pretty simple algorithms for solving that problem. One is to use a disjoint set data structure (also known as a "union-find" structure, since union and find are the two operations it supports) to connect groups of '#' characters together as they're read in from the file.
Here's a fairly minimal disjoint set I wrote to answer another question a while ago:
class UnionFind:
def __init__(self):
self.rank = {}
self.parent = {}
def find(self, element):
if element not in self.parent: # leader elements are not in `parent` dict
return element
leader = self.find(self.parent[element]) # search recursively
self.parent[element] = leader # compress path by saving leader as parent
return leader
def union(self, leader1, leader2):
rank1 = self.rank.get(leader1,1)
rank2 = self.rank.get(leader2,1)
if rank1 > rank2: # union by rank
self.parent[leader2] = leader1
elif rank2 > rank1:
self.parent[leader1] = leader2
else: # ranks are equal
self.parent[leader2] = leader1 # favor leader1 arbitrarily
self.rank[leader1] = rank1+1 # increment rank
And here's how you can use it for your problem, using x, y tuples for the nodes:
nodes = set()
groups = UnionFind()
with open('file.txt') as f:
for y, line in enumerate(f): # iterate over lines
for x, char in enumerate(line): # and characters within a line
if char == '#':
nodes.add((x, y)) # maintain a set of node coordinates
# check for neighbors that have already been read
neighbors = [(x-1, y-1), # up-left
(x, y-1), # up
(x+1, y-1), # up-right
(x-1, y)] # left
for neighbor in neighbors:
if neighbor in nodes:
my_group = groups.find((x, y))
neighbor_group = groups.find(neighbor)
if my_group != neighbor_group:
groups.union(my_group, neighbor_group)
# finally, count the number of unique groups
number_of_groups = len(set(groups.find(n) for n in nodes))