Sorting Objects based on properties - python

I am trying to sort the Points based on the X and Y properties of the point object.A small example below to explain my process:
class Point:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def __repr__(self):
return '[{},{},{}]'.format(self.x, self.y, self.z)
#point instances
p1,p2,p3 = Point(7,85,5), Point(56,16,20), Point(24,3,30)
point_list = [p1,p2,p3]
def get_X(point):
return point.x
def get_Y(point):
return point.y
sorted_points = sorted(point_list, key = get_X)
# print(sorted_points) // [[7,85,5], [24,3,30], [56,16,20]]
sorted_points = sorted(sorted(point_list, key = get_X), key = get_Y)
# print(sorted_points) // [[24,3,30], [56,16,20], [7,85,5]]
But I need an output like this sorting X first keep them in same order and then sort Y
[[7,3,5], [24,16,30], [56,85,20]]
I think I am trying to exchange the properties of each instances by achieving the above, But I don't know how to do that.

Tuples will naturally sort in the way you want. You can simplify things by adding a __lt__() function to your class. Sorted will use this function to compare. Then you can depend on the natural sorting order of tuples easily with something like this:
class Point:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def __repr__(self):
return '[{},{},{}]'.format(self.x, self.y, self.z)
def __iter__(self):
return iter((self.x, self.y, self.z))
def __lt__(self, other):
return (self.x, self.y, self.z) < (other.x, other.y, other.z)
#point instances
point_list = [Point(7,85,5), Point(56,16,20), Point(24,3,30), Point(7, 20, 0), Point(56,16,15)]
sorted(point_list)
# --> [[7,20,0], [7,85,5], [24,3,30], [56,16,15], [56,16,20]]
EDIT: Create new points
To create new points by combining the sorted elements of each point individually you can unzip your points, sort them and then zip them again. The nicest way to do this is to add an __iter__() function to your class to make it iterable so it can support zip. I've done this in the code above. This will allow you to do this:
point_list = [Point(7,85,5), Point(56,16,20), Point(24,3,30), Point(7, 20, 0), Point(56,16,15)]
newTuples = list(zip(*[sorted(l) for l in zip(*point_list)]))
sortedPoints = [Point(*p) for p in newTuples ]
#sortedPoint => [[7,3,0], [7,16,5], [24,16,15], [56,20,20], [56,85,30]]
This also sorts the z values, but it's easy enough to change that if you need it for some reason.

By placing the key elements in tuples in the order you want them sorted (primary value first, secondary value second), the ordering method of tuples will automatically perform in the way you are hoping to achieve.
All you need to change is that your value for key should be set to key=get_XY, where get_XY returns a tuple of x and y coordinates:
def get_XY(point):
return point.x, point.y

Related

Is there a magic method for sets in Python?

I have a custom object like this:
class MyObject:
def __init__(self, x, y):
self.x = x
self.y = y
I want it to work with sets according to the rule: if objects have the same x they are equal.
s = set()
s.add(MyObject(1, 2))
print(MyObject(1, 3) in s) # It is False. I want it to be True, because `x = 1` for both.
Is there a magic method I could implement in MyObject to reach my purpose?
__eq__(self, other)
__hash__(self)
See https://hynek.me/articles/hashes-and-equality/ for more

Python Set Intersection on Object Attributes

I have 2 sets, each containing objects of the following class:
class pointInfo:
x = 0
y = 0
steps = 0
I want to find the intersection of the two sets, but only on the x and y values.
something like:
def findIntersections(pointInfoSet1, pointInfoSetwire2):
return [pointInfo for pointInfo in pointInfoSet1 if pointInfo.x, pointInfo.y in pointInfoSet2]
I know if you have 2 sets in python you can just do set1.intersection(set2), but that wont work in this case because I just want to find where a certain subset of the object attributes are the same, not identical objects. Thanks in advance!
Here's a solution that makes Point objects that are Hashable on their x and y attributes:
class Point:
def __init__(self, x=0, y=0, step=0):
self.x = x
self.y = y
self.step = step
def __eq__(self, other):
if not isinstance(other, Point):
return NotImplemented
return (self.x, self.y) == (other.x, other.y)
def __hash__(self):
return hash((self.x, self.y))
def __repr__(self):
return "Point(x={0.x}, y={0.y}, step={0.step})".format(self)
We can then put them in sets and get intersections naturally:
{Point(1, 1, 1), Point(2, 2)} & {Point(1, 1, 2)}
# {Point(x=1, y=1, step=2)}
Assuming no wire crosses itself, a better solution might be to iterate through both wires, maintaining a dict of points to steps, and outputting the sum of the steps at each intersection:
from itertools import chain
def stepsToIntersections(wire1, wire2):
seen = {}
for point in chain(wire1, wire2):
if point in seen:
yield point.step + seen[point]
else:
seen[point] = point.step
closest = min(stepsToIntersections(wire1, wire2))

How to get values of class method after calculating min of other class value

I have a "Node" class which takes as arguments x and y. The class methods calculate different values. I have multiple instances of this class called "nodes". What I want is to find the node with the lowest "fcost" and get the x and y coordinates of that node.
I have no idea how to solve this problem so it would be much appreciated if you could help me.
class Node():
# Node class
def __init__(self, y, x):
self.y = y
self.x = x
def gcost(self):
return self.x + self.y
def hcost(self):
return self.x * self.y
def fcost(self):
return self.gcost() + self.hcost() # method that indicates
# which node to choose
node1 = Node(5,5)
node2 = Node(2,2)
nodes = [node1, node2] # I actually don't know if I should create a
# list of nodes so please tell me if I should
# not
### CODE TO SOLVE THE PROBLEM ###
In this case the lowest fcost between node1 and node2 is node2's fcost so I expect the output to be:
(2,2) or [2,2]
Either a list or a tuple, either way is fine.
You should use the min() function. You can use it in different ways, but in this scenario, I think that the simplest solution would be to use a lambda function - which is a shorter way to write and define functions in python.
You can read more about the min() function here, and more about lambda functions here.
Anyway, this piece of code should work just fine:
class Node():
# Node class
def __init__(self, y, x):
self.y = y
self.x = x
def gcost(self):
return self.x + self.y
def hcost(self):
return self.x * self.y
def fcost(self):
return self.gcost() + self.hcost()
node1 = Node(5,5)
node2 = Node(2,2)
nodes = [node1, node2]
needed_node = min(nodes, key=lambda x:x.fcost())
needed_list = [needed_node.x, needed_node.y] # in case you want the result as a list
needed_tuple = (needed_node.x, needed_node.y) # in case you want the result as a tuple
Use min(list, key=...).
min_node = min(nodes, key=lambda n:n.fcost())
print(min_node, min_node.fcost(), min_node.x, min_node.y)
key has to be name of function.
min will use it to get value which it will compare to find minimal one.

Access class object by its parameters?

Is there any way to find an object saved in a list, knowing only its parameters and without traversing the said list?
For example, there's a class, objects of which have an (x;y) coordinate, and none of the objects share the same coordinate (all x/y pairs are distinct and do not repeat). These objects are all saved in a list:
class Point():
def __init__(self, x, y):
self.x = x
self.y = y
points = [Point(...), Point(...), Point(...), Point(...), ...]
Whenever I need the specific instance, is there any way to find it (here: its index in the list) by using just its coordinates without traversing the whole list like here:
def find_objects_index(x, y):
for i in range(len(points)):
if points[i].x == x and points[i].y == y:
return i
EDIT: these Point()s are to be accessed for writing, not reading, and so object.x and object.y will be changing, you can't just create a dictionary with (object.x, object.y) as keys - you'd need to add a new entry and delete the old one each time.
You can use list aggregation with a condition to get the item(s) you are looking for:
matching = [p for p in points if p.x = VALX and p.y == VALX]
However, in this case having a dictionary with (x, y) as key is most likely the correct (and well performing) way to go.
Is there any way to find an object saved in a list, knowing only its
parameters and without traversing the said list?
Short answer: No.
If you want or need to traverse such a collection of data points rapidly, perhaps you should consider using an type other than a list--a binary tree based off x or y data, for example (or if you need to track them separately, perhaps one tree for each)?
Just put the Points in a dict():
class Point():
def __init__(self, x, y):
self.x = x
self.y = y
points_list = [Point(...), Point(...), Point(...), Point(...), ...]
points_dict = {(p.x,p.y):p for p in points_list}
def find_object(x, y):
if (x,y) in points_dict:
return points_dict[(x,y)]
def replace_object(x, y, new_point):
points_dict.pop((x, y), None)
points_dict[(new_point.x, new_point.y)] = new_point

instance variable not modified in a for loop

I have a class with instance variables which I want to modify in a loop using a class method, simplified version of what I'm doing:
class Example:
def __init__(self,x,z):
self.x=x
self.z=z
def run(self,y):
temp_list=[self.x, self.z]
for ind,item in enumerate(temp_list):
temp_list[ind] = temp_list[ind]+y
print (self.x, self.z)
ex = Example(5,6)
ex.run(5)
The output I get is [5,6] instead of the desired [10,11].
I was wondering if anyone could point me to an explanation as to why this happens?
In run(), you create a list of 2 items, the value self.x and self.z. Next, you iterate over that list, and modify each value by adding y to it.
After the for-loop, temp_list will be [10, 11], but you have not modified self.x or self.y in any way.
Hence, the print() call will print the unmodified self.x and self.y values, being 5 and 6 in your example.
If you don't actually write self.x = ..., then you can generally assume that self.x will not be modified. When you write temp_list[ind] + y and store it in temp_list[ind], you're updating the list with a new value, which has no bearing on any values that other variables happen to hold (including your object's x variable).
To get the desired result with code similar to yours, you could do something like this:
class Example:
def __init__(self,x,z):
self.list=[x, z]
def run(self,y):
for ind,item in enumerate(self.list):
self.list[ind] = self.list[ind]+y
print (self.list)
ex = Example(5,6)
ex.run(5)
This would create a self.list item in your init definition that would then be used to iterate over the initial values and add your y value to it. The main error you had was printing an unaltered list (but you were on the right track!)
Hope this helps!
You can use, the example under.
class Example:
def __init__(self, x, z):
self.x = x
self.z = z
def run(self,y):
# Put your init arguments into the array
result = [self.x, self.z]
# Modify your init arguments
for x in range(len(result)):
result[x] += y
# Return the array with new values
return result
ex = Example(5, 6)
print(ex.run(5))
Maybe the link would be helpful for you, learn for loop in python

Categories