# Module 02#

Exercise 2a/b

house = {
"living": {
"exits": {"north": "kitchen", "outside": "garden", "upstairs": "bedroom"},
"people": ["James"],
"capacity": 2,
},
"kitchen": {"exits": {"south": "living"}, "people": [], "capacity": 1},
"garden": {"exits": {"inside": "living"}, "people": ["Sue"], "capacity": 3},
"bedroom": {
"exits": {"downstairs": "living", "jump": "garden"},
"people": [],
"capacity": 1,
},
}


We can get a simpler dictionary with just capacities like this:

{name: room['capacity'] for name, room in house.items()}

{'living': 2, 'kitchen': 1, 'garden': 3, 'bedroom': 1}


To get the current number of occupants, we can use a similar dictionary comprehension. Remember that we can filter (only keep certain rooms) by adding an if clause:

{name: len(room["people"]) for name, room in house.items() if len(room["people"]) > 0}

{'living': 1, 'garden': 1}


Things to notice here:

1.99999 doesn’t round, even if you did int(1.9999999) you would get 1.

round(1.9999999) or int(1.9999999999999999) would give you 2

Strings aren’t integers

Even though 20 and 5 are integers and they divide to give 4, the result is a float, not an int. Floor division (20 // 5) will return an integer.

‘10.’ is a float not an integer

Can do this in one line using comprehension or could make an empty list and append to it.

def example_funct(*args):
op = [a for a in args if type(a)== int and a%2 == 0]
return op

example_funct(1, 1.99999999999, "three", 20/5, 5, 6, "sju", "8", 9, 10., 11, 12)

[6, 12]


After importing the libraries you can use dir(X) to list the attributes of each module

There will be some depreciation warnings from scipy instructing users to use numpy or numpy.lib (which can also be investigated via dir(numpy.lib)

pi: Use numpy.pi, scipy.pi, math.pi. statistics has tau but not pi.

log: Use numpy.log10, scipy.log10, math.log10. statistics has log but not log10: log(n)/log(10) can be used instead.

For log(+ive) we use +12.01 as an example while for log(-ive) we use -11.99 as an example.

Module

pi

log(+ive)

log(-ive)

mean

numpy

3.14159…

1.07954…

nan

5.0

scipy

3.14159…

1.07954…

(1.07881…+1.36437…j)

5.0

math

3.14159…

1.07954…

math domain error

n/a

statistics

n/a

1.07954…

math domain error

5

All libraries return the same value of pi. All libraries return the same value of log(+ive). scipy returns a complex number for the negative log example while all others produce an error.

statistics returns the mean as an integer whereas numpy and scipy return a float.

Broad range of options, this is simply one of the possibilities given in the original notebook with the inclusion of some typehinting and descriptions of methods/classes

import typing
class Maze:
"""
Here we can put a description of the class
"""
def __init__(self, name: str):
# We can also use typehints to signal what type a variable should be
# In this case the name of the maze would be a string.
self.name = name
self.rooms = {}

room.maze = self  # The Room needs to know which Maze it is a part of
self.rooms[room.name] = room # This means that we expect our Rooms class to have a 'name' property

def occupants(self):
"""
Return a list containing the occupants of the maze
"""
return [occupant for room in self.rooms.values() for occupant in room.occupants.values()]

def wander(self):
"""Move all the people in a random direction"""
for occupant in self.occupants():
occupant.wander()

def describe(self):
for room in self.rooms.values():
room.describe()

def step(self):
self.describe()
print("")
self.wander()
print("")

def simulate(self, steps):
for _ in range(steps):
self.step()

class Room:
def __init__(self, name: str, exits: dict, capacity: int, maze=None):
self.maze = maze
self.name = name
self.occupants = {}  # Note the default argument, occupants start empty
self.exits = exits  # Should be a dictionary from directions to room names
self.capacity = capacity

def has_space(self) -> bool:
"""
Check if the room has space and return a boolean (True/False)
"""
return len(self.occupants) < self.capacity

def available_exits(self) -> typing.List[str]:
return [
exit
for exit, target in self.exits.items()
if self.maze.rooms[target].has_space()
]

def random_valid_exit(self):
import random

if not self.available_exits():
return None
return random.choice(self.available_exits())

def destination(self, exit):
return self.maze.rooms[self.exits[exit]]

occupant.room = self  # The person needs to know which room it is in
self.occupants[occupant.name] = occupant

def delete_occupant(self, occupant):
del self.occupants[occupant.name]

def describe(self):
if self.occupants:
print(f"{self.name}: " + " ".join(self.occupants.keys()))

class Person:
def __init__(self, name: str, room=None):
self.name = name

def use(self, exit):
self.room.delete_occupant(self)
destination = self.room.destination(exit)
print(
"{some} goes {action} to the {where}".format(
some=self.name, action=exit, where=destination.name
)
)

def wander(self):
exit = self.room.random_valid_exit()
if exit:
self.use(exit)

james = Person("James")
sue = Person("Sue")
bob = Person("Bob")
clare = Person("Clare")

living = Room("livingroom", {"outside": "garden", "upstairs": "bedroom", "north": "kitchen"}, 2)
kitchen = Room("kitchen", {"south": "livingroom"}, 1)
garden = Room("garden", {"inside": "livingroom"}, 3)
bedroom = Room("bedroom", {"jump": "garden", "downstairs": "livingroom"}, 1)

house = Maze("My House")

for room in [living, kitchen, garden, bedroom]:


house.simulate(3)

livingroom: James
garden: Sue Clare
bedroom: Bob

James goes north to the kitchen
Sue goes inside to the livingroom
Clare goes inside to the livingroom

livingroom: Sue Clare
kitchen: James
garden: Bob

Sue goes upstairs to the bedroom
Clare goes outside to the garden
James goes south to the livingroom
Bob goes inside to the livingroom

livingroom: James Bob
garden: Clare
bedroom: Sue

James goes north to the kitchen
Bob goes outside to the garden
Clare goes inside to the livingroom


Something along the lines of this for the original question:

import requests
from IPython.display import Image

coordinates_as_lat_lon = [(36.2110,-115.2669),
(53.0066, 7.1920),
(41.3908, 2.1631),
(40.7822, -73.9653),
(25.8380, 50.6050)]

def op_response(lat, lon):
response = requests.get(
"https://static-maps.yandex.ru:443/1.x",
params={
"size": "400,400",  # size of map
"ll": str(lon) + "," + str(lat),  # longitude & latitude of centre
"z": 12,  # zoom level
"l": "sat",  # map layer (satellite image)
"lang": "en_US",  # language
},
)
return response.content

op = op_response(*coordinates_as_lat_lon[4])

Image(op)


def extended_op_response(lat, lon, zoom=15, opfname="tmp.png"):

response = requests.get(
"https://static-maps.yandex.ru:443/1.x",
params={
"size": "400,400",  # size of map
"ll": str(lon) + "," + str(lat),  # longitude & latitude of centre
"z": zoom,  # zoom level
"l": "sat",  # map layer (satellite image)
"lang": "en_US",  # language
},
)

with open(opfname, "wb") as png:
png.write(response.content)

extended_op_response(*coordinates_as_lat_lon[1], zoom=16, opfname="map_picture_1.png")

Image("map_picture_1.png")