1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Python] 自然数を序数に変換する

Last updated at Posted at 2020-09-05

Contents

integer型の自然数をstring型の序数(0th, 1st, 2nd, 3rd,..)に変換する方法です。ここでは0も自然数に含みます。
各コードはGitHub repositoryからダウンロードできます。

Rule

実装の前に、英語の序数1から1000までの表記と読み方一覧を参考にしてルールをまず考えます。

  1. 序数のsuffix (数字に続く文字列)は基本的に"th"です!(おおざっぱ)
  2. 例外として、下1桁が1の場合は"st", 2の場合は"nd", 3の場合は"rd"となります。
  3. さらに例外ルールに対する例外として、下2桁が11の場合は"th"になります(eleventh)。

解決方法

上記ルールを順番にコーディングし、最後にリファクタリング(挙動を変えずにコードを整理)します。

使用するパッケージ:

# 標準ライブラリ
from collections import defaultdict
実行環境
OS Windows Subsystem for Linux
Python version 3.8.5

1.基本的に"th"

collectionsパッケージのdefaultdictを使用すると1

def int2ordinal_1(num):
    ordinal_dict = defaultdict(lambda: "th")
    suffix = ordinal_dict[num]
    return f"{num}{suffix}"

# 見境なく"th"を返す
print(int2ordinal_1(0))
# -> '0th'
print(int2ordinal_1(1))
# -> '1th': ルール2で対応, 正しくは1st'
print(int2ordinal_1(11))
# -> '11th': ルール3で対応

2. 下1桁が1の場合は"st", 2の場合は"nd", 3の場合は"rd"

辞書を更新します。

ordinal_dict.update({1: "st", 2: "nd", 3: "rd"})

そして、入力値の下1桁に基づいて序数を判断するように変更します。

mod = num % 10
suffix = ordinal_dict[mod]

これを関数内の処理に加えると、

def int2ordinal_2(num):
    ordinal_dict = defaultdict(lambda: "th")
    ordinal_dict.update({1: "st", 2: "nd", 3: "rd"})
    mod = num % 10
    suffix = ordinal_dict[mod]
    return f"{num}{suffix}"

print(int2ordinal_2(0))
# -> '0th'
print(int2ordinal_2(1))
# -> '1st'
print(int2ordinal_2(11))
# -> '11st': ルール3で対応, 正しくは11th'

3. 下2桁が11の場合は"th"

100で割ったあまりが11のときに"th"を返せばよいので、

if num % 100 == 11:
    suffix = "th"
else:
    suffix = ordinal_dict[mod]

これを関数内の処理に加えると、

def int2ordinal_3(num):
    ordinal_dict = defaultdict(lambda: "th")
    ordinal_dict.update({1: "st", 2: "nd", 3: "rd"})
    mod = num % 10
    if num % 100 == 11:
        suffix = "th"
    else:
        suffix = ordinal_dict[mod]
    return f"{num}{suffix}"

print(int2ordinal_3(0))
# -> '0th'
print(int2ordinal_3(1))
# -> '1st'
print(int2ordinal_3(11))
# -> '11th'

4. リファクタリング

時間のかかる処理ではないので、あくまでおまけです。

num % 100 == 11は「商num // 10を10で割ったときの余りが1」と同じですね。

# 変更前
mod = num % 10
if num % 100 == 11:
    suffix = "th"
else:
    suffix = ordinal_dict[mod]
# 変更後
q = num // 10
mod = num % 10
if q % 10 == 1:
    suffix = "th"
else:
    suffix = ordinal_dict[mod]

さらに、商と余りはdivmod関数で一度に求められます。

また、if-else文を1行にします。今回は効果が薄いですが、代数suffixを書く回数が減るので誤記を減らせます。

q, mod = divmod(num, 10)
suffix = "th" if q % 10 == 1 else ordinal_dict[mod]

これを関数内の処理に加えると、

def int2ordinal_4(num):
    ordinal_dict = defaultdict(lambda: "th")
    ordinal_dict.update({1: "st", 2: "nd", 3: "rd"})
    q, mod = divmod(num, 10)
    suffix = "th" if q % 10 == 1 else ordinal_dict[mod]
    return f"{num}{suffix}"

print(int2ordinal_4(0))
# -> '0th'
print(int2ordinal_4(1))
# -> '1st'
print(int2ordinal_4(11))
# -> '11th'

完成形:序数を返す関数

引数の型チェックなどを加えると下記の通りです。

ordinal_func.py
def int2ordinal(num):
    """
    Convert a natural number to a ordinal number.

    Args:
        num (int): natural number

    Returns:
        str: ordinal number, like 0th, 1st, 2nd,...

    Notes:
        Zero can be used as @num argument.
    """
    if not isinstance(num, int):
        raise TypeError(
            f"@num must be integer, but {num} was applied.")
    if num < 0:
        raise ValueError(
            f"@num must be over 0, but {num} was applied.")
    ordinal_dict = defaultdict(lambda: "th")
    ordinal_dict.update({1: "st", 2: "nd", 3: "rd"})
    q, mod = divmod(num, 10)
    suffix = "th" if q % 10 == 1 else ordinal_dict[mod]
    return f"{num}{suffix}"

実行確認:

ordinal_func.py
print(int2ordinal(0)) # 0th
print(int2ordinal(1)) # 1st
print(int2ordinal(2)) # 2nd
print(int2ordinal(3)) # 3rd
print(int2ordinal(4)) # 4th
print(int2ordinal(11)) # 11th
print(int2ordinal(21)) # 21st
print(int2ordinal(111)) # 111th
print(int2ordinal(121)) # 121st

別解(defaultdictを使わない方法)

@shiracamusさんよりコメントいただいた、defaultdictを使わない方法をご紹介します。

def int2ordinal_5(num):
    ordinal_dict = {1: "st", 2: "nd", 3: "rd"}
    q, mod = divmod(num, 10)
    suffix = q % 10 != 1 and ordinal_dict.get(mod) or "th"
    return f"{num}{suffix}"

print(int2ordinal_5(0))
# -> '0th'
print(int2ordinal_5(1))
# -> '1st'
print(int2ordinal_5(11))
# -> '11th'
num q mod q % 10 != 1 ordinal_dict.get(mod) q % 10 != 1 and ordinal_dict.get(mod) suffix
0 0 0 False None False "th"
1 0 1 True "st" "st" "st"
11 1 1 False "st" False "th"

否定形とandを組み合わせるとは...

ここまではなかなか思いつかないとは思いますが、orを数値代入に使用する方法は便利です。たとえば、関数について引数のデフォルト値を空のリストを与えたいときに使えます。(関数の引数にdef func(x=[]): x.append(0); return xなどと設定すると想定外の挙動を示すので注意!2

def func(values=None):
    values = values or []
    if not isinstance(values, list):
        raise TypeError(f"@values must be a list or None, but {values} was applied.")
    values.append(0)
    return values

valuesがNoneやFalse, 空であったときは空のリスト[]が代入されます。それ以外のときはvalues引数がそのまま使用されます。

あとがき

以上、Pythonで自然数を序数に変換する方法でした!

  1. Python defaultdict の使い方

  2. 【python】引数のデフォルト値は定義時評価なので注意

1
2
2

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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?