Pythonic way to do `if x in y where x.attr == val`? - python

I have a class that represents a polynomial as a collection of terms where each term has a coefficient and an exponent. I am working on the __add__ method of the class and I am wondering what the most effective way to do something like:
def __add__(self, other):
new_terms = []
for term in self.terms:
if there is a term in other with an exponent == term.exponent
new_terms.append(Term(term.coef + other_term.coef, term.exponent))
It strikes me that I'm looking for something such as:
if x in y where x.attr == val
Or in my specific case:
if x in other where x.exponent == term.exponent
Does such a thing exist?

You need to filter your list before doing your contains check. As tobias_k suggested, you can either build a new list, e.g.
[x for x in other if x.exponent == term.exponent]
This works directly in an if statement, because an empty list is False:
if [x for x in other if x.exponent == term.exponent]:
But this does some wasteful work, since it a) has to construct a new list and b) doesn't short-circuit once a result is found. Better is to use the same syntax in a generator expression:
(True for x in other if x.exponent == term.exponent)
Which you can then similarly use in an if statement, but no wasteful work is done:
if next((True for x in other if x.exponent == term.exponent), False):

I think you want [x for x in y if x.attr == val], or use next with the same expression for just the first such value.
In your case, it could look something like this:
def __add__(self, other):
for term in self.terms:
for other_term in (x for x in other.terms
if x.exponent == term.exponent):
term.coefficient += other_term.coefficient
However, this will not work too well. First, __add__ should not modify neither self nor other but instead create a new polynomial. Also, this will ignore any values from other that have a different exponent that any of the terms in self. And third, the performance is pretty lousy, as it loops the list of terms in other for each term in self, giving it quadratic complexity.
Instead, I suggest using a dictionary, mapping exponents in the term to their coefficient. In fact, you could probably just use a collections.Counter for that; it already implements __add__ in the right way. Something like this:
class Poly:
def __init__(self, counts):
self.terms = collections.Counter(counts)
def __add__(self, other):
return Poly(self.terms + other.terms)
def __str__(self):
return " + ".join("%dx^%d" % (c, x) for x, c in self.terms.items())
Example:
>>> Poly({2: 1, 1: 3, 0: 5}) + Poly({3: 1, 1: 2, 0: 3})
8x^0 + 5x^1 + 1x^2 + 1x^3

Related

Python: Sum up list of objects

I am working with a number of custom classes X that have __add__(self), and when added together return another class, Y.
I often have iterables [of various sizes] of X, ex = [X1, X2, X3] that I would love to add together to get Y. However, sum(ex) throws an int error, because sum starts at 0 which can't be added to my class X.
Can someone please help me with an easy, pythonic way to do X1 + X2 + X3 ... of an interable, so I get Y...
Thanks!
Ps it’s a 3rd party class, X, so I can’t change it. It does have radd though.
My gut was that there was some way to do list comprehension? Like += on themselves
You can specify the starting point for a sum by passing it as a parameter. For example, sum([1,2,3], 10) produces 16 (10 + 1 + 2 + 3), and sum([[1], [2], [3]], []) produces [1,2,3].
So if you pass an appropriate ("zero-like") X object as the second parameter to your sum, ie sum([x1, x2, x3,...], x0) you should get the results you're looking for
Some example code, per request. Given the following definitions:
class X:
def __init__(self, val):
self.val = val
def __add__(self, other):
return X(self.val + other.val)
def __repr__(self):
return "X({})".format(self.val)
class Y:
def __init__(self, val):
self.val = val
def __add__(self, other):
return X(self.val + other.val)
def __repr__(self):
return "Y({})".format(self.val)
I get the following results:
>>> sum([Y(1), Y(2), Y(3)], Y(0))
X(6)
>>> sum([Y(1), Y(2), Y(3)], Y(0))
X(6)
>>>
(note that Y returns an X object - but that the two objects' add methods are compatible, which may not be the case in the OP's situation)
Assuming that you can add an X to a Y (i.e. __add__ is defined for Y and accepts an object of class X), then you can use
reduce from functools, a generic way to apply an operation to a number of objects, either with or without a start value.
from functools import reduce
xes = [x1, x2, x3]
y = reduce(lambda a,b: a+b, xes)
What if you did something like:
Y = [ex[0]=+X for x in ex[1:]][0]
I haven’t tested it yet though, on mobile

How can I sum (make totals) on multiple object attributes with one loop pass?

I want to sum multiple attributes at a time in a single loop:
class Some(object):
def __init__(self, acounter, bcounter):
self.acounter = acounter
self.bcounter = bcounter
someList = [Some(x, x) for x in range(10)]
Can I do something simpler and faster than it?
atotal = sum([x.acounter for x in someList])
btotal = sum([x.bcounter for x in someList])
First off - sum doesn't need a list - you can use a generator expression instead:
atotal = sum(x.acounter for x in someList)
You could write a helper function to do the search of the list once but look up each attribute in turn per item, eg:
def multisum(iterable, *attributes, **kwargs):
sums = dict.fromkeys(attributes, kwargs.get('start', 0))
for it in iterable:
for attr in attributes:
sums[attr] += getattr(it, attr)
return sums
counts = multisum(someList, 'acounter', 'bcounter')
# {'bcounter': 45, 'acounter': 45}
Another alternative (which may not be faster) is to overload the addition operator for your class:
class Some(object):
def __init__(self, acounter, bcounter):
self.acounter = acounter
self.bcounter = bcounter
def __add__(self, other):
if isinstance(other, self.__class__):
return Some(self.acounter+other.acounter, self.bcounter+other.bcounter)
elif isinstance(other, int):
return self
else:
raise TypeError("useful message")
__radd__ = __add__
somelist = [Some(x, x) for x in range(10)]
combined = sum(somelist)
print combined.acounter
print combined.bcounter
This way sum returns a Some object.
I doubt that this is really faster, but you can do it like thus:
First define padd (for "pair add") via:
def padd(p1,p2):
return (p1[0]+p2[0],p1[1]+p2[1])
For example, padd((1,4), (5,10)) = (6,14)
Then use reduce:
atotal, btotal = reduce(padd, ((x.acounter,x.bcounter) for x in someList))
in Python 3 you need to import reduce from functools but IIRC it can be used directly in Python 2.
On edit: For more than 2 attributes you can replace padd by vadd ("vector add") which can handle tuples of arbitrary dimensions:
def vadd(v1,v2):
return tuple(x+y for x,y in zip(v1,v2))
For just 2 attributes it is probably more efficient to hard-wire in the dimension since there is less function-call overhead.
Use this line to accumulate all of the attributes that you wish to sum.
>>> A = ((s.acounter,s.bcounter) for s in someList)
Then use this trick from https://stackoverflow.com/a/19343/47078 to make separate lists of each attribute by themselves.
>>> [sum(x) for x in zip(*A)]
[45, 45]
You can obviously combine the lines, but I thought breaking it apart would be easier to follow here.
And based on this answer, you can make it much more readable by defining an unzip(iterable) method.
def unzip(iterable):
return zip(*iterable)
[sum(x) for x in unzip((s.acounter,s.bcounter) for s in someList)]

Countdown in Python using permutations and lambda

I am trying to make a program that solves the following with permutations and lambda:
You pick 5 numbers and a random number is generated, the aim is to use those 5 numbers to reach the target number. You are allowed to use each number once with as many operators as you want (+-*/). I want the program to print() all of the solutions
this is the code i have created so far
from itertools import permutations
import operator
num1=3
num2=5
num3=7
num4=2
num5=10
class Infix:
def __init__(self, function):
self.function = function
def __ror__(self, other):
return Infix(lambda x, self=self, other=other: self.function(other, x))
def __or__(self, other):
return self.function(other)
def __rlshift__(self, other):
return Infix(lambda x, self=self, other=other: self.function(other, x))
def __rshift__(self, other):
return self.function(other)
def __call__(self, value1, value2):
return self.function(value1, value2)
"""what the class infix does is it allows me to substitute operators such as +-*/ to functions as follows:"""
multiply=Infix(lambda x,y: x*y) #this is a way of telling it that whenever i call x multiply y is the same as x*y
add=Infix(lambda x,y: x+y) #same here just for adding
minus=Infix(lambda x,y: x-y)
divide=Infix(lambda x,y: x/y)
"""the way infix can be called is if i say 3 |add| 4, which allows me to substitute
variables as the operators"""
target = 50
for w,x,y,z in permutations((|multiply|,|add|,|minus|,|divide|), 4): #telling it to substitute operators to the variables
for a,b,c,d,e in permutations(num1,num2,num3,num4,num5), 5):
if a(w)b(x)c(y)d(z)e == target: #works out the sum
print(a(w)b(x)c(y)d(z)e) #prints it if it equals my target
The only error in this code is the for loops because i don't know how to substitute both operators and numbers at the same time, all the other parts of the code are absolutely fine.
The expected output is all answers that work e.g. 3*2+5-7=4 if target==4.
What i asked this to do is run through all of the numbers with all of the operators again and again to find answers that match the target, and then print them out, but i am having difficulties trying to substitute the number together with the operator.
I thought that a(X)b(y)... would work because if you substitute it, it is a(|add|)b(|multiply|)d... and so on, but it turns out that it doesn't.
None of these work but i am trying to find a solution that does. It needs to loop through the numbers and the operators at the same time and i don't know how to do it!
Error when running:
Infix working correctly:
Finally, I think we get to the bottom of it. You are expecting the "bitwise OR" operators | to somehow modify the Infix objects, so that when you do
permutations((|multiply|,|add|,|minus|,|divide|), 4)
they "stay with" the objects. That can't happen; for example, you wouldn't do:
x, y = 1, 2
a = x+
print(ay)
and expect 3. Instead, you need to do:
permutations((multiply, add, minus, divide), 4)
i.e. actually shuffle the Infix instances, then apply the operators later, when calculating the comparison with target:
if a |w| b |x| c |y| d |z| e == target:
There are a few other minor errors, but that should allow you to move forwards.

Python - use an operator at runtime

Is it possible to insert an operation (e.g *, +) between two variables at runtime?
My solution without doing this is multiple if, elif statements, but I don't think that's the most efficient way to do it.
EDIT: What I meant is I get two integers, and I want to apply an operation on one of them with the other, e.g x * y, but I want to change * to another operator (maybe they're called functions? Not sure) e.g -, +,^ based on input.
Does that make sense? Basically think of it as a calculator.
I'm not sure if this is what you're looking for but the operator module has a lot of operations, e.g. add and mul (multiply):
import operator
var_1 = 2
var_2 = 3
print(operator.add(var_1, var_2))
print(operator.mul(var_1, var_2))
will print
5
6
#AaronHall's is the answer you're looking for, but for completeness, I'd mention you can also use eval.
var_1 = 2
var_2 = 3
op = '+'
print eval('%s%s%s' % (var_1, op, var_2))
However, eval is evil, so either don't use it, or use with caution.
To answer the follow-on question, for example, you can subclass int and then implement __xor__ to exponentiate instead of apply a bitwise or, which is called by the ^ operator:
import operator
class MyInt(int):
def __xor__(self, other):
return operator.pow(self, other)
and then:
>>> i = MyInt(2)
>>> i
2
>>> type(i)
<class '__main__.MyInt'>
>>> j = 3
>>> i^j
8
If your list of operators is small enough you can probably use lambdas.
def operator_factory(op):
if op == '+':
return lambda x,y: x + y
elif op == '-':
return lambda x,y: x - y
elif op == '*':
return lambda x,y: x * y
elif op == '/':
return lambda x,y: x / y
elif op == '^':
return lambda x,y: x ^ y
Your if statements can depend on user input. Then you just use this like so:
>>> f = operator_factory('+')
>>> f(2,3)
5

How to add with tuples

I have pseudo-code like this:
if( b < a)
return (1,0)+foo(a-b,b)
I want to write it in python. But can python add tuples? What is the best way to code something like that?
I'd go for
>>> map(sum, zip((1, 2), (3, 4)))
[4, 6]
or, more naturally:
>>> numpy.array((1, 2)) + numpy.array((3, 4))
array([4, 6])
Do you want to do element-wise addition, or to append the tuples? By default python does
(1,2)+(3,4) = (1,2,3,4)
You could define your own as:
def myadd(x,y):
z = []
for i in range(len(x)):
z.append(x[i]+y[i])
return tuple(z)
Also, as #delnan's comment makes it clear, this is better written as
def myadd(xs,ys):
return tuple(x + y for x, y in izip(xs, ys))
or even more functionally:
myadd = lambda xs,ys: tuple(x + y for x, y in izip(xs, ys))
Then do
if( b < a) return myadd((1,0),foo(a-b,b))
tuple(map(operator.add, a, b))
In contrast to the answer by highBandWidth, this approach requires that the tuples be of the same length in Python 2.7 or earlier, instead raising a TypeError. In Python 3, map is slightly different, so that the result is a tuple of sums with length equal to the shorter of a and b.
If you want the truncation behavior in Python 2, you can replace map with itertools.imap:
tuple(itertools.imap(operator.add, a, b))
If you want + itself to act this way, you could subclass tuple and override the addition:
class mytup(tuple):
def __add__(self, other):
if len(self) != len(other):
return NotImplemented # or raise an error, whatever you prefer
else:
return mytup(x+y for x,y in izip(self,other))
The same goes for __sub__, __mul__, __div__, __gt__ (elementwise >) etc. More information on these special operators can be found e.g. here (numeric operations) and here (comparisions)
You can still append tuples by calling the original tuple addition: tuple.__add__(a,b) instead of a+b. Or define an append() function in the new class to do this.

Categories

Resources