6

Really not sure where this fits. Say, I have a list:

>>>a = [1, 2, 3, 4, 5, 6, 7]

How can I iterate it in such a way, that it will check 4 first, then 5, then 3, then 6, and then 2(and so on for bigger lists)? I have only been able to work out the middle which is

>>>middle = [len(a)/2 if len(a) % 2 = 0 else ((len(a)+1)/2)]

I'm really not sure how to apply this, nor am I sure that my way of working out the middle is the best way. I've thought of grabbing two indexes and after each iteration, adding 1 and subtracting 1 from each respective index but have no idea how to make a for loop abide by these rules.

With regards as to why I need this; it's for analysing a valid play in a card game and will check from the middle card of a given hand up to each end until a valid card can be played.

Laefica
  • 489
  • 1
  • 5
  • 13
  • Why start in the middle? A valid card is just a s likely to be at the beginning or end as the middle. Why not start at the ends and work your way in? – kylieCatt May 22 '15 at 12:27
  • Just for the sake of a smarter AI considering everything else. – Laefica May 22 '15 at 12:28
  • what do you mean by "check 4 first, then 5, then 3, then 6, and then 2(and so on for bigger lists"? – farhawa May 22 '15 at 12:28
  • I don't see how that makes for smarter AI. You're are still doing the same thing, picking the first valid card. Where you start in a list of cards (that I assume is randomly drawn) doesn't make that choice any different. – kylieCatt May 22 '15 at 12:30
  • Ian it's because the better choice is closer to the middle for this case. Wajdi that means check the value in the middle, and then check the values +- 1 from the middle, then +- 2 from the middle, etc. – Laefica May 22 '15 at 12:33
  • How are you determining the order of the cards in the first place? It sounds like you should just sort them first based on their value – chepner May 22 '15 at 13:14
  • The cards have actually already been presorted. I dunno... This is some hybrid game and yeah, it's weird. – Laefica May 22 '15 at 13:51

5 Answers5

8

You can just keep removing from the middle of list:

lst = range(1, 8)
while lst:
    print lst.pop(len(lst)/2)

This is not the best solution performance-wise (removing item from list is expensive), but it is simple - good enough for a simple game.

EDIT:

More performance stable solution would be a generator, that calculates element position:

def iter_from_middle(lst):
    try:
        middle = len(lst)/2
        yield lst[middle]

        for shift in range(1, middle+1):
            # order is important!
            yield lst[middle - shift]
            yield lst[middle + shift]

    except IndexError: # occures on lst[len(lst)] or for empty list
        raise StopIteration
m.wasowski
  • 6,329
  • 1
  • 23
  • 30
  • Hey, this seems like the simplest solution. Could I duplicate the list and then iterate through the duplicated list so as to preserve the values in the original list? I'm thinking shallow copies could come into play here too. – Laefica May 22 '15 at 12:36
  • I have another idea, inspired by @Ami Tavory, I add it in few minutes ;-) – m.wasowski May 22 '15 at 12:46
  • If the length of the list is not an even value, how does this work? – Laefica May 22 '15 at 13:15
  • it quits either when `not middle - shift >= 0` or when `middle + shift > len(lst)` - when the later occurs, `IndexError` is raised, which is conversed to `StopIteration` inside our except, marking end of generator. – m.wasowski May 22 '15 at 13:18
  • you can print `shift` and `middle` inside the loop and in `except` clause, to see the flow, then test it on few lists ;-) – m.wasowski May 22 '15 at 13:20
  • I'm having a little bit of trouble understanding how it would always iterate through `3` first in `[1, 2, 3, 4]`. What would I have to change for it to iterate through the `2` first? – Laefica May 22 '15 at 15:09
  • in the first line add `lst = lst[::-1]` ;o) - which reverses list; but more seriously, it becomes complicated then, because lst[-1] does not raise exception. So you should add condition to break out of the loop on this. Probably would be better to write a `while` loop instead, but if you want to inquire, post a seperate question, please. – m.wasowski May 22 '15 at 18:53
  • I applied this into my function, with the `lst = lst[::-1]` and it did raise the `IndexError` and complete the loop. Cheers. – Laefica May 23 '15 at 03:42
6

To begin with, here is a very useful general purpose utility to interleave two sequences:

def imerge(a, b):
    for i, j in itertools.izip_longest(a,b):
        yield i
        if j is not None:
            yield j

with that, you just need to imerge

a[len(a) / 2: ]

with

reversed(a[: len(a) / 2])
Community
  • 1
  • 1
Ami Tavory
  • 74,578
  • 11
  • 141
  • 185
  • @m.wasowski Thanks! That was a good point. Have updated. In the new version, fiddling with the fill-value of ``izip_longest`` would also take care of lists containing ``None``; that's the basic idea of this answer, though. – Ami Tavory May 22 '15 at 12:44
  • Seems all the experts think your answer is the best. I'm not sure if I should mark yours right or not, because your solution is actually too advanced for me, whilst wasowski's I understand perfectly. – Laefica May 22 '15 at 12:44
  • You should mark whatever works for **you**, so feel free to accept his. I think the other answers are really great, BTW, and have learned from them (mine is more intuitive to me, but that's subjective). – Ami Tavory May 22 '15 at 12:51
5

You could also play index games, for example:

>>> a = [1, 2, 3, 4, 5, 6, 7]
>>> [a[(len(a) + (~i, i)[i%2]) // 2] for i in range(len(a))]
[4, 5, 3, 6, 2, 7, 1]

>>> a = [1, 2, 3, 4, 5, 6, 7, 8]
>>> [a[(len(a) + (~i, i)[i%2]) // 2] for i in range(len(a))]
[4, 5, 3, 6, 2, 7, 1, 8]
Stefan Pochmann
  • 27,593
  • 8
  • 44
  • 107
  • pretty nice. this is a much smarter version of what i'm doing in my answer (yours was posted first). – Rick May 22 '15 at 13:56
  • I love this bit negation trick, but I would personally crucify anyone who dared to put something like this into our codebase :D – m.wasowski May 22 '15 at 14:16
  • 1
    @m.wasowski I don't think it's that bad, at least not because of the bit negation. After all, it's a straight-forward way to index from the back. Quite neat for example to get the median of a sorted list: `m = len(a)//2; median = (a[m] + a[~m]) / 2`. – Stefan Pochmann May 22 '15 at 14:43
  • I don't mind bit negation, just took me a minute or two to figure out how does it ALL works - today, knowing the context. Probably I would be perplexed if I run on something like this two weeks from now. And I think when you read a complex code all day long, the last thing you need is more complexity in simple stuff - I prefer to read through code quickly without adding too much to my mind stack ;) – m.wasowski May 22 '15 at 14:53
  • 1
    @m.wasowski Yeah, ok, the whole took me a minute or four to *write* as well :-). Mostly a fun solution just because I can. – Stefan Pochmann May 22 '15 at 14:56
1

Here's a generator that yields alternating indexes for any given provided length. It could probably be improved/shorter, but it works.

def backNforth(length):
    if length == 0:
        return
    else:
        middle = length//2
        yield middle

        for ind in range(1, middle + 1):
            if length > (2 * ind - 1):
                yield middle - ind
            if length > (2 * ind):
                yield middle + ind 

# for testing:
if __name__ == '__main__':
    r = range(9)
    for _ in backNforth(len(r)):
        print(r[_])

Using that, you can just do this to produce a list of items in the order you want:

a = [1, 2, 3, 4, 5, 6, 7]
a_prime = [a[_] for _ in backNforth(len(a))]
Rick
  • 43,029
  • 15
  • 76
  • 119
1

In addition to the middle elements, I needed their index as well. I found Wasowski's answer very helpful, and modified it:

def iter_from_middle(lst):
    index = len(lst)//2
    for i in range(len(lst)):
        index = index+i*(-1)**i
        yield index, lst[index]

>>> my_list = [10, 11, 12, 13, 14, 15]
>>> [(index, item) for index, item in iter_from_middle(my_list)]
[(3, 13), (2, 12), (4, 14), (1, 11), (5, 15), (0, 10)]