Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Résolution des plus court chemins et des cycles hamiltioniens en python (Thibaut Juzeau) #1

Open
wants to merge 33 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
0f86371
start transcript to python
Thiebosh Jun 20, 2021
8ea413d
bugfix + first challenge
Thiebosh Jun 20, 2021
8cf4123
packages + begin read from resources
Thiebosh Jun 21, 2021
88cc57a
finish read from resources
Thiebosh Jun 23, 2021
1d70734
begin transcript root classes
Thiebosh Jun 23, 2021
c3ddca4
linter + types
Thiebosh Jun 23, 2021
c05a03a
debug
Thiebosh Jun 24, 2021
73cba49
path printing correct
Thiebosh Jun 24, 2021
6103084
make edge comparable
Thiebosh Jun 24, 2021
61a6a61
shortest path generation
Thiebosh Jun 25, 2021
daea5f5
shortest path correction
Thiebosh Jun 25, 2021
bff941e
shortest path python done without opti
Thiebosh Jun 26, 2021
dd3cffb
débug base cycle
Thiebosh Jun 26, 2021
3526ddd
base algo cycle
Thiebosh Jun 26, 2021
f3f7f79
first working shot
Thiebosh Jun 27, 2021
c063374
add graph
Thiebosh Jun 27, 2021
7ce5307
add looper + better random selection
Thiebosh Jun 27, 2021
afde43f
cleaning
Thiebosh Jun 27, 2021
6cb3f10
cycle opti
Thiebosh Jun 28, 2021
5234176
bug fixes
Thiebosh Jun 28, 2021
547d9b7
range reduction
Thiebosh Jun 28, 2021
380af85
real cycle + visu
Thiebosh Jun 29, 2021
6235aff
algo opti 2
Thiebosh Jun 30, 2021
e9d9faa
debug phase
Thiebosh Jun 30, 2021
181157c
impair fix
Thiebosh Jun 30, 2021
2232cbf
cleaning + perfs
Thiebosh Jul 1, 2021
8a1f085
good cycle
Thiebosh Jul 1, 2021
d8f4df6
reduce nb of optimization loop
Thiebosh Jul 1, 2021
9b2813d
good uncrosser
Thiebosh Jul 1, 2021
e2ad7e9
opti algo chaining
Thiebosh Jul 2, 2021
9559ef4
end cycle + cleaning
Thiebosh Jul 2, 2021
696dbf2
reduce nb gen
Thiebosh Jul 2, 2021
ae01e31
Update README.md
Thiebosh Feb 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
.idea
target


__pycache__
.vscode
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,49 @@
# Subject

## Introduction

In this exercise, you will implement 2 algorithms around the same kind of concepts (points, distances …) that we use at ***.


You are provided with a basic Scala program which runs the algorithms on several datasets. We expect a reasonable response time.


If Scala doesn’t suit you, you can use your preferred language, but we appreciate a lot:
- A functional style (no mutation of variables, recursion over iteration …)
- A functional language (Scala, Haskell, Caml …)
- Unit tests

Your program should run with the provided inputs.

## Algorithms
Tip: don’t reinvent the wheel! You can look for existing algorithms.

### Shortest path
Implement an algorithm that returns the shortest path from a set of points, a starting and an ending point.

Input:
- N points (x, y)
- M edges (one edge connects two points)
- Start point
- End point

Output:
- Set of edges that makes the path, starting with the start point and ending with the end point
- Distance travelled (euclidian)


### Shortest hamiltonian cycle
Implement an algorithm that returns a path as short as possible which visits every given point once, starting and ending at the same point (hamiltonian cycle).


Input:
- N points (x, y)

Output:
- Path using the N points
- Distance travelled (use the euclidean distance).


# Instructions

Once you have installed [sbt](https://www.scala-sbt.org/) to your machine, run
Expand Down
50 changes: 50 additions & 0 deletions python/domain/Edge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from .Point import Point

from math import sqrt


class Edge:
def __init__(self, from_: Point, to: Point) -> None:
self.from_: Point = from_
self.to: Point = to

def __str__(self) -> str:
return f"{self.from_}-{self.to}"

def __eq__(self, other: object) -> bool:
return self.from_ == other.from_ and self.to == other.to

def __hash__(self) -> int:
return hash((self.from_, self.to))

def _sqr(self, d: float) -> float:
return d**2

def _distance2(self) -> float:
return self._sqr(self.from_.x - self.to.x) + self._sqr(self.from_.y - self.to.y)

def distance(self) -> float:
return sqrt(self._distance2())

def _equation(self) -> "tuple[float, float]":
m = (self.to.y - self.from_.y) / ((self.to.x - self.from_.x) if self.from_.x != self.to.x else 0.01)
b = self.to.y - m * self.to.x
return m, b

def cross(self, other: object) -> bool:
if self == other:
return False

mSelf, bSelf = self._equation()
mOther, bOther = other._equation()

if mSelf == mOther:
return False

x = (bOther - bSelf) / (mSelf - mOther)
y = mSelf * x + bSelf

return ((min(self.from_.x, self.to.x) < x < max(self.from_.x, self.to.x) and
min(other.from_.x, other.to.x) < x < max(other.from_.x, other.to.x)) or
(min(self.from_.y, self.to.y) < y < max(self.from_.y, self.to.y) and
min(other.from_.y, other.to.y) < y < max(other.from_.y, other.to.y)))
10 changes: 10 additions & 0 deletions python/domain/Path.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from .Point import Point
from .Edge import Edge


class Path:
def __init__(self, points: "list[Point]" = []) -> None:
self.points: "list[Point]" = points

def length(self) -> float:
return 0 if len(self.points) < 2 else Edge(self.points[0], self.points[1]).distance() + Path(self.points[1:]).length()
9 changes: 9 additions & 0 deletions python/domain/PathProblem.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from .Point import Point
from .Edge import Edge


class PathProblem:
def __init__(self, graph: "list[Edge]", start: Point, end: Point) -> None:
self.graph: "list[Edge]" = graph
self.start: Point = start
self.end: Point = end
17 changes: 17 additions & 0 deletions python/domain/Point.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class Point:
def __init__(self, x: float, y: float) -> None:
self.x: float = float(x)
self.y: float = float(y)

def __str__(self) -> str:
return f"{self.x}x{self.y}"

def __eq__(self, other: object) -> bool:
return self.x == other.x and self.y == other.y

def __hash__(self) -> int:
return hash((self.x, self.y))

def isNeighbor(self, other: object) -> bool:
return (self.x == other.x and (self.y == other.y - 1 or self.y == other.y + 1)) or \
(self.y == other.y and (self.x == other.x - 1 or self.x == other.x + 1))
Empty file added python/domain/__init__.py
Empty file.
71 changes: 71 additions & 0 deletions python/mainCycle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from domain.Point import Point
from domain.Path import Path
from parsing.readFromResources import cycle
from shortestCycle import shortestCycle

from sys import argv
import random
import numpy as np
from functools import lru_cache
import matplotlib.pyplot as plt


def mainCycle() -> None:
try:
seed = float(argv[1])
np.random.seed(seed)
random.seed(seed)
except Exception:
pass

shortestCycle(pointsInGrid(10, 8))

print("80 points in a 8x10 grid")
printResult(shortestCycle(shuffle(pointsInGrid(10, 8))))

# take 40ms to generate only
# print("200 random points")
# printResult(shortestCycle(shuffle([val for liste in [pointsInGrid(random.randint(0, 700), random.randint(0, 700)) for _ in range(200)] for val in liste])))

print("File 14 nodes")
printResult(shortestCycle(cycle("14_nodes.txt")))

print("File 52 nodes")
printResult(shortestCycle(cycle("52_nodes.txt")))

print("File 202 nodes")
printResult(shortestCycle(cycle("202_nodes.txt")))

# more than 1000 depth recursion allowed by python
# print("File 1002 nodes")
# printResult(shortestCycle(cycle("1002_nodes.txt")))

# print("File 5915 nodes")
# printResult(shortestCycle(cycle("5915_nodes.txt")))


@lru_cache()
def pointsInGrid(width: int, height: int) -> "list[Point]":
return [Point(x, y) for x in range(width) for y in range(height)]


def shuffle(grid: list) -> list:
random.shuffle(grid)
return grid


def printResult(path: Path) -> None:
length = f"{path.length():,.4f}"
print(f"{length}\n{','.join(map(lambda x: str(x), path.points[::-1]))}\n\n")

xs: "list[float]" = tuple(map(lambda point: point.x, path.points))
ys: "list[float]" = tuple(map(lambda point: point.y, path.points))
plt.plot(xs, ys, c='orange')
# plt.scatter(xs[1:-1], ys[1:-1], c='red', marker='.')
# plt.scatter(xs[0], ys[0], c='blue', marker='.')
plt.title(length)
plt.show()


if __name__ == "__main__":
mainCycle()
92 changes: 92 additions & 0 deletions python/mainPath.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from domain.Point import Point
from domain.Path import Path
from domain.Edge import Edge
from domain.PathProblem import PathProblem
from parsing.readFromResources import path
from shortestPath import shortestPath

from sys import argv
import random
from functools import lru_cache


def mainPath() -> None:
try:
random.seed(float(argv[1]))
except Exception:
pass

printResult("grid", path("grid.txt"))
printResult("spiral", path("spiral.txt"))
printResult("bunker", path("bunker.txt"))
printResult("line", path("line.txt"))
printResult("Small hole", path("small_hole.txt"))
printResult("Stick man", path("stickman.txt"))

printResult("Simple graph 5x4 grid", PathProblem(shuffle(gridNorthEast(5, 4)), Point(0, 0), Point(3, 3)), False)
printResult("Large graph 50x40 grid", PathProblem(shuffle(gridNorthEast(50, 40)), Point(0, 0), Point(30, 30)), False)
printResult("Large graph with cycle 50x40 grid", PathProblem(shuffle(gridFull(50, 40)), Point(0, 0), Point(30, 30)))
# too long yet for mapping in printPath
# printResult("Huge graph 200x300 grid", PathProblem(shuffle(gridNorthEast(200, 300)), Point(0, 0), Point(30, 30)), False)


@lru_cache()
def gridNorthEast(width: int, height: int) -> "list[Edge]":
return [elem for elemList in [[Edge(Point(x, y), Point(x + 1, y)),
Edge(Point(x, y), Point(x, y + 1)),
Edge(Point(x, y), Point(x + 1, y + 1))]
for x in range(width)
for y in range(height)]
for elem in elemList]


def gridFull(width: int, height: int) -> "list[Edge]":
return [elem for elemList in [[Edge(Point(x, y), Point(x + 1, y)),
Edge(Point(x, y), Point(x, y + 1)),
Edge(Point(x, y), Point(x, y - 1)),
Edge(Point(x, y), Point(x - 1, y))]
for x in range(width)
for y in range(height)]
for elem in elemList]


def shuffle(grid: list) -> list:
random.shuffle(grid)
return grid


def printResult(title: str, problem: PathProblem, isGrid: bool = True) -> None:
print(f"{title}\nfrom {problem.start} to {problem.end}")
result: Path = shortestPath(problem, isGrid)
if result.length():
print(result.length())
printPath(problem, result)
else:
print("no path found")
print("\n\n")


def printPath(problem: PathProblem, path: Path) -> None:
points: "iter[Point]" = sum(map(lambda edge: (edge.to, edge.from_), problem.graph), ())

xs: "tuple[int]" = tuple(map(lambda point: int(point.x), path.points))
ys: "tuple[int]" = tuple(map(lambda point: int(point.y), path.points))

for i in range(min(xs)-1, max(xs)+2):
for j in range(min(ys)-1, max(ys)+2):
point = Point(i, j)
if problem.start == point:
print("S", end="")
elif problem.end == point:
print("E", end="")
elif point in path.points:
print(".", end="")
elif point in points:
print(" ", end="")
else:
print("#", end="")
print()


if __name__ == "__main__":
mainPath()
Empty file added python/parsing/__init__.py
Empty file.
42 changes: 42 additions & 0 deletions python/parsing/readFromResources.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from domain.Point import Point
from domain.Edge import Edge
from domain.PathProblem import PathProblem


def unwrapCycle(line: str) -> Point:
_, x, y, *_ = line.split(" ")
return Point(x, y)


def cycle(filename: str) -> "list[Point]":
with open(f"resources/cycle/{filename}") as file:
maze: "list[str]" = [line for line in file.read().splitlines() if line]
return list(map(unwrapCycle, maze))


def neighbors(row: int, col: int) -> "iter[Point]":
return map(lambda r, c: Point(r, c),
[row, row, row + 1, row - 1],
[col + 1, col - 1, col, col])


def findChar(maze: "list[str]", c: str) -> Point:
row: int = next((index for index, val in enumerate(maze) if c in val), None)
col: int = maze[row].find(c)
return Point(row, col)


def path(filename: str) -> PathProblem:
with open(f"resources/path/{filename}") as file:
maze: "list[str]" = file.read().splitlines()

edges: "list[Edge]" = [Edge(Point(rowIndex, colIndex), neighbor)
for rowIndex in range(len(maze))
for colIndex in range(len(maze[rowIndex]))
if maze[rowIndex][colIndex] != "#"
for neighbor in neighbors(rowIndex, colIndex)
if neighbor.x >= 0 and neighbor.x < len(maze)
and neighbor.y >= 0 and neighbor.y < len(maze[int(neighbor.x)])
and maze[int(neighbor.x)][int(neighbor.y)] != "#"]

return PathProblem(edges, findChar(maze, "S"), findChar(maze, "E"))
Loading