ssm
ssm

Reputation: 5373

getting rid of loops

So recently, I wrote this piece of code. This gets a list of boolean values which comprises if all False values interspersed with n islands of True values. Something like this:

[False, False, False, True, True, False, False, False, True, True, True, False]
#                     [island 1]                       [   island 2    ]

Now I want to separate these into n lists where each of the islands are seaprated. For the above list, I would obtain:

[[False, False, False, True, True, False, False, False, False, False, False, False], #-> list has island 1
 [False, False, False, False, False, False, False, False, True, True, True, False]] #-> list has island 2

This is the program I used to solve the problem:

import itertools as it

def getPosList(x):

    segs = [ (s[0], list(s[1])) for s in  it.groupby(x)]

    allSegs = [ []  for s in segs if s[0] ]

    i = 0

    for s in segs:
        s1, s2 = s
        s2 = list(s2)

        if s1:
            for j, s in enumerate(allSegs):
                if i == j: s += s2
                else:      s += len(s2)*[False]
            i += 1
        else:
            for s in allSegs: s += s2

    return allSegs 

if __name__ == '__main__':

    print getPosList([False, False, False, True, True, False, False, False, True, True, True, False])

This looks very unPythonic with loops and dummy variables. I feel there should be a more elegant solution than the one I just wrote. Something to do with dropwhile, reduce etc. However, I dont seem to be able to come up with something better. Any suggestions on improvement? I cringe every time I see this ugly piece of code.

Edit:

I took inspiration from feseje and came up with a recursive version.

import itertools as it

def ppListTF(xs):
    return '[' + '|'.join([ '#' if x else ' '  for x in xs ]) + ']'

def getPosList1(x):
    '''
    '''
    if x == []: return []
    curX     = list(it.takewhile(lambda m: m == x[0] , x))
    nextVals = getPosList1(list(it.dropwhile(lambda m: m == x[0], x)))
    curX1    = [False]*len(curX)
    if nextVals:
        curX2    = [False]*len(nextVals[0])

    if x[0]:
        # The first element is True. Split the list here
        if nextVals: return [curX+curX2] + [ curX1+j for j in nextVals] 
        else:        return   [curX] + [curX1]

    else:
        # Just add the series of False to whatever is returned
        if nextVals: return [ curX+j for j in nextVals]
        else:        return   [curX]


if __name__ == '__main__':

    inp = [False, False, False, True, True, False, False, False, True, True, True, False, True, True, False, False, False, False]
    val = getPosList1(inp)
    if any(inp):
        val = filter( any, val )

    print 'i', ppListTF(inp)
    for i, v in enumerate(val):
        print i, ppListTF(v)

I still like Hrabals answer. It is the best. Thanks to everyone who posted answers.

Upvotes: 0

Views: 1152

Answers (3)

Hrabal
Hrabal

Reputation: 2525

Similar approach to Chris Martin's answer, but with no lambdas, just list comprehensions:

islands = [False, False, False, True, True, False, False, False, True, True, True, False]
islands_starts = [v[0]+1 for v in list(enumerate(zip(islands, islands[1:]))) if v[1] == (False, True)]
#[3, 8]
islands_ends = [v[0] for v in list(enumerate(zip(islands, islands[1:]))) if v[1] == (True, False)]
#[4, 10]
separated_islands = [[False if p not in range(i[0],i[1]+1) else True for p in range(0,len(islands))] for i in zip(islands_starts,islands_ends)]
#[[False, False, False, True, True, False, False, False, False, False, False, False],
#[False, False, False, False, False, False, False, False, True, True, True, False]]

Upvotes: 2

Chris Martin
Chris Martin

Reputation: 30736

I'm going to use 0 and 1 for brevity. This is Python 2.7. Not crazy about how this turned out, but maybe it'll give you some other ideas.

from itertools import *

a = map(int, list('1000111001001'))

start_indices = [i[0] for i in list(enumerate(zip(a, [0]+a))) if i[1] == (1, 0)]
# [0, 4, 9, 12]

get_island_length = lambda i: len(list(takewhile(lambda x: x, a[i:])))

pad = lambda b: b + [0]*(len(a)-len(b))

islands = [pad([0]*i + [1]*get_island_length(i)) for i in start_indices]

[''.join(map(str, i)) for i in islands]
# ['1000000000000', '0000111000000', '0000000001000', '0000000000001']

Upvotes: 2

fejese
fejese

Reputation: 4628

Although I agree with @grc that this is not really a suitable question here, I'd like to point out that you can do it with only one iteration. You go and process the elements one by one and create a new list for an island when you hit one.

 def separateIslands(islandSegments):
     separateIslands = []
     empty = [False for x in islandSegments]

     for i in range(0, len(islandSegments)-1):
         if islandSegments[i]:
             if i == 0 or not islandSegments[i-1]:
                 separateIslands.append(empty)

             separateIslands[len(separateIslands) - 1][i] = True;

     return separateIslands

 islandSegments = [False, False, False, True, True, False, False, False, True, True, True, False]
 print separateIslands(islandSegments)

This is probably not in any way more "pythonian" but still a simplification :)

Upvotes: 1

Related Questions