Architectural Units <-> Decimal Feet
Converting between the two in python without importsI made a simple and configurable AV tool. It displays height & width, minimum viewing distance and more based on screen size class and resolution class.But that isn't very interesting, really just an exercise in creating a user experience in TKinter. What maybe is interesting, is that in making it, I was briefly consumed with this more generalizable sub-task of working with imperial and architectural units.
Specifically I want to be able to go back and forth between architectural units and decimal feet in a configurable way, and I wanted to do it without any imports.
What I'm calling architectural units is lengths given in the format of
1'-2 3/4"
That means one foot plus two inches plus three quarters of one inch. But if you're processing keyboard input, so should these:
#Put the dash in the wrong place
1' 2-3/4"
# Call the units something else
1ft 2 3/4inches
# Omit units altogether
1 2 3/4
Going from decimal feet to architectural units is easier than the other way around. If you're learning python, you should tackle this first as I think it illuminates the way to do the reverse. Here's how I did it. It's a pretty readable (read: naïve) solution:
def arch_units(num, max_denominator=16):
# Special case for omitting fraction in output
if max_denominator == 0:
max_denominator = 1
# Calculate values
ft = math.floor(num)
inch = math.floor((num - ft) * 12)
error = (num - ft - (inch / 12)) * 12
frac = round(error * max_denominator)
error = (num - ft - (inch / 12) - (frac / (12 * max_denominator))) / (12 * max_denominator)
# Carry the one
if frac == max_denominator:
inch = inch + 1
frac = frac - max_denominator
Then just build the output string how you like
# Build output string
output = []
# Output format
ft_display = f"{ft}' "
inch_display = f'{inch}'
frac_display = f'{frac}/{max_denominator}'
if ft != 0:
output.append(ft_display)
if inch != 0:
output.append(inch_display)
if inch != 0 and frac != 0:
output.append('-')
if frac != 0:
output.append(frac_display)
if frac + inch != 0:
output.append('"')
sep = ""
output = str(sep.join(output))
return output
And then here is where I do the opposite:
def arch2dec(s): ## input is any common format for imperial length measurement, ex: 6'4-/4", 10.5 mi, 8f oot 1
s = s.strip() # trim whitespace
s = s.replace('-', ' ')
indices = []
n = 0
mode = 'U' # mode is U so first number increments n counter
Setting things up
for i, c in enumerate(s): # check every character
number_bool = c.isnumeric() or c == '.' # is it a number or '.'
if mode == 'N': # if we're reading a number
if not number_bool: # and we find a non-number
mode = 'U' # switch to reading a unit
indices.append(i) # and record the index of where this happened
if mode == 'U': # if we're reading a unit
if number_bool: # and we find a number
mode = 'N' # switch to reading a number
n = n + 1 # increment the number of number-unit pairs we're dealing with
indices.append(i) # and record the index of where this happened
indices.append(len(s))
# print(i, s[i], mode)
phrases = []
# Create phrases of entire number strings and strings of whatever is between them:
for a, b in zip(indices, indices[1:]):
phrase = s[a:b]
phrase = phrase.strip()
phrases.append(phrase) # make a list of phrases
# If a non-number phrase is recognized as an alias for a unit, replace it with that unit
phrases = [aliases.get(item, item) for item in phrases] # lookup units
phrases = [phrase for phrase in phrases if phrase != ''] # remove empty items
# print(phrases, n)
# Find fractions by looking for '/ and evaluating it as division:
for i in phrases:
if i == '/':
div_index = phrases.index('/')
numerator = float(phrases[div_index - 1])
denominator = float(phrases[div_index + 1])
phrases[div_index - 1] = numerator / denominator
del phrases[div_index]
del phrases[div_index]
n = n - 1
# Create separate lists for numbers and non-numbers:
numbers = []
factors = []
total = 0
# Create and sum components
for i, phrase in enumerate(phrases):
d = (i, phrase)
if isfloat(phrase):
numbers.append(d)
else:
factors.append(d)
# print(f'number, factors: {numbers}, {factors}. n = {n}')
# If list of units is empty, assume numbers are presented as feet>inches>inches
# example: 6 4 1/2 is interpreted as 6' 4-1/2"
if not factors:
# print('no units')
for i, number in enumerate(numbers):
total = total + unit_order[i] * float(number[1])
return total
# Units affect numbers that are before themselves but after the previous unit,
# and numbers after themselves if they are the last unit
step = 0
multipy = 1
for j, number in numbers:
component = 0
for index, factor in enumerate(factors):
i, f = factor
f = units[f]
if step <= j < i:
multipy = f
# print(f'i={i} j={j}')
# print(f'value is {number}, unit is {factor}, factor is,{f}')
step = i
component = component + float(number) * float(multipy)
# print(f'component: {number} {factor[1]} * {multipy}')
total = total + component
return total
-Published 8 pm Tue, Feb 7 2023-