Python Set Intersection on Object Attributes - python

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))

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

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.

How can I hash an object with two symmetrically equivalent characteristics?

I have an object (Edge) which contains two other objects (points A and B) in 3D. Geometrically, an edge from A = (0, 0, 0) to B = (1, 0, 0) should be the same as an edge from A = (1, 0, 0) to B = (0, 0, 0), and it's easy to make an equality statement of two edges. However, I'm having some conceptual problems implementing a way to hash this object (in Python). For example, hash((A, B)) will return a different value from hash((B, A)).
I've seen answers about similar problems on this site, but they all involve making a comparison between the two elements. I don't really want to do this, because while I can think of a rigorous way to compare two points (compare x-coordinates first, then y-coordinates if x are equal, then z's if y's are equal), I don't know if I want to implement a comparison which seems meaningless mathematically and only useful for this single instance. The statement (1, 0, 0) > (0, 300, 10^10) might be correct with this method, but it isn't very meaningful.
class Edge(object):
def __init__(self, pointA, pointB):
self._A = pointA
self._B = pointB
ab = pointA + pointB
self._midpoint = Vector(ab.x / 2, ab.y / 2, ab.z / 2)
def get_A(self):
return self._A
def set_A(self, point):
self._A = point
def get_B(self):
return self._B
def set_B(self, point):
self._B = point
A = property(get_A, set_A)
B = property(get_B, set_B)
def __eq__(self, other):
if isinstance(other, Edge):
if (self.A == other.A) and (self.B == other.B):
return True
elif (self.B == other.A) and (self.A == other.B):
return True
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash((self.A, self.B)) # =/= hash((self.B, self.A))!
def __str__(self):
return "[{}, {}]".format(self.A, self.B)
In conclusion, I'm wondering if there's an implementation which will give two equivalent edges the same hash value without creating some arbitrary comparison function between points. (P.S. my "point" class is called "Vector")
Combine the hashes of A and B with XOR:
def __hash__(self):
return hash(self.A) ^ hash(self.B)

How to make a class object iterable

I have the following point class:
import math
class Point:
"""Two-Dimensional Point(x, y)"""
def __init__(self, x=0, y=0):
# Initialize the Point instance
self.x = x
self.y = y
# def __iter__(self):
# return iter(int(self.x))
# return iter(int(self.y))
return self
#property
def magnitude(self):
# """Return the magnitude of vector from (0,0) to self."""
return math.sqrt(self.x ** 2 + self.y ** 2)
def distance(self, self2):
return math.sqrt((self2.x - self.x) ** 2 + (self2.y - self.y) ** 2)
def __str__(self):
return 'Point at ({}, {})'.format(self.x, self.y)
def __repr__(self):
return "Point(x={},y={})".format(self.x, self.y)
I want to make 'points' iterable so that the following is possible:
point = Point(2, 3)
x, y = point
print(x)
2
print(y)
3
If you see my commented code, that is what I attempted but it says TypeError: iter() returned non-iterator of type 'Point'. Any ideas on how to correctly do this?
tl;dr:
def __iter__(self):
yield self.x
yield self.y
To make something iterable, you need your __iter__ method to return an iterator.
You can't just return self, unless self is already an iterator (which means it has a __next__ method). But you don't want points to be an iterator, you want it to be iterable over and over.
You can't return iter(int(self.x)) then return iter(int(self.y)), for two reasons. First, after you return, your function is done; any code that happens after that never gets run. Second, you can't call iter on an int. (Also, there's no reason to call int on an int.)
You could fix that last set of problems by creating an iterable out of each int, like a single-element list, iterating that iterable, and then delegating to it with yield from instead of return. Using yield or yield from in a function makes it a generator function, and the generators created by generated functions are iterators:
yield from iter([self.x])
yield from iter([self.y])
… but this is kind of silly. Once we know we want a generator, we can just yield the values we want to be iterated. Hence the code at the top.
Alternatively, you could explicitly create a single-element iterables for both elements and chain them together and return that:
def __iter__(self):
return itertools.chain(iter([self.x]), iter([self.y]))
But that's also silly. Chaining two iterators over single-element lists is the same as just iterating a two-element list:
def __iter__(self):
return iter([self.x, self.y])
Finally, rather than relying on a generator, list iterator, chain, or other iterator type that comes with Python, you can always write one explicitly:
class PointIterator:
def __init__(self, point):
self.values = [point.x, point.y]
def __next__(self):
try:
return self.values.pop(0)
except IndexError:
raise StopIteration
def __iter__(self):
return self
class Point:
# ... other code
def __iter__(self):
return PointIterator(self)
You can use yield:
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __iter__(self):
yield self.x
yield self.y
a, b = Point(4, 5)
print([a, b])
Output:
[4, 5]
You should implement a special __iter__(self) method, which should return an iterator. Add something like
def __iter__(self):
return iter([self.x, self.y])
Your __iter__ method should yield x and y values
def __iter__(self):
yield self.x
yield self.y
With this, you get the following output
>>> point = Point(2, 3)
>>> x, y = point
>>> print (x)
2
>>> print (y)
3
>>>

Sorting Objects based on properties

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

Categories