0

I have a list of python objects:

fruits = [ 'apple', 'orange', 'banana', 'grape', 'cherry' ]

I currently have a for loop in a class method that returns "prev_fruit" and "next_fruit" objects for a given object:

def get_prev_next(self, fruit_list):
    prev_fruit = next_fruit = None
    fruit_list_length = len(fruit_list)
    for idx, fruit in enumerate(fruit_list):
        if fruit == self:
            if idx > 0:
                prev_fruit = fruit_list[idx-1]
            if idx < (fruit_list_length-1):
                next_fruit = fruit_list[idx+1]
    return prev_fruit, next_fruit

This works although there are probably more efficient ways of doing it (which I'm happy to learn about).

I now want the list to optionally be "looping" (previous for first index is last and next index for last is first).

def get_prev_next(self, fruit_list, looping=False):
    ...

What is an efficient way to do this on lists of objects with 1-10000 values?

"efficient" is necessarily not "most efficient" as code legibility and portability is a factor - I don't want to relearn the approach six months from now

chapelo
  • 2,519
  • 13
  • 19
rjmoggach
  • 1,458
  • 15
  • 27
  • 1
    You can make the objects part of a [Linked List](http://stackoverflow.com/questions/280243/python-linked-list)? – Bryce Drew Jul 21 '16 at 20:26
  • 1-10000 values... Are all unique? When looking for an index of one value, does it matter if it returns the first value only, or do you need all occurrences? – chapelo Jul 22 '16 at 01:59
  • to be specific the lists are django querysets that return a list of objects - the question was written with an desire to make it a bit more generic - ie. changing the data structure and refactoring is not really a solution that is viable - question is not wholy about how to make it more efficient but how to make it optionally looping, efficiently – rjmoggach Jul 27 '16 at 16:51

1 Answers1

0

If the input is a list, then you're kind of stuck searching the list for the index of the fruit that you have and then getting the previous and next ones from that ...

def forgiving_getitem(lst, ix, default=None):
    if 0 <= ix < len(lst):
        return lst[ix]
    else:
        return default

ix = fruit_list.index(self)
previous = forgiving_getitem(fruit_list, ix - 1)
next = forgiving_getitem(fruit_list, ix + 1)

Unfortunately, this has O(N) runtime complexity. If you're going to do this a lot with the same fruit_list, it might be better to come up with a different datastructure (e.g. a doubly linked list) where each node carries around the fruit and the previous and next fruits. In this case, getting the previous and next is O(1) and you only need to spend O(N) time constructing the nodes in the first place, however, there could be significant other code refactoring involved to work with nodes instead of fruits directly...

mgilson
  • 300,191
  • 65
  • 633
  • 696
  • But if you know the previous and next indices in an array, *accessing* them is still constant time, no? But the iteration over the array is linear just as it will be with a linked list. All the linked structure really gives you is in a handy way of accessing prev and next. But this can be achieved using an array with the same time complexity. Indeed, accessing a particular index in your array is linear for linked lists! – juanpa.arrivillaga Jul 21 '16 at 20:45
  • @juanpa.arrivillaga -- no, you're missing the point of what I'm saying. Of course, if you have to search the linked list to _find_ the fruit, it'll be O(N). However, OP already has that problem in the original code (where did the fruit [`self`] come from?). I'm advocating that instead of passing around individual fruits as `self`, start working with nodes that hold references to fruits. – mgilson Jul 21 '16 at 20:50
  • Ah, OK. I see. That will probably involve significant refactoring, as you've said. – juanpa.arrivillaga Jul 21 '16 at 20:53
  • changing the data structure and refactoring is not viable - this answer will not return sane values for a looping list with length of 1 although it may work if the default is `0` instead of `None` – rjmoggach Jul 27 '16 at 16:53
  • @mogga -- Good point on the `len(fruits) == 1` wraparound. I've updated the answer and I _think_ it should get that right now. Realistically speaking, this isn't much different than your current solution -- though it short-circuits on the first index found rather than looking at all values and returning the next/prev for the _last_ instance of fruit), but it's really the best you can do without changing the data-structure or giving more constraints... It also raises `IndexError` if the fruit isn't found, but that can be handled via `try`/`except` as needed. – mgilson Jul 27 '16 at 17:01