9

I have a database that was created and is used by an architecture firm. All measurements are stored in a format like this: 15-3/4" and 12' 6-3/4".

Is there a way to convert these types of measurements into floating point in Python? Or is there a library out there that provides this functionality?

Likewise, how would you convert from a floating point to the above format?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Clayton
  • 285
  • 1
  • 17
  • 1
    Of course there is a way. But those are values with units, so converting directly to floating point will also require defining the implied unit that it will be. you could also lose precision. – Keith Dec 30 '11 at 04:59
  • 1
    Also, Django is just a web framework, so it really doesn't have anything to do with this. Now you could create an object, that when stringified produces that format. Then you can just use any template. – Keith Dec 30 '11 at 05:00
  • Thanks Keith... I removed the Django portion. – Clayton Dec 30 '11 at 19:36

3 Answers3

5

Depending on how regular the patterns are, you can use str.partition to do the parsing:

def architectural_to_float(text):
    ''' Convert architectural measurements to inches.

        >>> for text in """15-3/4",12' 6-3/4",3/4",3/4',15',15",15.5'""".split(','):
        ...     print text.ljust(10), '-->', architectural_to_float(text)
        ...
        15-3/4"    --> 15.75
        12' 6-3/4" --> 150.75
        3/4"       --> 0.75
        3/4'       --> 9.0
        15'        --> 180.0
        15"        --> 15.0
        15.5'      --> 186.0

    '''
    # See http://stackoverflow.com/questions/8675714
    text = text.replace('"', '').replace(' ', '')
    feet, sep, inches = text.rpartition("'")
    floatfeet, sep, fracfeet = feet.rpartition('-')
    feetnum, sep, feetdenom = fracfeet.partition('/')
    feet = float(floatfeet or 0) + float(feetnum or 0) / float(feetdenom or 1)
    floatinches, sep, fracinches = inches.rpartition('-')
    inchesnum, sep, inchesdenom = fracinches.partition('/')
    inches = float(floatinches or 0) + float(inchesnum or 0) / float(inchesdenom or 1)
    return feet * 12.0 + inches
Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
1

Consider the following self commented code. I tried to keep in simple

>>> from fractions import Fraction
>>> def Arch2Float(num):
    #First Partition from Right so that the Feet and Unit always
    #Remains aligned even if one of them is absent
    ft,x,inch=num.rpartition("\'")
    #Convert the inch to a real and frac part after stripping the
    #inch (") identifier. Note it is assumed that the real and frac
    #parts are delimited by '-'
    real,x,frac=inch.strip("\"").rpartition("-")
    #Now Convert every thing in terms of feet which can then be converted
    #to float. Note to trap Error's like missing or invalid items, its better
    #to convert each items seperately
    result=0
    try:
        result = int(ft.strip("\'"))
    except ValueError:
        None
    #Convert the real inch part as a fraction of feet
    try:
        result +=  Fraction(int(real),12)
    except ValueError:
        None
    #Now finally convert the Fractional part using the fractions module and convert to feet
    try:
        result+=Fraction(frac)/12
    except ValueError:
        None
    return float(result)    

Acid Test

>>> print Arch2Float('15-3/4"')     # 15-3/4" (without ft)
1.3125
>>> print Arch2Float('12\' 6-3/4"') #12' 6-3/4"
12.5625
>>> print Arch2Float('12\'6-3/4"')  #12'6-3/4" (without space)
12.5625
>>> print Arch2Float('3/4"')        #3/4" (just the inch)
0.0625
>>> print Arch2Float('15\'')        #15' (just ft)
15.0
>>> print Arch2Float('15')          #15 (without any ascent considered as inch)
1.25

Converting from Float to Architecture would be easy as you don't have to take the pain to parsing

>>> def Float2Arch(num):
    num=Fraction(num)
    ft,inch=Fraction(num.numerator/num.denominator),Fraction(num.numerator%num.denominator)/num.denominator*12
    real,frac=inch.numerator/inch.denominator,Fraction(inch.numerator%inch.denominator,inch.denominator)
    return '{0}\' {1}-{2}"'.format(ft,real,frac)

Acid Test

>>> print Float2Arch(Arch2Float('12\' 6-3/4"'))
12' 6-3/4"
>>> print Float2Arch(Arch2Float('15-3/4"'))
1' 3-3/4"
>>> print Float2Arch(Arch2Float('12\'6-3/4"'))
12' 6-3/4"
>>> print Float2Arch(Arch2Float('3/4"'))
0' 0-3/4"
>>> print Float2Arch(Arch2Float('15\''))
15' 0-0"
>>> print Float2Arch(Arch2Float('15'))
1' 3-0"
>>> 

Note*** Its important to keep the float representation in either the lowest denominator (inch) or the highest represented denominator (feet). I opted for the highest in this case feet. If you wan't to lower it you can multiply it by 12.


Update to cater Rounding Request (Not sure if this is elegant but does the Job)

def Float2Arch(num):
    num=Fraction(num)
    ft,inch=Fraction(num.numerator/num.denominator),Fraction(num.numerator%num.denominator)/num.denominator*12
    real,frac=inch.numerator/inch.denominator,Fraction(inch.numerator%inch.denominator,inch.denominator)
    for i in xrange(1,17):
        if Fraction(frac) < Fraction(1.0/16*i): break
    frac=Fraction(1.0/16*i)
    if frac>= 1:
        real+=1
        frac=0
    return '{0}\' {1}-{2}"'.format(ft,real,frac)
Abhijit
  • 62,056
  • 18
  • 131
  • 204
  • Per @Keith's comment, I would add unit to this and turn the float into a struct or class with a float and a unit. – Jesse Smith Dec 30 '11 at 16:47
  • @JesseSmith, You just need to convert it to a string and add the unit. For ex `str(Arch2Float('12\' 6-3/4"'))+ ' ft'` – Abhijit Dec 30 '11 at 21:12
  • @Abhijit, this solutions works as well. I have combined @RaymondHettinger with your `Float2Arch` function. How would you suggest rounding the returned value so an input like this **18.33** returns **18' 4"**? – Clayton Dec 31 '11 at 17:17
  • 1
    @Clayton, This is not rounding. But you can achieve something similar by doing `math.ceil(18.33*10)/10` before passing the value to Float2Arch – Abhijit Dec 31 '11 at 17:37
  • @Abhijit, sorry for the confusion... what I meant was .33 of 1 foot is approximately 4". So if you `Float2Arch('18.33')` the returned value expected would be 18' 4" rather than 18' 3-24/25". How would you suggest rounding this up based on a specified increment (ie: 1/16" which is typically the smallest increment on a tape measure)? – Clayton Dec 31 '11 at 18:05
  • @Clayton, Just see the update (BTW not tested well and I feel this can be done more elegantly then the posted solution) – Abhijit Dec 31 '11 at 18:34
0

Architectural to floating point:

import re

regex = re.compile('(\d+\' )*(\d+)-(\d+)\/(\d+)"')
regex.sub(lambda m: str((int((m.group(1) or '0').split("'")[0]) * 12)
  + int(m.group(2)))
  + ('%.2f' % (int(m.group(3)) / float(m.group(4))))[1:], measurement)

It's really atrocious, but I haven't worked with Python in a while; I don't doubt there's a much cleaner way to do this, but it does handle lack of feet nicely. It does always expect inches, though, so measurements like 12' would have to be 12' 0" to be parsed properly.

ranksrejoined
  • 1,229
  • 9
  • 9