memorandums

日々の生活で問題解決したこと、知ってよかったことなどを自分が思い出すために記録しています。

moodleのVPLモジュールで提出されたファイル群をCSVに変換するスクリプト

はじめに

前期に2年生向けの科目でAtCoderを使ったプログラミング演習を担当しました(ちなみにこれまではAOJでした)。

学生さんにコーディングしてもらうときに過程を調べたかったので学内のmoodleのVPLモジュール上でプログラミングしてもらいました。

Virtual Programming Lab for Moodle (VPL) — Virtual Programming Lab for Moodle (VPL) 3.4.3+ documentation

VPLはColaboratoryみたいなやつですね。ブラウザ上でプログラミングができます。さらにエディタの実行時のコードをそのまま提出することができます。イメージは以下のような感じです。

このモジュールのよいところは上にも書きましたが、学生さんがこのエディタ上で実行するとそのときのプログラムを保存してあとで見れるようになっているんです。どういう流れでコーディングしていったのかよくわかるんです(といってもこれを分析するのはなかなか難しいですけどね)。例えばこんな感じです。なんかいい感じですが、でも大事なのは中身なのでこれだけではよいプロセスを踏んでいるのかどうかは何とも言えませんね。。。

で、以下、今日やったことをざーーっと書いていきます。

まず、提出ファイルをダウンロードする

ということでもう少しちゃんと分析できたらということで、学生さんから提出されたファイルをまるごとmoodleサーバーからダウンロードしたいなと思ったんです。これが最初わからなくて困りました。わかってしまえば簡単でした。絶対、来年忘れるので書いておきます。

下図の赤矢印のギアアイコンをクリックします。

以下のメニューが表示されるので すべての提出物をダウンロード を選択します。これで作成途中の履歴ファイルもダウンロードできます。ちなみに最終提出物だけダウンロードしたい場合は(恐らく) 提出物をダウンロード を選択すればよいと思います。

かなりわかりにくいと思いますが、この講義は15回講義なので01〜15までありまして、各回で問題を2〜3問出題しています。それがex1〜ex3という感じになります。その中に学生ごとのディレクトリがあり、その中にさらに複数回実行したタイムスタンプのディレクリがあり、その中にプログラムファイルがある感じになります。この辺はmoodleのページの作り方によるので参考程度でお願いします。

このファイル群を1つのCSVファイルにまとめる

1つ1つファイルを開いて見るのは現実的ではないので、Excelであれこれ試したいと思うわけです。で、プログラムでこの辺の情報を集約して1つのCSVファイルにした、というのがこのエントリーの主題です。Rubyで作りたかったのですが、Pythonの方が知られているのでPythonで作りました(といっても小さなコードですが💦)

import glob
fnames = glob.glob("./**/**/**/**/*")

rst = ""
for f in fnames:
    l = f.split("/")
    if len(l) == 6:
        _, no, ex, st, t, _ = l
        try:
            with open(f) as f:
                p = f.read().replace("\"","\"\"")
            rst += f"\"{no}\",\"{ex}\",\"{st.split()[3]}\",\"{t}\",\"{p}\"\n"
        except IsADirectoryError: 
            print(f"pyではなくディレクトリなので無視します{no}_{ex}_{st.split()[3]}_{t}")

with open("output.csv", "wb") as f:
    rst = rst.encode("shift_jis", errors='ignore')
    f.write(rst)

glob.globの引数は上記で説明したディレクトリ構造に依存するので足したり減らしたりしてください。このスクリプトは短いのですが結局、動かすまで1、2時間くらい掛かったと思います。はまったところを以下書きます。長くなるので結論だけ書きます。

ExcelでCSVデータがうまく認識されない

以下の箇所の書き方が悪かったんです。プログラミングするときにどうしても癖で カンマのあとに空白をいれがち です。Java的なコーディング規約をまだ引きずっていて。CSVデータを吐き出すコードはこれまで書いたことがあるはずなんですが、この空白があるとCSVデータとしてうまく認識してくれませんでした。気をつけましょう>自分

rst += f"\"{no}\",\"{ex}\",\"{st.split()[3]}\",\"{t}\",\"{p}\"\n"

CSVのデータにダブルクォートが入っている場合は重ねる必要がある

以下の素晴らしい記事のおかげで気づきました。あーそんなことがあったっけ。。。これを修正するのに結構時間がかかりましたね。

ダブルクォーテーションが含まれる文字列を項目として読み込みたい場合は、ダブルクォーテーションを2つ重ねる必要があります。

CSV/Excelファイル取扱上の注意点 – freee ヘルプセンター

なのでコード的には以下で対応しています。

p = f.read().replace("\"","\"\"")

SJISに変換するのがなかなか難しかった

これもなかなかうまく行かなかった。。。色々と試したんです。ネットの記事をあれこれ参照しながら、結局、以下のようにしました。学生が作ったPythonファイルにSJISに変換できない文字が含まれているファイルもあってこれが例外で落ちたのが始まりでした。で、encodeメソッドに「errors='ignore'」を与えることで、変換できない文字は出力しない指定ができました。これで作り出したSJISの文字列が入った変数をwriteに与えます。utf-8ではないので怒られるのでwbにしてバイナリで出力すると怒られません。この辺は日常的に経験があればすぐに作れるんでしょうけど。。。調べながらやるとどうしても時間がかかります。

with open("output.csv", "wb") as f:
    rst = rst.encode("shift_jis", errors='ignore')
    f.write(rst)

Python小技:文字列から特定の文字コードに含まれない文字を削除する

(Windows) Python3でのUnicodeEncodeErrorの原因と回避方法 #encode - Qiita

Pythonファイルを提出するはずなのにディレクトリを提出している学生がいた💦

これは例外中の例外なんでしょうけど、ディレクトリ階層的にPythonファイルがあるはずなのにそこがディレクトリだったのでopen関数でエラーが発生する問題がありました。しかたがなく例外処理を入れて何件あったのかわかるようにprintしておきました。

        try:
            with open(f) as f:
                p = f.read().replace("\"","\"\"")
            rst += f"\"{no}\",\"{ex}\",\"{st.split()[3]}\",\"{t}\",\"{p}\"\n"
        except IsADirectoryError: 
            print(f"pyではなくディレクトリなので無視します{no}_{ex}_{st.split()[3]}_{t}")

CSVファイルがやっと入手できたので、とりあえず1つやってみたこと

とりあえずCSVファイルができました。以下のような感じです。あとはExcel関数を使って色々と分析ができそうです。

ちょっと横道にそれますが。今回の演習では正解を出題時に合わせて学生に提示するという実験をしてみました。

プログラミングってできそうな問題とそうではない問題があるはずで、それは個人差が大きいはずなんですね。また、人によって考えるより覚えるのが好きだという子もいるでしょうし。90分という時間をそれぞれの気質に合わせて有効に使ってもらいたいという考えからでした。

性悪説で考えると正解をコピペして提出してオシマイってことになるんでしょうけど、一応、その辺は最後に実力テストを行うので、解答を見ても構わないので何度もトライして最後には空で書けるようになればいい、というスタンスでした。受験で 数学の問題を見てわからなかったら解答をさっさと見てできるようになるまで繰り返す という勉強テクニックがある可と思いますが、それです。

で、まずは、正解のコードと提出されたコードの差異を調べてみようという思ったんですね。文字列の差とくればとりあえずレーベンシュタイン距離かと思います。もちろん、他にもありますが。

Excel関数にはないのでネットからVBAのコードを引っ張ってきました。ありがたく使わせていただきました。

見た目が似た文字列を検索するVLOOKUP的なExcelの関数をレーベンシュタイン距離を使って作る。 #VBA - Qiita

作成したCSVファイルをExcelブックで保存して、開発メニューからVBAを開き、標準モジュールを追加して上記のサイトからLevenshteinDistance関数をコピペしました。

正解のセルと学生のセルを比較してみました。まだ、全体を調べていませんが、序盤はみなさん解答を見ずに自分で取り組んでいる様子でした。ちなみにこれはレーベンシュタイン距離を調べなくてもコードを見ればわかるし、実行回数を見ればわかるんですよね。。。20回も30回も実行している人がコピペしているわけがないので。。。

とりあえず分析する土台はできました。

帰ります👍