カレー問題別解解説
2014 SoftUmeYa, LLC
Masashi Umezawa
CROSS 2014
言語CROSS
Smalltalkによる
カレーのお題
長いので略。以下のような文字列をパース
1;パリパリチキン;肉類;500;0;
2;ロースカツ;肉類;300;0;なす,ゆでタマゴ
3;海の幸;魚介類;300;1;
4;やさい;野菜類;400;0;ゆでタマゴ
...
解答の方針
関数型言語っぽく書く
ブロッククロージャやコレクション系の
プロトコルを駆使
変数の代入は極力避ける
ちょっとやり過ぎでアレな感じにする
結構まともになった...
• Smalltalk for Lispers – http://live.exept.de/doc/online/english/programming/stForLi
spers.html
クロージャ駆使はSmalltalk的スタイル
getOrderHistory
「関数を定義」とあるので、全体をクロージャに
注文クラスを導入せず、辞書を返している
getOrderHistory := [:src | | rows rowStream | rows := OrderedCollection new. rowStream := src readStream. [rowStream atEnd] whileFalse: [rows add: ((rowStream nextLine) findTokens: ';')]. rows collect: [:eachRow | Dictionary new in: [:map | #(#OrderId #CurryMenu #Category #RiceWeight #HotFlavor #Toppings) paddedWith: eachRow do: [:a :b | map add: (a->b)]. map] ]. ].
A1
「辛さ」が2以上の注文を抽出し、
その「注文 ID」をすべて取得せよ
注文についてのクラスを導入せず、辞書を返している orders := getOrderHistory value: source. orders select: [:each | (each at: #HotFlavor) asInteger >= 2] thenCollect: [:each | each at: #OrderId].
A2
「分類」ごとに「ライスの量」の平均を取得せよ。
なお、平均値が大きい順に並べる
((orders groupBy: [:each | (each at: #Category)] having: [:each | true]) collect: [:each | (each collect: [:e | e at: #RiceWeight]) average]) associations sorted: [:a :b | a value > b value]
A3
「メニュー」がロースカツの注文について、
各「トッピング」の出現回数をカウント
Bag new in: [:bag | (orders select: [:each | (each at: #CurryMenu) = 'ロースカツ']) do: [:each | (each at: #Toppings) ifNotNil:[:toppings | bag addAll: (toppings findTokens: ',')]]. bag sortedElements]
F-like
もう少し工夫してみる
括弧が多いのはいかがなものか
F#のパイプライン演算子(|>)は、やはり綺麗
パイプラインの実装(1)
>> というメソッドをBlockClosureに定義
Objectにも定義
>> otherBlock ^ otherBlock value: self value
>> other ^ [self] >> other
パイプラインの実装(2)
BlockClosureにselectやらcollectを定義
同様に、gather, sortedも用意
できた!!
collect ^ [:col | col collect: self]
select ^ [:col | col select: self]
A1
「辛さ」が2以上の注文を抽出し、
その「注文 ID」をすべて取得せよ
注文についてのクラスを導入せず、辞書を返している orders := getOrderHistory value: source. orders >> [:each | (each at: #HotFlavor) asInteger >= 2] select >> [:each | each at: #OrderId] collect.
A2
「分類」ごとに「ライスの量」の平均を取得せよ。
なお、平均値が大きい順に並べる
(orders groupBy: [:each | (each at: #Category)] having: [:each | true]) >> [:each | (each collect: [:e | e at: #RiceWeight]) average]) collect >> #associations >> [:a :b | a value > b value] sorted
A3
「メニュー」がロースカツの注文について、
各「トッピング」の出現回数をカウント
(orders select: [:each | (each at: #CurryMenu) = 'ロースカツ']) >> [:each | (each at: #Toppings) ifNil:[#()] ifNotNil: [:toppings | (toppings findTokens: ',')]] collect >> [:each | each] gather >> #asBag >> #sortedElements