letsc
letsc

Reputation: 2567

Python : Split list based on negative integers

I have a list say l = [1,5,8,-3,6,8,-3,2,-4,6,8]. Im trying to split it into sublists of positive integers i.e. the above list would give me [[1,5,8],[6,8],[2],[6,8]]. I've tried the following:

l = [1,5,8,-3,6,8,-3,2,-4,6,8]
index = 0
def sublist(somelist):
    a = []
    for i in somelist:
        if i > 0:
            a.append(i)
        else:
            global index
            index += somelist.index(i)
            break
    return a

print sublist(l)

With this I can get the 1st sublist ( [1,5,8] ) and the index number of the 1st negative integer at 3. Now if I run my function again and pass it l[index+1:], I cant get the next sublist and assume that index will be updated to show 6. However i cant, for the life of me cant figure out how to run the function in a loop or what condition to use so that I can keep running my function and giving it l[index+1:] where index is the updated, most recently encountered position of a negative integer. Any help will be greatly appreciated

Upvotes: 2

Views: 2015

Answers (4)

TMWP
TMWP

Reputation: 1625

This code makes use of concepts found at this URL: Python list comprehension- "pop" result from original list?

Applying an interesting concept found here to your problem, the following are some alternatives to what others have posted for this question so far. Both use list comprehensions and are commented to explain the purpose of the second option versus the first. Did this experiment for me as part of my learning curve, but hoping it may help you and others on this thread as well:

What's nice about these is that if your input list is very very large, you won't have to double your memory expenditure to get the job done. You build one up as you shrink the other down.

This code was tested on Python 2.7 and Python 3.6:

o1 =  [1,5,8,-3,6,9,-4,2,-5,6,7,-7, 999, -43, -1, 888]    
                                # modified version of poster's list
o1b = [1,5,8,-3,6,8,-3,2,-4,6,8]    # poster's list

o2 = [x for x in (o1.pop() for i in range(len(o1))) \
if (lambda x: True if x < 0 else o1.insert(0, x))(x)]

o2b = [x for x in (o1b.pop() for i in range(len(o1b))) \
if (lambda x: True if x < 0 else o1b.insert(0, x))(x)]

print(o1)
print(o2)
print("")

print(o1b)
print(o2b)

It produces result sets like this (on iPython Jupyter Notebooks):

[1, 5, 8, 6, 9, 2, 6, 7, 999, 888]
[-1, -43, -7, -5, -4, -3]

[1, 5, 8, 6, 8, 2, 6, 8]
[-4, -3, -3]

Here is another version that also uses list comprehensions as the work horse, but functionalizes the code in way that is more read-able (I think) and easier to test with different numeric lists. Some will probably prefer the original code since it is shorter:

p1 =  [1,5,8,-3,6,9,-4,2,-5,6,7,-7, 999, -43, -1, 888]    
                                # modified version of poster's list
p1b = [1,5,8,-3,6,8,-3,2,-4,6,8]    # poster's list

def lst_mut_byNeg_mod(x, pLst):     # list mutation by neg nums module
    # this function only make sense in context of usage in 
    # split_pos_negs_in_list()

    if x < 0: return True
    else: 
        pLst.insert(0,x)
        return False

def split_pos_negs_in_list(pLst):
    pLngth = len(pLst)              # reduces nesting of ((()))
    return [x for x in (pLst.pop() for i in range(pLngth)) \
            if lst_mut_byNeg_mod(x, pLst)]

p2 = split_pos_negs_in_list(p1)
print(p1)
print(p2)
print("")
p2b = split_pos_negs_in_list(p1b)
print(p1b)
print(p2b)

Final Thoughts: Link provided earlier had a number of ideas in the comment thread:

  • It recommends a Google search for the "python bloom filter library" - this sounds promising from a performance standpoint but I have not yet looked into it
  • There is a post on that thread with 554 up-voted, and yet it has at least 4 comments explaining what might be faulty with it. When exploring options, it may be advisable to scan the comment trail and not just review what gets the most votes. There are many options proposed for situations like this.

Upvotes: 1

vks
vks

Reputation: 67978

Just for fun you can use re too for a one liner.

l = [1,5,8,-3,6,8,-3,2,-4,6,8]
print map(lambda x: map(int,x.split(",")), re.findall(r"(?<=[,\[])\s*\d+(?:,\s*\d+)*(?=,\s*-\d+|\])", str(l)))

Output:[[1, 5, 8], [6, 8], [2], [6, 8]]

Upvotes: 0

ShadowRanger
ShadowRanger

Reputation: 155506

Since somelist never changes, rerunning index will always get index of the first instance of an element, not the one you just reached. I'd suggest looking at enumerate to get the index and element as you loop, so no calls to index are necessary.

That said, you could use the included batteries to solve this as a one-liner, using itertools.groupby:

from itertools import groupby

def sublist(somelist):
    return [list(g) for k, g in groupby(somelist, key=(0).__le__) if k]

Still worth working through your code to understand it, but the above is going to be fast and fairly simple.

Upvotes: 3

TigerhawkT3
TigerhawkT3

Reputation: 49330

You need to keep track of two levels of list here - the large list that holds the sublists, and the sublists themselves. Start a large list, start a sublist, and keep appending to the current sublist while i is non-negative (which includes positive numbers and 0, by the way). When i is negative, append the current sublist to the large list and start a new sublist. Also note that you should handle cases where the first element is negative or the last element isn't negative.

l = [1,5,8,-3,6,8,-3,2,-4,6,8]

def sublist(somelist):
    result = []
    a = []
    for i in somelist:
        if i > 0:
            a.append(i)
        else:
            if a: # make sure a has something in it
                result.append(a)
            a = []
    if a: # if a is still accumulating elements
        result.append(a)
    return result

The result:

>>> sublist(l)
[[1, 5, 8], [6, 8], [2], [6, 8]]

Upvotes: 4

Related Questions