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

Refactored the code and improved the output format #5

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
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
252 changes: 88 additions & 164 deletions JigsawInferenceGizmo.py
Original file line number Diff line number Diff line change
@@ -1,191 +1,115 @@
# JIG code from Stand-up Maths video "Why don't Jigsaw Puzzles have the correct number of pieces?"

def low_factors(n):
# all the factors which are the lower half of each factor pair
lf = []
for i in range(1, int(n**0.5)+1):
if n % i == 0:
lf.append(i)
return lf


def jig(w,h,n,b=0):

# Usage:
# from jig import *
# jig(PhysicalWidth, PhysicalHeight, NumberOfPieces, MoreVerboseOutput? (default = 1))
# jig_v0(a, b, c) is the same as jig(a, b, c, verbose = 0)

class JigClass:
"""
Since the bulk of the code is heavily state reliant, this refactoring
makes use of some basic encapsulation.
"""
# percentage we'll check in either direction
threshold = 0.1

JIG_THRESHOLD = 0.1
# the extra badness per piece
penalty = 1.005

ratio = max(w,h)/min(w,h) # switched to be greater than 1

print("")
print(f"{w} by {h} is picture ratio {round(ratio,4)}")
print("")

max_cap = int((1+threshold)*n)
min_cap = int((1-threshold)*n)

up_range = [i for i in range(n,max_cap+1)]
down_range = [i for i in range(min_cap,n)] # do not want n included again
down_range.reverse()

# start at 100 which is silly high and then move down.
up_best = 100
up_best_deets = []
down_best = 100
down_best_deets = []

# I am using the run marker so I know if looking above or below n
run = 0

for dis_range in [up_range,down_range]:
PIECE_PENALTY = 1.005

def __init__(self, w, h, n, verbose):
self.w = w
self.h = h
self.n = n
self.verbose = verbose
self.ratio = max(w, h) / min(w, h)
self.dir_best = [100, 100]
self.dir_best_stats = [None, None]
self.dir_range = (
range(n, int((1 + self.JIG_THRESHOLD) * n) + 1),
range(n-1, int((1 - self.JIG_THRESHOLD) * n) - 1, -1)
)

def search_range(self, direction):
best_n = 0
best_n_ratio = 0
best_n_sides = []

if run == 0:
print(f"Looking for >= {n} solutions:")
print("")
else:
print("")
print("Just out of interest, here are smaller options:")
print("")

for i in dis_range:
best_stats_list = []

for i in self.dir_range[direction]:
this_best = 0

for j in low_factors(i):
j2 = int(i/j) # must be a whole number anyway
j2 = int(i/j)
this_ratio = j2/j
if this_best == 0:
this_best = this_ratio
best_sides = [j,j2]
else:
if abs(this_ratio/ratio - 1) < abs(this_best/ratio - 1):
this_best = this_ratio
best_sides = [j,j2]
yes = 0
if best_n == 0:
yes = 1
else:
if abs(this_best/ratio - 1) < abs(best_n_ratio/ratio - 1):
yes = 1
if yes == 1:

if this_best == 0 or abs(this_ratio / self.ratio - 1) < abs(this_best / self.ratio - 1):
this_best = this_ratio
best_sides = [j, j2]

if best_n == 0 or abs(this_best / self.ratio - 1) < abs(best_n_ratio / self.ratio - 1):
best_n = i
best_n_ratio = this_best
best_n_sides = best_sides
piece_ratio = max(ratio,this_best)/min(ratio,this_best)
badness_score = (penalty**(abs(i-n)))*piece_ratio
if run == 0:
if badness_score < up_best:
up_best = badness_score
up_best_deets = [best_n,best_n_sides,best_n_ratio]
else:
if badness_score < down_best:
down_best = badness_score
down_best_deets = [best_n,best_n_sides,best_n_ratio]
print(f"{best_n} pieces in {best_n_sides} (grid ratio {round(best_n_ratio,4)}) needs piece ratio {round(piece_ratio,4)}")
if b==1:
print(f"[badness = {round(badness_score,5)}]")


print(f"for {n} the best is {best_n} pieces with size {best_n_sides}")

run += 1
print("")
print(f"If I had to guess: I think it's {up_best_deets[0]} pieces.")
piece_ratio = max(self.ratio, this_best) / min(self.ratio, this_best)
badness_score = (self.PIECE_PENALTY ** (abs(i - self.n))) * piece_ratio

if down_best < up_best:
print("")
print(f"BUT, fun fact, {down_best_deets[0]} would be even better.")
if badness_score < self.dir_best[direction]:
self.dir_best[direction] = badness_score
self.dir_best_stats[direction] = (best_n, best_n_sides, best_n_ratio)

print("")
return 'DONE'
best_stats_list.append((
best_n,
dimstr(best_n_sides),
round(best_n_ratio, 4),
round(piece_ratio, 4),
round(badness_score, 5)
))

# I duplicated jig_v0 to make is easier to show in the video
def jig_v0(w,h,n,b=0):

# percentage we'll check in either direction
threshold = 0.1
# I changed the printout format to be a table and used the
# extra space to include a badness score column
# -Braden
print("pieces size g. ratio p. ratio badness")

penalty = 1.005
for selstat in best_stats_list:
print("%-6d %-10s %6.4f %6.4f %.5f" % selstat)

ratio = max(w,h)/min(w,h) # switched to be greater than 1

print("")
print(f"{w} by {h} is picture ratio {round(ratio,4)}")
print("")

max_cap = int((1+threshold)*n)
min_cap = int((1-threshold)*n)
if self.verbose:
print(f"for {self.n} the best is {best_n} pieces with size {dimstr(best_n_sides)}")

up_range = [i for i in range(n,max_cap+1)]
down_range = [i for i in range(min_cap,n)] # do not want n included again
down_range.reverse()
def report_pr(self):
print(f"\n{self.w} by {self.h} is picture ratio {round(self.ratio, 4)}\n")

# start at 100 which is silly high and then move down.
up_best = 100
up_best_deets = []
down_best = 100
down_best_deets = []
def report_end(self):
if not self.verbose:
return

run = 0
print(f"\nIf I had to guess: I think it's {self.dir_best_stats[0][0]} pieces.")

for dis_range in [up_range,down_range]:
best_n = 0
best_n_ratio = 0
best_n_sides = []
if self.dir_best[1] < self.dir_best[0]:
print(f"\nBUT, fun fact, {self.dir_best_stats[1][0]} would be even better.")

if run == 0:
print(f"Looking for >= {n} solutions:")
print("")
else:
print("")
print("Just out of interest, here are smaller options:")
print("")

for i in dis_range:
this_best = 0
for j in low_factors(i):
j2 = int(i/j) # must be a whole number anyway
this_ratio = j2/j
if this_best == 0:
this_best = this_ratio
best_sides = [j,j2]
else:
if abs(this_ratio/ratio - 1) < abs(this_best/ratio - 1):
this_best = this_ratio
best_sides = [j,j2]
yes = 0
if best_n == 0:
yes = 1
else:
if abs(this_best/ratio - 1) < abs(best_n_ratio/ratio - 1):
yes = 1
if yes == 1:
best_n = i
best_n_ratio = this_best
best_n_sides = best_sides
piece_ratio = max(ratio,this_best)/min(ratio,this_best)
badness_score = (penalty**(abs(i-n)))*piece_ratio
if run == 0:
if badness_score < up_best:
up_best = badness_score
up_best_deets = [best_n,best_n_sides,best_n_ratio]
else:
if badness_score < down_best:
down_best = badness_score
down_best_deets = [best_n,best_n_sides,best_n_ratio]
print(f"{best_n} pieces in {best_n_sides} (grid ratio {round(best_n_ratio,4)}) needs piece ratio {round(piece_ratio,4)}")
if b==1:
print(f"[badness = {round(badness_score,5)}]")



run += 1

def dimstr(dim):
"""Turns tuple or list (A, B) into string repr 'AxB'"""
return f"{dim[0]}x{dim[1]}"

def low_factors(n):
# all the factors which are the lower half of each factor pair
lf = []
for i in range(1, int(n**0.5)+1):
if n % i == 0:
lf.append(i)
return lf

def jig(w, h, n, verbose = 1):
jc = JigClass(w, h, n, verbose)
jc.report_pr()
print(f"Looking for >= {n} solutions:\n")
jc.search_range(0)
print("\nJust out of interest, here are smaller options:\n")
jc.search_range(1)
jc.report_end()
print("")
return 'DONE'


# I duplicated jig_v0 to make is easier to show in the video
def jig_v0(w, h, n):
return jig(w, h, n, 0)