I have a somewhat working A* algorithm here. It can find a path to the destination, however, it is unable to update its path if a better one becomes available.
for example:
s = start
e = end
x = wall
. = path
it is doing this:
x
s......x e
.x .
.x .
.x.
.
and I want it to do this:
x
s . x e
. x .
. x .
. x.
.
What I need is for the tiles(nodes) to change their parent node to one with a lower G - cost if it is available. The struggle in implementing this is real.
Any help is greatly appreciated,
Cheers
map = [
['w','w','w','w','w'],
['w','w','x','w','w'],
['w','w','x','w','w'],
['w','w','x','w','w'],
['w','w','w','w','w'],
]
""" make copy of dict in the function! """
tile_data = {}
class Tile:
def __init__(self, pos, char):
self.pos = pos
self.char = char
self.parent = None
self.H = 0
self.G = float('inf')
self.F = 0
#Gen Tiles
for y in range(len(map)):
for x in range(len(map[0])):
char = map[y][x]
tile = Tile((x,y), char)
tile_data[(x,y)] = tile
def a_star(start, end, map):
unchecked_tiles = []
checked_tiles = []
# set start position
tile_data[start].parent = None
tile_data[start].pos = start
tile_data[start].G = 0
unchecked_tiles.append(tile_data[start])
while unchecked_tiles:
#Get the tile with lowest F score
current_tile = min(unchecked_tiles, key=lambda tile: tile.F)
#move tile to checked list
checked_tiles.append(current_tile)
unchecked_tiles.remove(current_tile)
# If the End is found return the path of parent tiles
if current_tile.pos == end:
path = []
while current_tile.parent is not None:
path.append(current_tile.pos)
# Sets current tile to parent tile
current_tile = current_tile.parent
for tile in path:
print(tile, ": F = ", tile_data[tile].F, ": G = ", tile_data[tile].G, ": H = ", tile_data[tile].H)
return path[::-1] # Return reversed path
# Gets valid neighbors for current tile
neighbors = []
for dir in [(0, -1), (0, 1), (-1, 0), (1, 0), (-1, -1), (-1, 1), (1, -1), (1, 1)]:
#get tile pos
neighbor = (current_tile.pos[0] + dir[0], current_tile.pos[1] + dir[1])
#check if tile on map and is valid
if 0 <= neighbor[0] < len(map[0]) and 0 <= neighbor[1] < len(map) and tile_data[neighbor].char == 'w' and tile_data[neighbor] not in checked_tiles:
# Set parent for neighbors
tile_data[neighbor].parent = current_tile
# Add neighbor to lists
unchecked_tiles.append(tile_data[neighbor])
neighbors.append(neighbor)
for neighbor in neighbors:
# Set G cost (14 for diagonal, 10 for othogonal move + parent.G cost)
if tile_data[neighbor].pos[0] == current_tile.pos[0] or tile_data[neighbor].pos[1] == current_tile.pos[1]:
tile_data[neighbor].G = 10 + current_tile.G
else:
tile_data[neighbor].G = 14 + current_tile.G
if tile_data[neighbor].G < current_tile.G:
current_tile.parent = tile_data[neighbor].parent
# Set H cost (a**2 + b**2 = c**2)
tile_data[neighbor].H = ((neighbor[0] - end[0]) ** 2) + ((neighbor[1] - end[1]) ** 2)
# Set F cost (G + H)
tile_data[neighbor].F = tile_data[neighbor].H + tile_data[neighbor].G
print('finished')
a_star((0,2),(4,2),map)
The problem was I was moving duplicate neighbors to "unchecked" tiles with incorrect G-costs. Anyhow here is a working A* algorithm :)
'''
map = [
['w','w','w','w','w'],
['w','w','x','w','w'],
['w','w','x','w','w'],
['w','w','x','w','w'],
['w','w','w','w','w'],
]
""" make copy of dict in the function! """
tile_data = {}
class Tile:
def __init__(self, pos, char):
self.pos = pos
self.char = char
self.parent = None
self.H = 0
self.G = 0
self.F = 0
#Gen Tiles
for y in range(len(map)):
for x in range(len(map[0])):
char = map[y][x]
tile = Tile((x,y), char)
tile_data[(x,y)] = tile
def a_star(start, end, map):
unchecked_tiles = []
checked_tiles = []
# set start position
tile_data[start].parent = None
tile_data[start].pos = start
tile_data[start].G = 0
unchecked_tiles.append(tile_data[start])
while unchecked_tiles:
#Get the tile with lowest F score
current_tile = min(unchecked_tiles, key=lambda tile: tile.F)
#move tile to checked list
checked_tiles.append(current_tile)
unchecked_tiles.remove(current_tile)
# If the End is found return the path of parent tiles
if current_tile.pos == end:
path = []
while current_tile.parent is not None:
path.append(current_tile.pos)
# Sets current tile to parent tile
current_tile = current_tile.parent
for tile in path:
print(tile, ": F = ", tile_data[tile].F, ": G = ", tile_data[tile].G, ": H = ", tile_data[tile].H)
return path[::-1] # Return reversed path
# Gets valid neighbors for current tile
neighbors = []
for dir in [(0, -1), (0, 1), (-1, 0), (1, 0), (-1, -1), (-1, 1), (1, -1), (1, 1)]:
#get tile pos
neighbor = (current_tile.pos[0] + dir[0], current_tile.pos[1] + dir[1])
#check if tile on map and is valid
if 0 <= neighbor[0] < len(map[0]) and 0 <= neighbor[1] < len(map) and tile_data[neighbor].char == 'w' and tile_data[neighbor] not in checked_tiles:
if tile_data[neighbor] not in unchecked_tiles:
# Add neighbor to lists
unchecked_tiles.append(tile_data[neighbor])
neighbors.append(neighbor)
# Set parent for neighbors
tile_data[neighbor].parent = current_tile
for neighbor in neighbors:
# Set G cost (14 for diagonal, 10 for othogonal move + parent.G cost)
if tile_data[neighbor].pos[0] == current_tile.pos[0] or tile_data[neighbor].pos[1] == current_tile.pos[1]:
tile_data[neighbor].G = 10 + current_tile.G
else:
tile_data[neighbor].G = 14 + current_tile.G
if tile_data[neighbor].G < current_tile.G:
current_tile.parent = tile_data[neighbor].parent
if tile_data[neighbor].pos[0] == current_tile.pos[0] or tile_data[neighbor].pos[1] == current_tile.pos[1]:
current_tile.G = tile_data[neighbor].G + 10
else:
current_tile.G = tile_data[neighbor].G + 14
# Set H cost (a**2 + b**2 = c**2)
tile_data[neighbor].H = ((neighbor[0] - end[0]) ** 2) + ((neighbor[1] - end[1]) ** 2)
# Set F cost (G + H)
tile_data[neighbor].F = tile_data[neighbor].H + tile_data[neighbor].G
print('finished')
a_star((0,2),(4,2),map)
'''
Related
I am trying to write a search algorithm that takes in a start point and then return the path to the end point, I originally tried just doing it via some nested for loops and a list of lists so that I could just loop through and try to find a path but the RAM requirements convinced me to try it using a class-based system. However, all it is doing is taking like 2gb of RAM and 100% of one of my CPU cores and just sitting there without exiting. If anyone sees a problem in my code, any help would be greatly appreciated.
import csv
import math
from multiprocessing import Process
from rplidar import RPLidar
import heapq
lidar = RPLidar('/dev/ttyUSB0')
file = "lidar01.csv"
def calc_offset():
# take in argos ros data and calculate offset
x_offset = 0
y_offset = 0
return x_offset, y_offset
def find_fix_quad_convert(x, y):
offset_x, offset_y = calc_offset()
if x >= 0 and y >= 0:
x = abs(x + 12000 + offset_x)
y = abs(y + offset_y)
return x,y
elif x < 0 and y >= 0:
x = abs(x - 12000 + offset_x)
y = abs(y + offset_x)
return x,y
elif x < 0 and y < 0:
x = abs(x - 12000 + offset_x)
y = abs(y - 12000 + offset_y)
return x,y
elif x >= 0 and y < 0:
x = abs(x + 12000 + offset_x)
y = abs(y - 12000 + offset_y)
return x,y
def scan():
try:
for scan in enumerate(lidar.iter_scans()):
list_version_data = list(scan)
for data in list_version_data:
if isinstance(data, list):
for indiv_data_points in data:
if isinstance(indiv_data_points, tuple):
list_indiv_data_points = list(indiv_data_points)
list_indiv_data_points.pop(0)
angle = list_indiv_data_points[0]
distance = list_indiv_data_points[1]
length = distance
angle = angle
angle = math.radians(angle)
x,y = (length * math.cos(angle)), (length * math.sin(angle))
x = int(x)
y = int(y)
new_x,new_y = find_fix_quad_convert(x,y)
with open(file=file, mode="w") as f:
writer = csv.writer(f)
writer.writerow([new_x,new_y])
except Exception as e:
print(e)
pass
def eliminate_duplicates():
unique_coords = set()
with open(file, 'r') as f:
reader = csv.reader(f)
for row in reader:
coord = (row[0], row[1])
if coord not in unique_coords:
unique_coords.add(coord)
with open(file, 'w') as f:
writer = csv.writer(f)
for coord in unique_coords:
writer.writerow(coord)
# create the node class that takes in the individual data points and creates a node for the nav graph
class Node:
def __init__(self, x, y):
self.x = x
self.y = y
self.neighbors = []
self.parent = None
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __lt__(self, other):
return self.f < other.f
def scan_eliminate_duplicates():
scan_process = Process(target=scan)
eliminate_duplicates_process = Process(target=eliminate_duplicates)
scan_process.start()
scan_process.join()
eliminate_duplicates_process.start()
eliminate_duplicates_process.join()
def find_path(start, end, nodes):
open_set = []
closed_set = set()
start.f = 0
heapq.heappush(open_set, start)
while open_set:
current_node = heapq.heappop(open_set)
closed_set.add(current_node)
if current_node == end:
print(f"Path found: {0}".format(construct_path(current_node)))
return construct_path(current_node)
for neighbor in current_node.neighbors:
if neighbor in closed_set:
continue
tentative_g = current_node.f + 1
if neighbor not in open_set or tentative_g < neighbor.f:
neighbor.parent = current_node
neighbor.f = tentative_g
if neighbor not in open_set:
heapq.heappush(open_set, neighbor)
return None
def construct_path(node):
path = []
while node.parent:
path.append((node.x, node.y))
node = node.parent
return path[::-1]
if __name__ == "__main__":
scan_elim_dupl_process = Process(target=scan_eliminate_duplicates)
nodes = []
with open(file, "r") as f:
reader = csv.reader(f)
for row in reader:
node = Node(int(float(row[0])), int(float(row[1])))
nodes.append(node)
# set start and end nodes
start = Node(3201, 3201)
end = Node(23000, 23000)
# connect the nodes to their neighbors
for i, node in enumerate(nodes):
for j in range(i+1, len(nodes)):
if abs(node.x - nodes[j].x) <= 1 and abs(node.y - nodes[j].y) <= 1:
node.neighbors.append(nodes[j])
nodes[j].neighbors.append(node)
find_path_process = Process(target=find_path, args=(start, end, nodes))
scan_elim_dupl_process.start(), find_path_process.start()
scan_elim_dupl_process.join(), find_path_process.join()
CSV Data(example):
-224.45409129769087,-75.30553365940557
-225.4021550412925,-75.62361405501024
-221.37533513849013,-86.0186665341958
-222.02088232366805,-83.6318737815909
-219.05825287406182,-90.4570718504838
-216.1406631194247,-97.22249609167298
-212.35203834877927,-105.80252506022047
-210.74781416150145,-110.5864314739799
-209.03673236351906,-114.8298503124623
-207.00083783790242,-118.46055518359869
-202.61438759451943,-126.76123200607452
-200.80257079121006,-132.35776351858277
-198.46526749871208,-137.60010027854142
-200.72914689131528,-136.5114357417897
-198.8372035969446,-141.42053056663028
-195.46212772818174,-148.12872484421098
-192.555826974252,-155.49438413737627
-191.2678199044531,-159.4290471306835
-204.80806340541443,-686.6046221546457
-189.9329560617947,-692.9413555284663
-174.4435476087335,-698.0327346891975
-157.25903052807882,-703.8971230352976
-142.50543063768973,-710.3467126965301
-44.080364168658264,-424.9699801100761
-12.039081185863548,-420.3276228422303
151.3137816891034,-880.5943387683925
171.58805621421078,-880.1807209116937
192.6920068673774,-879.3860659513674
213.97191813826333,-877.540073585379
235.7914668768005,-874.2611933215878
257.2898049769299,-872.088022366397
279.8247876333225,-870.8993344388122
301.1827105821156,-869.3037299163104
323.32023991345227,-866.9207489513143
344.60980320968105,-865.141980273634
368.38864460533046,-862.6319067399766
390.5808612705762,-860.7811297357389
413.666591519788,-858.81634448839
437.78734499029486,-856.893985896942
462.98529035913396,-854.9354849408629
488.6789701052747,-851.7727773161384
513.1091380975454,-851.3254444692665
540.2315442531086,-849.5265982262719
566.5849433716348,-845.456423740787
593.946149447507,-843.3387347658589
620.2144841174974,-841.046368335817
649.1761458917417,-837.0071051700481
678.6559458023329,-834.341870714362
709.32872426918,-831.894418437014
739.9610013434269,-829.0562580373138
772.0166065569867,-826.5297086011094
807.4258588219725,-823.4373352026249
841.6994505337709,-821.2904693049519
878.8426320460152,-818.1908032961703
917.0058399786907,-814.8716782076648
953.9215320868914,-809.3421468211068
989.2825428144441,-801.5520026689394
1089.7385480638236,-803.6080492775999
1124.2452992304018,-789.0340659048534
1161.1577259536114,-774.4874420920189
1207.7231504414601,-765.7054210907444
1256.6619459378417,-758.3474244247931
1312.4869985934681,-749.6733478810021
1436.2817842205613,-736.2680465130896
560.4785752204706,-119.13339883857641
561.947341484636,-105.41939052351769
562.7845996626268,-91.97041308256136
562.0728347190211,-80.08864445677706
572.2556983202762,-67.73092528507895
3073.007418570301,775.0941671254502
3076.1322580025953,851.280443670507
3085.7160627583366,932.367218447319
3079.5934798899584,1010.8439845590693
3065.409617566463,1086.4593591253329
3049.6010101414113,1162.0524639380467
553.4062949898384,280.6451899919539
517.2886535413827,292.48164287240894
504.22866463801756,302.9711475658394
493.01596196440715,311.63411839577225
457.35133669092596,320.98491760053656
448.3587658812583,330.0681438088732
438.5893339206918,341.3172405124065
430.36674574745746,351.23313362315815
396.83008722312223,357.699516877629
385.27345578604894,365.4112262460958
375.7639852425002,372.7022805064037
366.43229041712107,379.4517446786384
355.57191272554525,387.6140024311522
346.70803875198897,395.5623033919553
332.8988762943624,398.49579754616076
315.8504082369599,398.19163993804005
300.8542343626438,400.6091981795568
289.23868520741775,402.93545758531144
277.5650855793172,406.2285357620096
272.3911822392343,417.2003191972799
262.73233641927243,427.16971966616535
253.20792971902577,432.01424377837924
249.1985319999312,447.5490522267691
292.7465904906362,640.521269167627
272.64249172224527,636.02678536952
804.8967316661586,3209.614034639233
724.5030467972489,3205.9040686959897
646.5556113779995,3209.2685932928116
567.8035492364211,3204.8395735160475
486.7038419116777,3179.46404607261
409.31126573218995,3155.564654343928
335.12423534291634,3147.2077698947405
111.3757146106589,3140.2755472561585
39.18188674771358,3123.254237130063
-35.079705269662,3137.303884592341
-108.12103095017433,1135.8656798522752
-135.12589586812848,1133.7257405412631
-478.8748400704411,2350.463879758102
-523.4289972333165,2298.6579421165134
-321.0672961603582,1006.7950902920995
-337.5896468300176,906.691502305599
-362.5912398939693,906.686187582095
-386.4405220459153,909.0180336059789
-348.11121502543114,476.6693240324135
-709.9009007847953,689.6707718650517
-705.7154352397234,654.50131738936
-225.61815673209847,73.37581245076781
-224.96174273211477,60.673011355377774
-221.7058096465867,57.33702092846706
-218.36099776953716,54.2537985130275
-217.26139804313215,45.62329361569259
-215.60048241436377,36.98640943229841
-212.9460118930461,31.409529108961046
-210.41143597187929,27.876336062182475
-209.29301055390292,20.037420824145237
-207.148062758457,18.806384432377264
-205.20651537090765,10.97889107922301
-203.40844501247898,6.103646254929753
-201.49568420532205,1.3188050004214316
-199.7214487838248,-3.3771875414384667
-198.08854835941534,-7.9993442768494845
-196.3529499548831,-12.493259943472601
-194.511648279817,-16.964114579569504
-192.81795218045707,-21.3831082150133
-191.27242817254086,-25.765446260062987
-189.63616139426645,-30.0354172877243
-188.37840041163838,-34.43585716012369
-226.49932324600476,-68.91022470651103
-226.1737175842578,-73.28377701863081
-224.04801115257553,-79.54590623391874
-221.56247177423467,-85.53549614804056
-217.15764842108885,-95.55165216898537
-215.7143937962088,-98.164659165782
-213.46548271945335,-102.96352600484683
-212.697138624531,-105.10703935006946
-210.6167482193979,-110.29799576368899
-205.56454558388867,-120.93579949249667
-203.34370277416045,-126.06308358156993
-202.659741276799,-128.5652723935235
-198.05436632376652,-137.315213942561
-200.01801379745294,-136.66695524713327
-195.89271630106168,-144.21028465470746
-196.10659851005252,-147.2744530487792
-191.887743885396,-155.12787063120754
-190.4663185563427,-159.99732496386864
-240.31744343455787,-628.0953561212486
-194.36681058896912,-689.8928217060037
-179.58107052974322,-695.954166312259
-162.78643452157806,-701.6128841717147
-148.54493769413048,-708.3420529556655
-48.61733572180791,-427.74595809582235
-36.52211770286281,-421.42037791081987
-14.701911453818868,-417.7413719032436
121.73474418061696,-881.8875861238097
142.58207691392846,-880.0242049755851
164.10647050731015,-880.0804672515084
185.3187865324572,-878.9254859532392
207.6388003465223,-877.5187696514856
227.2478526229215,-875.7459540176426
247.99909241264257,-872.9562991138248
269.3479160261434,-870.7950103970358
292.78503356447436,-868.7390210072583
313.92451252961064,-866.9114144092499
335.83498334245536,-864.2959137143787
358.6456719461373,-861.8558649764493
381.566664921735,-860.1673559374968
403.5993305416195,-860.0044071900775
427.212685906662,-858.0277215803786
451.6489095368339,-855.0451815630497
476.82947920642863,-854.1701881122556
501.87089118531856,-853.0713165268506
528.0321382457586,-850.695964184392
554.4110689448593,-846.6240999590187
579.1151539748655,-843.6031062867585
606.096392136026,-841.4369634379586
635.4714561227303,-839.0067213397381
664.7834755172479,-837.1522744872694
695.0210066681005,-833.8201546437099
724.3369310372825,-830.5058761595194
755.2483075753681,-828.7349132289179
790.0536662312429,-825.3008266532705
823.3915496973328,-822.0453794572566
857.6177770538111,-819.1994558599753
896.6246516997196,-816.2233052078068
933.9759389836386,-814.1314054866272
965.93739908313,-802.0256797961757
1106.8124778852348,-795.8748025270977
1145.3782516272215,-784.4403885569442
1185.8751583397407,-772.8779715017727
1231.3279001110159,-763.8275999256613
1283.8274490578733,-756.2321608114493
1339.6622193807364,-748.3283958021902
1399.0838013856292,-739.5071106489357
559.0470439861833,-113.7394065850111
556.9664328067189,-99.30486003493867
558.3537490936984,-86.40654415616522
565.1640657918545,-74.09169142097093
576.7394965367929,-61.412972037564295
3083.2240320377678,726.8903842841579
3075.974377735485,801.7687883765465
3077.959314383161,878.9581668099685
3081.5656872937416,958.6946737068328
3079.603817010731,1036.9621884393555
3065.787463056901,1112.0695993790728
3041.893912737369,1184.8382478850592
565.3389250472349,272.8771927560765
548.9161042252287,281.1968581300259
512.1181201590852,295.48535243684216
500.3702034741492,306.4395894385034
489.0168090088083,315.1120800712703
453.6658726040589,323.5967220699212
445.1879662404556,334.3325249129779
436.9264391821313,344.2518689036028
404.1328350154679,352.8581891672072
393.65549849104536,359.33876566238513
382.9718830476642,366.0158456883065
373.6310075466433,373.4272789977725
362.9032487936391,381.7941782099644
354.38182598682874,389.7166713270566
342.20930022858226,397.153443063338
One problem is that this line does not behave like you seem to be expecting:
for scan in enumerate(lidar.iter_scans()):
Looking at the source code, this appears to iterate through scans as they come in. In other words, it's a continual stream of incoming data. You need to update your code to have a non-error exit condition. The README in the source repo has this as an example:
for i, scan in enumerate(lidar.iter_scans()):
print('%d: Got %d measurments' % (i, len(scan)))
if i > 10:
break
Another problem is that you've got multiple processes running, which makes debugging significantly more challenging. I would suggest simplifying your __main__ section to this until you've made sure your find_path method is correct:
if __name__ == "__main__":
nodes = []
with open(file, "r") as f:
reader = csv.reader(f)
for row in reader:
node = Node(int(float(row[0])), int(float(row[1])))
nodes.append(node)
# set start and end nodes
start = Node(3201, 3201)
end = Node(23000, 23000)
# connect the nodes to their neighbors
for i, node in enumerate(nodes):
for j in range(i+1, len(nodes)):
if abs(node.x - nodes[j].x) <= 1 and abs(node.y - nodes[j].y) <= 1:
node.neighbors.append(nodes[j])
nodes[j].neighbors.append(node)
find_path(start, end, nodes)
It would also be helpful for readability if you moved most of this into a separate read_nodes method.
I recently coded the A* pathfinding algorithm in Python. This is based on a coordinate system where each coordinate has a value which is its elevation and there is a maximum elevation difference (the difference between the elevation of 2 adjacent nodes) which decides whether we can move to that node or not.
How do I optimize this more?
Also, any suggestions to improve the code or if you see any feedback if I am going wrong anywhere are welcome!
I tried multiple heuristics like the distance formula but the one mentioned here seems to be working best for me. Correct me if I am wrong.
def astar(target_cord):
def find_neighbor_astar(current):
neighbor_mat = dict()
current_matrix_cord = matrix[current[0]][current[1]]
for x in neighbor_scope:
for y in neighbor_scope:
x_cord = current[0]+x
y_cord = current[1]+y
if(x != 0 or y != 0):
if((x_cord) >= 0 and x_cord < max_cols and y_cord >= 0 and y_cord < max_rows and abs(current_matrix_cord - matrix[x_cord][y_cord]) <= elevation_max):
if(x!=0 and y!=0):
neighbor_mat[x_cord, y_cord] = 14 + abs(current_matrix_cord - matrix[x_cord][y_cord])
else:
neighbor_mat[x_cord, y_cord] = 10 + abs(current_matrix_cord - matrix[x_cord][y_cord])
return neighbor_mat
def getheuristic(current, target_cord):
# return (int(abs(((((target_cord[0]-current[0])**2)+((target_cord[1]-current[1])**2))**0.5))*10))
dx = abs(current[0] - target_cord[0])
dy = abs(current[1] - target_cord[1])
return (dx + dy) + (2**0.5 - 2) * min(dx, dy)
parent_cord = {start_cord: (None, 0)}
frontier_nodes_heap = [(0, start_cord)]
frontier_nodes = dict()
frontier_nodes[start_cord] = 0
node_gofx = dict()
node_gofx[start_cord] = 0
explored_nodes = set()
while frontier_nodes_heap:
curr_node = heapq.heappop(frontier_nodes_heap)[1]
del frontier_nodes[curr_node]
curr_cost = node_gofx[curr_node]
if(curr_node == target_cord):
return tracepath_ucs(parent_cord, target_cord)
explored_nodes.add(curr_node)
for x, step_cost in find_neighbor_astar(curr_node).items():
gofx = curr_cost + step_cost
path_cost = gofx + getheuristic(x, target_cord)
if x in explored_nodes:
continue
if(x not in frontier_nodes):
if(x not in parent_cord):
parent_cord[x] = (curr_node, step_cost)
heapq.heappush(frontier_nodes_heap,(path_cost,x))
frontier_nodes[x] = path_cost
node_gofx[x] = gofx
elif(x in frontier_nodes and path_cost < frontier_nodes[x]):
frontier_nodes_heap[frontier_nodes_heap.index((frontier_nodes[x],x))] = (path_cost, x)
frontier_nodes[x] = path_cost
parent_cord[x] = (curr_node, step_cost)
node_gofx[x] = gofx
return []
I want to create a row of tiles with a random width. i can do it for 2 cubes, but i don't know how to do it for 100 cubes.
import maya.cmds as cmds
import random
cubeList = cmds.ls( 'Tiles*' )
if len(cubeList) > 0:
cmds.delete( cubeList )
#create row and col list
cols = 2
arr = []
for col in xrange (cols):
width_rand_Size = random.uniform(0.8,3)
arr.append(cmds.polyCube (ax = (0,0,1), w = width_rand_Size, h = 1, d =1 , n='Tiles#'))
if col != 0:
cmds.setAttr( "Tiles2.tx",(cmds.polyCube('Tiles1', q = 1, w = 1))/2 + (cmds.polyCube('Tiles2', q = 1, w = 1))/2)
You have to make the script automatically find the name of your object and previous object each time you're iterating. Then you compute the space between the current tile and all the previous created tiles.
Here is the code:
import maya.cmds as cmds
import random
cubeList = cmds.ls( 'Tiles*' )
if len(cubeList) > 0:
cmds.delete( cubeList )
#create row and col list
cols = 10 # number of tiles to create
x = 1 # increment variable
arr = []
allTilesSpace = [] # cumalated space between tiles
for col in xrange (cols):
# if there is no tile to create, do nothing
if not cols:
break
# get the names of the objects
currentTile = 'Tiles%d' % x
previousTile = "Tiles%d" % (x - 1)
# set random width
width_rand_Size = random.uniform(0.8,3)
arr.append(cmds.polyCube (ax = (0,0,1), w = width_rand_Size, h = 1, d =1 , n=currentTile))
# Move the tiles
currentTileWidth = cmds.polyCube(currentTile, q = 1, w = 1)
if cmds.objExists(previousTile):
previousTileWidth = cmds.polyCube(previousTile, q = 1, w = 1)
allTilesSpace.append(previousTileWidth)
tilesSpace = sum(allTilesSpace) + (currentTileWidth / 2)
cmds.setAttr(currentTile + ".tx",tilesSpace)
else:
cmds.setAttr(currentTile + ".tx", currentTileWidth / 2)
x += 1 # increment
We have a code to draw circles on the Location on the map with the name of each category. Now the circles and text are one color. How do we get them in different color's by category? Example: Category Garden: Blue, Category Stone: Grey.
So far the code:
size(1500,800)
background(1)
nofill()
stroke('#f91')
pen(.2)
fill('#f91', 0.05)
rotate(90)
font("Avenir", "bold", 10)
align('left')
def mapValue(value, fromMin, fromMax, toMin, toMax):
# Figure out how 'wide' each range is
fromSpan = fromMax - fromMin
toSpan = toMax - toMin
# Convert the from range into a 0-1 range (float)
valueScaled = float(value - fromMin) / float(fromSpan)
# Convert the 0-1 range into a value in the to range.
return toMin + (valueScaled * toSpan)
def xOfDot(lon):
return mapValue(lon, -100, 100, 0, WIDTH)
def yOfDot(lat):
return mapValue(lat, -90, 90, HEIGHT, 0)
with open('theft-alerts.json', 'r') as inputFile:
data = json.load(inputFile)
print len(data)
artworksPerCity = {}
for stolenArt in data:
if stolenArt.has_key('Category'):
city = stolenArt['Category']
if stolenArt.has_key('nItemsStolen'):
numbersStolen = int(stolenArt['nItemsStolen'])
if artworksPerCity.has_key(city):
# Adjust the value stored for this city
artworksPerCity[city] = artworksPerCity[city] + numbersStolen
else:
# Create new key with new value
artworksPerCity[city] = numbersStolen
# Draw circle on the map
radius = artworksPerCity[city] /2
x = xOfDot(stolenArt['Lon'])
y = yOfDot(stolenArt['Lat'])
arc(x, y, radius)
text(city, x, y)
print artworksPerCity
Here is a sketch of what I intend to include in my pure python data utility.
def hexidecimalDiget(n,deHex = false):
if(n<0):
print "negitive values not supported by call to hexidecimalDiget("+str(n)+")"
return None
elif(n < 10):
return str(n)
elif(n < 15):
return ["a","b","c","d","e"][n-10]
elif(n in ["a","b","c","d","e"]):
if deHex:
return ["a","b","c","d","e"].index(n)
return n
else:
print "call to hexidecimalDiget("+str(n)+") not supported!"
return None
def colorFormHexArray(arr):
if len(arr)!=3 and len(arr)!=6:
print "invalid length for color on call to colorFormHexArray("+str(arr)+")"
return None
elif None in arr:
print "cannot make color from None arguments in "+str(arr)
return None
else:
ret = "#"
for k in arr:
if(type(k) == list):
for k2 in k:
ret+=hexidecimalDiget(k)
else:
ret+=hexidecimalDiget(k)
return ret
def arrayFromColor(c):
c = c.replace("#","")
col = []
for n,k in enumerate(c):
if(len(c) == 3):
col.append([hexidecimalDiget(k,deHex = True)])
elif(len(c) == 6):
col.append([hexidecimalDiget(c[(n+1)*2-2],deHex = True),hexidecimalDiget(c[(n+1)*2-2],deHex = True)])
return(col)
def intFromHexPair(hp):
ret = 0
for n,k in enumerate(hp):
digBase = 16**(len(hp)-n-1)
ret+=digBase*hexidecimalDiget(hp[0],deHex = True)
return ret
def hexPairFromInt(I,minDigits = 1,maxDigits = 256):
if I<0:
print "negitive numbers not supported by hexPairFromInt"
k= 0
while(16**(k+1) <= I):
k+=1
if k < minDigits:
k = minDigits
if k > minDigits:
print("maxDigitsExceeded")
ret = []
while k>=0
dig = 16**k
ret.append(hexidecimalDiget(int(I)%(dig))
I -= dig
k-=1
return ret
def specColor(start,end,bottom,top):
start = arrayFromColor(start)
end = arrayFromColor(end)
def ret(v):
if( v<start or c>end ):
print("value out of range "+str([start,end]))
return('#aa0000') #eyo <- error red
else:
starts = [intFromHexPair(k) for k in start]
ends = [intFromHexPair(hp) for k in end]
normalized = (v-bottom)/(top-bottom)
return colorFormHexArray([hexPairFromInt(int((starts[n]-ends[n])*normalized),minDigits = 1,maxDigits = 256) for n,k in enumerate(starts)])
return ret
This seems excessive and hasn't even been slightly tested yet (just a stetch up atm) but I'll be testing and incorporating this code here tonight :: http://krewn.github.io/KPlot/
I want to print my binary tree in the following manner:
10
6 12
5 7 11 13
I have written code for insertion of nodes but can't able to write for printing the tree. so please help on this . My code is :
class Node:
def __init__(self,data):
self.data=data
self.left=None
self.right=None
self.parent=None
class binarytree:
def __init__(self):
self.root=None
self.size=0
def insert(self,data):
if self.root==None:
self.root=Node(data)
else:
current=self.root
while 1:
if data < current.data:
if current.left:
current=current.left
else:
new=Node(data)
current.left=new
break;
elif data > current.data:
if current.right:
current=current.right
else:
new=Node(data)
current.right=new
break;
else:
break
b=binarytree()
Here's my attempt, using recursion, and keeping track of the size of each node and the size of children.
class BstNode:
def __init__(self, key):
self.key = key
self.right = None
self.left = None
def insert(self, key):
if self.key == key:
return
elif self.key < key:
if self.right is None:
self.right = BstNode(key)
else:
self.right.insert(key)
else: # self.key > key
if self.left is None:
self.left = BstNode(key)
else:
self.left.insert(key)
def display(self):
lines, *_ = self._display_aux()
for line in lines:
print(line)
def _display_aux(self):
"""Returns list of strings, width, height, and horizontal coordinate of the root."""
# No child.
if self.right is None and self.left is None:
line = '%s' % self.key
width = len(line)
height = 1
middle = width // 2
return [line], width, height, middle
# Only left child.
if self.right is None:
lines, n, p, x = self.left._display_aux()
s = '%s' % self.key
u = len(s)
first_line = (x + 1) * ' ' + (n - x - 1) * '_' + s
second_line = x * ' ' + '/' + (n - x - 1 + u) * ' '
shifted_lines = [line + u * ' ' for line in lines]
return [first_line, second_line] + shifted_lines, n + u, p + 2, n + u // 2
# Only right child.
if self.left is None:
lines, n, p, x = self.right._display_aux()
s = '%s' % self.key
u = len(s)
first_line = s + x * '_' + (n - x) * ' '
second_line = (u + x) * ' ' + '\\' + (n - x - 1) * ' '
shifted_lines = [u * ' ' + line for line in lines]
return [first_line, second_line] + shifted_lines, n + u, p + 2, u // 2
# Two children.
left, n, p, x = self.left._display_aux()
right, m, q, y = self.right._display_aux()
s = '%s' % self.key
u = len(s)
first_line = (x + 1) * ' ' + (n - x - 1) * '_' + s + y * '_' + (m - y) * ' '
second_line = x * ' ' + '/' + (n - x - 1 + u + y) * ' ' + '\\' + (m - y - 1) * ' '
if p < q:
left += [n * ' '] * (q - p)
elif q < p:
right += [m * ' '] * (p - q)
zipped_lines = zip(left, right)
lines = [first_line, second_line] + [a + u * ' ' + b for a, b in zipped_lines]
return lines, n + m + u, max(p, q) + 2, n + u // 2
import random
b = BstNode(50)
for _ in range(50):
b.insert(random.randint(0, 100))
b.display()
Example output:
__50_________________________________________
/ \
________________________43_ ________________________99
/ \ /
_9_ 48 ____________67_____________________
/ \ / \
3 11_________ 54___ ______96_
/ \ \ \ / \
0 8 ____26___________ 61___ ________88___ 97
/ \ / \ / \
14_ __42 56 64_ 75_____ 92_
/ \ / / \ / \ / \
13 16_ 33_ 63 65_ 72 81_ 90 94
\ / \ \ / \
25 __31 41 66 80 87
/ /
28_ 76
\
29
class Node(object):
def __init__(self, value, left=None, right=None):
self.value = value
self.left = left
self.right = right
def printTree(node, level=0):
if node != None:
printTree(node.left, level + 1)
print(' ' * 4 * level + '-> ' + str(node.value))
printTree(node.right, level + 1)
t = Node(1, Node(2, Node(4, Node(7)),Node(9)), Node(3, Node(5), Node(6)))
printTree(t)
output:
-> 7
-> 4
-> 2
-> 9
-> 1
-> 5
-> 3
-> 6
What you're looking for is breadth-first traversal, which lets you traverse a tree level by level. Basically, you use a queue to keep track of the nodes you need to visit, adding children to the back of the queue as you go (as opposed to adding them to the front of a stack). Get that working first.
After you do that, then you can figure out how many levels the tree has (log2(node_count) + 1) and use that to estimate whitespace. If you want to get the whitespace exactly right, you can use other data structures to keep track of how many spaces you need per level. A smart estimation using number of nodes and levels should be enough, though.
I am leaving here a stand-alone version of #J. V.'s code. If anyone wants to grab his/her own binary tree and pretty print it, pass the root node and you are good to go.
If necessary, change val, left and right parameters according to your node definition.
def print_tree(root, val="val", left="left", right="right"):
def display(root, val=val, left=left, right=right):
"""Returns list of strings, width, height, and horizontal coordinate of the root."""
# No child.
if getattr(root, right) is None and getattr(root, left) is None:
line = '%s' % getattr(root, val)
width = len(line)
height = 1
middle = width // 2
return [line], width, height, middle
# Only left child.
if getattr(root, right) is None:
lines, n, p, x = display(getattr(root, left))
s = '%s' % getattr(root, val)
u = len(s)
first_line = (x + 1) * ' ' + (n - x - 1) * '_' + s
second_line = x * ' ' + '/' + (n - x - 1 + u) * ' '
shifted_lines = [line + u * ' ' for line in lines]
return [first_line, second_line] + shifted_lines, n + u, p + 2, n + u // 2
# Only right child.
if getattr(root, left) is None:
lines, n, p, x = display(getattr(root, right))
s = '%s' % getattr(root, val)
u = len(s)
first_line = s + x * '_' + (n - x) * ' '
second_line = (u + x) * ' ' + '\\' + (n - x - 1) * ' '
shifted_lines = [u * ' ' + line for line in lines]
return [first_line, second_line] + shifted_lines, n + u, p + 2, u // 2
# Two children.
left, n, p, x = display(getattr(root, left))
right, m, q, y = display(getattr(root, right))
s = '%s' % getattr(root, val)
u = len(s)
first_line = (x + 1) * ' ' + (n - x - 1) * '_' + s + y * '_' + (m - y) * ' '
second_line = x * ' ' + '/' + (n - x - 1 + u + y) * ' ' + '\\' + (m - y - 1) * ' '
if p < q:
left += [n * ' '] * (q - p)
elif q < p:
right += [m * ' '] * (p - q)
zipped_lines = zip(left, right)
lines = [first_line, second_line] + [a + u * ' ' + b for a, b in zipped_lines]
return lines, n + m + u, max(p, q) + 2, n + u // 2
lines, *_ = display(root, val, left, right)
for line in lines:
print(line)
print_tree(root)
__7
/ \
___10_ 3
/ \
_19 13
/ \
9 8_
/ \ \
4 0 12
Simple solution with no recursion
def PrintTree(root):
def height(root):
return 1 + max(height(root.left), height(root.right)) if root else -1
nlevels = height(root)
width = pow(2,nlevels+1)
q=[(root,0,width,'c')]
levels=[]
while(q):
node,level,x,align= q.pop(0)
if node:
if len(levels)<=level:
levels.append([])
levels[level].append([node,level,x,align])
seg= width//(pow(2,level+1))
q.append((node.left,level+1,x-seg,'l'))
q.append((node.right,level+1,x+seg,'r'))
for i,l in enumerate(levels):
pre=0
preline=0
linestr=''
pstr=''
seg= width//(pow(2,i+1))
for n in l:
valstr= str(n[0].val)
if n[3]=='r':
linestr+=' '*(n[2]-preline-1-seg-seg//2)+ '¯'*(seg +seg//2)+'\\'
preline = n[2]
if n[3]=='l':
linestr+=' '*(n[2]-preline-1)+'/' + '¯'*(seg+seg//2)
preline = n[2] + seg + seg//2
pstr+=' '*(n[2]-pre-len(valstr))+valstr #correct the potition acording to the number size
pre = n[2]
print(linestr)
print(pstr)
Sample output
1
/¯¯¯¯¯¯ ¯¯¯¯¯¯\
2 3
/¯¯¯ ¯¯¯\ /¯¯¯ ¯¯¯\
4 5 6 7
/¯ ¯\ /¯ /¯
8 9 10 12
I enhanced Prashant Shukla answer to print the nodes on the same level in the same line without spaces.
class Node(object):
def __init__(self, value, left=None, right=None):
self.value = value
self.left = left
self.right = right
def __str__(self):
return str(self.value)
def traverse(root):
current_level = [root]
while current_level:
print(' '.join(str(node) for node in current_level))
next_level = list()
for n in current_level:
if n.left:
next_level.append(n.left)
if n.right:
next_level.append(n.right)
current_level = next_level
t = Node(1, Node(2, Node(4, Node(7)), Node(9)), Node(3, Node(5), Node(6)))
traverse(t)
Just use this small method of print2DTree:
class bst:
def __init__(self, value):
self.value = value
self.right = None
self.left = None
def insert(root, key):
if not root:
return bst(key)
if key >= root.value:
root.right = insert(root.right, key)
elif key < root.value:
root.left = insert(root.left, key)
return root
def insert_values(root, values):
for value in values:
root = insert(root, value)
return root
def print2DTree(root, space=0, LEVEL_SPACE = 5):
if (root == None): return
space += LEVEL_SPACE
print2DTree(root.right, space)
# print() # neighbor space
for i in range(LEVEL_SPACE, space): print(end = " ")
print("|" + str(root.value) + "|<")
print2DTree(root.left, space)
root = insert_values(None, [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15])
print2DTree(root)
Results:
code Explanation:
by using the BFS get the lists of list contains elements of each level
number of white spaces at any level = (max number of element in tree)//2^level
maximum number of elements of h height tree = 2^h -1; considering root level height as 1
print the value and white spaces
find my Riple.it link here print-bst-tree
def bfs(node,level=0,res=[]):
if level<len(res):
if node:
res[level].append(node.value)
else:
res[level].append(" ")
else:
if node:
res.append([node.value])
else:
res.append([" "])
if not node:
return
bfs(node.left,level+1,res)
bfs(node.right,level+1,res)
return res
def printTree(node):
treeArray = bfs(node)
h = len(treeArray)
whiteSpaces = (2**h)-1
def printSpaces(n):
for i in range(n):
print(" ",end="")
for level in treeArray:
whiteSpaces = whiteSpaces//2
for i,x in enumerate(level):
if i==0:
printSpaces(whiteSpaces)
print(x,end="")
printSpaces(1+2*whiteSpaces)
print()
#driver Code
printTree(root)
#output
class magictree:
def __init__(self, parent=None):
self.parent = parent
self.level = 0 if parent is None else parent.level + 1
self.attr = []
self.rows = []
def add(self, value):
tr = magictree(self)
tr.attr.append(value)
self.rows.append(tr)
return tr
def printtree(self):
def printrows(rows):
for i in rows:
print("{}{}".format(i.level * "\t", i.attr))
printrows(i.rows)
printrows(self.rows)
tree = magictree()
group = tree.add("company_1")
group.add("emp_1")
group.add("emp_2")
emp_3 = group.add("emp_3")
group = tree.add("company_2")
group.add("emp_5")
group.add("emp_6")
group.add("emp_7")
emp_3.add("pencil")
emp_3.add("pan")
emp_3.add("scotch")
tree.printtree()
result:
['company_1']
['emp_1']
['emp_2']
['emp_3']
['pencil']
['pan']
['scotch']
['company_2']
['emp_5']
['emp_6']
['emp_7']
As I came to this question from Google (and I bet many others did too), here is binary tree that has multiple children, with a print function (__str__ which is called when doing str(object_var) and print(object_var)).
Code:
from typing import Union, Any
class Node:
def __init__(self, data: Any):
self.data: Any = data
self.children: list = []
def insert(self, data: Any):
self.children.append(Node(data))
def __str__(self, top: bool=True) -> str:
lines: list = []
lines.append(str(self.data))
for child in self.children:
for index, data in enumerate(child.__str__(top=False).split("\n")):
data = str(data)
space_after_line = " " * index
if len(lines)-1 > index:
lines[index+1] += " " + data
if top:
lines[index+1] += space_after_line
else:
if top:
lines.append(data + space_after_line)
else:
lines.append(data)
for line_number in range(1, len(lines) - 1):
if len(lines[line_number + 1]) > len(lines[line_number]):
lines[line_number] += " " * (len(lines[line_number + 1]) - len(lines[line_number]))
lines[0] = " " * int((len(max(lines, key=len)) - len(str(self.data))) / 2) + lines[0]
return '\n'.join(lines)
def hasChildren(self) -> bool:
return bool(self.children)
def __getitem__(self, pos: Union[int, slice]):
return self.children[pos]
And then a demo:
# Demo
root = Node("Languages Good For")
root.insert("Serverside Web Development")
root.insert("Clientside Web Development")
root.insert("For Speed")
root.insert("Game Development")
root[0].insert("Python")
root[0].insert("NodeJS")
root[0].insert("Ruby")
root[0].insert("PHP")
root[1].insert("CSS + HTML + Javascript")
root[1].insert("Typescript")
root[1].insert("SASS")
root[2].insert("C")
root[2].insert("C++")
root[2].insert("Java")
root[2].insert("C#")
root[3].insert("C#")
root[3].insert("C++")
root[0][0].insert("Flask")
root[0][0].insert("Django")
root[0][1].insert("Express")
root[0][2].insert("Ruby on Rails")
root[0][0][0].insert(1.1)
root[0][0][0].insert(2.1)
print(root)
This is part of my own implementation of BST. The ugly part of this problem is that you have to know the space that your children occupies before you can print out yourself. Because you can have very big numbers like 217348746327642386478832541267836128736..., but also small numbers like 10, so if you have a parent-children relationship between these two, then it can potentially overlap with your other child. Therefore, we need to first go through the children, make sure we get how much space they are having, then we use that information to construct ourself.
def __str__(self):
h = self.getHeight()
rowsStrs = ["" for i in range(2 * h - 1)]
# return of helper is [leftLen, curLen, rightLen] where
# leftLen = children length of left side
# curLen = length of keyStr + length of "_" from both left side and right side
# rightLen = children length of right side.
# But the point of helper is to construct rowsStrs so we get the representation
# of this BST.
def helper(node, curRow, curCol):
if(not node): return [0, 0, 0]
keyStr = str(node.key)
keyStrLen = len(keyStr)
l = helper(node.l, curRow + 2, curCol)
rowsStrs[curRow] += (curCol -len(rowsStrs[curRow]) + l[0] + l[1] + 1) * " " + keyStr
if(keyStrLen < l[2] and (node.r or (node.p and node.p.l == node))):
rowsStrs[curRow] += (l[2] - keyStrLen) * "_"
if(l[1]):
rowsStrs[curRow + 1] += (len(rowsStrs[curRow + 2]) - len(rowsStrs[curRow + 1])) * " " + "/"
r = helper(node.r, curRow + 2, len(rowsStrs[curRow]) + 1)
rowsStrs[curRow] += r[0] * "_"
if(r[1]):
rowsStrs[curRow + 1] += (len(rowsStrs[curRow]) - len(rowsStrs[curRow + 1])) * " " + "\\"
return [l[0] + l[1] + 1, max(l[2] - keyStrLen, 0) + keyStrLen + r[0], r[1] + r[2] + 1]
helper(self.head, 0, 0)
res = "\n".join(rowsStrs)
#print("\n\n\nStart of BST:****************************************")
#print(res)
#print("End of BST:****************************************")
#print("BST height: ", h, ", BST size: ", self.size)
return res
Here's some examples of running this:
[26883404633, 10850198033, 89739221773, 65799970852, 6118714998, 31883432186, 84275473611, 25958013736, 92141734773, 91725885198, 131191476, 81453208197, 41559969292, 90704113213, 6886252839]
26883404633___________________________________________
/ \
10850198033__ 89739221773___________________________
/ \ / \
6118714998_ 25958013736 65799970852_______________ 92141734773
/ \ / \ /
131191476 6886252839 31883432186_ 84275473611 91725885198
\ / /
41559969292 81453208197 90704113213
Another example:
['rtqejfxpwmggfro', 'viwmdmpedzwvvxalr', 'mvvjmkdcdpcfb', 'ykqehfqbpcjfd', 'iuuujkmdcle', 'nzjbyuvlodahlpozxsc', 'wdjtqoygcgbt', 'aejduciizj', 'gzcllygjekujzcovv', 'naeivrsrfhzzfuirq', 'lwhcjbmcfmrsnwflezxx', 'gjdxphkpfmr', 'nartcxpqqongr', 'pzstcbohbrb', 'ykcvidwmouiuz']
rtqejfxpwmggfro____________________
/ \
mvvjmkdcdpcfb_____________________________ viwmdmpedzwvvxalr_______________
/ \ \
iuuujkmdcle_________ nzjbyuvlodahlpozxsc_ ykqehfqbpcjfd
/ \ / \ /
aejduciizj_____________ lwhcjbmcfmrsnwflezxx naeivrsrfhzzfuirq_ pzstcbohbrb wdjtqoygcgbt_
\ \ \
gzcllygjekujzcovv nartcxpqqongr ykcvidwmouiuz
/
gjdxphkpfmr
Here's a 2-pass solution with no recursion for general binary trees where each node has a value that "fits" within the allotted space (values closer to the root have more room to spare). (Pass 0 computes the tree height).
'''
0: 0
1: 1 2
2: 3 4 5 6
3: 7 8 9 a b c d e
h: 4
N: 2**4 - 1 <--| 2**0 + 2**1 + 2**2 + 2**3
'''
import math
def t2_lvl( i): return int(math.log2(i+1)) if 0<i else 0 # #meta map the global idx to the lvl
def t2_i2base(i): return (1<<t2_lvl(i))-1 # #meta map the global idx to the local idx (ie. the idx of elem 0 in the lvl at idx #i)
def t2_l2base(l): return (1<< l) -1 # #meta map the lvl to the local idx (ie. the idx of elem 0 in lvl #l)
class Tree2: # #meta a 2-tree is a tree with at most 2 sons per dad
def __init__(self, v=None):
self.v = v
self.l = None
self.r = None
def __str__(self): return f'{self.v}'
def t2_show(tree:Tree2): # #meta 2-pass fn. in the 1st pass we compute the height
if not tree: return
q0 = [] # perm queue
q1 = [] # temp queue
# pass 0
h = 0 # height is the number of lvls
q0.append((tree,0))
q1.append((tree,0))
while q1:
n,i = q1.pop(0)
h = max(h, t2_lvl(i))
if n.l: l=(n.l, 2*i+1); q0.append(l); q1.append(l)
if n.r: r=(n.r, 2*i+2); q0.append(r); q1.append(r)
h += 1 # nlvls
N = 2**h - 1 # nelems (for a perfect tree of this height)
W = 1 # elem width
# pass 1
print(f'\n\x1b[31m{h} \x1b[32m{len(q0)}\x1b[0m')
print(f'{0:1x}\x1b[91m:\x1b[0m',end='')
for idx,(n,i) in enumerate(q0):
l = t2_lvl(i) # lvl
b = (1<<l)-1 # base
s0 = (N // (2**(l+1)))
s1 = (N // (2**(l+0)))
s = 3+1 + s0 + (i-b)*(s1+1) # absolute 1-based position (from the beginning of line)
w = int(2**(h-l-2)) # width (around the element) (to draw the surrounding #-)
# print(f'{i:2x} {l} {i-b} {s0:2x} {s1:2x} {s:2x} {w:x} {n.v:02x}')
if 0<idx and t2_lvl(q0[idx-1][1])!=l: print(f'\n{l:1x}\x1b[91m:\x1b[0m',end='') # new level: go to the next line
print(f"\x1b[{s-w}G{w*'-'}\x1b[1G", end='')
print(f"\x1b[{s}G{n.v:1x}\x1b[1G", end='') # `\x1b[XG` is an ANSI escape code that moves the cursor to column X
print(f"\x1b[{s+W}G{w*'-'}\x1b[1G", end='')
print()
And an example:
tree = Tree2(0)
tree.l = Tree2(1)
tree.r = Tree2(2)
tree.l.l = Tree2(3)
tree.r.l = Tree2(4)
tree.r.r = Tree2(5)
tree.l.l.l = Tree2(3)
tree.r.l.l = Tree2(6)
tree.r.l.r = Tree2(7)
tree.l.l.l.l = Tree2(3)
tree.r.l.l.l = Tree2(8)
tree.r.l.l.r = Tree2(9)
t2_show(tree)
Output:
5 12
0: --------0--------
1: ----1---- ----2----
2: --3-- --4-- --5--
3: -3- -6- -7-
4: 3 8 9
Another output example:
7 127
0: --------------------------------0--------------------------------
1: ----------------1---------------- ----------------2----------------
2: --------3-------- --------4-------- --------5-------- --------6--------
3: ----7---- ----8---- ----9---- ----a---- ----b---- ----c---- ----d---- ----e----
4: --f-- --0-- --1-- --2-- --3-- --4-- --5-- --6-- --7-- --8-- --9-- --a-- --b-- --c-- --d-- --e--
5: -f- -0- -1- -2- -3- -4- -5- -6- -7- -8- -9- -a- -b- -c- -d- -e- -f- -0- -1- -2- -3- -4- -5- -6- -7- -8- -9- -a- -b- -c- -d- -e-
6: f 0 1 2 3 4 5 6 7 8 9 a b c d e f 0 1 2 3 4 5 6 7 8 9 a b c d e f 0 1 2 3 4 5 6 7 8 9 a b c d e f 0 1 2 3 4 5 6 7 8 9 a b c d e
Record Each Level Separately using Breadth First Approach
You can use a breadth first traversal and record node values in a dictionary using level as key. This helps next when you want to print each level in a new line. If you maintain a count of nodes processed, you can find current node's level (since it's a binary tree) using -
level = math.ceil(math.log(count + 1, 2) - 1)
Sample Code
Here's my code using the above method (along with some helpful variables like point_span & line_space which you can modify as you like). I used my custom Queue class, but you can also use a list for maintaining queue.
def pretty_print(self):
q, current, count, level, data = Queue(), self.root, 1, 0, {}
while current:
level = math.ceil(math.log(count + 1, 2) - 1)
if data.get(level) is None:
data[level] = []
data[level].append(current.value)
count += 1
if current.left:
q.enqueue(current.left)
if current.right:
q.enqueue(current.right)
current = q.dequeue()
point_span, line_space = 8, 4
line_width = int(point_span * math.pow(2, level))
for l in range(level + 1):
current, string = data[l], ''
for c in current:
string += str(c).center(line_width // len(current))
print(string + '\n' * line_space)
And here's how the output looks:
Similar question is being answered over here This may help following code will print in this format
>>>
1
2 3
4 5 6
7
>>>
Code for this is as below :
class Node(object):
def __init__(self, value, left=None, right=None):
self.value = value
self.left = left
self.right = right
def traverse(rootnode):
thislevel = [rootnode]
a = ' '
while thislevel:
nextlevel = list()
a = a[:len(a)/2]
for n in thislevel:
print a+str(n.value),
if n.left: nextlevel.append(n.left)
if n.right: nextlevel.append(n.right)
print
thislevel = nextlevel
t = Node(1, Node(2, Node(4, Node(7)),Node(9)), Node(3, Node(5), Node(6)))
traverse(t)
Edited code gives result in this format :
>>>
1
2 3
4 9 5 6
7
>>>
This is just a trick way to do what you want their maybe a proper method for that I suggest you to dig more into it.