python membership testing order by rarity - python

suppose I have a list X that contains a bunch of different items and I am testing if it contains any of the following: (a, b, c).
If a occurs vastly more often than b, which in turn is more common than c, is there a way to force
any(True for v in X if v in (a, b, c))
to check for a first so that it can return faster?
if a in X or b in X or c in X:
runs much faster than the any() statement but it’s messy and not extensible.

any(v in X for v in (a, b, c))
You've put together your any in a pretty weird way. This way gets the effect you want.
If you wanted to do the checks in the way your existing code does it (for example, if earlier elements of X were more likely to match), it'd be cleaner to do
any(v in (a, b, c) for v in X)
If instead of 3 elements in (a, b, c), you have quite a lot, it'd be faster to use a set:
not {a, b, c}.isdisjoint(X)

Did you try?
any(True for v in (a, b, c) if v in X)
That form seems more comparable to your faster example.

Related

Iterate a tuple with dict inside

Having trouble iterating over tuples such as this:
t = ('a','b',{'c':'d'})
for a,b,c in t:
print(a,b,c) # ValueError: not enough values to unpack (expected 3, got 1)
for a,b,*c in t:
print(a,b,c) # ValueError: not enough values to unpack (expected 2, got 1)
for a,b,**c in t:
print (a,b,c) # Syntax error (can't do **c)
Anyone know how I can preserve the dictionary value? I would like to see a='a', b='b', and c={'c':'d'}
You'd need to put t inside some other iterable container:
for a, b, c in [t]:
print(a, b, c)
The problem with your attempts is that each on is iterating over a single element from t and trying to unpack that. e.g. the first turn of the loop is trying to unpack 'a' into three places (a, b and c).
Obviously, it's also probably better to just unpack directly (no loop required):
a, b, c = t
print(a, b, c)
Why are you iterating at all when it's a single tuple? Just unpack the single tuple, if that's what you need to do:
a, b, c = t
print(a, b, c)
Or if it's just printing you want to do unpack in the call itself:
print(*t)
Try this:
for a,b,c in [t]:
print(a,b,c)
Putting t inside a list will allow you to unpack it.
You can use tuple unpacking with multiple assignment:
a, b, c = t
print(a, b, c)
try this
for x in t:
print(x)
x will take all the values in t iteratively, so 'a', then 'b' and finally {'c':'d'}.
And to print exactly a = 'a' etc you can do:
for param, val in zip(["a","b","c"], t):
print(param,"=",val)

Test if all values are in an iterable in a pythonic way [duplicate]

This question already has answers here:
How to check if all of the following items are in a list?
(8 answers)
Closed 7 months ago.
I am currently doing this:
if x in a and y in a and z in a and q in a and r in a and s in a:
print(b)
Is there a more Pythonic way to express this if statement?
Using the all function allows to write this in a nice and compact way:
if all(i in a for i in (x, y, z, q, r, s)):
print b
This code should do almost exactly the same as your example, even if the objects are not hashable or if the a object has some funny __contains__ method. The all function also has similar short-circuit behavior as the chain of and in the original problem. Collecting all objects to be tested in a tuple (or a list) will guarantee the same order of execution of the tests as in the original problem. If you use a set, the order might be random.
Another way to do this is to use subsets:
if {x, y, z, q, r, s}.issubset(a):
print(b)
REPL example:
>>> {0, 1, 2}.issubset([0, 1, 2, 3])
True
>>> {0, 1, 2}.issubset([1, 2, 3])
False
One caveat with this approach is that all of x, y, z, etc. must be hashable.
if all(v in a for v in {x, y, z, q, r, s}):
print(b)
Converting to set, either:
if len({x, y, z, q, r, s} - set(a)) == 0:
print b
or
t = {x, y, z, q, r, s}
if t & set(a) == t:
print b

How to combine tuples from two generators in python

I want to use two generators in a single for loop. Something like:
for a,b,c,d,e,f in f1(arg),f2(arg):
print a,b,c,d,e,f
Where a,b,c,d and e come from f1 and f comes from f2. I need to use the yield operator because of space constraints.
The above code however doesn't work. Due to some reason it keeps on taking values (for all six variables) from f1 until it is exhausted and then starts taking values from f2.
Please let me know if this is possible and if not is there any workaround. Thank you in advance.
You can use zip (itertools.izip if you're using Python 2) and sequence unpacking:
def f1(arg):
for i in range(10):
yield 1, 2, 3, 4, 5
def f2(arg):
for i in range(10):
yield 6
arg = 1
for (a, b, c, d, e), f in zip(f1(arg), f2(arg)):
print(a, b, c, d, e, f)

What is an elegant way to "change" an element of a tuple? [duplicate]

This question already has answers here:
How to change values in a tuple?
(17 answers)
Closed 8 years ago.
This is extremely likely to be a duplicate of something, but my serach foo is failing me.
It is known that tuples are immutable, so you can't really change them. Sometimes, however, it comes in handy to do something to the effect of changing, say, (1, 2, "three") to (1, 2, 3), perhaps in a similar vein to the Haskell record update syntax. You wouldn't actually change the original tuple, but you'd get a new one that differs in just one (or more) elements.
A way to go about doing this would be:
elements = list(old_tuple)
elements[-1] = do_things_to(elements[-1])
new_tuple = tuple(elements)
I feel that changing a tuple to a list however kind of defeats the purpose of using the tuple type for old_tuple to begin with: if you were using a list instead, you wouldn't have had to build a throw-away list copy of the tuple in memory per operation.
If you were to change, say, just the 3rd element of a tuple, you could also do this:
def update_third_element(a, b, c, *others):
c = do_things_to(c)
return tuple(a, b, c, *others)
new_tuple = update_third_element(*old_tuple)
This would resist changes in the number of elements in the tuple better than the naive approach:
a, b, c, d, e, f, g, h, i, j = old_tuple
c = do_things_to(c)
new_tuple = (a, b, c, d, e, f, g, h, j, j) # whoops
...but it doesn't work if what you wanted to change was the last, or the n-th to last element. It also creates a throw away list (others). It also forces you to name all elements up to the n-th.
Is there a better way?
I would use collections.namedtuple instead:
>>> from collections import namedtuple
>>> class Foo(namedtuple("Foo", ["a", "b", "c"])):
pass
>>> f = Foo(1, 2, 3) # or Foo(a=1, b=2, c=3)
>>> f._replace(a = 5)
Foo(a=5, b=2, c=3)
namedtuples also support indexing so you can use them in place of plain tuples.
If you must use a plain tuple, just use a helper function:
>>> def updated(tpl, i, val):
return tpl[:i] + (val,) + tpl[i + 1:]
>>> tpl = (1, 2, 3)
>>> updated(tpl, 1, 5)
(1, 5, 3)

Managing *args variance in calls to functions

Have a method with the following signature:
def foo(self, bar, *uks):
return other_method(..., uks)
Normally this is called as:
instance.foo(1234, a, b, c, d)
However in some cases I need to do something like this:
p = [a, b, c, d]
instance.foo(1234, p)
At the receiving end this does not work, because other_method sees *args being made up of a single list object instead of simply a [a, b, c, d] list construct. If I type the method as:
def foo(self, bar, uks = []):
return other_method(..., uks)
It works, but then I'm forced to do this every time:
instance.foo(1234, [a, b, c, d])
It's not a huge deal I guess, but I just want to know if I'm missing some more pythonic way of doing this?
Thanks!
Python supports unpacking of argument lists to handle exactly this situation. The two following calls are equivalent:
Regular call:
instance.foo(1234, a, b, c, d)
Argument list expansion:
p = [a, b, c, d]
instance.foo(1234, *p)
p = [a, b, c, d]
instance.foo(1234, *p)
The *p form is the crucial part here -- it means "expand sequence p into separate positional arguments".
I think the answers you have here are correct. Here's a fully fleshed out example:
class MyObject(object):
def foo(self, bar, *uks):
return self.other_method(1, uks)
def other_method(self, x, uks):
print "uks is %r" % (uks,)
# sample data...
a, b, c, d = 'a', 'b', 'c', 'd'
instance = MyObject()
print "Called as separate arguments:"
instance.foo(1234, a, b, c, d)
print "Called as a list:"
p = [a, b, c, d]
instance.foo(1234, *p)
When run, this prints:
Called as separate arguments:
uks is ('a', 'b', 'c', 'd')
Called as a list:
uks is ('a', 'b', 'c', 'd')
You said on Alex's answer that you got ([a, b, c, d],), but I don't see how.

Categories