2

The following code gets the last present value from an ordered list of dated values:

def test_get_last_value(self):
    dated_optional_values = [(datetime.datetime(2015, 2, 23), None),
                    (datetime.datetime(2015, 2, 24), 1.23),
                    (datetime.datetime(2015, 2, 25), None),
                    (datetime.datetime(2015, 2, 26), 2.34),
                    (datetime.datetime(2015, 2, 27), None),
                    ]
    present_values = [dated_optional_value[1] for dated_optional_value in dated_optional_values
        if dated_optional_value[1] is not None]
    last_present_value = present_values[-1]
    print('last_present_value=', last_present_value)

I can easily write a loop which starts at the end of the list and iterates back until it finds a value.

Is there a more Pythonic way to accomplish that?

AlexC
  • 3,343
  • 6
  • 29
  • 38
  • Use `reversed()`, and [find the first non-`None` value](http://stackoverflow.com/q/8534256/4279) – jfs Feb 27 '15 at 21:05
  • I don't think that `reversed()` is any better than the solution you already have. Its common and "pythonic" to filter lists into new lists. – tdelaney Feb 27 '15 at 21:13
  • @tdelaney: `reversed()` allows you to avoid enumerating all values unnecessarily if there is a non-`None` value near the end. In the same way like `any()`, `all()` functions have the short-circuit behavior. It doesn't matter in OPs case because the list is very short. But I don't see any indication that a very small list is expected in the title. – jfs Feb 27 '15 at 21:37

4 Answers4

3

Maybe:

last_present_value = next(x for x in reversed(dated_optional_values) 
                          if x[1] is not None)
kindall
  • 178,883
  • 35
  • 278
  • 309
3
next((dop[1] for dop in reversed(date_optional_values) if dop[1] is not None), None)

You can replace the final None with whatever other value you prefer to return the case where every value is None. Or omit that argument to throw an exception in the case where they're all None.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
1

It may be nice to factor the separate steps:

  1. You want the second column (x[1])

    def column(data, index): return [x[index] for x in data]

  2. You want to filter out None

    def notNone(data): return [x for x in data if x is not None]

  3. You want the last element

    def last(data): return data[-1]

Then your problem becomes

last_preset_value = last(notNone(column(data, 1)))
6502
  • 112,025
  • 15
  • 165
  • 265
0

You can also generalise this to take the last n non None values - for instance, to take the last 2 non blanks:

from itertools import islice

candidates = (el[1] for el in reversed(dated_optional_values) if el[1] is not None)
last_n = list(islice(candidates, 2)) or []

Also, if you don't have a Sized container, such as a list, and have some arbitrary iterable, you can use a collections.deque with a fixed size, eg:

from collections import deque

candidates = (el[1] for el in dated_optional_values if el[1] is not None)
d = deque([], 2)
d.extendleft(candidates)
matches = list(d) or []

Both of these options return:

[2.34, 1.23]
Jon Clements
  • 138,671
  • 33
  • 247
  • 280