QLITRE DIALY

Paizaのスキルチェックが楽しい

2022年10月10日

さいきん、paizaのプログラミングスキルチェックにはまっている。

paiza自体はITエンジニア向けの転職サービスだ。

コンテンツの一つとして、プログラミングスキルチェックがあり、難易度に応じて、コーディングテストを受けられる。

https://paiza.jp/challenges

コーディングテストについて

簡単にコーディングテストの概要について触れておこう。

難易度はS,A,B,C,Dの5段階あり、Sが一番難しい。

問題に応じて、制限時間内にコードを提出する。

提出後は10個前後のテストケースでコードの挙動をチェックし、見事パスすればクリア、となるわけだ。

問題をクリアするたびに自分のレーティングがあがっていく。

転職目的でやっているわけではないが、このレートに応じて企業がスカウトを行ったり、面接の足切りに使うといった意図があるらしい。

自分の状況

現在ランクAのレーティングが1906となっている。

ランクごとのスキル目安は下記のページに詳しい。

https://paiza.jp/guide/career?service_id=career

ランクAを持っていると、上位8%に入るらしいが、とてもそんな実力があるとは考えていない。

paizaスキルチェックではそのランクの問題を1問でも正解すると、ランクがあがる。

自分の場合、たまたま開いたAランクの問題がラッキーパンチというか、相性が良くて正解したのである。

実際はBランクの問題でも自分には厳しいと感じるところが多い。

今はCランクの問題をしらみつぶしに解いている、という状況だ。

これが結構おもしろく、以下におもしろいと感じる理由をまとめてみる。

コードを見直すきっかけになる

いままでプログラミングというと、ブログを作ったり、ある目的のために行うことが多かった。

それもそれで意義のあることだが、「動いていれば良し」と感じてしまい、コードの良し悪しについて客観的な判断が難しい。

こういうスキルチェックプログラミングだと、悪いコードだと、提出後に時間オーバーなどではじかれる。

なんだか悔しい気持ちがするものだが、自分のコードや考え方を見直すきっかけになって面白い。

標準の機能で頑張る

Pythonだったらpandasを使って計算をしたり、WEB系のことをやるときはDjango、というようにライブラリに依存したプログラミングを行う機会が多かったように思う。そのため、道筋を考える時にライブラリやフレームワークに完全に頼っている、という面があったのは否定できない。

コーディングテストは一部使用できる外部ライブラリもあるものの、基本は標準的な機能で問題を解いていく。

この過程で、標準機能に対する理解が深まるし、ライブラリを使わないで工夫できた、という発見があった時がうれしい。

たとえば、移動平均

先ほど解いていた問題で移動平均の計算が必要なものがあった。

移動平均とは、ある数値のリストが渡されたときに、一定の期間の平均をとっていくことを指す。

temperature = [18, 20, 22, 24, 20]

例えば上のように天気を記録したリストの2日間の移動平均をとると、以下のような計算で移動平均が算出できる。

[(18 + 20) / 2, (20 + 22) / 2, (22 + 24) / 2, (24 + 20) / 2]
[19, 21, 23, 22]

移動平均の計算に関してはnumpypandasなどを使えば一瞬だ。

import numpy as np

data = np.array([18, 20, 22, 24, 20])

print(np.convolve(data, np.ones(2) / 2, mode='valid'))
>>>
[19. 21. 23. 22.]

import pandas as pd

data = pd.Series([18, 20, 22, 24, 20])

print(data.rolling(2).mean())
>>>
0     NaN
1    19.0
2    21.0
3    23.0
4    22.0

コーディングテストでは、numpyも使えるみたいだったが、自作関数を作ってみた。

普通にfor loopを使って書くとこんな感じだろうか。

def moving_average_for_loop(a_list, n):
    results = []
    for i, item in enumerate(a_list, start=1):
        # 指定された期間未満の時は飛ばす
        if i < n:
            continue
        # スライスして平均を計算する
        tmp = a_list[i - n:i]
        _sum = sum(tmp)
        avg = _sum / n
        results.append(avg)
    return results

temperature = [18, 20, 22, 24, 20]
print(moving_average_for_loop(temperature, 2))
>>>
[19.0, 21.0, 23.0, 22.0]

ただしfor loopを使うのはあまり好きじゃない。

配列のインデックスがどこにあるのかを意識するのはあまり得意ではない。

tmp = a_list[i - n:i]

スライスが最悪で、特に直感的に分かりづらいと感じる。

実際のテストではwhile ループを使って実装することにした。

個人的にこちらの方が直感的に理解しやすい。

def moving_average(a_list: List, n: int) -> List:
    results = []
    # 一時リスト
    tmp = []
    while a_list:
        # 一時リストにa_listの先頭の数字を渡す        
        tmp.append(a_list.pop(0))
        # 一時リストの数が指定された期間になったら
        if len(tmp) == n:
            # 平均を計算する
            _sum = sum(tmp)
            avg = _sum / n
            results.append(avg)
            # 一時リストの先頭の数字を破棄する
            tmp.pop(0)

    return results

temperature = [18, 20, 22, 24, 20]
print(moving_average(temperature, 2))
>>>
[19.0, 21.0, 23.0, 22.0]

リストのpopメソッドを使うと、指定した位置の値を取り出すことができる。

つまり一回目のループの際にこうなる。

tmp = [18]
a_list = [20, 22, 24, 20]

これを繰り返していくとtmpに数字が移されていく。

tmp = [18, 20]
a_list = [22, 24, 20]

数字が指定した数、ここでいう2になったら平均を計算して、tmpの先頭の数字を破棄する。

tmp = [20]
a_list = [22, 24, 20]

配列のインデックスがどこにあるのかを意識しなくてよく、ひたすらなくなるまで繰り返す。

こちらの方がずいぶんと直感的だと思う。

おわりに

こういうよく使うメソッドはどっかにまとめておくとよいのかもしれない。

とりあえずgithubにリポジトリを作ってみた。適宜更新したい。ではでは。

https://github.com/qlitre/qlitre-utils