I have a simple program that takes a JSON representation of some planets and builds a representation of those planets. It is very straight-forward:
The json input maintains the structure of what celestial bodies(CB) have satellites, what CBs are satellites of what, and various attributes about said CB. That is fed into a simple parser which builds a representation of the system from that input.
The catch here is that each CB can have an n-deep list of orbiting objects. The cleanest/most elegant way to solve this problem is with recursion. Anytime we see there are orbiting bodies, we recurse by use of "get_satellites" until there are no more orbiting objects for the given body.
Below is the json and simplified code, which is runnable, but isn't functioning correctly.
What happens below is that earth, venus, mars, and mercury all end up having 3 satellites each, even though earth should only have one and mars should only have 2.
I have looked at: Python recursive function variable scope
and "Least Astonishment" and the Mutable Default Argument
These may be relevant, but I' not 100% sure because I don't use default arguments in the get_satellites() method call. It seems like it shouldn't apply, but the symptoms that are presenting suggest otherwise.
Code
import sys
import json
"""
Parent class for bodies
"""
class Body(object):
# name of entity
name = ""
type = None
#type of entity
class Types:
Planet, BlackHole, Star, Comet, Asteroid, Satellite = range(6)
def get_type(t):
t = t.lower()
if t == "planet":
return Body.Types.Planet
elif t == "star":
return Body.Types.Star
elif t == "comet":
return Body.Types.Comet
elif t == "asteroid":
return Body.Types.Asteroid
elif t == "satellite":
return Body.Types.Satellite
elif t == "blackhole":
return Body.Types.BlackHole
else:
return Body.Types.Planet
def __init__(self, name, t):
self.name = name
self.type = t
def to_string(self):
return "Name: %s\t Type: %s" % (self.name, self.type)
"""
planet definition
"""
class Planet(Body):
# point where body is farthest to orbiting body in AU
aphelion = 0
# point where body is closest to orbiting body in AU
perihelion = 0
# List of orbiting bodies
satellites = {}
# orbiting body of this body
orbiting_body = {}
def __init__(self, name, t):
Body.__init__(self, name, t)
def add_satellites(self, bodies):
for body in bodies:
print("PAS: adding: %s to: %s" % (body.name, self.name))
self.satellites[body.name] = body
def add_satellite(self, body):
print("PAS: adding: " + body.name)
self.satellites[body.name] = body
def add_orbiting_body(self, body):
self.orbiting_body[body.name] = body
def simulate(self):
print("simulating a planet")
def to_string(self):
s = Body.to_string(self)
s += "Planet: %s has %d satellites\n" % (self.name, len(self.satellites))
return s
def __str__(self):
return self.to_string()
"""
star definition
"""
class Star(Body):
# weak stellar classification system
class Stages:
Nebula, Protostar, RedGiant, RedDwarf, WhiteDwarf, SuperNova, NeutronStar, BlackHole = range(8)
# which stage is the star in
stage = Stages.RedGiant
# light observed on earth
brightness = 0
# intrinsic brightness
absolute_magnitude = 0
# total amount of energy emitted
luminosity = 0
# list of orbiting bodies
satellites = {}
#
def __init__(self, name, t, stage=Stages.RedGiant):
Body.__init__(self, name, t)
self.stage = stage
def add_satellites(self, bodies):
for body in bodies:
#print("Star: adding: " + body.name)
self.satellites[body.name] = body
def add_satellite(self, body):
#print("Star: adding: " + body.name)
self.satellites[body.name] = body
def simulate(self):
print("simulating a star")
def to_string(self):
s = "%sStage: %s\n" % (Body.to_string(self), self.stage)
#if self.satellites:
for satellite in self.satellites:
s += self.satellites[satellite].to_string()
return s
def __str__(self):
return self.to_string()
"""
Allows us to build the body we need
"""
def build_body(body):
print(body)
t = Body.Types.get_type(body['type'])
if t == Body.Types.Planet:
b = Planet(body['name'], t)
elif t == Body.Types.Star:
b = Star(body['name'], t)
return b
"""
Get the orbiting bodies recursively
"""
def get_satellites(satellites):
bodies = []
x=0
# iterate mercury, venus, earth....
if satellites['orbiting_objects']:
for satellite in satellites['orbiting_objects']:
print("x is: %s" % x)
# build the body that satellites orbit
body = build_body(satellite)
# if mercury, venus, earth... have satellites
if satellite['orbiting_objects']:
#print("%s has satellites!" % body.name)
# recursively build the satellites
sats = get_satellites(satellite)
#for s in sats:
#print("on sats: %s"%s)
# body has satellites, add them
body.add_satellites(sats)
else:
print("%s has no satellites" % body.name)
#print("%s has %d satellites" % (body.name, len(body.satellites)))
bodies.append(body)
#if len(body.satellites) > 0:
#for b in body.satellites:
#print("satellites: %s" % b)
print("==========")
x += 1
else:
# add a satellite-less body to the system
bodies.append(build_body(satellites))
return bodies
def main():
if len(sys.argv) < 2:
print("Usage: python main.py path/to/simulation/data.json")
exit()
with open(sys.argv[1]) as file:
data = json.load(file)
print(data[0])
b = build_body(data[0])
sats = get_satellites(data[0])
b.add_satellites(sats)
print(b.to_string())
if __name__ == "__main__":
main()
JSON
[
{
"name": "sun",
"type": "star",
"mass": 1988550000000000000000000000000,
"volume": 1410000000000000000,
"gravity": 27.94,
"aphelion": 0,
"perihelion": 0,
"radius": 696300,
"orbiting_objects": [
{
"name": "mercury",
"type": "planet",
"mass": 330110000000000000000000,
"volume": 60830000000,
"gravity": 0.38,
"aphelion": 0.466697,
"perihelion": 0.307499,
"radius": 2440,
"orbiting_objects": []
},
{
"name": "venus",
"type": "planet",
"mass": 4867500000000000000000000,
"volume": 928430000000,
"gravity": 0.904,
"aphelion": 0.728213,
"perihelion": 0.718440,
"radius": 6051.8,
"orbiting_objects": []
},
{
"name": "earth",
"type": "planet",
"mass": 5972370000000000000000000,
"volume": 1083210000000,
"gravity": 9.807,
"aphelion": 1.01673,
"perihelion": 0.9832687,
"radius": 6371.0,
"orbiting_objects": [
{
"name": "moon",
"type": "planet",
"mass": 73420000000000000000000,
"volume": 21958000000,
"gravity": 0.1654,
"aphelion": 0.00270993162,
"perihelion": 0.00242383129,
"radius": 1737.1,
"orbiting_objects": []
}
]
},
{
"name": "mars",
"type": "planet",
"mass": 641710000000000000000000,
"volume": 163180000000,
"gravity": 0.376,
"aphelion": 1.6660,
"perihelion": 1.3814,
"radius": 3389.5,
"orbiting_objects": [
{
"name": "phobos",
"type": "planet",
"mass": 0,
"volume": 0,
"gravity": 0,
"aphelion": 0,
"perihelion": 0,
"radius": 0,
"orbiting_objects": []
},
{
"name": "deimos",
"type": "planet",
"mass": 0,
"volume": 0,
"gravity": 0,
"aphelion": 0,
"perihelion": 0,
"radius": 0,
"orbiting_objects": []
}
]
}
]}]
Output
{'mass': 1988550000000000000000000000000, 'type': 'star', 'volume': 1410000000000000000, 'orbiting_objects': [{'mass': 330110000000000000000000, 'type': 'planet', 'volume': 60830000000, 'orbiting_objects': [], 'perihelion': 0.307499, 'name': 'mercury', 'aphelion': 0.466697, 'radius': 2440, 'gravity': 0.38}, {'mass': 4867500000000000000000000, 'type': 'planet', 'volume': 928430000000, 'orbiting_objects': [], 'perihelion': 0.71844, 'name': 'venus', 'aphelion': 0.728213, 'radius': 6051.8, 'gravity': 0.904}, {'mass': 5972370000000000000000000, 'type': 'planet', 'volume': 1083210000000, 'orbiting_objects': [{'mass': 73420000000000000000000, 'type': 'planet', 'volume': 21958000000, 'orbiting_objects': [], 'perihelion': 0.00242383129, 'name': 'moon', 'aphelion': 0.00270993162, 'radius': 1737.1, 'gravity': 0.1654}], 'perihelion': 0.9832687, 'name': 'earth', 'aphelion': 1.01673, 'radius': 6371.0, 'gravity': 9.807}, {'mass': 641710000000000000000000, 'type': 'planet', 'volume': 163180000000, 'orbiting_objects': [{'mass': 0, 'type': 'planet', 'volume': 0, 'orbiting_objects': [], 'perihelion': 0, 'name': 'phobos', 'aphelion': 0, 'radius': 0, 'gravity': 0}, {'mass': 0, 'type': 'planet', 'volume': 0, 'orbiting_objects': [], 'perihelion': 0, 'name': 'deimos', 'aphelion': 0, 'radius': 0, 'gravity': 0}], 'perihelion': 1.3814, 'name': 'mars', 'aphelion': 1.666, 'radius': 3389.5, 'gravity': 0.376}], 'perihelion': 0, 'name': 'sun', 'aphelion': 0, 'radius': 696300, 'gravity': 27.94}
{'mass': 1988550000000000000000000000000, 'type': 'star', 'volume': 1410000000000000000, 'orbiting_objects': [{'mass': 330110000000000000000000, 'type': 'planet', 'volume': 60830000000, 'orbiting_objects': [], 'perihelion': 0.307499, 'name': 'mercury', 'aphelion': 0.466697, 'radius': 2440, 'gravity': 0.38}, {'mass': 4867500000000000000000000, 'type': 'planet', 'volume': 928430000000, 'orbiting_objects': [], 'perihelion': 0.71844, 'name': 'venus', 'aphelion': 0.728213, 'radius': 6051.8, 'gravity': 0.904}, {'mass': 5972370000000000000000000, 'type': 'planet', 'volume': 1083210000000, 'orbiting_objects': [{'mass': 73420000000000000000000, 'type': 'planet', 'volume': 21958000000, 'orbiting_objects': [], 'perihelion': 0.00242383129, 'name': 'moon', 'aphelion': 0.00270993162, 'radius': 1737.1, 'gravity': 0.1654}], 'perihelion': 0.9832687, 'name': 'earth', 'aphelion': 1.01673, 'radius': 6371.0, 'gravity': 9.807}, {'mass': 641710000000000000000000, 'type': 'planet', 'volume': 163180000000, 'orbiting_objects': [{'mass': 0, 'type': 'planet', 'volume': 0, 'orbiting_objects': [], 'perihelion': 0, 'name': 'phobos', 'aphelion': 0, 'radius': 0, 'gravity': 0}, {'mass': 0, 'type': 'planet', 'volume': 0, 'orbiting_objects': [], 'perihelion': 0, 'name': 'deimos', 'aphelion': 0, 'radius': 0, 'gravity': 0}], 'perihelion': 1.3814, 'name': 'mars', 'aphelion': 1.666, 'radius': 3389.5, 'gravity': 0.376}], 'perihelion': 0, 'name': 'sun', 'aphelion': 0, 'radius': 696300, 'gravity': 27.94}
x is: 0
{'mass': 330110000000000000000000, 'type': 'planet', 'volume': 60830000000, 'orbiting_objects': [], 'perihelion': 0.307499, 'name': 'mercury', 'aphelion': 0.466697, 'radius': 2440, 'gravity': 0.38}
mercury has no satellites
==========
x is: 1
{'mass': 4867500000000000000000000, 'type': 'planet', 'volume': 928430000000, 'orbiting_objects': [], 'perihelion': 0.71844, 'name': 'venus', 'aphelion': 0.728213, 'radius': 6051.8, 'gravity': 0.904}
venus has no satellites
==========
x is: 2
{'mass': 5972370000000000000000000, 'type': 'planet', 'volume': 1083210000000, 'orbiting_objects': [{'mass': 73420000000000000000000, 'type': 'planet', 'volume': 21958000000, 'orbiting_objects': [], 'perihelion': 0.00242383129, 'name': 'moon', 'aphelion': 0.00270993162, 'radius': 1737.1, 'gravity': 0.1654}], 'perihelion': 0.9832687, 'name': 'earth', 'aphelion': 1.01673, 'radius': 6371.0, 'gravity': 9.807}
x is: 0
{'mass': 73420000000000000000000, 'type': 'planet', 'volume': 21958000000, 'orbiting_objects': [], 'perihelion': 0.00242383129, 'name': 'moon', 'aphelion': 0.00270993162, 'radius': 1737.1, 'gravity': 0.1654}
moon has no satellites
==========
PAS: adding: moon to: earth
==========
x is: 3
{'mass': 641710000000000000000000, 'type': 'planet', 'volume': 163180000000, 'orbiting_objects': [{'mass': 0, 'type': 'planet', 'volume': 0, 'orbiting_objects': [], 'perihelion': 0, 'name': 'phobos', 'aphelion': 0, 'radius': 0, 'gravity': 0}, {'mass': 0, 'type': 'planet', 'volume': 0, 'orbiting_objects': [], 'perihelion': 0, 'name': 'deimos', 'aphelion': 0, 'radius': 0, 'gravity': 0}], 'perihelion': 1.3814, 'name': 'mars', 'aphelion': 1.666, 'radius': 3389.5, 'gravity': 0.376}
x is: 0
{'mass': 0, 'type': 'planet', 'volume': 0, 'orbiting_objects': [], 'perihelion': 0, 'name': 'phobos', 'aphelion': 0, 'radius': 0, 'gravity': 0}
phobos has no satellites
==========
x is: 1
{'mass': 0, 'type': 'planet', 'volume': 0, 'orbiting_objects': [], 'perihelion': 0, 'name': 'deimos', 'aphelion': 0, 'radius': 0, 'gravity': 0}
deimos has no satellites
==========
PAS: adding: phobos to: mars
PAS: adding: deimos to: mars
==========
Name: sun Type: 2Stage: 2
Name: earth Type: 0Planet: earth has 3 satellites
Name: mars Type: 0Planet: mars has 3 satellites
Name: mercury Type: 0Planet: mercury has 3 satellites
Name: venus Type: 0Planet: venus has 3 satellites
I would expect that earth has 1 satellite, mars has 2, and venus and mercury have 0.