Python - Implement iteration over certain class attributes on given order - python

I have a Position class, and it has two attributes, Lat and Lon.
I would like the following API by implementing iterator protocol (but some googling just confused me more):
pos = Position(30, 50)
print pos.Latitude
> 30
print pos.Longitude
> 50
for coord in pos:
print coord
> 30
> 50
print list(pos)
> [30, 50]

You need to define an __iter__ method:
class Position(object):
def __init__(self, lat, lng):
self.lat = lat
self.lng = lng
def __iter__(self):
yield self.lat
yield self.lng
pos = Position(30, 50)
print(pos.lat)
# 30
print(pos.lng)
# 50
for coord in pos:
print(coord)
# 30
# 50
print(list(pos))
# [30, 50]
PS. The PEP8 style guide recommends reserving capitalized names for classes. Following the conventional will help others understand your code more easily, so I've resisted the urge to use your attribute names, and have instead replaced them with lat and lng.

Related

Having difficulty constructing a Binary tree from a list of class instances

I'm trying to construct a KDtree for finding 'K nearest neighbour'
I've created a class called 'Point' which holds attributes: pointID, lat (latitude) and Lon (longitude).
the input for the build_index is an array 'points' contains all instances of points.
in the code below I'm trying to construct the KDtree but I'm having issues with trying to retrieve just the lat and Lon of each Point to sort, but I understand just using 'points' will not work as it is a an array with just the class instances.
thanks for the help in advance!
class KDTreeNN(NearestNeigh):
def build_index(self, points: [Point]):
depth = 0
n = len(points)
if n <0:
return None
axis = depth % 2
sorted_points = sorted(points, key = lambda point: point[axis])
depth + 1
return {
'point' : sorted_points[int(n/2)],
'left' : self.build_index(sorted_points[:int(n/2)]),
'right' : self.build_index(sorted_points[int(n/2) + 1:])
}
point[axis] is not working because point does not support such bracket notation.
There are several solutions:
Define Point as a named tuple:
Instead of defining Point as something like:
class Point:
def __init__(self, pointID, lat, lon):
self.pointID = pointID
self.lat = lat
self.lon = lon
...define it as a named tuple. Make sure to make lon and lat its first two members:
Point = namedtuple("Point", "lat,lon,pointID")
Also ensure that were create Point instances, you put the arguments in the right order.
With that change it will work. This requires that you treat Point instances as immutable.
Access the attribute (lon or lat) depending on a string variable:
axis = ("lon", "lat")[depth % 2]
sorted_points = sorted(points, key = lambda point: getattr(point, axis))

python - divide world into bins

I am working on trying to put moving balls into appropriate bins. I like to think I'm on the right track but I've been stuck for awhile now.
I left code out that didn't seem relevant to my question but if those who answer need further details I can provide them. Basically, I have a world of 200 moving balls. They have an X and Y coordinate. I want to divide the world into square bins of width 256 and place the balls in the appropriate bin.
My approach to this was to put them into a dictionary. It looked like this:
dict_of_balls = {}
for i in range(len(balls)):
xb = int(balls[i].x/256)
yb = int(balls[i].y/256)
and I wanted to make the keys a tuple of the (xb, yb) pairs and then place the appropriate balls in that bin but I don't think you can use tuples as keys...
The code is below:
import math
import random
import time
import sys
ball_min_radius = 16.0 #world coordinates
ball_max_radius = 128.0 #world coordniates
number_balls = 200
class Ball:
"""
Implements a point/ball
"""
def __init__(self):
self.x = random.uniform(world_min_x,world_max_x)
self.y = random.uniform(world_min_y,world_max_y)
self.radius = int(random.uniform(ball_min_radius,ball_max_radius))
def __lt__(self, other):
return self.id < other.id
def main():
world_min_x = -200.0*number_balls**.5 # minimum x in world coordinates
world_max_x = +200.0*number_balls**.5 # maximum x in world coordinates
world_min_y = -200.0*number_balls**.5 # minimum y in world coordinates
world_max_y = +200.0*number_balls**.5 # maximum y in world coordinates
balls = [Ball() for i in range(number_balls)]
so does anyone have any ideas for how to divide the world into bins based on the given world coordinates? I am unsure of which data structure to use since I can't use tuples for keys. Thanks in advance for any feedback.
Why do you want a dictionary? Here's how you would do this, but keep in mind you will only get one ball per bin because you are specifically casting their key to be (int, int) and keys are unique.
If you use a collection, you can also sort (in my example I sort by the region identifiers):
I am not sure what you are doing that for, but you can do it:
import math
import random
import time
import sys
ball_min_radius = 16.0 #world coordinates
ball_max_radius = 128.0 #world coordniates
number_balls = 200
world_min_x = -200.0*number_balls**.5 # minimum x in world coordinates
world_max_x = +200.0*number_balls**.5 # maximum x in world coordinates
world_min_y = -200.0*number_balls**.5 # minimum y in world coordinates
world_max_y = +200.0*number_balls**.5 # maximum y in world coordinates
class Ball:
"""
Implements a point/ball
"""
def __init__(self):
self.x = random.uniform(world_min_x,world_max_x)
self.y = random.uniform(world_min_y,world_max_y)
self.radius = int(random.uniform(ball_min_radius,ball_max_radius))
def __lt__(self, other):
return self.id < other.id
def __str__(self):
return 'x={x} y={y} r={r}'.format(x=self.x, y=self.y, r=self.radius)
def main():
balls = [Ball() for i in range(number_balls)]
dict_of_balls = {}
ball_collection = []
for b in balls:
xb = int(b.x/256)
yb = int(b.y/256)
key = (xb, yb)
dict_of_balls[key] = b
ball_collection.append((key, b))
print 'length of dictionary:{}'.format(len(dict_of_balls.keys()))
print 'length of collection:{}'.format(len(ball_collection))
Notice that the dictionary has fewer items than the collection.
You can also print each item this way pretty trivially:
for b in ball_collection:
print 'ball region: {r} with coords: {c}'.format(r=b[0], c=b[1])
Or, sort them if you want:
print 'Collections also let you sort the collection by region(s)...'
sorted_list = sorted(ball_collection, key= lambda x: (x[0][0], x[0][1]))
for b in sorted_list:
print 'ball region: {r} with coords: {c}'.format(r=b[0], c=b[1])
You can also pretty simply get balls in a specific region too:
print '... or get only ones in a specific region'
subset = [b for b in ball_collection if b[0][0] == 1]
for b in subset:
print 'ball region: {r} with coords: {c}'.format(r=b[0], c=b[1])
main()
A collection seems to do what you are actually wanting.
You can use tuple for keys in a dictionary, since tuple is immutable. The only data type you can't use for a dictionary key is a list [] or set {}
**a = {(1,2):'example1', (2,3):'example2'}
>>> a[(1,2)]
'example1'**
So I believe this should make it much easier to solve your problem.

Forming a polygon with classes

So, my problem is: I am trying to create a program which would create a polygon that has atleast 3 points(that are composed of coordinates x and y) or angles. I would like that, if there are less than 3 points or angles submitted, the program returns an error saying there are insufficient number of points. I need to create this with classes.
I have created this so far: `
class Polygon:
number_points = 0
number_angles = 0
def __init__(self, coordinate_x, coordinate_y, angles):
s = []
self.coordinate_x = coordinate_x
self.coordinate_y = coordinate_y
self.angles = angles
self.s = s.append([coordinate_x, coordinate_y])
Polygon.number_points = Polygon.number_points + 1
Nkotnik.number_angles = Polygon.number_angles + 1
# Here i would like the program to check if there are enough points
# and angles to form a polygon and to check if all coordinates are
# numbers. If this requirement is not met, the program prints an
# error message.
def creation(self):
if not isinstance(coordinate_x, (int,float)):
#raise Exception("That is not a number")
if Polygon.number_points <= 3:
`
The idea that I had is that i store the coordinates in a list and then when the user enters enough points, a polygon can be formed.
I am not a native speaker, so if I need to clear things a bit further feel free to ask :) thank you for any possible answers :)
I see an error here:
Polygon.number_points = Polygon.number_points + 1
Nkotnik.number_angles = Polygon.number_angles + 1
Nkotnik should be Polygon. Also, to make it shorter, you could do Polygon.number_points += 1 and same for number_angles.
So now, the creation of the program:
def creation(self):
This is bad design. The function should take the number of points and the number of angles as parameters. So, do this:
def creation(self, points, angles):
But creation is basically initialization, so you should integrate it into your __init__.
Also, your __init__ is strange. number_points and number_angles should be defined in the __init__, not the object body, because those variables are different for different Polygon objects. So after modification, your code looks like this:
class Polygon:
def __init__(self, coord_list, angles):
if len(coord_list) // 2 < 3:
raise Exception("Side count must be 3 or more.")
s = []
self.number_points = 0
self.number_angles = 0
self.coordinates_x = coord_list[::2]
self.coordinates_y = coord_list[1::2]
self.angles = angles
self.s = s.append([coordinate_x, coordinate_y])
self.number_points += len(coord_list // 2)
self.number_angles += len(angles)
num_sides = int(input('Number of sides: ')) #raw_input if you're using Python 2
points = []
angles = []
for i in range(num_sides):
points.append(int(input('X value of point: ')))
points.append(int(input('Y value of point: ')))
for i in range(num_sides):
angles.append(int(input('Angle value: ')))
polygon_object = Polygon(points, angles)
And you're done!
You can do the check at creation time in the class, like this, also you need more that just a angle to define a point
import collections
PointCartesian = collections.namedtuple("PointCartesian","coordinate_x coordinate_y")
PointPolar = collections.namedtuple("PointPolar","magnitude angle")
#this is a easy way to make a class for points, that I recommend have
#a class too
class Polygon(object):
def __init__(self,*argv,**kargv):
points = list()
for elem in argv:
if isinstance(elem,(PointCartesian,PointPolar ) ):
points.append(elem)
else:
raise ValueError("Element "+str(elem)+" of wrong type")
if len(points) <3:
raise ValueError("Insufficient data")
self.points = points
and in other place you have the routine that ask the user for the data, you can check every input or leave it to the class.
to call it do something like this
Polygon(PointCartesian(1,2),PointCartesian(4,7),PointPolar(5,28.2))
Polygon(*list_of_points)

Extending list class to make a list of objects

I have a particle list of objects of type Particle, which takes two parameters, position and energy:
class Particle(object):
def __init__(self, pos, energy):
self.x = pos
self.E = energy
The only way I've managed to do it so far is to create a list of particles using a list comprehension:
number_of_particles = 10
initial_energy = 0
particle_list = [Particle(initial_energy,i) for i in range(number_of_particles)]
which now allows me to do things like:
particle_list[0].x
which is what I want.
However, what I would really, really like is to do something as follows:
particle_list = ParticleList(no_of_particles, initial_energy)
and it create the exact same list.
I assume I have to extend the list class somehow but I'm at a loss as to how to do this.
Why not just build a function to do this for you. You could do something simple like:
def ParticleList(no_of_particles, initial_energy):
return [Particle(initial_energy,i) for i in range(number_of_particles)]
This should be a simple way of getting your list.
class Particle(object):
def __init__(self, pos, energy):
self.x = pos
self.E = energy
#classmethod
def make_particle_list(self, no_particles, initial_energy=0):
return [Particle(i, initial_energy) for i in range(no_particles)]
# this is just for display purposes
def __repr__(self):
return 'pos: {p.x} - Energy: {p.E}'.format(p=self)
This offers you a little flexibility. If you only need one particle you can make just one the normal way or:
>>> lst = Particle.make_particle_list(10)
>>> lst
[pos: 0 - Energy: 0, pos: 1 - Energy: 0, pos: 2 - Energy: 0, pos: 3 - Energy: 0, pos: 4 - Energy: 0, pos: 5 - Energy: 0, pos: 6 - Energy: 0, pos: 7 - Energy: 0, pos: 8 - Energy: 0, pos: 9 - Energy: 0]
This also allows you to pass in a different initial_energy if you ever need a different value.
You also had your arguments backwards in your example. You had initial_energy as the first positional argument in your list comprehension but you have it as the second in your __init__() method.
Create your class with your custom __init__ method.
class ParticleList(list):
def __init__(self, num, energy):
self.particle_list = [Particle(energy,i) for i in range(num)]
particles = ParticleList(2, 0).particle_list
for particle in particles:
print (particle.x, particle.E)
>>(0, 0)
>>(0, 1)
You may create your own method and not use the __init__, this way you will be able to simply return the created list and not assign it to a member (__init__ is not allowed to have a return value).
class ParticleList(list):
def create_list(self, num, energy):
return [Particle(energy,i) for i in range(num)]
my_list = ParticleList().create_list(2, 0)
And as others have said, you don't even need the class and can get away with only creating a function:
def create_list(num, energy):
return [Particle(energy,i) for i in range(num)]
my_list = create_list(2, 0)

Adding records to a numpy record array

Let's say I define a record array
>>> y=np.zeros(4,dtype=('a4,int32,float64'))
and then I proceed to fill up the 4 records available. Now I get more data, something like
>>> c=('a',7,'24.5')
and I want to add this record to y. I can't figure out a clean way to do it. The best I have seen in np.concatenate(), but that would require turning c into an record array in and of itself. Is there any simple way to tack my tuple c onto y? This seems like it should be really straightforward and widely documented. Apologies if it is. I haven't been able to find it.
You can use numpy.append(), but as you need to convert the new data into a record array also:
import numpy as np
y = np.zeros(4,dtype=('a4,int32,float64'))
y = np.append(y, np.array([("0",7,24.5)], dtype=y.dtype))
Since ndarray can't dynamic change it's size, you need to copy all the data when you want to append some new data. You can create a class that reduce the resize frequency:
import numpy as np
class DynamicRecArray(object):
def __init__(self, dtype):
self.dtype = np.dtype(dtype)
self.length = 0
self.size = 10
self._data = np.empty(self.size, dtype=self.dtype)
def __len__(self):
return self.length
def append(self, rec):
if self.length == self.size:
self.size = int(1.5*self.size)
self._data = np.resize(self._data, self.size)
self._data[self.length] = rec
self.length += 1
def extend(self, recs):
for rec in recs:
self.append(rec)
#property
def data(self):
return self._data[:self.length]
y = DynamicRecArray(('a4,int32,float64'))
y.extend([("xyz", 12, 3.2), ("abc", 100, 0.2)])
y.append(("123", 1000, 0))
print y.data
for i in xrange(100):
y.append((str(i), i, i+0.1))
This is because concatenating numpy arrays is typically avoided as it requires reallocation of contiguous memory space. Size your array with room to spare, and then concatenate in large chunks if needed. This post may be of some help.

Categories

Resources