AlgorithmPython

Python リストの値を往復させる方法

更新日:2022-11-23 公開日:2022-11-22

今回は小ネタでPythonでリストの値を往復させる方法について考えてみました。
例えば[1,2,3,4]というようなリストがあった時に、指定した数だけ1,2,3,4,3,2...という風に表示させる実装です。

フラグで管理する

まず、シンプルな実装としては、配列の長さとインデックスを比較して、インデックスを足すのか減らすのか判断する方法でしょうか。
以下のように書いてみました。

def gen_round_trip(a_list, count):
    # indexを正負どちらに向けるかフラグ
    reverse_flg = False
    index = 0
    length = len(a_list)
    for i in range(count):
        # 値を取り出す
        ret = a_list[index]
        if not reverse_flg:
            index += 1
            # 配列の最後までインデックスが来ていたら反転
            if index == length-1:
                reverse_flg = True
        else:
            index -= 1
            # 配列の最初、つまりゼロまできたら反転
            if index == 0:
                reverse_flg = False
        yield ret


動作確認すると意図していた通りに出力ができています。

numbers = [1, 2, 3, 4]
for num in gen_round_trip(numbers, 9):
    print(num)
>>>
1
2
3
4
3
2
1
2
3


dequeを用いた実装

別バージョンとして、dequeを用いた方法も考えてみました。
配列を2つ用意して一方から値を取り出して、入れ替えるようなイメージです。

def generate_round_trip_value(a_list, count):
    if len(a_list) < 2:
        raise ValueError("要素は2つ以上必要")
    a_list = deque(a_list)
    # 値の一時保持リスト
    tmp = deque()
    last = None
    cnt = 0
    while cnt < count:
        # 値を取り出して一時保持リストに詰めていく
        ret = a_list.popleft()
        # [1,2,3,4] → [4,3,2,1]となっていく
        tmp.appendleft(ret)
        if not a_list:
            a_list = tmp
            tmp = deque()
        # 配列の最初と最後が2回続くのを避ける
        if ret == last:
            continue
        else:
            last = ret
            cnt += 1
            yield ret

numbers = [1, 2, 3, 4]
for num in generate_round_trip_value(numbers, 9):
    print(num)
>>>
1
2
3
4
3
2
1
2
3


この方法の分かりづらい点が変数のlastでしょうか。
配列をdequeのappendleftを使って反転させていっているのですが、何も手を加えないと値が2回続いて返されてしまいます。
a_listとtmpの動きを追うとこのようになります。

[1,2,3,4]
[]

# 1を取り出す
1 [2,3,4]
# 1をtmpにappendleft
[1]

2 [3,4]
[2,1]

3 [4]
[3,2,1]

4 []
[4,3,2,1]

# ここで取り出しが2回続いてしまう
4 [3,2,1]
[4]


これを避けるために変数lastに最後に返した値を保持しておいて、一緒だったらskipさせるという処理を加えています。

rotateでシンプルに考える

同じdequeを使うならrotateメソッドを使った方がシンプルでしょうか。
rotateメソッドは以下のような動きです。

numbers = [1, 2, 3, 4]
numbers = deque(numbers)
numbers.rotate(-1)
print(numbers)
>>>
deque([2, 3, 4, 1])


-1を指定すると左方向にベルトコンベアのように配列が組み変わります。
逆に1を指定すると右方向ですね。

numbers = [1, 2, 3, 4]
numbers = deque(numbers)
numbers.rotate(1)
print(numbers)
>>>
deque([4, 1, 2, 3])


この習性を利用して回転する方向を制御するとシンプルな実装になります。

def generate_round_trip_value(a_list, count):
    a_list = deque(a_list)
    n = -1
    first = a_list[0]
    last = a_list[-1]
    for i in range(count):
        ret = a_list[0]
        if ret == first:
            n = -1
        if ret == last:
            n = 1

        a_list.rotate(n)

        yield ret


numbers = [1, 2, 3, 4]
for num in generate_round_trip_value(numbers, 9):
    print(num)
>>>
1
2
3
4
3
2
1
2
3


おわりに

以上、今回はリストの値を往復させる方法でした。