patternpythonModerate
Tapes, Trees, Trunks & Tallies
Viewed 0 times
tapestrunkstalliestrees
Problem
I've written these two command-line tools to be used to facilitate forestry surveys.
For a little background, the idea here is that often a landowner needs to know precisely how much wood is coming out of his dirt. Not only that, but he has to account for the fact that you can't turn a cylinder (trees) into rectangular prisms (boards) without a bit of loss.
So what the landowner does is he pays my client to come down and run around the woods with a bit of measuring tape, taking the circumferences of trees. He then apparently works out the radius in his head (I hope that's what he does, otherwise everything he's ever done is completely wrong) and shouts it into a radio along with the species and how tall he believes the tree is (in 16-foot logs). Sometimes, such as in the case of a tree that has two trunks, he'll call out more than one of the same sort of tree at one time.
The computer operator - that's me - then types a three-part statement into the computer containing a short arbitrary string that identifies the tree species ("WO" for White Oak, "ASH" for Ash, "YGG" for Yggdrasil, etc...), the diameter, and the length in 16-foot logs or halves thereof. If the forester called in more than one of the same kind of tree, I can also add a number on the end that tells the program how many of the specified tree I want to add.
This is all saved to disk. Once we're back at home base, we can take the data file the tallying program produced and feed it into the analysis program, which computes (using a formula derived from the original table the client used to do this process by hand, itself derived from arcane magics) the usable board footage of each variety of tree and lists the total board footage, total number of trees, board footage by species, board footage by species and size (one log that the sawmill can produce two boards from is worth more than two boards the 'mill can produce one from), etc. etc.
The client was very (indeed, quite irrationally) concerned about
For a little background, the idea here is that often a landowner needs to know precisely how much wood is coming out of his dirt. Not only that, but he has to account for the fact that you can't turn a cylinder (trees) into rectangular prisms (boards) without a bit of loss.
So what the landowner does is he pays my client to come down and run around the woods with a bit of measuring tape, taking the circumferences of trees. He then apparently works out the radius in his head (I hope that's what he does, otherwise everything he's ever done is completely wrong) and shouts it into a radio along with the species and how tall he believes the tree is (in 16-foot logs). Sometimes, such as in the case of a tree that has two trunks, he'll call out more than one of the same sort of tree at one time.
The computer operator - that's me - then types a three-part statement into the computer containing a short arbitrary string that identifies the tree species ("WO" for White Oak, "ASH" for Ash, "YGG" for Yggdrasil, etc...), the diameter, and the length in 16-foot logs or halves thereof. If the forester called in more than one of the same kind of tree, I can also add a number on the end that tells the program how many of the specified tree I want to add.
This is all saved to disk. Once we're back at home base, we can take the data file the tallying program produced and feed it into the analysis program, which computes (using a formula derived from the original table the client used to do this process by hand, itself derived from arcane magics) the usable board footage of each variety of tree and lists the total board footage, total number of trees, board footage by species, board footage by species and size (one log that the sawmill can produce two boards from is worth more than two boards the 'mill can produce one from), etc. etc.
The client was very (indeed, quite irrationally) concerned about
Solution
def load_trees():
trees=dict()You only use this variable in the case that you successfully opened the file. I'd move it after the open.
try:
with open("trees.txt","r") as treefil:I recommend not shortening names like this. Call it
tree_file it not really any serious amount of more typing, but its way easier to see what it is.while True:
lin=treefil.readline()
if lin=="":
breakThe line can be replaced by
for lin in treefile:. It'll give you each line in the file one at a time.species, count = lin.split()
trees[species]=int(count)What if the file is messed up and count isn't a number here? You may very well simply not care.
except FileNotFoundError:
return dict()
return treesI recommend moving
return trees into the try: block. def save_trees(trees):
with open("trees.txt","w") as treefil:
for thetree in trees:
treefil.write(thetree+" "+str(trees[thetree])+"\n")
returnThis line does nothing.
def main():
print("Autotally V0")
print("Ready")
while True:
cmd=input(">").upper()
cmdtup=list(cmd.split())
print (cmdtup)You note that you aren't following PEP8. That's okay. But you should probably be consistent. Here you've got an extra space after print, but not for any other function.
if cmdtup[0]=="LIST":
print(load_trees())The output of printing a python dict would seem rather programmer friendly, not use friendly. I can't imagine that the output of this statement is very useful.
else:
trees=load_trees()
if len(cmdtup) >= 3:
cmdtup.append(1)Why >=? If the user types 5 parameters, you want to add a sixth? It seems to me that you really want is ==. That is if the user only passes three parameters, add a fourth.
key=cmdtup[0]+"_"+cmdtup[1]+"_"+cmdtup[2]For keys in internal data structures, its probably best to use a tuple, not a string. It's just less awkward to work with.
try:
trees[key]=int(trees[key])+int(cmdtup[3])
except KeyError:
trees[key]=int(cmdtup[3])Why don't you use a defaultdict here?
What happens when the user passes 2 or 4 parameters? Your program will either die or do the wrong thing.
try:
print("Inserted "+str(cmdtup[3])+" "+str(cmdtup[0])+" of diameter "+str(cmdtup[1])+" with "+str(cmdtup[2])+" logs.")
except IndexError:
print("Inserted "+str(cmdtup[0])+" of diameter "+str(cmdtup[1])+" with "+str(cmdtup[2])+" logs.")So,
cmd_tup already contains strings, there is no need to pass it to str again. In what situations would this throw an IndexError?save_trees(trees)
if __name__ == '__main__':
main()
def scribner(diameter,length):
"Convert length and diameter to board-feet via the Scribner method. WARNING: Only valid for measurements at top of tree. Do not use."
a = (0.0494 * diameter * diameter * length)
b = (0.124 * diameter * length)
c = (0.269 * length)
return a-b-c
def regressive_scribner(d,l):
"Convert length (in feet) and diameter (in inches) to board-feet via an equation derived from the Scribner tables. Accurate to around 10 board feet."
v = 0.0942919095863512*d**2 + 0.0231348479474668*l*d**2 - 16.494587251523 - 0.119077488412871*l - 0.00210681861605682*d*l**2
return v
def tree_to_dl(tree):
"Takes a string tree representation and turns it into a tuple of (species, diameter, logs)"
species, diameter, logs = tree.split("_")
diameter=int(diameter)
logs=float(logs)
return species, diameter, logs
from treetally import load_trees
if __name__ == '__main__':
trees=load_trees()
totaltrees=sum(trees.values())
print("# of trees:",totaltrees)
#We want all these dicts to start out with values of zero for every type of tree;
#so we set their default_factory to a lambda that simply returns 0
retzero=lambda: 0Actually you can use int for this purpose.
int() returns 0. Furthermore, collections.Counter() is actually even better.species_counts=collections.defaultdict(retzero)
spec_diam_counts=collections.defaultdict(retzero)
species_bf=collections.defaultdict(retzero)
spec_diam_bf=collections.defaultdict(retzero)
for tree, count in trees.items():
spec, diam, logs = tree_to_dl(tree)
species_counts[spec]+=count
spec_diam_counts[(spec,diam)]+=count
bf=scribner(diam,int(logs*16)) #Scribner wants feet; 16 feet to a log
species_bf[spec]+=int(bf)*count
spec_diam_bf[(spec, diam)]+=int(bf)*count
totalbf=int(sum(species_bf.values()))
print("Total board feet:",totalbf)
print("Avg. BF per tree: ",(totalbf//totaltrees))Why round for the average?
```
print("Board feet by species:")
ppri
Code Snippets
def load_trees():
trees=dict()try:
with open("trees.txt","r") as treefil:while True:
lin=treefil.readline()
if lin=="":
breakspecies, count = lin.split()
trees[species]=int(count)except FileNotFoundError:
return dict()
return treesContext
StackExchange Code Review Q#52162, answer score: 10
Revisions (0)
No revisions yet.