Find monotonic sequences in a list?

后端 未结 4 397
一整个雨季
一整个雨季 2020-12-11 23:52

I\'m new in Python but basically I want to create sub-groups of element from the list with a double loop, therefore I gonna compare the first element with the next to figure

相关标签:
4条回答
  • 2020-12-12 00:08

    @uselpa's version is fine. Here's mine (same issue with [50] instead of just 50) that uses collections.deque to be a little more efficient, and also some long comments...

    #! /usr/bin/env python
    
    from collections import deque
    
    def process(lst):
        """
        Given a list of values that is not sorted (such that for some
        valid indices i,j, i<j, sometimes lst[i] > lst[j]), produce a
        new list-of-lists, such that in the new list, each sublist *is*
        sorted:
            for all sublist \elem returnval:
                assert_is_sorted(sublist)
        and furthermore this is the minimal set of sublists required
        to achieve the condition.
    
        Thus, if the input list lst is actually sorted, this returns
        [list(lst)].
        """
        def sublist(deq):
            """
            Pop items off the front of deque deq until the next one
            goes backwards.  Return the constructed sub-list.
            """
            sub = [deq.popleft()]
            while deq and deq[0] >= sub[-1]:
                sub.append(deq.popleft())
            return sub
    
        # Make a copy of lst before modifying it; use a deque so that
        # we can pull entries off it cheaply.
        deq = deque(lst)
        output = []
        ret = []
        while deq:
            ret.append(sublist(deq))
        return ret
    
    print process([45,78,120,47,58,50,32,34])
    

    (Incidentally, in the days before collections.deque I'd probably just use a reversed copy of lst and use lst.pop() in sublist. That's not quite as obvious, though.)

    0 讨论(0)
  • 2020-12-12 00:12

    I used a double loop as well, but put the inner loop in a function:

    #!/usr/bin/env python
    
    def process(lst):
    
        def prefix(lst):
            pre = []
            while lst and (not pre or pre[-1] <= lst[0]):
                pre.append(lst[0])
                lst = lst[1:]
            return pre, lst
    
        res=[]
        while lst:
            subres, lst = prefix(lst)
            res.append(subres) 
        return res
    
    print process([45,78,120,47,58,50,32,34])
    => [[45, 78, 120], [47, 58], [50], [32, 34]]
    

    The prefix function basically splits a list into 2; the first part is composed of the first ascending numbers, the second is the rest that still needs to be processed (or the empty list, if we are done).

    The main function then simply assembles the first parts in a result lists, and hands the rest back to the inner function.

    I'm not sure about the single value 50; in your example it's not in a sublist, but in mine it is. If it is a requirement, then change

            res.append(subres) 
    

    to

            res.append(subres[0] if len(subres)==1 else subres)
    
    print process([45,78,120,47,58,50,32,34])
    => [[45, 78, 120], [47, 58], 50, [32, 34]]
    
    0 讨论(0)
  • 2020-12-12 00:20

    No loop! Well at least, no explicit looping...

    import itertools
    
    def process(lst):
        # Guard clause against empty lists
        if len(lst) < 1:
            return lst
    
        # use a dictionary here to work around closure limitations
        state = { 'prev': lst[0], 'n': 0 }
    
        def grouper(x):
            if x < state['prev']:
                state['n'] += 1
    
            state['prev'] = x
            return state['n']
    
        return [ list(g) for k, g in itertools.groupby(lst, grouper) ]
    

    Usage (work both with Python 2 & Python 3):

    >>> data = [45,78,120,47,58,50,32,34]
    >>> print (list(process(data)))
    [[45, 78, 120], [47, 58], [50], [32, 34]]
    

    Joke apart, if you need to group items in a list itertools.groupby deserves a little bit of attention. Not always the easiest/best answer -- but worth to make a try...


    EDIT: If you don't like closures -- and prefer using an object to hold the state, here is an alternative:

    class process:
        def __call__(self, lst):
            if len(lst) < 1:
                return lst
    
            self.prev = lst[0]
            self.n = 0
    
            return [ list(g) for k, g in itertools.groupby(lst, self._grouper) ]
    
        def _grouper(self, x):
            if x < self.prev:
                self.n += 1
    
            self.prev = x
            return self.n
    
    
    
    data = [45,78,120,47,58,50,32,34]
    print (list(process()(data)))
    

    EDIT2: Since I prefer closures ... but @torek don't like the dictionary syntax, here a third variation around the same solution:

    import itertools
    
    def process(lst):
        # Guard clause against empty lists
        if len(lst) < 1:
            return lst
    
        # use an object here to work around closure limitations
        state = type('State', (object,), dict(prev=lst[0], n=0))
    
        def grouper(x):
            if x < state.prev:
                state.n += 1
    
            state.prev = x
            return state.n
    
        return [ list(g) for k, g in itertools.groupby(lst, grouper) ]
    
    data = [45,78,120,47,58,50,32,34]
    print (list(process(data)))
    
    0 讨论(0)
  • 2020-12-12 00:28

    why not simply use a single for loop instead of using itertool and all fancy stuff.

    def list_test(inlist, outlist):
        if not inlist:
            return []
        prev=inlist[0]
        x=[]
        for item in inlist:
            if item >= prev:
                x.append(item)
            else:
                outlist.append(x)
                x=[]
                x.append(item)
            prev=item
        if x:
            outlist.append(x)
    
    
    in_list=[1,0,1,2,3]
    out_list = []
    list_test(in_list, out_list)    
    print(out_list)
    
    

    O/p:[[1], [0, 1, 2, 3]]

    0 讨论(0)
提交回复
热议问题