The other day I was skimming trough articles on different subjects on Wikipedia. After a while I stumbled across the article on L-systems. In short, L-systems is method for generating fractals using simple string manipulations. You start out with an initial string, and for each character (called variable in L-system terminology) in that string you apply a set of rules that change that character into a set of new characters. Then you repeat n times. The resulting string is then a recipe for a graph or geometric structure. See the Wikipedia article for more details and examples.
I decided to try to implement this in Python. I only implemented the string manipulation engine and not a graphing tool. It was fairly easy, and the first method I came up with takes only about 15 lines of code and relies on recursion. A method without recursion is a couple of lines more, but probably more efficient. The user have to supply their own substitution rules by using a Python dictionary. Each key in the dictionary corresponds to a variable, and constants are defined implicitly by the production rules.
Here are two example from the Wikipedia article:
#Algae example algaeRules = { "A" : "AB", "B" : "A",} algaeExample = lsystem("A", 7, algaeRules) print(algaeExample) #ABAABABAABAABABAABABAABAABABAABAAB
#Sierpinski triangle example sierpinskiRules = {"A": "B-A-B", "B": "A+B+A"} sierpinskiExample = lsystem("A", 3, sierpinskiRules) print(sierpinskiExample) #B-A-B+A+B+A+B-A-B-A+B+A-B-A-B-A+B+A-B-A-B+A+B+A+B-A-B
Here is the code for the lsystem() function, including the function that does not use recursion.
def _applyRules(letter, rules): """Internal function for applying production rules on a single character.""" try: return rules[letter] except: return letter def lsystem(init, n, rulesDict): """Return the n-th iteration of a L-system (generated recursively).""" if n <= 0: return init if n == 1: return "".join([_applyRules(i, rulesDict) for i in init]) else: newCond = "".join([_applyRules(i, rulesDict) for i in init]) return lsystem(newCond, n-1, rulesDict) def lsystemNonRecursive(init, n, rulesDict): """Return the n-th iteration of a L-system.""" if n <= 0: return init else: currentCond = init while True: currentCond = "".join([_applyRules(i, rulesDict) for i in currentCond]) n -= 1 if n == 0: return currentCond
Jeg forsøkte meg på min egen variant, før jeg leste denne posten, og det ble stort sett ganske likt. Jeg hadde ikke tenkt på try-except-varianten for å fylle ut dicten. Jeg håper det blir riktig formatert:
Jeg er ikke helt fornøyd med fyllfunksjonfunksjonen min, den ser litt uelegant ut, men jeg er ikke fornøyd med den mer elegante try-except varianten heller, for den skaper unødvendig mange function calls, som koster mer enn å looke up i en dictionary, regner jeg med?
Tenkt på å lage en variant for stokastiske L-systemer? Det letteste blir kanskje forandre inputet til en liste av 3-tupler (som (“A”,0.3,”AB”),(“A”,0.7,”BA”)), sjekke at sannsynlighetene summerer seg til en, og kjøre en uniform fordeling for å velge riktig variant.
Synes ideen om å fylle opp dict-en med de manglende verdiene var smart. Det hadde jeg ikke tenkt på. Det er sikkert litt raskere enn min løsning. Jeg vet ikke hvor mye ett funksjonskall koster, men try/except koster ihvertfall litt mer enn en en enkel if/else.
Jeg har tenkt så mye på hvordan jeg ville gjort det med stokastiske regler. Hvis jeg skulle fortsatt med et dict-basert system ville jeg kanskje prøvd å ha en 2-tuppel, med ett av elementene en liste over sannsynlighetene og det andre elementet en liste over tilsvarende outputs.