第 41 回シェル芸勉強会の問題の Python ワンライナーでの解答です。
真面目なほうは 第41回シェル芸勉強会に参加しました。 - nogiro_iotaのメモ を参照してください。
けっこう改訂しています。(解説も追加する予定です。しました。)
改訂履歴を最下段に載せています。
まとめ
python3 -c 'import sys; [print(x[:-1]) for x in sorted(sys.stdin, key=lambda x:hoge)]'
で hoge をいじれば標準入力を好き勝手ソートできる。
全体に共通する解説
第 41 回シェル芸勉強会はソート回とのことでした。
Python でのソートは sort()
・sorted()
の 2 種類です。この記事中の回答ではすべてsorted()
を使用しています。sorted()
は引数として iterable なオブジェクト (sys.stdin やリスト) を受け取って、ソート済みのリストを返すためワンライナーで扱い易いためです。(sort()
はリスト型クラスのメソッドで、呼ぶとリスト内部をソートして返り値は None になります。)
printf デバッグ
Python は短絡評価してくれて print()
は None を返すので、値を見たい部分で print(hoge) or hoge
すると printf デバッグできます。
以下は A4-2 を例にしています。
$ python3 -c '[print(x.strip()) for x in sorted(open("ShellGeiData/vol.41/sjis", "r", encoding="cp932"), key=lambda x:print(int(x.split()[0])) or int(x.split()[0]))]' 123 31 20 2242 20 ほじぱんふんじこみ 31 こきたてひーひー 123 ずんごるももう 2242 うえってきたかるとらまん
問題と Python ワンライナー版解答
sorted()
の key でソート基準を決める関数を渡せるのでけっこう簡単でした。
Q1
次のファイルについて、2列目をキーにしてエクセルの横列の記号(A, B, ..., Z, AA, AB, ...のやつ)順に並べ替えてください。
$ cat excel 114514 B 1192296 AA 593195 CEZ 4120 TZ 999 QQQ
A1
$ cat ShellGeiData/vol.41/excel | python3 -c 'import sys; [print(x[:-1]) for x in sorted(sys.stdin, key=lambda x:("%%%"+x.split()[1])[-4:])]' 114514 B 593195 AA 4120 TZ 1192296 CEZ 999 QQQ
解説
解き方自体は前記事と同じです。列番号は
x.split()[1]
で得られるので、前部分に"%%%"
をつけて後ろ 4 文字 ([-4:]
) を返すようにします。
Q2
次のファイルのレコードを干支順にソートしてください。
$ cat eto_yomi 申 さる 子 ね 寅 とら 卯 う 巳 み 辰 たつ 丑 うし 酉 とり 戌 いぬ 亥 い 午 うま 未 ひつじただし、次のファイルを補助に使って良いこととします。
$ cat eto 子丑寅卯辰巳午未申酉戌亥
A2
$ cat ShellGeiData/vol.41/eto_yomi | python3 -c 'import sys; [print(x[:-1]) for x in sorted(sys.stdin, key = lambda x:open("ShellGeiData/vol.41/eto", "r").read().index(x.split()[0]))]' 子 ね 丑 うし 寅 とら 卯 う 辰 たつ 巳 み 午 うま 未 ひつじ 申 さる 酉 とり 戌 いぬ 亥 い
解説
補助ファイルを文字列として持てば、その index でソートすればキレイにできます。ただ上の回答だと、eto_yomi
を 1 行読むたびに eto
を読み直しているのでファイルが大きくなると遅くなりそうですね。。。
以下のように、先に変数に入れておけば良いですね。
cat ShellGeiData/vol.41/eto_yomi | python3 -c 'import sys; eto = open("ShellGeiData/vol.41/eto", "r").read(); [print(x[:-1]) for x in sorted(sys.stdin, key=lambda x:eto.index(x.split()[0]))]'
Q3
次のファイルのレコードを数字(第一フィールドの計算結果)が小さい順に並べてください。
$ cat kim_calc 1+2+4 金正日 4*3 金正男 3-1-5 金日成 495/3 金正恩 0x1F 金正哲
A3
$ cat ShellGeiData/vol.41/kim_calc | python3 -c 'import sys;[print(x.strip()) for x in sorted(sys.stdin, key=lambda x:eval(x.split()[0]))]' 3-1-5 金日成 1+2+4 金正日 4*3 金正男 0x1F 金正哲 495/3 金正恩
解説
eval()
がつよい。
Q4
次のファイルはシフトJISのテキストですが、これを1) 辞書順、2) 数字の小さい順、にソートしてください。出力もシフトJISとします。
$ cat sjis | nkf -g Shift_JIS $ cat sjis | nkf -wLux 123 ずんごるももう 31 こきたてひーひー 9 ほじぱんふんじこみ 2242 たまもとやろう
A4-1
$ python3 -c 'import codecs;[print(x.strip()) for x in sorted(codecs.open("ShellGeiData/vol.41/sjis", "r", "cp932"))]' 123 ずんごるももう 20 ほじぱんふんじこみ 2242 うえってきたかるとらまん 31 こきたてひーひー
解説
Python で SJIS のファイルを読み込むにはcodecs.open()
を使うか open()
に encoding
を渡します。
A4-2
$ python3 -c '[print(x.strip()) for x in sorted(open("ShellGeiData/vol.41/sjis", "r", encoding="cp932"), key=lambda x:int(x.split()[0]))]' 20 ほじぱんふんじこみ 31 こきたてひーひー 123 ずんごるももう 2242 うえってきたかるとらまん
解説
Python では全角の数字を int にキャストできるようです。すごい。Q5
サイズの小さい順にソートしてください。
$ cat size 2GB 1.2GB 40000MB 1000000000kB 0.4GB 410MB
A5
$ cat ShellGeiData/vol.41/size | python3 -c 'import sys; d = {"k":1,"m":2,"g":3}; [print(x.strip()) for x in sorted(sys.stdin, key=lambda x:eval("".join([j.replace(i,"*1000" * d[i]) if i in j else "" for j in [x[:-2].lower()] for i in d.keys()])))]' 0.4GB 410MB 1.2GB 2GB 40000MB 1000000000kB
解説
SI 接頭辞をディクショナリで定義して (d = {"k":1,"m":2,"g":3}
)、その値の数 "*1000"
をつけたものを eval()
しています。想像しにくいように思うので、 eval()
で計算する前の文字列を出力したものを以下に貼ります。(改行していますが cat
~ ()])))]'
までコピペで動きます。)
$ cat ShellGeiData/vol.41/size | python3 -c 'import sys; d = {"k":1,"m":2,"g":3}; [print(x.strip()) for x in sorted(sys.stdin, key=lambda x: print("".join([j.replace(i,"*1000" * d[i]) if i in j else "" for j in [x[:-2].lower()] for i in d.keys()])) or eval("".join([j.replace(i,"*1000" * d[i]) if i in j else "" for j in [x[:-2].lower()] for i in d.keys()])))]' 2*1000*1000*1000 1.2*1000*1000*1000 40000*1000*1000 1000000000*1000 0.4*1000*1000*1000 410*1000*1000 0.4GB 410MB 1.2GB 2GB 40000MB 1000000000kB
途中で出てくる for j in [x[:-2].lower()]
はリスト内包表記内で変数を使う方法です。
Q7
次のローマ数字をソートしてください。
$ cat roman IV XI LXXXIX IX XLIII XX VIII
A7
$ cat ShellGeiData/vol.41/roman | python3 -c 'import sys;d={"I":1,"V":5,"X":10,"L":50};[print(x[:-1]) for x in sorted(sys.stdin, key=lambda x:eval("+".join([[a[j]+"*-1" if j < len(a) - 1 and a[j] < a[j+1] else a[j] for j in range(len(a))] for a in [[str(d[i]) for i in x[:-1]]]][0])))]' IV VIII IX XI XX XLIII LXXXIX
解説
A5 と大きく変わっていません。IV
やXL
などの減算則の部分に "*-1"
をつける処理 ([a[j]+"*-1" if j < len(a) - 1 and a[j] < a[j+1] else a[j] for j in range(len(a))]
) が主な違いです。
Q8
次のファイルを辞書順にソートしてください。ただし、濁点がついているものが先に来るようにしてください。できる人はワンライナー中で「かきくけこがぎぐげご」の文字を使わないでください。
$ cat gagigugego かき氷 ぎ・おなら吸い込み隊 きつねうどん ぐりこもりなが事件 きききりん がきの使い くその役にも立たない げんしりょく発電 ごりらいも こじんてきにはクソ
- 例
がきの使い かき氷 ぎ・おなら吸い込み隊 きききりん きつねうどん ぐりこもりなが事件 くその役にも立たない げんしりょく発電 ごりらいも こじんてきにはクソ
A8
$ cat ShellGeiData/vol.41/gagigugego | python3 -c 'import sys; from unicodedata import normalize; c=u"\U00003099"; [print(x[:-1]) for x in sorted(sys.stdin, key=lambda x:["".join(c in a and a.insert(a.index(c), u"\U00000000") or a) for a in [[i for i in normalize("NFD",x)]]])]' がきの使い かき氷 ぎ・おなら吸い込み隊 きききりん きつねうどん ぐりこもりなが事件 くその役にも立たない げんしりょく発電 ごりらいも こじんてきにはクソ
解説
Python だとunicodedata.normalize()
で Unicode 正規化できます。そのままソートすると期待どおりにソートされないので、濁点 (u"\U00003099"
) の前に小さな値 (u"\U00000000"
) を追加してソートしています。
改訂
- A8.
a[a.index(c):]+[u"\U00000000"] + a[:a.index(c)]
->a.insert(a.index(c), u"\U00000000")
- A2.
[j for j in [i for i in open("ShellGeiData/vol.41/eto", "r")][0]]
->[i for i in open("ShellGeiData/vol.41/eto", "r")][0]
- A1.
lambda x:("%%%"+x[1])[:-4]
->lambda x:("%%%"+x[1])[-4:]
eban on Twitter: "最後は[-4:]じゃないでしょうか?… "
- 全体.
sorted([i for i in sys.stdin], key=
->sorted(sys.stdin, key=
- まとめを追加
- A2.
[i for i in open("ShellGeiData/vol.41/eto", "r")][0]
->open("ShellGeiData/vol.41/eto", "r").read()
- A5. SI接頭辞をディクショナリで定義して汎用性を上げました
- 前.
cat ShellGeiData/vol.41/size | python3 -c 'import sys;[print(x.strip()) for x in sorted(sys.stdin, key=lambda x:eval(x[:-2].replace("k","*1000").replace("M","*1000"*2).replace("G","*1000"*3)))]'
- 後.
cat ShellGeiData/vol.41/size | python3 -c 'import sys; d = {"k":1,"m":2,"g":3}; [print(x.strip()) for x in sorted(sys.stdin, key=lambda x:eval("".join([j.replace(i,"*1000" * d[i]) if i in j else "" for j in [x[:-2].lower()] for i in d.keys()])))]'
- 前.
- A7. 減算則が適用されるのが、最初にある場合のみになっていたのを修正しました。 (
LXXXIX
が 89 ではなく 91 に計算されていた。)- 前.
eval("+".join([[a[0]+"*-1"]+a[1:] if a[0] < a[1] else a for a in [[str(d[i]) for i in x[:-1]]]][0])) or x
- 後.
eval("+".join([[a[j]+"*-1" if j < len(a) - 1 and a[j] < a[j+1] else a[j] for j in range(len(a))] for a in [[str(d[i]) for i in x[:-1]]]][0]))
- 前.
- 解説を追加