开发者

Pythonic method of determining if a list's contents change from odd to even values

Writing some test cases and my mind wanders, assuming there is a better way to write something like this. I have a list, its numbers transition from all odd values to all even, doesn't matter where. I need to assert this is the case, here's what I came up with:

values = [1, 3, 5, 7, 5, 3, 5, 3, 5, 7, 4, 6, 8, 4, 2, 2, 8, 6]

# find all the indexes of odd and even values
odds = [i for (i, v) in enumerate(values) if v % 2 == 1]
evens = [i for (i, v) in enumerate(values) if v % 2 == 0]

# indexes should be a continuous sequence: 0, 1, 2, 3 ... n
assert odds + evens == range(evens[-1开发者_Go百科] + 1)

Seems like a long way to go. Suggestions on how this could be reduced?


A possible solution is to consider that you allow only

odd->odd
odd->even
even->even

in other words the only forbidden transition is

even->odd

and this translates to

(0, 1) not in ((x%2, y%2) for x, y in zip(values, values[1:]))


[x for x in values if x % 2 == 1] + [x for x in values if x % 2 == 0] == values

This is only true, if values starts with all of it's own odd values, followed by all of its even values.


Well, you don't need to calculate evens:

assert odds == range(len(odds))


Inspired by the clarifications and solution from @6502, this generator approach uses any() to short circuit the iteration as soon as the test fails and only fails if an even to odd transition is detected. The worst case performance is one full iteration if the test passes:

iter_val = iter(values)
assert not any(next(iter_val)%2 < v%2 for v in values[1:])

or

from itertools import izip
assert not any(i[0]%2 < i[1]%2 for i in izip(vals, vals[1:]))


(values[0] % 2) and (len(list(itertools.groupby(values, lambda x: x%2))) == 2)


Rather than collecting the indices, you can just calculate the transition point based on the assumption that all the odd values are at the start; and then check that there are no more odd values after that transition point.

Case where the assertion is true:

values = [1, 3, 5, 7, 5, 3, 5, 3, 5, 7, 4, 6, 8, 4, 2, 2, 8, 6]
odd_count = len([x for x in values if (x % 2)])
assert (not any(x for x in values[odd_count:] if (x % 2) != 0))

Case where the assertion is false:

values = [1, 3, 5, 7, 5, 3, 5, 3, 44, 5, 7, 4, 6, 8, 4, 2, 2, 8, 6]
odd_count = len([x for x in values if (x % 2)])
assert (not any(x for x in values[odd_count:] if (x % 2) != 0))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError


assert zip(*itertools.groupby(x%2 for x in values))[0] == (1, 0)

Or an easier to understand two-liner:

odds_and_evens = [x%2 for x in values]
assert odds_and_evens.index(0) == odds_and_evens.count(1)

If values is valid then odds_and_evens will be some number of 1 followed by only 0, so it is valid if the first 0 comes after every 1.

Both of these methods assume you need to have at least one odd followed by at least one even, which I don't think the OP has clarified.

If empty lists, all odd, or all even should be considered valid the following method works:

odds_and_evens = [x%2 for x in values]
assert odds_and_evens == sorted(odds_and_evens, reverse=True)


I think filter reads better than list comprehensions here, e.g.,

filter(isodd, values) + filter(iseven, values) == values


Another option is to sort values by parity and see if anything changed:

assert sorted(values, key=lambda x: x % 2, reverse=True) == values


Somewhat longer, but this would seem to capture all (even only, odd only, empty) in addition to requirements. It only requires one modulo and one compare on the full list. Not nearly as succinct (or clever) as Andrew's sorted answer, but faster(?) for long lists.

values= [1, 3, 5, 7, 5, 3, 5, 3, 5, 7, 2, 4, 6, 8, 10]
evenOdd = [x%2 for x in values]

try:
    evenLoc=evenOdd.index(0)
    assert evenLoc != 0
except ValueError:
    evenLoc=len(evenOdd)

try:
    badActor=evenOdd[evenLoc:].index(1)
    assert False 
except ValueError:
    pass
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜