Python assign tuple to set() without unpacking - python

How can I assign a tuple to a set without the members being unpacked and added separately?
For example (python 3.9.11):
from collections import namedtuple
Point = namedtuple('Point', 'x y')
p = Point(5, 5)
set(p)
produces {5}, whereas I would like {Point(5, 5)}

If you want to make a set from a specific number of individual values, just use a set literal:
{p}
If you must, for some reason, specifically use the set constructor, just wrap the item in question in some other container literal before passing it to set, e.g.:
set((p,))
set([p])
set({p}) # Maximal redundancy
but all of them will be slower and uglier than just using the set literal. The only time people typically would do this is in code written for ancient Python (from when set was introduced in 2.4 until 2.6, the set constructor was the only way to make a set; in 2.7 and higher, the set literal syntax was available).
Minor side-note: Be aware that {} is an empty dict, not a set. To make an empty set, you typically use set() (or {*()}, the one-eyed monkey "operator", if you're having fun). But as long as you have at least one item to put in the set literal, it works (it can tell it's a set because you didn't use : to separate key-value pairs).

>>> from collections import namedtuple
>>> Point = namedtuple('Point', 'x y')
>>> p = Point(5, 5)
>>> p
Point(x=5, y=5)
>>> s = set()
>>> s.add(p)
>>> s
{Point(x=5, y=5)}

You can wrap the tuple in another tuple, may ne something like this:
from collections import namedtuple
Point = namedtuple('Point', 'x y')
p = Point(5, 5)
s = set()
s.add((p,)) # Add the tuple (p,) to the set
print(s) # Output: {(Point(x=5, y=5),)}

Related

Python set unpacking and pattern matching

If this unpacks a set:
>>> a = {1,2,3}
>>> (x, y, z) = a
>>> x
1
>>> y
2
>>> z
3
Then why the following doesnt?
>>> a = {1,2,3}
>>> match a:
... case (p, q, r):
... print(f'{p} {q} {r}')
...
>>>
I would expect the code above to print "1 2 3".
I understand how sets aren't ordered and I wouldn't be surprised if both didn't work, but the former works, only the latter doesn't.
I've always thought that pattern matching IS based on unpacking.
This is not about types not matching, because if we replace the set {1,2,3} with a list [1,2,3] it would work even though we unpack with a tuple syntax.
match statement sequence patterns have more restrictive rules than sequence unpacking. Sequence unpacking will try to unpack any iterable, but a sequence pattern is specified to require
one of the following:
a class that inherits from collections.abc.Sequence
a Python class that has been registered as a collections.abc.Sequence
a builtin class that has its Py_TPFLAGS_SEQUENCE bit set
a class that inherits from any of the above (including classes defined before a parent’s Sequence registration)
set doesn't meet any of those conditions.
Note that since sets are semantically unordered, there is no guarantee which elements will be assigned to which variables if you try to unpack a set. Your (x, y, z) = a unpacking is not safe.

python - dictionary with set of tuples as value is not created correctly

I have read this but it does not seem to work for me -
>>> dict = {}
>>> dict[ "a" ] = set(("1111","test1"))
>>> dict
{'a': set(['1111', 'test1'])}
>>> dict[ "a" ].add( ("2222","test2"))
>>> dict
{'a': set(['1111', 'test1', ('2222', 'test2')])}
I was expecting also the first one to look like a tuple - why is that splitted?
I want when doing :
>>> for key,val_set in dict.items() :
... for val in val_set :
... print val
...
1111
test1
('2222', 'test2')
to have 2 items printed not three.
set() takes the values from an iterable and adds each value separately. You passed in a tuple, which is an iterable with two values.
If you wanted to add the tuple itself, you need to wrap that in an iterable, like a list:
set([("1111","test1")])
In Python 3, you can instead use a set display to create a new set:
{("1111","test1")}
A set display takes elements separated by commas, and ("1111", "test1") is one tuple element here.
In the other post, the set was created with a set display (so {tuple1, tuple2, etc.}) and the answers used either an itertools function or a generator expression to produce the sequence of tuples for the set() function to process; a both produce an iterable type.
You have got your answer but I will advice you to use defaultdict instead. All these type of cases like this set([("1111","test1")]) are properly handled. You don't have to worry about initialize too.
Try This.
from collections import defaultdict
s = defaultdict(set)
s["a"].add(("pw", "sq"));
s["a"].add(("wq", "qq"));
s["a"].add(("aa", "bb"));
for k, v in s.items():
print (k)
for g in v:
print (g)
And please note this, I have assumed all your values opposite your keys are sets.
if this is not the case, go with the first one.

Python unpacking from list comprehension over empty input

When working with a function that returns multiple values with a tuple, I will often find myself using the following idiom to unpack the results from inside a list comprehension.
fiz, buz = zip(*[f(x) for x in input])
Most of the time this works fine, but it throws a ValueError: need more than 0 values to unpack if input is empty. The two ways I can think of to get around this are
fiz = []
buz = []
for x in input:
a, b = f(x)
fiz.append(a)
buz.append(b)
and
if input:
fiz, buz = zip(*[f(x) for x in input])
else:
fiz, buz = [], []
but neither of these feels especially Pythonic—the former is overly verbose and the latter doesn't work if input is a generator rather than a list (in addition to requiring an if/else where I feel like one really shouldn't be needed).
Is there a good simple way to do this? I've mostly been working in Python 2.7 recently, but would also be interested in knowing any Python 3 solutions if they are different.
If f = lambda x: (x,x**2) then this works
x,y = zip(*map(f,input)) if len(input) else ((),())
If input=[], x=() and y=().
If input=[2], x=(2,) and y=(4,)
If input=[2,3], x=(2,3) and y=(4,9)
They're tuples (not lists), but thats thats pretty easy to change.
I would consider using collections.namedtuple() for this sort of thing. I believe the named tuples are deemed more pythonic, and should avoid the need for complicated list comprehensions and zipping / unpacking.
From the documentation:
>>> p = Point(11, y=22) # instantiate with positional or keyword arguments
>>> p[0] + p[1] # indexable like the plain tuple (11, 22)
33
>>> x, y = p # unpack like a regular tuple
>>> x, y
(11, 22)
>>> p.x + p.y # fields also accessible by name
33
>>> p # readable __repr__ with a name=value style
Point(x=11, y=22)
You could use:
fiz = []
buz = []
results = [fiz, buz]
for x in input:
list(map(lambda res, val: res.append(val), results, f(x)))
print(results)
Note about list(map(...)): in Python3, map returns a generator, so we must use it if we want the lambda to be executed.list does it.
(adapted from my answer to Pythonic way to append output of function to several lists, where you could find other ideas.)

How to make sets of iterators

I want to iterate over a certain range and create several sets that contain only the current i. (In the code I don't want to do this for every i, but it's about the general principle).
for i in range(5):
s=set(i)
print(s)
It says int object is not iterable. Why doesn't this work? Please keep the answers simple, I'm a newbie.
set() takes a sequence of values to add, and a single integer is not a sequence.
You could wrap it in a tuple or a list:
s = set((i,))
s = set([i])
but the better option is to use the {..} set literal notation:
s = {i}
The notation looks a lot like creating a dictionary, but you only list values, not keys.
Demo (on Python 2, where the representation uses set([..]) notation):
>>> i = 42
>>> set((i,))
set([42])
>>> set([i])
set([42])
>>> {i}
set([42])
Python 3 reflects sets using the literal notation:
>>> i = 42
>>> {i}
{42}
Set constructor set(x) requires x to be some container, like a list, or other set. You pass an integer, python tries to iterate over it, and fails.
In order to do so you need to pass a singleton of x, like that:
for i in range(5):
s = set([i]) # or s = set((i,))
print(s)
or through simplified set constructor
for i in range(5):
s = {i}
print(s)
or construct an empty set and add your element
for i in range(5):
s = set()
s.add(i)
print(s)
instead of using a for loop in range, you can also feed it to set (or a frozenset if you dont need to change it's content):
>>> set(range(5))
# {0, 1, 2, 3, 4}

Python dictionary keys besides strings and integers?

Anyone have some neat examples of dictionaries with some interesting keys (besides the canonical string or integer), and how you used these in your program?
I understand all we need for a key is something hashable, meaning it must be immutable and comparable (has an __eq__() or __cmp__() method).
A related question is: how can I quickly and slickly define a new hashable?
Let's go for something a bit more esoteric. Suppose you wanted to execute a list of functions and store the result of each. For each function that raised an exception, you want to record the exception, and you also want to keep a count of how many times each kind of exception is raised. Functions and exceptions can be used as dict keys, so this is easy:
funclist = [foo, bar, baz, quux]
results = {}
badfuncs = {}
errorcount = {}
for f in funclist:
try:
results[f] = f()
except Exception as e:
badfuncs[f] = e
errorcount[type(e)] = errorcount[type(e)] + 1 if type(e) in errorcount else 1
Now you can do if foo in badfuncs to test whether that function raised an exception (or if foo in results to see if it ran properly), if ValueError in errorcount to see if any function raised ValueError, and so on.
You can use a tuple as a key, for example if you want to create a multi-column index. Here's a simple example:
>>> index = {("John", "Smith", "1972/01/01"): 123, ("Bob", "Smith", "1972/01/02"): 124}
>>> index
{('Bob', 'Smith', '1972/01/02'): 124, ('John', 'Smith', '1972/01/01'): 123}
>>> index.keys()
[('Bob', 'Smith', '1972/01/02'), ('John', 'Smith', '1972/01/01')]
>>> index['John', 'Smith', '1972/01/01']
123
For an example of how to use a dict as a key (a hashable dict) see this answer:
Python hashable dicts
You left out the probably most important method for an object to be hashable: __hash__().
The shortest implementation of your own hashable type is this:
class A(object):
pass
Now you can use instances of A as dictionary keys:
d = {}
a = A()
b = A()
d[a] = 7
d[b] = 8
This is because user-defined classes are hashable by default, and their hash value is their id -- so they will only compare equal if they are the same object.
Note that instances of A are by no means immutable, and they can be used as dictionary keys nevertheless. The statement that dictionary keys must be immutable only holds for the built-in types.
Note that I've never really used this, but I've always thought using tuples as keys could let you do some interesting things. I would find it a convenient way to map grid coordinates, for example. You can think of this like a grid on a video game (maybe some kind of tactics game like Fire Emblem):
>>> Terrain = { (1,3):"Forest", (1,5):"Water", (3,4):"Land" }
>>> print Terrain
{(1, 5): 'Water', (1, 3): 'Forest', (3, 4): 'Land'}
>>> print Terrain[(1,3)]
Forest
>>> print Terrain[(1,5)]
Water
>>> x = 3
>>> y = 4
>>> print Terrain[(x,y)]
Land
Something like that.
Edit: As Mark Rushakof pointed out in the comments, I'm basically intending this to be a sparse array.
I have no idea why you'd want to do it (and preferably, please don't do it)... but alongside strings and integers, you can also use both simultaneously. That is, as a beginner, I found it both powerful and surprising that:
foo = { 1:'this', 2:'that', 'more':'other', 'less':'etc' }
is an entirely valid dictionary, which provides access to foo[2] as easily as it does to foo['more'].

Categories

Resources