How to get an item from a simple set?

P

Pete Forman

I have a set that contains one item. What is the best way of getting
at that item? Using pop() empties the set. Here is what I've tried.

Python 2.3.4 (#1, Jun 13 2004, 11:21:03)
[GCC 3.3.1 (cygming special)] on cygwin
Type "help", "copyright", "credits" or "license" for more information.
from sets import Set
s = Set(['foo'])
s.copy().pop() 'foo'
[x for x in s][0] 'foo'
s[0]
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: unindexable object
 
S

Skip Montanaro

Pete> I have a set that contains one item. What is the best way of
Pete> getting at that item? Using pop() empties the set.

Do you want to enumerate all the items in the set? If so:

for elt in s:
print elt

If you just want to grab one arbitrary (though not random) item from the
set, try:

elt = iter(s).next()

Note that repeating this operation will always return the same item:
>>> s set(['jkl', 'foo', 'abc', 'def', 'ghi'])
>>> iter(s).next() 'jkl'
>>> iter(s).next() 'jkl'
>>> iter(s).next() 'jkl'
>>> iter(s).next()
'jkl'

Skip
 
P

Pete Forman

Skip Montanaro said:
> Pete> I have a set that contains one item. What is the best way of
> Pete> getting at that item? Using pop() empties the set.
>
> If you just want to grab one arbitrary (though not random) item from the
> set, try:
>
> elt = iter(s).next()

I actually wanted to append the single item to a string, Steven's
solutions work for assignment.

So this looks like my best bet. I'll probably use join instead of +=
in my code.
'bar foo'
 
S

Steven Bethard

Pete said:
I actually wanted to append the single item to a string, Steven's
solutions work for assignment. [snip]'bar foo'

Yeah, using the assignment's an extra line:
>>> line_list = ['bar ']
>>> item, = s
>>> line_list.append(item)
>>> ''.join(line_list)
'bar foo'

I still tend to write the extra line in cases like this -- it guarantees
that the set is really the size that I think it is, where the
iter(s).next() solution will not raise an exception if the set is
actually larger.

Steve
 
P

Pete Forman

Steven Bethard said:
I still tend to write the extra line in cases like this -- it
guarantees that the set is really the size that I think it is, where
the iter(s).next() solution will not raise an exception if the set
is actually larger.

The preceding line in my code is
if len(s) == 1:

:)
 
B

Bengt Richter

Pete> I have a set that contains one item. What is the best way of
Pete> getting at that item? Using pop() empties the set.

Do you want to enumerate all the items in the set? If so:

for elt in s:
print elt

If you just want to grab one arbitrary (though not random) item from the
set, try:

elt = iter(s).next()

Note that repeating this operation will always return the same item:
s set(['jkl', 'foo', 'abc', 'def', 'ghi'])
iter(s).next() 'jkl'
iter(s).next() 'jkl'
iter(s).next() 'jkl'
iter(s).next()
'jkl'
Lest someone else not realize that the operation you are repeating includes creating
a fresh initialized iterator each time, and you're just doing it as a way to grab one element:
>>> s = set(['jkl', 'foo', 'abc', 'def', 'ghi'])
>>> it = iter(s)
>>> it.next() 'jkl'
>>> it.next() 'foo'
>>> it.next() 'abc'
>>> it.next() 'def'
>>> it.next() 'ghi'
>>> it.next()
Traceback (most recent call last):
File "<stdin>", line 1, in ?
StopIteration

Regards,
Bengt Richter
 
B

Bengt Richter

Pete said:
I have a set that contains one item. What is the best way of getting
at that item? Using pop() empties the set. Here is what I've tried.

This is what tuple unpacking is for:
s = set(['foo'])
item, = s
item 'foo'
[item] = s
item
'foo'

It's up to you whether you like the tuple or list syntax better. =)
Thanks. I didn't realize a list format could be used to specify target names
like that. My intial reaction is a bendy feeling in my list expression syntax
recognizer, though. I'm not sure I like that on the left hand side.
It feels too much like __setitem__ on some implied object. The tuple syntax
on the left hand side is only for unpacking (unless you want to imagine invoking
an implied unnamed function, but that's a stretch IMO), so it doesn't trigger
that near-miss syntax recognition feeling.

Regards,
Bengt Richter
 
S

Steven Bethard

Pete said:
The preceding line in my code is
if len(s) == 1:

So is this just one branch of a case statement? What do you do in the
case that len(s) != 1? And which one happens more often?

If I have two possible unpackings of an iterable and I know one is much
more common than the other, I often do something like:

try:
x, y = s # more common unpacking
except ValueError:
[x], y = s, None # less common ('exceptional') unpacking

This is a reasonable pattern if your code really does favor one branch
substantially over the other. But dont' take my word for it. ;) Here's
what timeit says:

----- test.py ----
def test_cond(*args):
if len(args) == 1:
[x], y = args, None
elif len(args) == 2:
x, y = args
else:
raise ValueError('wrong number of arguments')

def test_try(*args):
try:
x, y = args
except ValueError:
[x], y = args, None

def test(fn, single_times, double_times):
for _ in range(single_times):
fn(1)
for _ in range(double_times):
fn(0, 1)


---- command prompt ----
>python -m timeit -s "import test" "test.test(test.test_cond, 10, 10)"
10000 loops, best of 3: 26.7 usec per loop
>python -m timeit -s "import test" "test.test(test.test_try, 10, 10)"
10000 loops, best of 3: 116 usec per loop
>python -m timeit -s "import test" "test.test(test.test_cond, 1, 100)"
10000 loops, best of 3: 132 usec per loop
>python -m timeit -s "import test" "test.test(test.test_try, 1, 100)"
10000 loops, best of 3: 99.8 usec per loop


As you can see, when the try/except block is slower when the two
branches get traversed approximately equally, but faster when one branch
is substantially favored over the other.

Steve
 
S

Steven Bethard

Bengt said:
[item] = s
item

'foo'

It's up to you whether you like the tuple or list syntax better. =)

Thanks. I didn't realize a list format could be used to specify target names
like that. My intial reaction is a bendy feeling in my list expression syntax
recognizer, though. I'm not sure I like that on the left hand side.
It feels too much like __setitem__ on some implied object. The tuple syntax
on the left hand side is only for unpacking (unless you want to imagine invoking
an implied unnamed function, but that's a stretch IMO), so it doesn't trigger
that near-miss syntax recognition feeling.

Yeah, I almost always prefer the tuple (comma) syntax, but occasionally
I find the list syntax clearer, if, for example, I'm unpacking a nested
single-item list:
>>> t [['abcd'], 1, 2]
>>> (x,), y, z = t
>>> x, y, z
('abcd', 1, 2)

The ,), in the tuple-only unpacking makes me uncomfortable for some
reason. I feel marginally more comfortable with:
('abcd', 1, 2)

Of course, I generally feel uncomfortable if I have a weird unpacking
thing like this anyway. It pretty much only comes up for me when I want
to assign some default values in one branch of a try/except or if/else
statement, e.g.

try:
x, y = s
except ValueError:
[x], y = s, None

Steve
 
P

Pete Forman

Steven Bethard said:
> So is this just one branch of a case statement? What do you do in
> the case that len(s) != 1? And which one happens more often?

My s contains a set of possible values that are being refined. I'm
printing either a single resolved value or "many" to indicate there is
still some way to go. len(s) > 1 is not exceptional though len(s) ==
0 would be. Over the run of the program one and many will happen
equally often. Performance is less of an issue than clarity of the
code.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top