10

Python programs are often short and concise and what usually requires bunch of lines in other programming languages (that I know of) can be accomplished in a line or two in python. One such program I am trying to write was to extract every other letters from a string. I have this working code, but wondering if any other concise way is possible?

>>> s
'abcdefg'
>>> b = ""
>>> for i in range(len(s)):
...   if (i%2)==0:
...      b+=s[i]
... 
>>> b
'aceg'
>>> 
rlms
  • 10,650
  • 8
  • 44
  • 61
eagertoLearn
  • 9,772
  • 23
  • 80
  • 122

6 Answers6

35
>>> 'abcdefg'[::2]
'aceg'
Maciej Gol
  • 15,394
  • 4
  • 33
  • 51
  • wow! that is really concise. although I know how slice works, I did not think of this..interestingly, all other answers too posted use slice notations – eagertoLearn Dec 30 '13 at 20:44
  • 1
    @eagertoLearn: ["There should be one-- and preferably only one --obvious way to do it."](http://www.python.org/dev/peps/pep-0020/). That's why all of the posted answers do the same thing. – abarnert Dec 30 '13 at 20:48
  • @abarnert: haha! thats the `zen of python` I guess..beautiful – brain storm Dec 30 '13 at 20:54
18

Use Explain Python's slice notation:

>>> 'abcdefg'[::2]
'aceg'
>>>

The format for slice notation is [start:stop:step]. So, [::2] is telling Python to step through the string by 2's (which will return every other character).

Community
  • 1
  • 1
2

The right way to do this is to just slice the string, as in the other answers.

But if you want a more concise way to write your code, which will work for similar problems that aren't as simple as slicing, there are two tricks: comprehensions, and the enumerate function.

First, this loop:

for i in range(len(foo)):
    value = foo[i]
    something with value and i

… can be written as:

for i, value in enumerate(foo):
    something with value and i

So, in your case:

for i, c in enumerate(s):
    if (i%2)==0:
        b+=c

Next, any loop that starts with an empty object, goes through an iterable (string, list, iterator, etc.), and puts values into a new iterable, possibly running the values through an if filter or an expression that transforms them, can be turned into a comprehension very easily.

While Python has comprehensions for lists, sets, dicts, and iterators, it doesn't have comprehensions for strings—but str.join solves that.

So, putting it together:

b = "".join(c for i, c in enumerate(s) if i%2 == 0)

Not nearly as concise or readable as b = s[::2]… but a lot better than what you started with—and the same idea works when you want to do more complicated things, like if i%2 and i%3 (which doesn't map to any obvious slice), or doubling each letter with c*2 (which could be done by zipping together two slices, but that's not immediately obvious), etc.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • 2
    correct me if I am wrong: the one you had is not list comprehension right; it is generator expression, but this is list comprehension: `b = "".join([c for i, c in enumerate(s) if i%2 == 0])`. ofcourse, generator are best here. but just want to clarify. do not mistake me – brain storm Dec 30 '13 at 21:17
  • @user1988876: You're exactly right. Using parentheses (or nothing) makes a generator expression, square brackets makes a list comprehension, curly braces makes a set or dict comprehension. – abarnert Dec 30 '13 at 21:23
  • @user1988876: Also, in the case of passing to `str.join`, there's really no difference—if `str.join` gets something that isn't fast-indexable from C, it just makes a list out of it so it can fast-index it. In fact (especially before 3.4), counter-intuitively, the listcomp may even be faster and use less memory. But conceptually, I think it's clearer to not build a list when you don't need it. (And, unless you knew how `str.join` worked under the covers, you wouldn't know that you needed it.) – abarnert Dec 30 '13 at 21:26
2

Here is another example both for list and string:

sentence = "The quick brown fox jumped over the lazy dog."

sentence[::2]

Here we are saying: Take the entire string from the beginning to the end and return every 2nd character.

Would return the following:

'Teqikbonfxjme vrtelz o.'

You can do the same for a list:

colors = ["red", "organge", "yellow","green", "blue"]
colors[1:4]

would retrun:

['organge', 'yellow', 'green']

The way I read the slice is: If we have sentence[1:4] Start at index 1 (remember the starting position is index 0) and Stop BEFORE the index 4

Stryker
  • 5,732
  • 1
  • 57
  • 70
0

you could try using slice and join:

>>> k = list(s)
>>> "".join(k[::2])
'aceg'
brain storm
  • 30,124
  • 69
  • 225
  • 393
0

Practically, slicing is the best way to go. However, there are also ways you could improve your existing code, not by making it shorter, but by making it more Pythonic:

>>> s
'abcdefg'
>>> b = []
>>> for index, value in enumerate(s):
      if index % 2 == 0:
         b.append(value)
>>> b = "".join(b)

or even better:

>>> b = "".join(value for index, value in enumerate(s) if index % 2 == 0)

This can be easily extended to more complicated conditions:

>>> b = "".join(value for index, value in enumerate(s) if index % 2 == index % 3 == 0)
rlms
  • 10,650
  • 8
  • 44
  • 61