LoginSignup
8
4

More than 1 year has passed since last update.

Pythonで分数のリストを誤差なしでソートする

Last updated at Posted at 2021-10-31

はじめに

 AtCoder Beginner Contest 225 (ABC 225) の E 問題 で、表題のことができずに詰んだので、勉強した結果をまとめます。

普通にソートするときの問題点

 分数のリストをソートしたいときは、普通は以下のように分数を少数に直し、小数のリストでソートしますよね。

l = [5 / 7, 3 / 8, 1 / 2]
# l = [0.7142857142857143, 0.375, 0.5]

l = sorted(l)
# l = [0.375, 0.5, 0.7142857142857143]

 しかし、分母・分子の値が大きくなってくると誤差が出ることがあります。それが原因で、正しく順序がソートされないことがあるのです。誤差が出るパターンを1つ示します。

>>> (10 ** 9 + 1) / 10 ** 9
1.000000001

>>> 10 ** 9 / (10 ** 9 - 1)
1.000000001

$(10^9 + 1) / 10^9$ と $10^9 / (10^9 - 1)$ は異なるにもかかわらず、小数で表すと等しくなってしまいます。
 これを避けるためには小数を登場させなければいいです。つまり、 $x_a / y_a$ という分数が $x_b / y_b$ という分数より小さいかどうかを調べるときは、$x_a / y_a < x_b / y_b$ の真偽を確かめるのではなく、 $x_a \times y_b < x_b \times y_a$ の真偽を確かめればよいです。

>>> (10 ** 9 + 1) / 10 ** 9 == 10 ** 9 / (10 ** 9 - 1)
True
# 本当は等しくない

>>> (10 ** 9 + 1) * (10 ** 9 - 1) < 10 ** 9 * 10 ** 9
True
# 大小が正しく判定される

誤差なしでソートする方法

 組み込みの sorted() 関数の key 引数に functools.cmp_to_key(cmp) を渡してやることで、自前の cmp() 関数で定義した比較関数に従って、ソートすることができます。
 cmp_to_key() については こちら を参考にしました。

from functools import cmp_to_key

def cmp(a, b):  # a = [x_a, y_a], b = [x_b / y_b]
    # 比較対象の分数 a と b が等しければ 0 を返す
    if a[0] * b[1] == b[0] * a[1]:
        return 0
    # a, bという順で並んでほしい条件のときは-1を返し、それ以外では1を返す
    return -1 if a[0] * b[1] < b[0] * a[1] else 1

l = [[5, 7], [3, 8], [1, 2]]  # リストの各要素は [分子, 分母] とする
l = sorted(l, key=cmp_to_key(cmp))
# l = [[3, 8], [1, 2], [5, 7]]

 最後までご覧いただきありがとうございます。

8
4
3

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
8
4