#:g1: データの検索に組み込みPrologを使ってみる(2)

Posted 2021-10-12 02:09:57 GMT

データの検索に組み込みPrologを使ってみる試みの続きですが、CSVファイルを集計する記事を読みこちらお題を真似てみることにしました。

まずは手続き的に書いてみる

とりあえず比較のため普通に書いてみます。

(cl:in-package "CL-USER")

(ql:quickload '(drakma fare-csv babel srfi-2))

(defpackage covid19.mhlw.go.jp (:use cl fare-csv drakma babel srfi-2))

(cl:in-package covid19.mhlw.go.jp)

(defun read-csv/utf-8 (url) (and-let* ((csv-bin (http-request url :force-binary T)) (csv (octets-to-string csv-bin :encoding :utf-8))) (with-input-from-string (in csv) (read-csv-stream in))))

(defvar *newly_confirmed_cases_daily.csv* (read-csv/utf-8 "https://covid19.mhlw.go.jp/public/opendata/newly_confirmed_cases_daily.csv"))

;;ヘッダを確認 (car *newly_confirmed_cases_daily.csv*)("Date" "Prefecture" "Newly confirmed cases")

;;ヘッダ情報からリストのアクセサを作成してみる (defstruct (covid19 (:type list)) date prefecture cases)

;;元データの型変換: 数値文字列→数値 (dolist (row (cdr *newly_confirmed_cases_daily.csv*)) (setf (covid19-cases row) (parse-integer (covid19-cases row))))

(defun group-by (accessor list) (let ((tab (make-hash-table :test #'equal))) (dolist (r (reverse list)) (push r (gethash (funcall accessor r) tab '()))) tab))

(defun sum (accessor list) (reduce #'+ list :key accessor))

;;愛知の2021年9月の合計 (loop :for (date prefecture newly-confirmed-cases) :in (cdr *newly_confirmed_cases_daily.csv*) :when (and (equal "Aichi" prefecture) (search "2021/9/" date)) :sum newly-confirmed-cases) → 21255

;;都道府県ごとの合計 (loop :for v :being :the :hash-values :of (group-by #'covid19-prefecture (cdr *newly_confirmed_cases_daily.csv*)) :repeat 2 :for pref := (covid19-prefecture (car v)) :unless (equal pref "ALL") ;ALLを除外したい場合 :collect (list pref (sum #'covid19-cases v)))(("Shizuoka" 26308) ("Shiga" 12388))

PAIProlog 篇

組み込みPrologであれば色々と複雑なクエリを簡単に記述できますが、今回のデータ量約30,000をそのままPAIPrologの述語として登録して検索してみると非常に遅いため、別途方策を練る必要があるようです。
また、LispWorksのCommon PrologやAllegro CLのAllegro Prologに比べるとProlog側のユーティリティが少ないため殆どプリミティブで書くことになります。
とはいえ、今回の集計に必要なSQLでいうところのgroup byのようなユーティリティはCommon PrologでもAllegro Prologでも別途用意する必要はありますが……。

(ql:quickload 'paiprolog)

(use-package 'paiprolog)

(defun parse-date (date) (ppcre:register-groups-bind ((#'parse-integer y) (#'parse-integer m) (#'parse-integer d)) ("(\\d+)/(\\d+)/(\\d+)" date) (list y m d)))

;;元データの変換: 日付をリストへ: "2021/1/1" → (2021 1 1) (dolist (row (cdr *newly_confirmed_cases_daily.csv*)) (setf (covid19-date row) (parse-date (covid19-date row))))

;;オブジェクトをPrologの項として登録するためのユーティリティ (defun add-object-clause (name obj &key asserta) (let ((pred name)) (assert (and (symbolp pred) (not (paiprolog::variable-p pred)))) (pushnew pred paiprolog::*db-predicates*) (pushnew pred paiprolog::*uncompiled*) (setf (get pred 'paiprolog::clauses) (if asserta (nconc (list (list (list name obj))) (paiprolog::get-clauses pred)) (nconc (paiprolog::get-clauses pred) (list (list (list name obj)))))) pred))

;;節となるオブジェクト定義 (defclass covid19-newly-confirmed-cases-daily () (date prefecture cases))

;;データを登録 (dolist (row (cdr *newly_confirmed_cases_daily.csv*)) (let ((obj (make-instance 'covid19-newly-confirmed-cases-daily))) (with-slots (date prefecture cases) obj (setf (values date prefecture cases) (values (covid19-date row) (covid19-prefecture row) (covid19-cases row)))) (add-object-clause 'covid19-newly-confirmed-cases-daily obj)))

(length (paiprolog::get-clauses 'covid19-newly-confirmed-cases-daily)) → 29952

;;愛知の2021年9月の合計 (let ((sum 0)) (prolog (covid19-newly-confirmed-cases-daily ?obj) (is ?pref (slot-value ?obj 'prefecture)) (is ?date (slot-value ?obj 'date)) (is ?cases (slot-value ?obj 'cases)) (= "Aichi" ?pref) (= (2021 9 ?) ?date) (lisp (incf sum ?cases))) sum) → 21255 ;;初回問い合わせは5分位かかるかも…… ;;都道府県ごとの合計 (let ((tab (make-hash-table :test #'equal))) (prolog (covid19-newly-confirmed-cases-daily ?obj) (is ?pref (slot-value ?obj 'prefecture)) (is ?cases (slot-value ?obj 'cases)) (lisp (incf (gethash ?pref tab 0) ?cases))) (loop :for pref :being :the :hash-keys :of tab :using (:hash-value cases) :repeat 2 :collect (list pref cases)))(("Shizuoka" 26308) ("Shiga" 12388))

まとめ

今回のお題のクエリは、SQLでいうとselect prefecture,sum(cases) from table group by prefectureのようなところですが、どうもPAIPrologの場合は、データ量は少ないけれどクエリは複雑な場合に向いていそうです。


HTML generated by 3bmd in LispWorks 7.0.0

comments powered by Disqus