2

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

letsc
  • 2,515
  • 5
  • 35
  • 54

4 Answers4

4

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]]
TigerhawkT3
  • 48,464
  • 6
  • 60
  • 97
  • Im not sure I fully understand the second `if a:` statement – letsc Jan 26 '17 at 01:11
  • @letsc - Since sublist `a` is only appended to `result` when you encounter a negative number, leaving out that second `if a:` would mean that positive numbers at the end of the input list get added to `a` but `a` never gets added to `result`. – TigerhawkT3 Jan 26 '17 at 01:15
  • Oooh!! fantastic. Thanks a lot! Im accepting this answer as it was posted 1st and was closest to the code I already have. The other 2 answers also work. – letsc Jan 26 '17 at 01:17
  • @letsc for container objects `c` (list, tuples, set, dict, etc) when place in a Boolean context like `if c` is equivalent to doing `if len(c)!=0` – Copperfield Jan 26 '17 at 01:20
3

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.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • I would use a lambda function, I think that is more clear – Copperfield Jan 26 '17 at 01:21
  • @Copperfield: Of course, `lambda x: x >= 0` is an option, but I avoid `lambda`s on principle (I limit them to cases where it's impossible to avoid them so when I do use one, I know it's complex), but I'll admit directly accessing the special rich comparison methods is ugly. An alternative that would still get the higher speed of C level built-ins (`lambda`s are slow) and uses named functions with full documentation would be `key=functools.partial(operator.le, 0)`, though that involves additional imports. – ShadowRanger Jan 26 '17 at 01:36
1

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.
Community
  • 1
  • 1
TMWP
  • 1,545
  • 1
  • 17
  • 31
0

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]]

vks
  • 67,027
  • 10
  • 91
  • 124