kopia lustrzana https://github.com/villares/sketch-a-day
main
rodzic
910eb93e47
commit
e3d0e4ba65
|
|
@ -0,0 +1,299 @@
|
||||||
|
#*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
A simple Python graph class, demonstrating the essential facts and functionalities of graphs
|
||||||
|
based on https://www.python-course.eu/graphs_python.php and https://www.python.org/doc/essays/graphs/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from random import choice
|
||||||
|
|
||||||
|
class Graph(object):
|
||||||
|
|
||||||
|
def __init__(self, graph_dict=None):
|
||||||
|
"""
|
||||||
|
Initialize a graph object with dictionary provided,
|
||||||
|
if none provided, create an empty one.
|
||||||
|
"""
|
||||||
|
if graph_dict is None:
|
||||||
|
graph_dict = {}
|
||||||
|
self.__graph_dict = graph_dict
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.__graph_dict)
|
||||||
|
|
||||||
|
def vertices(self):
|
||||||
|
"""Return the vertices of graph."""
|
||||||
|
return list(self.__graph_dict.keys())
|
||||||
|
|
||||||
|
def edges(self):
|
||||||
|
"""Return the edges of graph """
|
||||||
|
return self.__generate_edges()
|
||||||
|
|
||||||
|
def add_vertex(self, vertex):
|
||||||
|
"""
|
||||||
|
If the vertex "vertex" is not in self.__graph_dict,
|
||||||
|
add key "vertex" with an empty list as a value,
|
||||||
|
otherwise, do nothing.
|
||||||
|
"""
|
||||||
|
if vertex not in self.__graph_dict:
|
||||||
|
self.__graph_dict[vertex] = []
|
||||||
|
|
||||||
|
def add_edge(self, edge):
|
||||||
|
"""
|
||||||
|
Assuming that edge is of type set, tuple or list;
|
||||||
|
add edge between vertices. Can add multiple edges!
|
||||||
|
"""
|
||||||
|
edge = set(edge)
|
||||||
|
vertex1 = edge.pop()
|
||||||
|
if edge:
|
||||||
|
# not a loop
|
||||||
|
vertex2 = edge.pop()
|
||||||
|
if vertex1 in self.__graph_dict:
|
||||||
|
self.__graph_dict[vertex1].append(vertex2)
|
||||||
|
else:
|
||||||
|
self.__graph_dict[vertex1] = [vertex2]
|
||||||
|
if vertex2 in self.__graph_dict:
|
||||||
|
self.__graph_dict[vertex2].append(vertex1)
|
||||||
|
else:
|
||||||
|
self.__graph_dict[vertex2] = [vertex1]
|
||||||
|
else:
|
||||||
|
# a loop
|
||||||
|
if vertex1 in self.__graph_dict:
|
||||||
|
self.__graph_dict[vertex1].append(vertex1)
|
||||||
|
else:
|
||||||
|
self.__graph_dict[vertex1] = [vertex1]
|
||||||
|
|
||||||
|
def remove_vertex(self, vert):
|
||||||
|
del self.__graph_dict[vert]
|
||||||
|
for k in self.__graph_dict.keys():
|
||||||
|
if vert in self.__graph_dict[k]:
|
||||||
|
self.__graph_dict[k].remove(vert)
|
||||||
|
|
||||||
|
def remove_edge(self, edge):
|
||||||
|
edge = set(edge)
|
||||||
|
vertex1 = edge.pop()
|
||||||
|
if edge:
|
||||||
|
vertex2 = edge.pop()
|
||||||
|
self.__graph_dict[vertex1].remove(vertex2)
|
||||||
|
self.__graph_dict[vertex2].remove(vertex1)
|
||||||
|
else:
|
||||||
|
self.__graph_dict[vertex1].remove(vertex1)
|
||||||
|
|
||||||
|
def __generate_edges(self):
|
||||||
|
"""
|
||||||
|
Generate the edges, represented as sets with one
|
||||||
|
(a loop back to the vertex) or two vertices.
|
||||||
|
"""
|
||||||
|
edges = []
|
||||||
|
for vertex in self.__graph_dict:
|
||||||
|
for neighbour in self.__graph_dict[vertex]:
|
||||||
|
if {neighbour, vertex} not in edges:
|
||||||
|
edges.append({vertex, neighbour})
|
||||||
|
return edges
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
res = "vertices: "
|
||||||
|
for k in self.__graph_dict:
|
||||||
|
res += str(k) + " "
|
||||||
|
res += "\nedges: "
|
||||||
|
for edge in self.__generate_edges():
|
||||||
|
res += str(edge) + " "
|
||||||
|
return res
|
||||||
|
|
||||||
|
def find_isolated_vertices(self):
|
||||||
|
"""
|
||||||
|
Return a list of isolated vertices.
|
||||||
|
"""
|
||||||
|
graph = self.__graph_dict
|
||||||
|
isolated = []
|
||||||
|
for vertex in graph:
|
||||||
|
print(isolated, vertex)
|
||||||
|
if not graph[vertex]:
|
||||||
|
isolated += [vertex]
|
||||||
|
return isolated
|
||||||
|
|
||||||
|
def find_path(self, start_vertex, end_vertex, path=[]):
|
||||||
|
"""
|
||||||
|
Find a path from start_vertex to end_vertex in graph.
|
||||||
|
"""
|
||||||
|
graph = self.__graph_dict
|
||||||
|
path = path + [start_vertex]
|
||||||
|
if start_vertex == end_vertex:
|
||||||
|
return path
|
||||||
|
if start_vertex not in graph:
|
||||||
|
return None
|
||||||
|
for vertex in graph[start_vertex]:
|
||||||
|
if vertex not in path:
|
||||||
|
extended_path = self.find_path(vertex,
|
||||||
|
end_vertex,
|
||||||
|
path)
|
||||||
|
if extended_path:
|
||||||
|
return extended_path
|
||||||
|
return None
|
||||||
|
|
||||||
|
def find_all_paths(self, start_vertex, end_vertex, path=[]):
|
||||||
|
"""
|
||||||
|
Find all paths from start_vertex to end_vertex.
|
||||||
|
"""
|
||||||
|
graph = self.__graph_dict
|
||||||
|
path = path + [start_vertex]
|
||||||
|
if start_vertex == end_vertex:
|
||||||
|
return [path]
|
||||||
|
if start_vertex not in graph:
|
||||||
|
return []
|
||||||
|
paths = []
|
||||||
|
for vertex in graph[start_vertex]:
|
||||||
|
if vertex not in path:
|
||||||
|
extended_paths = self.find_all_paths(vertex,
|
||||||
|
end_vertex,
|
||||||
|
path)
|
||||||
|
for p in extended_paths:
|
||||||
|
paths.append(p)
|
||||||
|
return paths
|
||||||
|
|
||||||
|
def is_connected(self,
|
||||||
|
vertices_encountered=None,
|
||||||
|
start_vertex=None):
|
||||||
|
"""Find if the graph is connected."""
|
||||||
|
if vertices_encountered is None:
|
||||||
|
vertices_encountered = set()
|
||||||
|
gdict = self.__graph_dict
|
||||||
|
vertices = list(gdict.keys()) # "list" necessary in Python 3
|
||||||
|
if not start_vertex:
|
||||||
|
# chosse a vertex from graph as a starting point
|
||||||
|
start_vertex = vertices[0]
|
||||||
|
vertices_encountered.add(start_vertex)
|
||||||
|
if len(vertices_encountered) != len(vertices):
|
||||||
|
for vertex in gdict[start_vertex]:
|
||||||
|
if vertex not in vertices_encountered:
|
||||||
|
if self.is_connected(vertices_encountered, vertex):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def vertex_degree(self, vertex):
|
||||||
|
"""
|
||||||
|
Return the number of edges connecting to a vertex (the number of adjacent vertices).
|
||||||
|
Loops are counted double, i.e. every occurence of vertex in the list of adjacent vertices.
|
||||||
|
"""
|
||||||
|
adj_vertices = self.__graph_dict[vertex]
|
||||||
|
degree = len(adj_vertices) + adj_vertices.count(vertex)
|
||||||
|
return degree
|
||||||
|
|
||||||
|
def degree_sequence(self):
|
||||||
|
"""Calculates the degree sequence."""
|
||||||
|
seq = []
|
||||||
|
for vertex in self.__graph_dict:
|
||||||
|
seq.append(self.vertex_degree(vertex))
|
||||||
|
seq.sort(reverse=True)
|
||||||
|
return tuple(seq)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_degree_sequence(sequence):
|
||||||
|
"""
|
||||||
|
Return True, if the sequence is a degree sequence (non-increasing),
|
||||||
|
otherwise return False.
|
||||||
|
"""
|
||||||
|
return all(x >= y for x, y in zip(sequence, sequence[1:]))
|
||||||
|
|
||||||
|
def delta(self):
|
||||||
|
"""Find minimum degree of vertices."""
|
||||||
|
min = 100000000
|
||||||
|
for vertex in self.__graph_dict:
|
||||||
|
vertex_degree = self.vertex_degree(vertex)
|
||||||
|
if vertex_degree < min:
|
||||||
|
min = vertex_degree
|
||||||
|
return min
|
||||||
|
|
||||||
|
def Delta(self):
|
||||||
|
"""Finde maximum degree of vertices."""
|
||||||
|
max = 0
|
||||||
|
for vertex in self.__graph_dict:
|
||||||
|
vertex_degree = self.vertex_degree(vertex)
|
||||||
|
if vertex_degree > max:
|
||||||
|
max = vertex_degree
|
||||||
|
return max
|
||||||
|
|
||||||
|
def density(self):
|
||||||
|
"""Calculate the graph density."""
|
||||||
|
g = self.__graph_dict
|
||||||
|
V = len(g.keys())
|
||||||
|
E = len(self.edges())
|
||||||
|
return 2.0 * E / (V * (V - 1))
|
||||||
|
|
||||||
|
def diameter(self):
|
||||||
|
"""Calculates the graph diameter."""
|
||||||
|
|
||||||
|
v = self.vertices()
|
||||||
|
pairs = [
|
||||||
|
(v[i],
|
||||||
|
v[j]) for i in range(
|
||||||
|
len(v)) for j in range(
|
||||||
|
i + 1,
|
||||||
|
len(v) - 1)]
|
||||||
|
smallest_paths = []
|
||||||
|
for (s, e) in pairs:
|
||||||
|
paths = self.find_all_paths(s, e)
|
||||||
|
smallest = sorted(paths, key=len)[0]
|
||||||
|
smallest_paths.append(smallest)
|
||||||
|
|
||||||
|
smallest_paths.sort(key=len)
|
||||||
|
|
||||||
|
# longest path is at the end of list,
|
||||||
|
# i.e. diameter corresponds to the length of this path
|
||||||
|
diameter = len(smallest_paths[-1]) - 1
|
||||||
|
return diameter
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def erdoes_gallai(dsequence):
|
||||||
|
"""
|
||||||
|
Check if Erdoes-Gallai inequality condition is fullfilled.
|
||||||
|
"""
|
||||||
|
if sum(dsequence) % 2:
|
||||||
|
# sum of sequence is odd
|
||||||
|
return False
|
||||||
|
if Graph.is_degree_sequence(dsequence):
|
||||||
|
for k in range(1, len(dsequence) + 1):
|
||||||
|
left = sum(dsequence[:k])
|
||||||
|
right = k * (k - 1) + sum([min(x, k) for x in dsequence[k:]])
|
||||||
|
if left > right:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# sequence is increasing
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# Code by Eryk Kopczyński
|
||||||
|
def find_shortest_path(self, start, end):
|
||||||
|
from collections import deque
|
||||||
|
graph = self.__graph_dict
|
||||||
|
dist = {start: [start]}
|
||||||
|
q = deque((start,))
|
||||||
|
while len(q):
|
||||||
|
at = q.popleft()
|
||||||
|
for next in graph[at]:
|
||||||
|
if next not in dist:
|
||||||
|
#dist[next] = [dist[at], next]
|
||||||
|
dist[next] = dist[at]+[next] # less efficient but nicer output
|
||||||
|
q.append(next)
|
||||||
|
return dist.get(end)
|
||||||
|
|
||||||
|
|
||||||
|
def get_random_vertex(self):
|
||||||
|
return choice(self.vertices())
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def random_graph(names, connect_rate=.9, allow_loops=True):
|
||||||
|
vertices = set(names)
|
||||||
|
graph = Graph()
|
||||||
|
for v in vertices:
|
||||||
|
graph.add_vertex(v)
|
||||||
|
if random(1) < connect_rate:
|
||||||
|
if allow_loops:
|
||||||
|
names = list(vertices)
|
||||||
|
else:
|
||||||
|
names = list(vertices - set((v,)))
|
||||||
|
graph.add_edge({v, choice(names)})
|
||||||
|
return graph
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
#*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import division
|
||||||
|
|
||||||
|
def setup_grid(graph, margin=None):
|
||||||
|
margin = margin or width / 40
|
||||||
|
cols, rows = dim_grid(len(graph))
|
||||||
|
w, h = (width - margin * 2) / cols, (height - margin * 2) / rows
|
||||||
|
points = []
|
||||||
|
for i in range(cols * rows):
|
||||||
|
c = i % cols
|
||||||
|
r = i // rows
|
||||||
|
x = margin + w * 0.5 + c * w - 14 * (r % 2) + 7
|
||||||
|
y = margin + h * 0.5 + r * h - 14 * (c % 2) + 7
|
||||||
|
z = 0
|
||||||
|
points.append((x, y, z))
|
||||||
|
points = sorted(
|
||||||
|
points, key=lambda p: dist(p[0], p[1], width / 2, height / 2))
|
||||||
|
v_list = reversed(sorted(graph.vertices(), key=graph.vertex_degree))
|
||||||
|
# v_list = sorted(graph.vertices(), key=graph.vertex_degree)
|
||||||
|
grid = {v: p for v, p in zip(v_list, points)}
|
||||||
|
return grid
|
||||||
|
|
||||||
|
def dim_grid(n):
|
||||||
|
a = int(sqrt(n))
|
||||||
|
b = n // a
|
||||||
|
if a * b < n:
|
||||||
|
b += 1
|
||||||
|
print(u'{}: {} × {} ({})'.format(n, a, b, a * b))
|
||||||
|
return a, b
|
||||||
|
|
||||||
|
def measure_graph_grid(graph, grid):
|
||||||
|
metric = 0
|
||||||
|
for edge in graph.edges():
|
||||||
|
if len(edge) == 2:
|
||||||
|
a, b = edge
|
||||||
|
d = PVector.dist(PVector(*grid[a]),
|
||||||
|
PVector(*grid[b]))
|
||||||
|
metric += d
|
||||||
|
return metric
|
||||||
|
|
||||||
|
def grid_swap(graph, grid):
|
||||||
|
from random import sample
|
||||||
|
fail = 0
|
||||||
|
n = m = measure_graph_grid(graph, grid)
|
||||||
|
while m <= n and fail < len(graph) ** 2:
|
||||||
|
new_grid= dict(grid)
|
||||||
|
a, b = sample(graph.vertices(), 2)
|
||||||
|
new_grid[a], new_grid[b] = new_grid[b], new_grid[a]
|
||||||
|
n = measure_graph_grid(graph, new_grid)
|
||||||
|
if m > n:
|
||||||
|
t = "{:.1%} at: {} tries of 2 vertex swaps"
|
||||||
|
print(t.format((n - m) / m, fail + 1))
|
||||||
|
return new_grid
|
||||||
|
else:
|
||||||
|
fail += 1
|
||||||
|
print("no new grid")
|
||||||
|
return grid
|
||||||
|
|
||||||
|
def grid_shuffle(graph, grid):
|
||||||
|
from random import sample
|
||||||
|
fail = 0
|
||||||
|
n = m = measure_graph_grid(graph, grid)
|
||||||
|
while m <= n and fail < len(graph) ** 2:
|
||||||
|
new_grid= dict(zip(grid.keys(), sample(grid.values(), len(grid))))
|
||||||
|
n = measure_graph_grid(graph, new_grid)
|
||||||
|
if m > n:
|
||||||
|
t = "{:.1%} at: {} tries of complete shuffle"
|
||||||
|
print(t.format((n - m) / m, fail + 1))
|
||||||
|
|
||||||
|
return new_grid
|
||||||
|
else:
|
||||||
|
fail += 1
|
||||||
|
print("no new grid")
|
||||||
|
return grid
|
||||||
|
|
||||||
|
def grid_swap_mult(graph, grid, num=3):
|
||||||
|
from random import sample
|
||||||
|
fail = 0
|
||||||
|
n = m = measure_graph_grid(graph, grid)
|
||||||
|
while m <= n and fail < len(graph) ** 2:
|
||||||
|
new_grid= dict(grid)
|
||||||
|
ks = sample(graph.vertices(), num)
|
||||||
|
vs = [grid[k] for k in sample(ks, num)]
|
||||||
|
for k, v in zip(ks, vs):
|
||||||
|
new_grid[k] = v
|
||||||
|
n = measure_graph_grid(graph, new_grid)
|
||||||
|
if m > n:
|
||||||
|
t = "{:.1%} at: {} tries of {}v swaps"
|
||||||
|
print(t.format((n - m) / m, fail + 1, num))
|
||||||
|
return new_grid
|
||||||
|
else:
|
||||||
|
fail += 1
|
||||||
|
print("no new grid")
|
||||||
|
return grid
|
||||||
Plik binarny nie jest wyświetlany.
|
Po Szerokość: | Wysokość: | Rozmiar: 663 KiB |
|
|
@ -0,0 +1,110 @@
|
||||||
|
from random import choice
|
||||||
|
from graph import Graph
|
||||||
|
from grid import setup_grid, grid_swap, grid_shuffle, grid_swap_mult
|
||||||
|
|
||||||
|
thread_count = 0
|
||||||
|
|
||||||
|
def setup():
|
||||||
|
size(400, 400)
|
||||||
|
fill(0)
|
||||||
|
textAlign(CENTER, CENTER)
|
||||||
|
f = createFont("Source Code Pro Bold", 14)
|
||||||
|
textFont(f)
|
||||||
|
setup_graph()
|
||||||
|
|
||||||
|
def setup_graph():
|
||||||
|
global graph, grid
|
||||||
|
graph = Graph.random_graph(range(36), allow_loops=False)
|
||||||
|
grid = setup_grid(graph, margin=10)
|
||||||
|
global sel_v
|
||||||
|
sel_v = graph.get_random_vertex()
|
||||||
|
global path_walker, t_walker
|
||||||
|
path_walker = []
|
||||||
|
t_walker = 0
|
||||||
|
print(graph)
|
||||||
|
|
||||||
|
def draw():
|
||||||
|
background(150)
|
||||||
|
noFill()
|
||||||
|
stroke(255)
|
||||||
|
strokeWeight(4)
|
||||||
|
for e in graph.edges():
|
||||||
|
va = e.pop()
|
||||||
|
xa, ya, za = grid[va]
|
||||||
|
if len(e) == 1:
|
||||||
|
vb = e.pop()
|
||||||
|
xb, yb, zb = grid[vb]
|
||||||
|
line(xa, ya, xb, yb)
|
||||||
|
else:
|
||||||
|
circle(20 + xa, ya, 30)
|
||||||
|
|
||||||
|
for v in grid.keys():
|
||||||
|
x, y, z = grid[v]
|
||||||
|
fill(255)
|
||||||
|
circle(x, y, 10)
|
||||||
|
fill(0)
|
||||||
|
text("{}".format(v).upper(), x - 15, y - 3)
|
||||||
|
|
||||||
|
walker()
|
||||||
|
|
||||||
|
def walker():
|
||||||
|
global t_walker, path_walker, sel_v
|
||||||
|
if path_walker and t_walker < 1:
|
||||||
|
# print(t_walker, path_walker)
|
||||||
|
p = lerpVectors(t_walker, path_walker)
|
||||||
|
noFill()
|
||||||
|
stroke(0, 0, 255)
|
||||||
|
circle(p.x, p.y, 10)
|
||||||
|
t_walker += .03 / len(path_walker)
|
||||||
|
else:
|
||||||
|
path_walker = []
|
||||||
|
noStroke()
|
||||||
|
fill(255, 0, 0)
|
||||||
|
x, y, _ = grid[sel_v]
|
||||||
|
circle(x, y, 10)
|
||||||
|
|
||||||
|
def lerpVectors(amt, vecs):
|
||||||
|
""" from Jeremy Douglass """
|
||||||
|
amt = constrain(amt, 0, 1) # let's play safe
|
||||||
|
if len(vecs) == 1:
|
||||||
|
return vecs[0]
|
||||||
|
cunit = 1.0 / (len(vecs) - 1)
|
||||||
|
return PVector.lerp(vecs[floor(amt / cunit)],
|
||||||
|
vecs[ceil(amt / cunit)],
|
||||||
|
amt % cunit / cunit)
|
||||||
|
|
||||||
|
def keyTyped():
|
||||||
|
if key == 'r':
|
||||||
|
setup_graph()
|
||||||
|
else:
|
||||||
|
thread("swapping")
|
||||||
|
|
||||||
|
def swapping():
|
||||||
|
global grid, thread_count
|
||||||
|
thread_count += 1
|
||||||
|
print("Starting thread {}.".format(thread_count))
|
||||||
|
if key == 's':
|
||||||
|
for _ in range(len(graph)):
|
||||||
|
grid = grid_swap(graph, grid)
|
||||||
|
if key == 'S':
|
||||||
|
for _ in range(len(graph)):
|
||||||
|
grid = grid_shuffle(graph, grid)
|
||||||
|
if key == '3':
|
||||||
|
for _ in range(len(graph)):
|
||||||
|
grid = grid_swap_mult(graph, grid, num=3)
|
||||||
|
if key == '4':
|
||||||
|
for _ in range(len(graph)):
|
||||||
|
grid = grid_swap_mult(graph, grid, num=4)
|
||||||
|
print("Ending thread {}.".format(thread_count))
|
||||||
|
|
||||||
|
|
||||||
|
def mousePressed():
|
||||||
|
global path_walker, t_walker, sel_v
|
||||||
|
for v in graph.vertices():
|
||||||
|
x, y, _ = grid[v]
|
||||||
|
if v != sel_v and dist(x, y, mouseX, mouseY) < 10:
|
||||||
|
path = graph.find_shortest_path(sel_v, v)
|
||||||
|
if path:
|
||||||
|
path_walker = [PVector(*grid[pv]) for pv in path]
|
||||||
|
t_walker = 0
|
||||||
|
sel_v = v
|
||||||
|
|
@ -22,6 +22,12 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
[sketch_2020_08_09a](https://github.com/villares/sketch-a-day/tree/master/2020/sketch_2020_08_09a) [[Py.Processing](https://villares.github.io/como-instalar-o-processing-modo-python/index-EN)]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
[sketch_2020_08_08a](https://github.com/villares/sketch-a-day/tree/master/2020/sketch_2020_08_08a) [[Py.Processing](https://villares.github.io/como-instalar-o-processing-modo-python/index-EN)]
|
[sketch_2020_08_08a](https://github.com/villares/sketch-a-day/tree/master/2020/sketch_2020_08_08a) [[Py.Processing](https://villares.github.io/como-instalar-o-processing-modo-python/index-EN)]
|
||||||
|
|
|
||||||
Ładowanie…
Reference in New Issue