paizaラーニングレベルアップ問題集の「みかんの仕分け」をやってみた

paiza×Qiita記事投稿キャンペーンということで、paizaラーニングレベルアップ問題集の[みかんの仕分け](https://paiza.jp/works/mondai/c_rank_skillcheck_archive/mikan)をやってみました。今回はPython3で書いていきます。
- - -
###### [part0](https://paiza.jp/works/mondai/c_rank_skillcheck_archive/mikan_00)
```py
print(*[[(int(input())+N//2)//N*N or N for _ in [0]*M]for N,M in(map(int,input().split())for _ in[0])][0],sep='\n')
```

この問題は、後に続くpart1から順番に取り組むことでプログラムを完成させることができるようになっています。
part1から順番にやっていくと、少なくとも上述のコードよりはきれいなコードが完成します。
- - -
###### [part1](https://paiza.jp/works/mondai/c_rank_skillcheck_archive/mikan_01)
$M+1$行にわたって入力が与えられます。
まずはこれを受けとって出力してみましょう。
```py
n, m = map(int, input().split())  # 整数2つ受け取る
print(n, m)  # 整数2つを半角空白区切りで出力する
for i in range(m):  # 以下、m回繰り返し
    w = int(input())  # 整数を受け取る
    print(w)  # 整数を出力する
```
- - -
###### [part2](https://paiza.jp/works/mondai/c_rank_skillcheck_archive/mikan_02)
与えられる数値が$N$の倍数かどうかを判定してみましょう。
```py
n, m = map(int, input().split())
# print(n,m)
for i in range(m):
    w = int(input())
    # print(w)
    if w % n == 0:
        # 与えられた数値がnの倍数ならばYesを出力
        print("Yes")
    else:
        print("No")
```
- - -
###### [part3](https://paiza.jp/works/mondai/c_rank_skillcheck_archive/mikan_03)
みかんは、そのみかんの重さが一番近い数の重さが書かれた箱に入れる必要があります。（但し、重さが一番近い箱が複数ある場合、数が大きい方の箱に仕分けられます）
2つの箱が与えられるのでどちらの箱に入れるのがよいか判定してみましょう。
```py
n, m = map(int, input().split())
k1, k2 = map(int, input().split())  # 制約条件より、k1<k2
for i in range(m):
    w = int(input())
    if abs(k2 - w) <= abs(w - k1):
        # みかんの重さwがk2の方が近い、またはwとk1、wとk2の差が等しい
        print(k2)
    else:
        print(k1)
```
- - -
###### [part4](https://paiza.jp/works/mondai/c_rank_skillcheck_archive/mikan_04)
次は3個以上の箱の中から適切な箱に仕分けましょう。
```py
n, m = map(int, input().split())
p = int(input())
K = list(map(int, input().split()))
for i in range(m):
    w = int(input())
    k0 = 0  # 入れるべき箱に書かれた数字。0で初期化。
    for j in range(p):
        if K[j] % n == 0:  # 箱に書かれた数字がnの倍数
            if k0 == 0:  # 入れるべき箱が未定
                k0 = K[j]
            elif abs(K[j] - w) <= abs(w - k0):
                # 箱の数字は昇順なので、K[j]>k0が保証されている
                k0 = K[j]
    print(k0)
```
Pythonでは、箱に書かれた数字を受け取る際、$N$の倍数の書かれた箱だけ残して受け取ることができます。
```py
n, m = map(int, input().split())
p = int(input())

# 入力値のうち、箱に書かれた数字がnの倍数の箱だけ残す
K = [k for k in list(map(int, input().split())) if k % n == 0]

for i in range(m):
    w = int(input())
    k0 = 0  # 入れるべき箱に書かれた数字。0で初期化。
    for k in K:  # nの倍数の書かれた箱についてループ
        if k0 == 0 or abs(k - w) <= abs(w - k0):
            # 入れるべき箱が未定（k0=0）または、
            # 新しい箱の数字との差が大きくない
            k0 = k
    print(k0)
```
- - -
###### [part5](https://paiza.jp/works/mondai/c_rank_skillcheck_archive/mikan_05)
書かれた数字が$1,2,...,1500$の箱を昇順に調べ、箱に書かれた数字が$N$の倍数である場合、「箱に書かれた数字」と「みかんの重さと箱に書かれた数字の差の絶対値」を出力しましょう。
※Pythonでは、$1$から$N$まで昇順にループさせる場合は、`for i in range(1,N+1)`と記述します。
```py
n, m = map(int, input().split())
for i in range(m):
    w = int(input())
    for k in range(1, 1501):  # 書かれた数字が1,2,...,1500の箱を昇順に調べる
        if k % n == 0:
            '''
            箱に書かれた数字がnの倍数である場合、
            「箱に書かれた数字」と
            「みかんの重さと箱に書かれた数字の差の絶対値」を
            半角空白区切りで出力
            '''
            print(k, abs(w - k))
```
上の`for k in range(1,1501)`の部分も、$N$の倍数の部分だけループさせることにします。
```py
n, m = map(int, input().split())
for i in range(m):
    w = int(input())
    for k in range(n, 1501, n):
        # nから始めて、nずつ増やす（k=(nの倍数)についてのみループ）
        print(k, abs(w - k))
```
- - -
###### [part6](https://paiza.jp/works/mondai/c_rank_skillcheck_archive/mikan)
いよいよ「みかんの仕分け」のプログラムを完成させます。
```py
n, m = map(int, input().split())
for i in range(m):
    w = int(input())
    k0 = 0
    for k in range(n, 1501, n):
        if k0 == 0:
            k0 = k
        elif abs(k - w) >= abs(w - k0):
            k0 = k
    print(k0)
```
ここで、
$k_1=\lfloor\frac{w}{N}\rfloor\times N$　但し、$\lfloor x\rfloor$は$x$以下の最大の整数（床関数）
$k_2=\lceil\frac{w}{N}\rceil\times N$　但し、$\lceil x\rceil$は$x$以上の最小の整数（天井関数）
とすると、
$k_1\le w\le k_2$
$k_2-k_1=0\ \text{or}\ N$（$w$が$N$の倍数の時は$k_1=k_2$）
となるので、入れるべき箱は$k_1$, $k_2$のどちらか$w$に近い方（但し、どちらも同じ差の場合$k_2$、$k_1=0$の場合は$k_2$）になります。
$\lceil\frac{w}{N}\rceil$は`(w+n-1)//N`と書けますので、part3に近い形で、以下のように書くことができます。
```py
n, m = map(int, input().split())
for i in range(m):
    w = int(input())
    # k1,k2はwに依るので、forループの中
    k1 = w // n * n
    k2 = (w + n - 1) // n * n  # wをnで割って切り上げた値は(w+n-1)//nと書ける
    assert (k1 <= w <= k2)
    if k1 == 0 or k2 - w <= w - k1:  # 上のassertが通っていれば、abs関数は不要
        print(k2)
    else:
        print(k1)
```
更に、入れるべき箱の数字は、$w$を$N$で割って四捨五入した値（整数）に$N$をかけた数字（この数字が$0$になった場合は$N$）となります。
$w$を$N$で割って四捨五入して整数にした値は`(w+N//2)//N`と書けますので、以下のように書くこともできます。
```py
n, m = map(int, input().split())
for i in range(m):
    w = int(input())
    k = (w + n // 2) // n * n
    if k == 0:
        k = n
    print(k)
```
:::note warn
多くの言語で組み込み関数として用意されているであろう`round`関数は、偶数丸め（「整数+0.5」を最も近い**偶数**に丸める）になっている言語がありますので、よくご確認の上ご使用ください。
:::