3

I have a little code that takes a list of objects, and only outputs the items in the list that are unique.

This is my code

def only_once(a):
    return [x for x in a if a.count(x) is 1]

My teacher requires us to use sets for this function though. Can someone show me what I can do?

My code has to take an input such as a=[1,4,6,7,3,2,4,5,7,5,6], and output [1, 3, 2]. Has to retain it's order also.

Peter O.
  • 32,158
  • 14
  • 82
  • 96
user1730056
  • 623
  • 3
  • 11
  • 19
  • 1
    Sets don't preserve order, so I can't really see how a set will be helpful here aside from providing fast `in` functionality. – Blender Oct 14 '12 at 21:11
  • 1
    @Blender: you can use sets and preserve order: [In Python, what is the fastest algorithm for removing duplicates from a list so that all elements are unique *while preserving order*?](http://stackoverflow.com/questions/89178/in-python-what-is-the-fastest-algorithm-for-removing-duplicates-from-a-list-so) – jfs Oct 14 '12 at 21:18

3 Answers3

3

To clarify, what you want is a set of items that appear once, and only once.

The best option here is to use collections.Counter(), as it means you only count the items once, rather than once per item, greatly increasing performance:

>>> import collections
>>> {key for key, count in collections.Counter(a).items() if count == 1}
{1, 2, 3}

We simply replace the square brackets with curly braces to signify a set comprehension over a list comprehension, to get a set of results.

Gareth Latty
  • 86,389
  • 17
  • 178
  • 183
3

If you need to remove any item that is in the list more than once, not just occurences after the first, you can use:

# without using generators / comprehensions
def only_once(iterable):
    seen = set()
    duplicates = set()
    for item in iterable:
        if item in seen:
            duplicates.add(item)
        seen.add(item)
    result = []
    for item in iterable:
        if item not in duplicates:
            result.append(item)
    return result

For general order-preserving duplicate elimination, see unique_everseen in the itertools recipes:

def unique_everseen(iterable, key=None):
    "List unique elements, preserving order. Remember all elements ever seen."
    # unique_everseen('AAAABBBCCDAABBB') --> A B C D
    # unique_everseen('ABBCcAD', str.lower) --> A B C D
    seen = set()
    seen_add = seen.add
    if key is None:
        for element in ifilterfalse(seen.__contains__, iterable):
            seen_add(element)
            yield element
    else:
        for element in iterable:
            k = key(element)
            if k not in seen:
                seen_add(k)
                yield element
agf
  • 171,228
  • 44
  • 289
  • 238
  • +1, I looked in ``itertools`` as I was sure there was something for this, and didn't see this one. The best solution here. – Gareth Latty Oct 14 '12 at 21:11
3

[I'm assuming that you're also user1744238 and user1744316 -- please pick a username and stick to it, that way it's easier to check to see what variants of a question you've asked and what you've already tried.]

One set-based approach is to use two sets as a counter. You only care about whether you've seen something once or more than once. For example, here's an easy-to-explain approach:

  1. Make an empty set for once and more.
  2. Loop over every element of your list, and:
    1. If you haven't seen it before, add it to once.
    2. If you've seen it once, remove it from once and add it to more.
  3. Now you know what elements you've seen exactly once, in the set once.
  4. Loop over the elements of the list, and if you've seen it once, add it to the output list, and remove it from the once set so you don't output the same element twice.

This gives me:

In [49]: f([1,4,6,7,3,2,4,5,7,5,6])
Out[49]: [1, 3, 2]
agf
  • 171,228
  • 44
  • 289
  • 238
DSM
  • 342,061
  • 65
  • 592
  • 494
  • thank you very much, I agree with Blender. Very helpful, trying to build using this – user1730056 Oct 14 '12 at 21:23
  • This is essentially the algorithm I used in my post (yes, I ignored the homework aspect of this) except that I don't bother to remove items from "once" since you can just invert the check and use "more". – agf Oct 14 '12 at 21:38
  • @agf: yep, that's the better way. – DSM Oct 14 '12 at 21:40