""" Calculate the total length of zippers, tabulated by item/category. """ ### # generalized summing and printing methods INDENTATION=3 indenter = ' '*INDENTATION def DefaultValueFormatter(value, total): return [str(value)] def FormatFraction(part, whole): f = part/whole text = '%2.2f' % f for level in (.04, .8, .2): if f >= level: text += '*' return text def PrintInfo( recursiveInfoDict, valueFormatter = DefaultValueFormatter, fractionLimit = None, indentLimit = None, ): """ Print tabulated totals based on an info dict. @param recursiveInfoDict: a recursive labeled dictionary, for example: dictionary = { 'Category Name' : { 'First Part' : 43, 'Second Part' : 2, }, 'Item' : 9, } @param valueFormatter: return a list of strings to be the formatted representation(s) of a value @param fractionLimit: values which are less than the given fraction of the total will be omitted @param indentLimit: entries which are indented more than the given number of levels will be omitted """ infoLines, total = GetInfoLines('Total', recursiveInfoDict) formattedLines = [] anyLimit = not ((fractionLimit is None) and (indentLimit is None)) for infoLine in infoLines: indentAmount, name, value = infoLine f = value/total if anyLimit: if fractionLimit is not None and f < fractionLimit: continue if (indentLimit is not None and indentAmount > indentLimit): continue formattedLine = [indenter*indentAmount + name] \ + valueFormatter(value, total) \ + [FormatFraction(value, total)] formattedLines.append(formattedLine) PrintAlignedColumns(formattedLines) COLUMN_SEP = 2 def PrintAlignedColumns(rows, padding=INDENTATION): """ Given a 2D array or strings, print them in (left-)aligned columns. (Indentation should be included in the input.) rows = [ ['a0', 'a1', 'a2'], [' b0', 'b1', 'b2'], ] """ if not rows: return columnWidths = [0,]*len(rows[0]) for row in rows: for i, s in enumerate(row): columnWidths[i] = max(columnWidths[i], len(s)) for row in rows: for w, s in zip(columnWidths, row): print s, print ' '*((w-1) - len(s) + COLUMN_SEP), print def GetInfoLines(name, info, indent=0): """ From a recursive info dictionary, order lines and calculate subtotals. """ lines = [] total = 0 for partName, mass in info.iteritems(): if isinstance(mass, dict): subLines, subTotal = GetInfoLines( partName, mass, indent=indent+1) lines += subLines total += subTotal else: lines.append((indent+1, partName, mass)) total += mass lines.insert(0, (indent, name, total)) return lines, total ### # zipper data and unit conversion / formatting import math CM_PER_METER = 100 CM_PER_INCH = 2.54 INCHES_PER_FOOT = 12 # Units are centimeters. ZIPPERS = { 'Pack' : { 'Floating Top Pocket' : { 'Outside' : 36, 'Outside' : 25, }, 'Side Pocket' : 23.5, 'Hip Mesh Pocket' : 19, 'J-Zip' : 56.5, }, 'Lens Pouch (Tamrac)' : 23, 'Sleeping Bag Liner Pouch' : 17, 'Fleece Vest' : { 'Front' : 68, '2x Pockets' : 2*17, }, 'REI Sahara Pants' : { 'Hip Pocket, Interior' : 7.5, 'Fly' : 15, 'Back' : 14, }, 'REI eVent Rain Shell' : { '2x Side' : 17.5, 'Sternum' : 16, 'Front' : 67, }, 'Sierra Designs Light Year 1 Tent' : { 'Rain Fly' : 94, 'Door' : 144, }, 'Sleeping Bag' : 171, } def FormatCentimeters(cm): if cm >= CM_PER_METER: v = cm/CM_PER_METER unit = 'm' else: v = cm unit = 'cm' return '%.2f%s' % (v, unit) def FormatImperial(cm): inches = cm/CM_PER_INCH if inches >= INCHES_PER_FOOT: partialFeet, feet = math.modf(inches/INCHES_PER_FOOT) inches = partialFeet*INCHES_PER_FOOT valueStr = "%d' " % feet else: valueStr = '' valueStr = '%s%.1f"' % (valueStr, inches) return valueStr def FormatInfo(value, total): return [FormatCentimeters(value), FormatImperial(value)] PrintInfo(ZIPPERS, valueFormatter=FormatInfo)