Best way to get nearest non-zero value from list I have a list of values and I need to, given any arbitrary starting index, find the closest value which is non zero, if the value at the starting index is zero...

Here is what I have:

def getNearestNonZero(start_index):
    mylist = [4,2,6,7,3,0,0,9,4,2,5,8,1,7]
    val = mylist[start_index]
    if val == 0:
        loop_length = 0
        after = mylist[start_index+1:]
        before = mylist[:start_index]
        before =  before[::-1]
        print(before, after)

        if len(before) &gt;= len(after):
            loop_length = len(before)
        else:
            loop_length = len(after)

        for i in range(loop_length):
            if i &lt; len(before):
                before_val = before[i]
            if i &lt; len(after):
                after_val = after[i]

            if before_val &gt; 0:
                return before_val

            if after_val &gt; 0:
                return after_val

    return val

result = getNearestNonZero(6)
print(result)

result = getNearestNonZero(5)
print(result)



  [0, 3, 7, 6, 2, 4] [9, 4, 2, 5, 8, 1, 7] 
  
  9 
  
  [3, 7, 6, 2, 4] [0, 9, 4, 2, 5, 8, 1, 7] 
  
  3


What I do, is I first check to see if the value at the start_index is > 0. If it is, great, return it. If however, the value is zero, we need to find the closest non-zero, with a preference for before, rather than after...

To do this, I split mylist into two separate lists, before and after. If my start index is 6, before will now look like: [4,2,6,7,3,0] and after will look like: [9,4,2,5,8,1,7].

Since I need the closest value to the start_index, I reverse my before list: before = before[::-1]

I then get the length of the longest of the two (before and after).

I then loop and check the value at each index of the two lists. The first one to have a value > 0 is returned and my work is done.

However, this feels very clunky and as if it can be done in a cleaner way.

Does anyone have any recommendations? What is the faster/cleaner/pythonic way for finding the nearest non-zero value in a list, given a starting index?
I really like your idea of splitting the list -- it's very functional! Let's look at optimizing that approach, though.

def nearest_non_zero(lst, idx):
    # note that I parameterized the list. Should make it easier to test.
    # though I'll use [4,2,6,7,3,0,0,9,4,2,5,8,1,7] as in your code for
    # all examples throughout

    if lst[idx] &gt; 0:
        return lst[idx]
        # I find it easier to track early-exit routes through code than
        # to rule everything out. Let's short-circuit if possible!

    before, after = reversed(lst[:idx]), lst[idx+1:]
    # using `reversed` here lets us avoid creating a new list, which
    # is otherwise necessary if we did `(lst[:idx])[::-1]`. Imagine
    # using that on a list with length of ten million. Yikes!


So far I've really only cleaned up a little bit of code, nothing big, but here is where I'll make a decent departure from your original code. Your code does this:

if len(before) &gt;= len(after):
    loop_length = len(before)
else:
    loop_length = len(after)


This is unnecessary, and it forces you to write in the guards inside your for loop to make sure you're not indexing a value outside of the list length. That's too much work. There's a couple better ways, the primary one being itertools.zip_longest.

The builtin zip pulls together two (or more!) lists into one list of tuples. For instance

&gt;&gt;&gt; a = [1, 2, 3]
&gt;&gt;&gt; b = ['a', 'b', 'c']
&gt;&gt;&gt; zip(a, b)
[(1, 'a'), (2, 'b'), (3, 'c')]
&gt;&gt;&gt; zip(b, a)
[('a', 1), ('b', 2), ('c', 3)]


However if you have lists of uneven size, it will only go so far as the shortest list

&gt;&gt;&gt; a = [1, 2]
&gt;&gt;&gt; b = ['a', 'b', 'c']
&gt;&gt;&gt; zip(a, b)
[(1, 'a'), (2, 'b')]  # where's 'c'?!?!


That's not what we want here. We want to zip together every value from both the before and after lists and compare them. That's what itertools.zip_longest does. But of course it needs another value: what should I use to pair with a value that doesn't exist? That's the parameter fillvalue, which by default is None

&gt;&gt;&gt; a = [1, 2]
&gt;&gt;&gt; b = ['a', 'b', 'c']
&gt;&gt;&gt; itertools.zip_longest(a, b)
[(1, 'a'), (2, 'b'), (None, 'c')]  # there's 'c', phew!

&gt;&gt;&gt; a = [1, 2]
&gt;&gt;&gt; b = ['a', 'b', 'c']
&gt;&gt;&gt; itertools.zip_longest(b, a, fillvalue="foobar")
[('a', 1), ('b', 2), ('c', 'foobar')]


That's exactly what we want! In this case we want our fillvalue to explicitly be a failing case (since if the number doesn't really exist we obviously don't want to use it), and our failing case is zero.

After that we compare to zero our "before" value, then our "after" value (to implement that preference for before rather than after), and return either if it's appropriate to do so.

import itertools

for b_val, a_val in itertools.zip_longest(before, after, fillvalue=0):
    if b_val &gt; 0:
        return b_val
    elif a_val &gt; 0:
        return a_val
else:
    # what do you do if you have a list that's all zeroes?


This keeps us from having to deal with any indexing at all, which is common idiomatically in Python. If you have to do for i in range(...): some_list[i], then you're probably doing something wrong!

Our final code, then is:

from itertools import zip_longest

def nearest_non_zero(lst, idx):
    if lst[idx] &gt; 0:
        return lst[idx]
    before, after = lst[:idx], lst[idx+1:]
    for b_val, a_val in zip_longest(reversed(before), after, fillvalue=0):
        # N.B. I applied `reversed` inside `zip_longest` here. This
        # ensures that `before` and `after` are the same type, and that
        # `before + [lst[idx]] + after == lst`.
        if b_val &gt; 0:
            return b_val
        if a_val &gt; 0:
            return a_val
    else:
        return 0  # all zeroes in this list