views:

138

answers:

7

Using Python, I'm trying to convert a sentence of words into a flat list of all distinct letters in that sentence.

Here's my current code:

words = 'She sells seashells by the seashore'

ltr = []

# Convert the string that is "words" to a list of its component words
word_list = [x.strip().lower() for x in words.split(' ')]

# Now convert the list of component words to a distinct list of
# all letters encountered.
for word in word_list:
    for c in word:
        if c not in ltr:
            ltr.append(c)

print ltr

This code returns ['s', 'h', 'e', 'l', 'a', 'b', 'y', 't', 'o', 'r'], which is correct, but is there a more Pythonic way to this answer, probably using list comprehensions/set?

When I try to combine list-comprehension nesting and filtering, I get lists of lists instead of a flat list.

The order of the distinct letters in the final list (ltr) is not important; what's crucial is that they be unique.

+3  A: 
set([letter.lower() for letter in words if letter != ' '])

Edit: I just tried it and found this will also work (maybe this is what SilentGhost was referring to):

set(letter.lower() for letter in words if letter != ' ')

And if you need to have a list rather than a set, you can

list(set(letter.lower() for letter in words if letter != ' '))
danben
you don't list comprehension there
SilentGhost
@Art Metzer: Note that splitting isn't really necessary, since a string is an iterable type. This is probably why you were getting lists of lists when you didn't want them.
danben
@SilentGhost: I don't understand your comment, can you clarify?
danben
@danben: you can use a generator expression to avoid creating a new list. Like in Ignacio's answer.
Adam Bernier
you're using list comprehension, you don't have to.
SilentGhost
Got it, thanks. Answer updated.
danben
+3  A: 

Make ltr a set and change your loop body a little:

ltr = set()

for word in word_list:
    for c in word:
       ltr.add(c)

Or using a list comprehension:

ltr = set([c for word in word_list for c in word])
Eli Bendersky
A: 
set(l for w in word_list for l in w)
Ignacio Vazquez-Abrams
This will include spaces.
danben
@danben: You *did* notice that `word_list` is already split by spaces, right?
Ignacio Vazquez-Abrams
Ah, sorry - I misread as `for w in words...`
danben
+2  A: 
>>> set('She sells seashells by the seashore'.replace(' ', '').lower())
set(['a', 'b', 'e', 'h', 'l', 'o', 's', 'r', 't', 'y'])
>>> set(c.lower() for c in 'She sells seashells by the seashore' if not c.isspace())
set(['a', 'b', 'e', 'h', 'l', 'o', 's', 'r', 't', 'y'])
>>> from itertools import chain
>>> set(chain(*'She sells seashells by the seashore'.lower().split()))
set(['a', 'b', 'e', 'h', 'l', 'o', 's', 'r', 't', 'y'])
ephemient
+2  A: 

here are some timings made with py3k:

>>> import timeit
>>> def t():                    # mine (see history)
    a = {i.lower() for i in words}
    a.discard(' ')
    return a

>>> timeit.timeit(t)
7.993071812372081
>>> def b():                    # danben
    return set(letter.lower() for letter in words if letter != ' ')

>>> timeit.timeit(b)
9.982847967921138
>>> def c():                    # ephemient in comment
    return {i.lower() for i in words if i != ' '}

>>> timeit.timeit(c)
8.241267610375516
>>> def d():                    #Mike Graham
    a = set(words.lower())
    a.discard(' ')
    return a

>>> timeit.timeit(d)
2.7693045186082372
SilentGhost
`{i.lower() for i in words if i != ' '}` works too.
ephemient
@ephemient: it works, but it's a bit slower. and btw, `set(i.lower for i in words if i != ' ')` version is 20% slower in py3k
SilentGhost
+14  A: 

Sets provide a simple, efficient solution.

words = 'She sells seashells by the seashore'

unique_letters = set(words.lower())
unique_letters.discard(' ') # If there was a space, remove it.
Mike Graham
this is the winner :)
SilentGhost
pretty much the same as ephemient, but your answer if formatted nicer.
tgray
Very nice!5chars
danben
@tgray - this was also posted first; ephemient edited his in afterwards (see edit logs).
danben
@tgray: it's not about formatting, it's about efficiently applying `.lower`!
SilentGhost
I'd make it one line and more general with unique_letters = set(c for c in words.lower() if c.isalpha())
job
Just the kind of shorthand answer I was looking for! Thanks!
Art Metzer
@job: Why though? This solution is cleaner, even if it is 2 lines *and* it doesn't run a comparison on every letter.
jcoon
I didn't notice this when I edited my answer, but in any case, Mike's answer is cleaner. +1
ephemient
unique_letters = set(words.lower()) - set(' ')
Paul Hankin
A: 
words = 'She sells seashells by the seashore'

ltr = list(set(list(words.lower())))
ltr.remove(' ')
print ltr
ZenGyro
oh, dear, oh dear, oh dear... here is the useful read: http://docs.python.org/reference/datamodel.html#the-standard-type-hierarchy
SilentGhost