質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

2回答

2791閲覧

Python - ソートする際に左辺、右辺両方を引数にとる比較関数を指定する方法

tiitoi

総合スコア21956

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

1グッド

0クリップ

投稿2019/07/10 17:32

質問内容

Python の sorted() の key 引数でソートする際に比較関数を指定できるようになっていますが、左辺、右辺両方を引数にとる比較関数を指定する方法はありますでしょうか。
C++ の場合、ソートする関数に以下のように左辺 (left-hand-side, lhs)、右辺 (right-hand-side, rhs) 両方を引数にとる比較関数を指定できますが、ソートする関数を自作する以外で Python でこれを実現する方法がわかりません。

cpp

1struct Point 2{ 3 double x; 4 double y; 5}; 6 7std::sort(points.begin(), points.end(), 8 [](const Point &lhs, const Point &rhs) -> bool 9{ 10 return lhs.y != rhs.y ? lhs.y > rhs.y : lhs.x > rhs.x; 11});

そのような事をしたい背景

右辺、左辺両方を引数にとる比較関数でないと、ソートできないケースが出てきたのが動機です。
sorted() の key 引数に関数を指定できますが、1つの引数をとる関数しか指定できないため、やりたいことが実現できないでいます。
ソート関数自体を実装すればいいと思いますが、標準ライブラリまたは numpy などの外部ライブラリの関数で可能であれば、やり方を教えていただきたいです。

python

1import matplotlib.pyplot as plt 2import numpy as np 3 4 5def draw_2d_points(points): 6 """2次元の点列を描画する。 7 """ 8 fig, ax = plt.subplots() 9 ax.grid() 10 ax.scatter(points[:, 0], points[:, 1]) 11 for i, p in enumerate(points): 12 ax.annotate(i, p, fontsize=14) 13 plt.plot() 14 15 16# 形状が (NumPoints, 2) である格子状の点列を作成する。 17X, Y = np.meshgrid(np.arange(10.0), np.arange(10.0), indexing="xy") 18points = np.column_stack([X.ravel(), Y.ravel()]) # [(N, M), (N, M)] -> (N*M, 2) 19 20# 点列に [-0.1, 0.1] の一様分布に従う乱数を加算する。 21points += np.random.uniform(-0.1, 0.1, points.shape) 22 23# シャッフルする。 24np.random.shuffle(points) 25draw_2d_points(points) # 図1 26 27# 単純に y > x 順でソートすると、加えたノイズの影響で左下から順番に並ばない。 28points = np.array(sorted(points, key=lambda p: (p[1], [0]))) 29draw_2d_points(points) # 図2 30 31# ソート関数自体を実装すれば実現できるが、それ以外に方法がないかという疑問 32def bubble_sort(arr, comp): 33 n = len(arr) 34 for i in range(n): 35 for j in range(0, n - i - 1): 36 if comp(arr[j], arr[j + 1]): 37 temp = arr[j].copy() # a, b = b, a 方式だと view がコピーされてしまうため 38 arr[j] = arr[j + 1] 39 arr[j + 1] = temp 40 41# 指定したい比較関数 42# y の差が 0.5 より大きい場合は y 座標で昇順、そうでない場合は x 座標で昇順ソートする。 43my_comp = lambda a, b: a[1] > b[1] if abs(a[1] - b[1]) > 0.5 else a[0] > b[0] 44 45bubble_sort(points, my_comp) 46draw_2d_points(points) # 図3

イメージ説明
図1 シャッフル後

イメージ説明
図2 y > x 順でソートした場合

イメージ説明
図3 y の差が 0.5 より大きい場合は y 座標で昇順、そうでない場合は x 座標で昇順ソートした場合

KSwordOfHaste👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

functools.cmp_to_keyを使えばできると思います。

https://qiita.com/norioc/items/cb533d009aa63453df40

python

1import numpy as np 2import functools 3 4my_comp = lambda a, b: a[1] - b[1] if abs(a[1] - b[1]) > 0.5 else a[0] - b[0] 5 6points = np.array(sorted(points, key=functools.cmp_to_key(my_comp))

投稿2019/07/10 22:44

編集2019/07/11 01:33
bsdfan

総合スコア4567

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tiitoi

2019/07/11 04:17

functools.cmp_to_key を使うと、そのような関数を指定することができたのですね。 知らなかったので、勉強になりました。ご回答ありがとうございます。
guest

0

bsdfanさんが既に回答しておられますが、ちょっと蛇足的コメントを・・・


C/C++やJavaをPythonより先に学ぶと「比較関数を指定する並べ替え」が自然な仕様に思えてしまいます。実際Python 2までは比較関数を指定するアプローチだったみたいです。しかし実は「比較キーを取り出す関数を指定する」ことでも「比較関数を指定する」のと同様の汎用性があるといえます。

比較関数my_compで順序を決めるようにしたいなら「そのような論理で大小が決まるクラスを定義し、key関数でそのクラスのインスタンスを返すようにすればよい」わけです。

python

1class MySortedKey: 2 def __init__(self, value): 3 self.value 4 5 def __lt__(self, that): 6 return my_comp(self.value, that.value) 7 8# bubble_sort(points, my_comp) 9points.sort(key=MySortedKey)

ただ、「比較関数を指定した方が比較キーを一々生成するよりメモリー消費をせずに並べ替えができるだろうに」とお考えになるだろうと思います。C/C++のプログラミングを経験するとどうしてもそういうところに目がいきます。Pythonはなぜキー「だけ」を指定するソート機能にしたんでしょうね。

https://docs.python.org/ja/3/howto/sorting.html#the-old-way-using-decorate-sort-undecorate

なんかをみると「安定ソートを実現する際など、並べ替え対象の列とは別の列を生成してからそれを用いてソートする」というイディオムを「プログラマーが一々配慮しなくてもよくする=>無用のものにする」というのが動機の一つだったのかもしれないと思いました。そうした方が「より広い範囲の並べ替え機能をより単純なアプローチでカバーできる」というわけです。


参考:リファレンスの以下からPython 3のソートノウハウの参考情報にたどり着けます。

Python標準ライブラリー > 組み込み関数 > sorted
https://docs.python.org/ja/3/library/functions.html#sorted

Python標準ライブラリー > 組み込み型 > シーケンス型 > list > sort
https://docs.python.org/ja/3/library/stdtypes.html#list.sort

上記の説明の中の

旧式の cmp 関数を key 関数に変換するには functools.cmp_to_key() を使用してください。

また本件とは直接関係ありませんが、

ソートの例と簡単なチュートリアルは ソート HOW TO を参照して下さい。

なんて記述もあります。Python 3における「並べ替えの際に役立つ基本ノウハウ」を容易に仕込むのに役立つと思います。

投稿2019/07/11 03:08

KSwordOfHaste

総合スコア18394

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tiitoi

2019/07/11 04:21 編集

なるほど。2系までは比較関数を指定するスタイルだったのですね。 また現状の仕様でも __lt__ を定義することでやりたいことはできたのですね。思いつきませんでした。 大変参考になるご回答ありがとうございます。
KSwordOfHaste

2019/07/11 05:07

自分も最初「キーしか指定するものが用意されてない」ことに疑問をもったのでtiitoiさんの疑問はよくわかる気がしました。関連することをおそらく調べられるであろうことから本回答は蛇足かと思ったのですが「ちょっと前の自分に回答するようなつもりでWHYについてコメントしてみるのも一興」と思い筆をとった次第です。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問