0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

リスト末尾の空要素を削除する

Posted at

読み飛ばし推奨

こんなCSVに遭遇しました。

image.png

列 ご と に 行 数 が 違 う。 おまけに途中に空欄がある。

取り合えずPython標準のcsvモジュールで列ごとにリストを作成してみると、

import csv

col_x = []
col_y = []
values = []
with open("mapdata.csv", encoding="utf-8") as f:
    reader = csv.reader(f)
    for row in reader:
        col_x.append(row[0])
        col_y.append(row[1])
        values.append(row[2])

print(col_x)
# ['X軸', '0', '10', '', '30', '40', '50', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']
print(col_y)
# ['Y軸', '100', '150', '200', '300', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']
print(values)
# ['値', '235', '393', '471', '501', '607', '736', '900', '975', '1022', '1180', '1247', '1394', '1436', '1506', '1675', '1721', '1855', '1942', '2096', '2177', '2285', '2331', '2420', '2519']

うーん、X軸Y軸の後ろが空文字列で埋められちゃってますね……

シンプルに解答

def remove_trailing(__l: Sequence, empty=("", None, [])):
    if __l[-1] not in empty:
        return __l
    index = next((i for i, c in enumerate(reversed(__l)) if c not in empty), default=0)
    return __l[:-index]

解説

まずは引数から

def remove_trailing(__l: Sequence, empty=("", None, [])):

__l が対象のリスト。関数の仕様上、リストでなくても動作するので型ヒントでは Sequence としている。
empty=("", None, []) は空と見なす値のタプル。例えば、 Nan という文字列も空の値と判定したいときはこのタプルを ("", None, [], "Nan") とすればよい。

if分岐

if __l[-1] not in empty:
    return __l

ここが今回の肝。最初にif文でチェックを入れること。これを省くと、最後の要素が空欄でなかったときに返り値が空のリストになってしまう。

indexの取得

index = next((i for i, c in enumerate(reversed(__l)) if c not in empty), default=0)

reversed(__l) とすることで、リストを逆順にしたイテレータが取得できる。イテレータってなんだっけ? という人向けに補足すると、要はループ処理時などで順に取得できるようになっているオブジェクトである。こういう適当なことを書くとコメント欄で詳しい人が論陣を張ってくれるそうなのでそちらを見てほしい。(おい)
逆順にしたうえで enumerate に放り込むと、リストの要素 c とそのインデックス i (厳密には逆順でのインデックス)が同時に得られる。そして、後ろの if c not in empty によって値が空でないときの i のみが残される。

next() は競技プログラミングをやる人には見慣れた物だろうが、馴染みのない人もいるであろうから簡単に説明しておくと、先に述べたイテレータの次の要素を取得する関数である*
今回は、next() の内部がジェネレータ式となっており、 reversed されたリストと繰り返し回数を順に取得している。取得された値は if 以下のチェックにかけられ、True となる値のみが、括弧内先頭の i として残される。
で、先頭の i として残された最初の一つ、すなわち、元のリストで言うと空の要素が何個続いたかの数値が、next() 関数によって取得される。

最後の default=0 は、 next() 関数が何も取得するものがなかったときに使われる値である。今回だと、リストの中身が頭から尾っぽまで空っぽだった時に 0 を返す、ということである。これをサボると StopIteration という例外発生してあらびっくり、となるので注意されたい。

Return

return __l[:-index]

特に説明は不要だろう。Pythonの名物スライスを用いて、後ろから数えて index 番目の要素より前の要素だけを返している。

なぜリスト内包表記でやらないのか?

def remove_trailing(__l: Iterable, empty=("", None, [])):
    return [c for c in __l if c not in empty]

1行で終わるじゃん。

と、思ったそこのあなた、賢い。これでいいならこうしてました。
今回はリストの途中にも空の要素があって、そこは残しておきたかったのでこういう少し回りくどい方法が必要でした。
とはいえ、他にもいろんな方法があるかと思いますので、こういうやり方もあるよ、という方は是非コメント欄かご自身の記事でご紹介ください。別記事立てる場合は、この記事へリンクを貼っていただけると探しやすくて助かります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?