lambda を隠した書き方 (20) - 第18回シェル芸勉強会 Q1 -- オトン、オカン

  • 6
    Like
  • 0
    Comment
More than 1 year has passed since last update.

Scheme、lambda を隠した書き方で、シェル芸勉強会の問題を解いてみることにした。今回は第18回のQ1。

問題

原文は上田隆一さんの記事にありまふ。

次のファイルは1列目がキー、2列目が値ですが、「オトン」と「オカン」の両方の値があるキーを探してください。

(define text 
 '(
  ("001" "オトン")
  ("001" "オトン")
  ("001" "アカン")
  ("002" "オカン")
  ("003" "オトン")
  ("003" "ヤカン")
  ("003" "オカン")
  ("004" "オカン")
  ("005" "オトン")
  ("005" "ミカン")
  ("005" "アカン")
  )
)

本当の問題だとデータはファイルにあるんだけど、ここでは、入力データはファイルでなくて変数に入ってることにしてある。もちろんファイルを読み出してもいいけど、やりたいのは Scheme(ってゆうか SKILL)としてのデータフローだからねっ。(b´∀`)ネッ!

作戦

キーと値のタプルのリストを、キーでまとめてこんな感じに変形する。

$$
\left\{
\begin{array}{lcl}
key_1 & \longrightarrow & [value_{11}, value_{12}, \cdots, value_{1n_1}]\newline
key_2 & \longrightarrow & [value_{21}, value_{22}, \cdots, value_{2n_2}]\newline
& \vdots \newline
key_m & \longrightarrow & [value_{m1}, value_{m2}, \cdots, value_{mn_m}]
\end{array}
\right.
$$

で、これをフィルターして値のリストに「オトン」と「オカン」を含むものだけ残す。(^o^)

ハッシュテーブルが…

最初の変形の部分は、イメージ的には、こんな感じにしたい…


cat text | awk '{kvs[$1] = kvs[$1] " " $2} ...'

…で、ハッシュテーブルが必要で、SRFI を探して見たんだど、SRFI のハッシュテーブル SRFI-69 だと、値を更新する関数 hash-table-update! は副作用式で、関数の評価値は未定義値になってて、更新後のハッシュテーブル(自身)を返してはくれないから、SRFI-1 の fold 関数を使って次々と値を追加・更新することができない。

しょーがないから、こんなのを作った。


(define (new_KVs) '())

  (define (new_KV key value) (list key value))
  (define KV_getKey   car )
  (define KV_getValue cadr)

; KVs_update
; キー key に対応する値(と args)を関数 function に与えて、その返り値で、ハッシュテー
; ブルを更新して、更新後のハッシュテーブルを返す。キー key に対応する値が存在しない場合
; は、function には default(と args)を与える。
; 元のハッシュテーブルを書き換えたりはしないけど、新しいハッシュテーブルを作るのには、
; 副作用つかいます。基本機能だから効率重視でねっ。

(define (KVs_update this key function default . args)
  (let*
    ((buffer (list #f)) (buffer_last buffer))
    (let trace ((ts this))
      (cond
        ((null? ts)
          (set-cdr! buffer_last (list
            (new_KV key (apply function (cons default args)))
          ))
          (cdr buffer)
        )
        ((equal? key (KV_getKey (car ts)))
          (set-cdr! buffer_last (cons
            (new_KV key (apply function (cons (KV_getValue (car ts)) args)))
            (cdr ts)
          ))
          (cdr buffer)
        )
        (else
          (set-cdr! buffer_last (list (car ts)))
          (set!     buffer_last (cdr buffer_last))
          (trace (cdr ts))
        )
      )
    )
  )
)

(define (KVs_updateBoolean this key function . args) (apply KVs_update `(,this ,key ,function #f ,@args)))
(define (KVs_updateNumber  this key function . args) (apply KVs_update `(,this ,key ,function 0  ,@args)))
(define (KVs_updateString  this key function . args) (apply KVs_update `(,this ,key ,function "" ,@args)))
(define (KVs_updateList    this key function . args) (apply KVs_update `(,this ,key ,function () ,@args)))

…ってかさぁ、SFRI、なんで、副作用のハッシュテーブルしかないの?

というわけで

ハッシュテーブル(というかハッシュ関数を使うかどぉかは問題じゃないから、正確には「辞書型データ」とそのインターフェイス関数)さえできてしまえば、問題を解くコードは実に簡単なのです。


(echo text
  (*~ fold
    (lambda (fields kvs)
      (KVs_updateList kvs (first fields) xcons (second fields))
    )
    (new_KVs)
  )
  (*~ filter ($$ KV_getValue (*~ member "オトン")))
  (*~ filter ($$ KV_getValue (*~ member "オカン")))
  (each KV_getKey)
)

→ ("003")

参考

関数 別名 機能 定義
filter 第2引数で与えられたリストの要素を調べて第1引数を満たすものだけを残す。 SRFI-1
firstsecondthirdfourthfifthsixthseventheighthninthtenth それぞれ、リストの1、2、3、4、5、6、7、8、9、10番目の要素を返す。 SRFI-1
each @$ “並列化関数”
与えられた関数を map に部分適用した関数を返す。
(Function_curryFore map)
echo 第2引数以降の引数として与えられた関数を合成した関数を作り、その合成関数に対して第1引数で与えられ値を適用して、その評価値を返す。 → 「lambda を隠した書き方 (8) - 個数を数える
Function_bindFore *~ “前側束縛関数”
引数列の前側を束縛した関数を返す
→「lambda を隠した書き方
Function_compose $$ “合成化関数”
与えられた関数を合成した関数を返す。
→「lambda を隠した書き方
xcons 引数の順序が逆の cons SRFI-1



≪前: lambda を隠した書き方 (19) - 特定ディレクトリ以下の特定ファイルを探す
次≫: lambda を隠した書き方 (21) - 第18回シェル芸勉強会 Q4