tags:

views:

146

answers:

3

I have a list of maybe a 100 or so elements that is actually an email with each line as an element. The list is slightly variable because lines that have a \n in them are put in a separate element so I can't simply slice using fixed values. I essentially need a variable start and stop phrase (needs to be a partial search as well because one of my start phrases might actually be Total Cost: $13.43 so I would just use Total Cost:.) Same thing with the end phrase. I also do not wish to include the start/stop phrases in the returned list. In summary:

>>> email = ['apples','bananas','cats','dogs','elephants','fish','gee']
>>> start = 'ban'
>>> stop = 'ele'

# the magic here

>>> print new_email
['cats', 'dogs']

NOTES

  • While not perfect formatting of the email, it is fairly consistent so there is a slim chance a start/stop phrase will occur more than once.
  • There are also no blank elements.

SOLUTION

Just for funzies and thanks to everybody's help here is my final code:

def get_elements_positions(stringList=list(), startPhrase=None, stopPhrase=None):
    elementPositionStart, elementPositionStop = 0, -1
    if startPhrase:
        elementPositionStart = next((i for i, j in enumerate(stringList) if j.startswith(startPhrase)), 0)
    if stopPhrase:
        elementPositionStop = next((i for i, j in enumerate(stringList) if j.startswith(stopPhrase)), -1)
    if elementPositionStart + 1 == elementPositionStop - 1:
        return elementPositionStart + 1
    else:
        return [elementPositionStart, elementPositionStop]

It returns a list with the starting and ending element position and defaults to 0 and -1 if the respective value cannot be found. (0 being the first element and -1 being the last).

SOLUTION-B

I made a small change, now if the list is describing a start and stop position resulting in just 1 element between it returns that elements position as an integer instead of a list which you still get for multi-line returns.

Thanks again!

+5  A: 
>>> email = ['apples','bananas','cats','dogs','elephants','fish','gee']
>>> start, stop = 'ban', 'ele'
>>> ind_s = next(i for i, j in enumerate(email) if j.startswith(start))
>>> ind_e = next(i for i, j in enumerate(email) if j.startswith(stop) and i > ind_s)
>>> email[ind_s+1:ind_e]
['cats', 'dogs']

To satisfy conditions when element might not be in the list:

>>> def get_ind(prefix, prev=-1):
    it = (i for i, j in enumerate(email) if i > prev and j.startswith(prefix))
    return next(it, None)


>>> start = get_ind('ban')
>>> start = -1 if start is None else start
>>> stop = get_ind('ele', start)
>>> email[start+1:stop]
['cats', 'dogs']
SilentGhost
This will only work if there is no occurrence of the stop phrase before the start phrase. Shouldn't be too hard to work around it, but just keep that in mind.
A. Levy
@a-levy: fixed.
SilentGhost
Will this work if one or both of the phrases are not present in the array?
Brenda Holloway
I like this a lot and it actually does what I need but it also produces an error: `Traceback (most recent call last): File "mail.py", line 21, in <module> ind_s = next(i for i, j in enumerate(email) if j.startswith('StartPhrase'))StopIteration`-- Sigh, why you gotta be like this comments?
TheLizardKing
@Brenda: surely, code is available to run, no? it's easy enough to workaround, but I don't think OP has such need.
SilentGhost
@TheLizardKind: no elements in `email` start with `'StartPhrase'`. Remember it is case sensitive.
SilentGhost
Ohhhh, interesting. So for @Brenda's request would it be possible to make neither the start nor stop phrase required? If no stop, go to eof? You did meet my requirements so SHAZHAM, accepted but it would be cool to see that little feature added.
TheLizardKing
@TheLizardKing: added. It actually ignores non-existing prefixes, not sure if that's what you meant
SilentGhost
@TheLizardKing: That's an easy feature to add. An ugly (and probably unpythonic) way to do it in the case of ind_e is to just catch `StopIteration` exceptions in the `ind_e=...` line and and set `ind_e` to `len(email)-1` in that case. You can do the same, setting `ind_s` to 0, for start phrases.
Brian
@SilentGhost: I want it to not error out if a start and/or stop phrase aren't present. In a perfect world I could run this without any parameters and it would return the whole list or supply just a start or stop parameter and it would just the respective list. You've already done enough though so don't worry about it.
TheLizardKing
@Brian: how's that unpythonic? it's the only solution! or do you have another way of solving it?
SilentGhost
@TheLizardKing: that's exactly how it works!
SilentGhost
@SilentGhost: Awesome, got it running flawlessly now. Thanks for all your help! This was a little more complex than I thought it would be!
TheLizardKing
@SilentGhost: I'm not sure it *is* unpythonic. Still, it strikes me as ugly to use exception handling for flow control. An alternative would be to avoid `next` entirely and find some other way...but there might not be a cleaner way.
Brian
@SilentGhost: Couldn't you just use `next(it, None)` call in `get_ind()`?
J.F. Sebastian
@J.F. Sebastian: indeed, don't know what was I thinking.
SilentGhost
@Brian: `StopIteration` is actually used internally by the iterators and loops. In this particular case, however, providing default value for `next` is more appropriate.
SilentGhost
+4  A: 

An itertools based approach:

import itertools
email = ['apples','bananas','cats','dogs','elephants','fish','gee']
start, stop = 'ban', 'ele'
findstart = itertools.dropwhile(lambda item: not item.startswith(start), email)
findstop = itertools.takewhile(lambda item: not item.startswith(stop), findstart)
print list(findstop)[1:]
// ['cats', 'dogs']
tzaman
Points for playin'
TheLizardKing
+2  A: 

Here you go:

>>> email = ['apples','bananas','cats','dogs','elephants','fish','gee']
>>> start = 'ban'
>>> stop = 'ele'
>>> out = []
>>> appending = False
>>> for item in email:
...     if appending:
...         if stop in item:
...             out.append(item)
...             break
...         else:
...             out.append(item)
...     elif start in item:
...         out.append(item)
...         appending = True
... 
>>> out.pop(0)
'bananas'
>>> out.pop()
'elephants'
>>> print out
['cats', 'dogs']

I think my version is much more readable than the other answers and doesn't require any imports =)

Dan McDougall
Your version is more readable if you are expecting to see every little step in the process. The other versions are written in a more functional programming style. Instead of specifying the algorithm in little steps, they compose the whole algorithm by linking smaller general algorithms together. This may be confusing at first, but when you get used to it, it is very readable! The "itertools" solution uses the dropwhile and takewhile algorithms to solve the problem in 2 lines. Once you start thinking functionally, you can read and understand an algorithm's implementation much more quickly.
A. Levy
It has nothing to do with "confusing"; none of the examples are confusing. It has to do with "Explicit is better than implicit" and "Sparse is better than dense."
Dan McDougall