Need help porting Perl function

John Machin sjmachin at lexicon.net
Sat Jun 7 20:01:33 EDT 2008


On Jun 8, 8:17 am, eat... at gmail.com wrote:
> On Jun 7, 5:56 pm, John Machin <sjmac... at lexicon.net> wrote:
>
>
>
> > On Jun 8, 6:05 am, eat... at gmail.com wrote:
>
> > > On Jun 7, 2:42 pm, "Daniel Fetchinson" <fetchin... at googlemail.com>
> > > wrote:
>
> > > > > Hi.  I'd like to port a Perl function that does something I don't
> > > > > know how to do in Python.  (In fact, it may even be something that
> > > > > is distinctly un-Pythonic!)
>
> > > > > The original Perl function takes a reference to an array, removes
> > > > > from this array all the elements that satisfy a particular criterion,
> > > > > and returns the list consisting of the removed elements.  Hence
> > > > > this function returns a value *and* has a major side effect, namely
> > > > > the target array of the original argument will be modified (this
> > > > > is the part I suspect may be un-Pythonic).
>
> > > > > Can a Python function achieve the same effect?  If not, how would
> > > > > one code a similar functionality in Python?  Basically the problem
> > > > > is to split one list into two according to some criterion.
>
> > > > This function will take a list of integers and modify it in place such
> > > > that it removes even integers. The removed integers are returned as a
> > > > new list (disclaimer: I'm 100% sure it can be done better, more
> > > > optimized, etc, etc):
>
> > > > def mod( alist ):
> > > >     old = alist[:]
> > > >     ret = [ ]
> > > >     for i in old:
> > > >         if i % 2 == 0:
> > > >             ret.append( alist.pop( alist.index( i ) ) )
>
> > > >     return ret
>
> > > > x = range(10,20)
>
> > > > print x
> > > > r = mod( x )
> > > > print r
> > > > print x
>
> > > > HTH,
> > > > Daniel
> > > > --
> > > > Psss, psss, put it down! -http://www.cafepress.com/putitdown
>
> > > def mod( alist ):
> > >     return [ alist.pop( alist.index( x ) ) for x in alist if x % 2 ==
> > > 0 ]
>
> > > alist = range(10,20)
> > > blist = mod( alist )
>
> > > print alist
> > > print blist
>
> > > The same thing with list comprehensions.
>
> > Not the same. The original responder was careful not to iterate over
> > the list which he was mutating.
>
> > >>> def mod(alist):
>
> > ...    return [alist.pop(alist.index(x)) for x in alist if x % 2 == 0]
> > ...>>> a = range(10)
> > >>> print mod(a), a
>
> > [0, 2, 4, 6, 8] [1, 3, 5, 7, 9]>>> a = [2,2,2,2,2,2,2,2]
> > >>> print mod(a), a
>
> > [2, 2, 2, 2] [2, 2, 2, 2]
> > # should be [2, 2, 2, 2, 2, 2, 2, 2] []
>
> Alas, it appears my understanding of list comprehensions is
> significantly less comprehensive than I thought =)

It's nothing to do with list comprehensions, which are syntactical
sugar for traditional loops. You could rewrite your list comprehension
in the traditional manner:

def mod(alist):
    ret = []
    for x in alist:
        if x % 2 == 0:
            ret.append(alist.pop(alist.index(x))
    return ret

and it would still fail for the same reason: mutating the list over
which you are iterating.

At the expense of even more time and memory you can do an easy fix:
change 'for x in alist' to 'for x in alist[:]' so that you are
iterating over a copy.

Alternatively, go back to basics:
>>> def modr(alist):
...    ret = []
...    for i in xrange(len(alist) - 1, -1, -1):
...       if alist[i] % 2 == 0:
...          ret.append(alist[i])
...          del alist[i]
...    return ret
...
>>> a = [2,2,2,2,2,2,2,2,2]
>>> print modr(a), a
[2, 2, 2, 2, 2, 2, 2, 2, 2] []
>>>

HTH,
John



More information about the Python-list mailing list