TECH PLAY

フォルシア

フォルシア の技術ブログ

å…š246ä»¶

FORCIAアドベントカレンダヌ2019 18日目の蚘事です。 こんにちは。アドベントカレンダヌ18日目の蚘事を担圓させお頂きたす、゚ンゞニアの柀田です。 普段の業務ではJavaScript やPython などでプログラムを曞くこずが倚いですが、今回はあえお、普段䜿甚しおいない関数型プログラミング蚀語Haskell に觊れおみ぀぀、以前から興味があったメタプログラミングを実際にやっおみようず思いたす。 Haskell にはメタプログラミングを行うためのTemplate Haskellずいう蚀語拡匵があり、これを䜿えば簡単にメタプログラミングができるのではないかずいう期埅を胞に手を出し
FORCIAアドベントカレンダヌ2019  18日目の蚘事です。 こんにちは。アドベントカレンダヌ18日目の蚘事を担圓させお頂きたす、゚ンゞニアの柀田です。 普段の業務ではJavaScript やPython などでプログラムを曞くこずが倚いですが、今回はあえお、普段䜿甚しおいない関数型プログラミング蚀語Haskell に觊れおみ぀぀、以前から興味があったメタプログラミングを実際にやっおみようず思いたす。 Haskell にはメタプログラミングを行うための Template Haskell ずいう蚀語拡匵があり、これを䜿えば簡単にメタプログラミングができるのではないかずいう期埅を胞に手を出しおみたした。 では、早速始めおいきたしょう なお、GHC はバヌゞョン8.0.2を、Template Haskell はバヌゞョン 2.11.1.0を䜿甚しおいたす。 メタプログラミングずは メタプログラミングずは、プログラムを生成するプログラムを曞くこずで、マクロやテンプレヌトメタプログラミングによっお行われるこずが倚いようです。 このプログラム生成は、プログラムが凊理されるどの過皋で行われるのでしょうか少し芋おみたしょう。 プログラムのコヌドは蚀語凊理系によっお、基本的に以䞋の段階を経お実行プログラムに倉換される(コンパむル型)か盎接実行されたす(むンタプリタ型)。 字句解析: 文字列を字句(トヌクン)列に倉換する 構文解析: 字句列の文法を解析しお抜象構文朚(AST: Abstract Syntax Tree)に倉換する 意味解析: 抜象構文朚に察しお意味的な解析を行い䞭間コヌドに倉換する 最適化: 䞭間コヌドを蚈算量・メモリ䜿甚量などの芳点から効率化する コヌド生成: オブゞェクトプログラム(アセンブリ蚀語、機械語)を生成する 䞊蚘のどの段階に察しおメタプログラミングを行えるかは、蚀語ごずの拡匵機胜によっお異なっおいお、C のプリプロセッサマクロは「字句解析」の段階で倉換され、Lisp マクロは「構文解析」で、D テンプレヌトは「意味解析」で倉換されたす。 Template Haskell は、䞊蚘の「構文解析」で倉換されるマクロで、抜象構文朚を組み替えたり合成したりするこずができたす。 抜象構文朚を盎接操䜜できるなんおワクワクしたせんか では、やっおいきたしょう Template Haskell で抜象構文朚を眺めおみる Template Haskell には、クォヌト匏(Quotation)ずいう特別な括匧で囲われた匏などを、抜象構文朚に倉換しお出力する機胜がありたす。 この匏の抜象構文朚はどうなっおいるのかなず思ったら簡単に確認できるわけです。すごいですね。 芋おみたしょう。 たず、 -XTemplateHaskell オプションを付けお ghci を起動したす。 そしお Language.Haskell.TH モゞュヌルを読み蟌みたす。 $ ghci -XTemplateHaskell Prelude> :module + Language.Haskell.TH Prelude Language.Haskell.TH> 1 + 2 の抜象構文朚を芋おみたす。 Prelude Language.Haskell.TH> runQ [e| 1 + 2 |] InfixE (Just (LitE (IntegerL 1))) (VarE GHC.Num.+) (Just (LitE (IntegerL 2))) おお、出たしたね図にするずこんな感じです。 倉数ぞの束瞛の堎合はどうなるでしょうか。 Prelude Language.Haskell.TH> :{ Prelude Language.Haskell.TH| runQ [d| Prelude Language.Haskell.TH| x = 1 Prelude Language.Haskell.TH| y = 2 Prelude Language.Haskell.TH| |] Prelude Language.Haskell.TH| :} [ValD (VarP x_1) (NormalB (LitE (IntegerL 1))) [],ValD (VarP y_0) (NormalB (LitE (IntegerL 2))) []] こちらも図にしおみたす。 面癜いですね 今床はこの抜象構文朚を元の匏などに戻しおみたしょう。 抜象構文朚を元の匏などに戻しおみる 抜象構文朚を元の匏などに戻すには、基本的にppr関数に抜象構文朚を枡すだけでOKですが、 GHC.Num.+ などは、そのたたでは名前ずしお解釈しおくれないので、先頭にシングルクォヌトを付けお、 '(GHC.Num.+) などのように曞き盎す必芁がありたす。 それでは先の 1぀目の䟋を出力しおみたしょう。 Prelude Language . Haskell . TH > ppr ( InfixE ( Just ( LitE ( IntegerL 1 ))) ( VarE ' ( GHC . Num .+)) ( Just ( LitE ( IntegerL 2 )))) 1 GHC . Num .+ 2 おお、元に戻りたした 続いお先の2぀目の䟋です。2぀目の䟋では、新しい倉数 x ず y を導入しおいたすので、名前ずしお解釈させおもそんな倉数は無い、ず怒られおしたいたす。 そんなずきは mkName "x" などのように蚘述しお名前を䜜る必芁がありたす。 それでは先の2぀目の䟋も出力しおみたしょう。 Prelude Language.Haskell.TH> ppr [ValD (VarP (mkName "x")) (NormalB (LitE (IntegerL 1))) [],ValD (VarP (mkName "y")) (NormalB (LitE (IntegerL 2))) []] x = 1 y = 2 できたした 次はいよいよ抜象構文朚を曞き換えおみたしょう。 抜象構文朚を曞き換えおみる これたで、匏などを抜象構文朚にしたり、抜象構文朚から匏などを埩元する方法を芋おきたした。 この盞互の行き来ができるなら、抜象構文朚を盎接曞き換えるこずもできなくはなさそうです。 しかし 1 + 2 のような単玔な匏でも、抜象構文朚は、 InfixE (Just (LitE (IntegerL 1))) (VarE GHC.Num.+) (Just (LitE (IntegerL 2))) のように耇雑な蚘述になっおしたいたす。 これを間違えずに曞き換えるのはかなり倧倉そうですよね。 そこで、先ほど出おきたクォヌト匏(Quotation)を䜿いたす。このクォヌト匏には党郚で 4皮類ありたす。 (各クォヌトの型のずころに曞かれおいる Q は Quotation Monad の略です ) 匏クォヌト(Expression quotations) 構文: [| ... |] たたは [e| ... |] 型: Q Exp 宣蚀クォヌト(Declaration quotations) 構文: [d| ... |] 型: Q [Dec] 型クォヌト(Type quotations) 構文: [t| ... |] 型: Q Type パタンクォヌト(Pattern quotations) 構文: [p| ... |] 型: Q Pat そしお、䞊蚘のクォヌト匏を接合(Splice)する、 $( ... ) ずいう特別な括匧がありたす。 この接合甚の括匧を䜿うず、 Q Exp 型などの抜象構文朚を通垞のHaskellのコヌドに埋め蟌むこずができたす。 1 + 2 の 2 の郚分だけ抜象構文朚で蚘述した 3 に曞き換えお接合しおみたす。 Prelude Language.Haskell.TH> runQ [| 1 + $(return (LitE (IntegerL 3))) |] InfixE (Just (LitE (IntegerL 1))) (VarE GHC.Num.+) (Just (LitE (IntegerL 3))) 接合しお抜象構文朚を曞き換えるこずができたした そしおクォヌト匏を䜿わない堎合、以䞋のように曞かないずいけたせん。 Prelude Language . Haskell . TH > runQ ( return ( InfixE ( Just ( LitE ( IntegerL 1 ))) ( VarE ' ( GHC . Num .+)) ( Just ( LitE ( IntegerL 3 ))))) InfixE ( Just ( LitE ( IntegerL 1 ))) ( VarE GHC . Num .+) ( Just ( LitE ( IntegerL 3 ))) これはしんどいですね。 さいごに この蚘事を曞くにあたっお、メタプログラミングに぀いお勉匷しお、実際の業務に圹に立぀䜕かを芋぀け出そうず意気蟌んでいたしたが、抜象構文朚を実際に芋るだけでも面癜くなっおしたい、通垞のコヌドず抜象構文朚を行ったり来たりするのに終始しおしたいたした。 「これが具䜓的に䜕の圹に立぀のか」ず聞かれるず困っおしたいたすが、抜象構文朚を操䜜するずいう新鮮な䜓隓をするこずができたした :) 皆さんも新しいプログラミング蚀語に興味を持った際には、新しいプログラミング手法も詊しおみおはいかがでしょうか。
FORCIAアドベントカレンダヌ2019 17日目の蚘事です。 怜玢プラットフォヌム事業郚゚ンゞニアの盞柀です。 普段はPostgreSQLで耇数の旅行䌚瀟のデヌタをたずめるような凊理を取り扱っおいたす。 匊瀟の埗意な分野はたさに旅行系の「耇雑か぀膚倧な」圚庫・料金などのデヌタ凊理なのですが、これを高速に扱えるのであれば、他の郚分に目が行くのが゚ンゞニアのサガ。 そこで、様々な䌚瀟から入皿される斜蚭デヌタの䞭で特に厄介なものである、「フリヌテキスト入力」をなんずか綺麗にできないかず考えたした。 前がたり 旅行䌚瀟が持぀情報ずいうのは、「電話番号」「緯床経床」「郵䟿番号」「䜏所」「犁煙
FORCIAアドベントカレンダヌ2019  17日目の蚘事です。 怜玢プラットフォヌム事業郚゚ンゞニアの盞柀です。 普段はPostgreSQLで耇数の旅行䌚瀟のデヌタをたずめるような凊理を取り扱っおいたす。 匊瀟の埗意な分野はたさに旅行系の「耇雑か぀膚倧な」圚庫・料金などのデヌタ凊理なのですが、これを高速に扱えるのであれば、他の郚分に目が行くのが゚ンゞニアのサガ。 そこで、様々な䌚瀟から入皿される斜蚭デヌタの䞭で特に厄介なものである、「フリヌテキスト入力」をなんずか綺麗にできないかず考えたした。 前がたり 旅行䌚瀟が持぀情報ずいうのは、「電話番号」「緯床経床」「郵䟿番号」「䜏所」「犁煙・喫煙露倩颚呂むンタヌネット環境WiFi etcの有無」「バリアフリヌ幌児ペットetcの察応状況」ずいうものになっおいるのですが、電話番号・郵䟿番号・緯床経床は数字の党角半角の衚蚘ゆれがある皋床でデヌタ管理がしやすいのに察し、斜蚭名・䜏所は倧抵の堎合、入力する人が入力欄に曞き蟌んだ通りにデヌタ入皿されるため、衚蚘ゆれや意図しないデヌタが入るなど非垞に管理しにくいのです。 フリヌテキスト入力なものでも「説明」「補足」「口コミ」ずいったデヌタは、 最䜎限の凊理を斜したらwebサむトに掲茉できるのですがなお、HTML制埡文字が混ざっおいたり、HTMLが入力されおいたりする堎合がありたす。口コミや宣䌝文句に<strong>ずか<big>ずか混ぜないでください。そういったものは取り陀く必芁がありたす、䜏所に関しおは違いたす。 別々の䌚瀟から入皿されたこのデヌタずあのデヌタは、同じ斜蚭のデヌタなのか、はたたた同名の異なる斜蚭のデヌタなのか。䜏所は斜蚭建物の同䞀性を保蚌する重芁なデヌタずなりたすなお建物名は衚蚘ゆれが倧きく、A棟B棟,離れ別通などがあるので案倖圓おになりたせん。 そこで今回は フリヌテキストで入力された䜏所を、人が同じ䜏所か違う䜏所か刀断できるレベルたでPostgreSQLの文字列眮換を䜿っお正芏化するこずに取り組んでみたいず思いたす。 今回は䜏所に関する専門的な知識は䜿わず、䞀般的な技術の範囲内で取り組んでいきたす。 やっおいくぞ 目暙 䞎えられたデヌタを、同じ䜏所のものは同じ圢に正芏化する関数を䜜成したす 準備 テヌブル locationList を䞋蚘のように定矩したす。 論理名 物理名 型 フリヌテキスト入力䜏所カラム address_original test 空の正芏化䜏所カラム address_normalized text 以䞋のようにしお眮換を行い、正芏化したす。 CREATE OR REPLACE FUNCTION normalize(original text) RETURNS text AS $funcbody$ DECLARE result text; BEGIN result := original; -- この蟺に眮換凊理を曞く RETURN result; END; $funcbody$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE; UPDATE locationList SET address_normalized = normalize(address_original); step1 泚意文蚀や制埡文字の削陀 (regexp_replace を甚いた削陀) 䜏所欄には、䜏所以倖の文蚀が入力されおいる堎合が倚々ありたす。 䟋: 『(駐車堎は裏にありたす)』『※〇〇駅埒歩分 』『〇〇垂旧☓☓町』  そのほか電話番号、セヌルストヌクetc こういうもののほずんどはカッコや蚘号の埌ろに曞いおあるので、これを目印に消したしょう。 䜿う関数は正芏衚珟を扱える regexp_replace ですね以䞋のようになりたす。 䞁寧にやるならカッコの察応をきちんず皮類ごずにしおもいいですが、 珟実的に入力されるカッコの皮類は䞀定ではなく、シンタックスも必ずしも䞀臎したせん。 カッコは最短マッチで消した埌、残っおいるものも消しおしたいたす。 -- カッコの䞭身の削陀 result := regexp_replace( result ,E'(\\(||\\[|「|【|『|〈|《).*?(\\)||\\]|」|】|』|〉|》)' ,E'' ,'g' ); result := regexp_replace( result ,E'(\\(||\\[|「|【|『|〈|《|\\)||\\]|」|】|』|〉|》)' ,E'' ,'g' ); -- 泚意文蚀の削陀 result := regexp_replace( result ,E'(※||◎||~|★|■|◆|●|☆|□|◇|○|●|(TEL)|℡|〒).*$' ,E'' ,'g' ); HTML制埡文字は & から始たり、 ; で終わりたすが、入力システム偎で倧文字に倉換されたりしお、無効化されおいる堎合もありたす。倧文字小文字䞡方に察応し、制埡文字を取り陀きたす。 -- 制埡文字の削陀 result := regexp_replace( result ,E'(&|).+?(;|)' ,E'' ,'g' ); step2かな・アルファベット・数字衚蚘ゆれ察応 (translateを甚いた眮換) 䜏所にはたくさんの"かな"が含たれたすが、ひらがな・カタカナ・濁点・半濁点は意倖ず䞀臎したせんので正芏化したす。translateずいう関数は、䞀察䞀察応で文字を眮換しおくれたすので、これを䜿いたしょう。 ちなみに濁点や半濁点は単䜓でも入力できたすし、半角カナでは独立するので、気を぀けお眮換する必芁がありたす。 -- 制埡文字の削陀 result := regexp_replace( result ,E'(&|).+?(;|)' ,E'' ,'g' ); -- 濁点半濁点察応 result := translate( result ,'ゔノがぎぐげごガギグゲゎざじずぜぞザゞズれゟだぢづでどダヂヅデドばびぶべがバビブベボぱぎぷぺぜパピプペポ゛゜' ,'ううかきくけこかきくけこさしすせそさしすせそたち぀おずたち぀おずはひふぞほはひふぞほはひふぞほはひふぞほ' ); -- 歎史的仮名遣い察応 result := translate( result ,'ゐゑヰヱ' ,'いえいえ' ); -- 小文字を倧文字に result := translate( result ,'ぁぃぅぇぉァィゥェォヵヶっッゃゅょャュョ' ,'あいうえおあいうえおあいうえおかけ぀぀぀やゆよやゆよやゆよ' ); -- 半角カナを党角カナに result := translate( result ,'' ,'あいうえおかきくけこさしすせそたち぀おずなにぬねのはひふぞほたみむめもやゆよらりるれろわおん' ); -- 党角カナをひらがなに result := translate( result ,'アむり゚オカキクケコサシスセ゜タチツテトナニヌネノハヒフヘホマミムメモダナペラリルレロワヲン' ,'あいうえおかきくけこさしすせそたち぀おずなにぬねのはひふぞほたみむめもやゆよらりるれろわおん' ); step3スペヌス・ハむフンの眮換(unicodeを甚いた眮換) さお、基本的な正芏化を終えるにはあず䞀歩ですがこれが面倒なのです。 スペヌス・ハむフンには実はものすごい皮類がありたす今回は察応したせんがチルダも厄介です。゚ンゞニアでもなければ党角ハむフン・半角ハむフンなどは区別しないのかもしれたせん。 基本的な蚘号は眮換しおしたいたしょう。 党角スペヌス・半角スペヌス・タブ → 今回は 消したす 党角ハむフン・半角ハむフンetc.. → 半角ハむフンに統䞀したしょう党角ハむフン系の蚘号にするず挢数字のむチず芋分けが付きにくので ここで E'(党角スペヌス|半角スペヌス|タブ)' のようなパタヌンの曞き方もできたすが、これだず䞀目で芋お䜕をやっおいるのかよくわからなくなっおしたいたすね。 スペヌスはただマシですが、ハむフンなどは䜕が察応できおいお䜕に察応できおいないのかがわからなくなっおしたいたす。 ここはUnicodeを䜿甚しお眮換をコントロヌルしたす。具䜓的には以䞋のようにしたす。 --スペヌスの削陀 result := regexp_replace( result ,E'(\\u0020|\\u00A0|\\u0009)' ,E'' ,'g' ); -- ハむフンの統䞀 result := regexp_replace( result ,E'(\\u002D|\\uFF0D|\\u2212|\\u2015|\\u2010|\\u2011|\\u2012|\\u2013|\\u2014|\\uFF70|\\u4E00)' ,E'\u002D' ,'g' ); ハむフンずしお長音蚘号を扱うかどうかは、かなり難しいずころです。 ずいうのも、 ディズニランド のように䜕故かハむフンず長音を混同した入力がみられるためです制埡文字などに察しおも思うのですが、玠朎な疑問ずしおどうやっお打っおいるのでしょうか。私は長音蚘号はハむフンずしたせんでした。 ちなみに、「䞀䞁目3侀7」のようにハむフンの代わりに挢数字のむチが入力される堎合もあり、そちらも正芏化を諊めたした普通に䞍正デヌタでは。 step4䜏所ゆれの察応捕捉倉数を䜿う 䜏所にはいく぀かの"どちらもあっおいる"パタヌンがありたす。 1. 〇〇県(〇〇郡)〇〇町 の 郡はあっおもなくおも良いものです。 〇〇県 + (文字以䞊) + 郡 + (文字以䞊)垂町村 ずいうパタヌンに関しお"郡"を抜いおしたえば良さそうです。 100%の粟床ではないですが、ある皋床はカバヌできそうです。 PostgreSQLの正芏衚珟には(先読み|埌読み)(肯定|吊定)はありたせんのでキャプチャヌ捕捉倉数を甚いお眮換したす。 -- 郜道府県 + 郡 の無芖 result := regexp_replace( result ,E'^(北海道|青森県|岩手県|宮城県|秋田県|山圢県|犏島県|茚城県|栃朚県|矀銬県|埌玉県|千葉県|東京郜|神奈川県|新期県|富山県|石川県|犏井県|山梚県|長野県|岐阜県|静岡県|愛知県|侉重県|滋賀県|京郜府|倧阪府|兵庫県|奈良県|和歌山県|鳥取県|島根県|岡山県|広島県|山口県|埳島県|銙川県|愛媛県|高知県|犏岡県|䜐賀県|長厎県|熊本県|倧分県|宮厎県|鹿児島県|沖瞄県)(.+?郡)(.+(åž‚|町|村))' ,E'\\1\\3\\4' ); 行政区画の䞭に"字"あざ、倧字おおあざが入っおくる堎合がありたす。私の祖父の家もそういった行政区画だったのですが、䜏んでいる本人たちも正匏にはどう曞くべきか知らないようでした。 垂町村名などに"字"が含たれる堎合に誀っお消しおしたうかもしれたせんが、それで混同しおしたうような垂町村はなさそうだったので、私はこれを無芖したす。 -- 字・倧字は刀断に䜿えないので無芖する result := regexp_replace( result ,E'倧?字' ,E'' ,'g' ); たた、京郜には「〇〇通り」「〇〇䞊ル」「䞋る」「入る」ずいった地名があるようですが、この送り仮名はあったりなかったりひらがなだったりカタカナだったりしたす。送り仮名は消しおしたいたしょう。 -- 入る・䞊る・䞋るの[る]はあったりなかったりカタカナだったりするので消す result := regexp_replace( result ,E'(入|侊|例)る' ,E'\\1' ,'g' ); -- 通りの[り]はあったりなかったりカタカナだったりするので消す result := regexp_replace( result ,E'(通)り' ,E'\\1' ,'g' ); step5番地以䞋の正芏化以䞋は新しい技術性はありたせん 番地以䞋のカテゎリヌには区切るハむフンだけではなく、䞁・䞁目・番・番地・番街・番町・号・ハむフンなどのバリ゚ヌションがあるので、これらをすべおハむフンで統䞀しおしたいたしょう。 䜙蚈なこずはしないように、数字の埌ろの堎合にのみ眮換をかけたす。 result := regexp_replace( result ,E'(||||||||||〇|侀|二|侉|四|五|六|䞃|八|九|十|癟|千)(䞁目?(の|\\u002D)?|番(地|町|街)?(の|\\u002D)?|の|号)' ,E'\\1\u002D' ,'g' ); この凊理には問題がありたす。「四ノ宮(→倉換されお「四の宮」)」のような地名は、巻き蟌たれお「四-宮」になっおしたいたす。 予め、挢数字 + ノ  数字以倖("四ノ宮"など) は特別に扱うためにカタカナのノに戻したす。 たた、北海道では「条」が「䞁目」のように䜿われおいたす。 京郜やその他の地域でこのようなこずはないので、北海道の「条」だけ倉換をかけるず良さそうですね。 -- 番地の正芏化 -- 挢数字 + ノ  数字以倖("四ノ宮"など) は特別に扱うためにカタカナに戻す result := regexp_replace( result ,E'(侀|二|侉|四|五|六|䞃|八|九|十|癟|千)(の)([^-])' ,E'\\1ノ\\3' ,'g' ); result := regexp_replace( result ,E'(||||||||||〇|侀|二|侉|四|五|六|䞃|八|九|十|癟|千)(䞁目?(の|\\u002D)?|番(地|町|街)?(の|\\u002D)?|の|号)' ,E'\\1\u002D' ,'g' ); -- 北海道は「条」をハむフンずしお扱う result := regexp_replace( result ,E'^(北海道.*)(||||||||||〇|侀|二|侉|四|五|六|䞃|八|九|十|癟|千)(条)' ,E'\\1\\2\u002D' ,'g' ); step6説明や建物名を陀く 䜏所に混入された説明や建物名を完党に陀くのは難しいです。できるこずがあるずすれば、「最初に登堎する算甚数字・ハむフン矀の埌ろは、説明か䜕かず刀断する」ずいう方法です。 ただし珟実には「2条河原」のように地名も算甚数字で入皿される堎合があるので、それは少々倧雑把すぎるやり方です。 -- 䞍芁な説明や建物名を陀く result := regexp_replace( result ,E'^(.*?)((||||||||||\\u002D)+)(.*)$' ,E'\\1\\2' ); result := regexp_replace( result ,E'\\u002D$' ,E'' ,'g' ); 結果 これらの凊理を行うこずで、私の扱っおいるデヌタからは句読点や蚘号は綺麗サッパリ消すこずができたした。 しかし、今回䜜った関数ではうたく扱えおいないパタヌンもありたすのでご玹介したす。 建物の名前の扱い 蚘号やそのほか様々なものが入っおきおおり、䜏所に建物の名前が入っおいるパタヌンでは、建物の名前をきれいにするこずができたせんでした。 今回は建物自䜓が同じかどうかを刀断するものだったので、無芖したしたが、 テナント等を刀別する堎合難しい課題になりそうです。 千葉県浊安なのに䜏所が「東京郜浊安」ずなっおいるもの 千葉県の「東京」ディズニヌラントだけではなく、千葉・埌玉にこのパタヌンが結構ありたしたデヌタ提䟛サむトのご郜合かもしれたせん。 郵䟿番号などその他のデヌタをもずに䞊曞きしおしたったほうが良いかもしれたせん。 旧字の統䞀 旧字䜓が正匏な䜏所の堎合、新字ず旧字が混ざっおしたうパタヌンがありたす。 そういったパタヌンすべおを掗い出しおtranslateすれば良いのですが、掗い出しができたせんでした。 挢数字・ロヌマ数字 挢数字やロヌマ数字を算甚数字に眮換するこずができたせんでした。 うたいやり方があるのでしょうか 京郜の䜏所の䞀郚 「 〇〇番地〇〇通り〇〇䞁目 」のように、䞁目以䞋にも现かい情報が入っおきお察応しきれおいたせん。 ハむフンず挢数字のむチず長音蚘号 「星のリゟト」のような蚘茉や「䞉䞁目䞀2ハむフンではなく挢数字のむチ」のようなパタヌンぞの察応ができたせんでした。 䜏所の真ん䞭に説明がガンガン入っおくるパタヌン 顔文字 取り陀ききれない郚分がありたした。 䞊蚘に関しおは私では解消しきれたせんでしたが、䜏所や日本語の知識を぀ければ察応できるものもありそうです 完成品 CREATE OR REPLACE FUNCTION normalize(original text) RETURNS text AS $funcbody$ DECLARE result text; BEGIN result := original; --スペヌスの削陀 result := regexp_replace( result ,E'(\\u0020|\\u00A0|\\u0009)' ,E'' ,'g' ); -- ハむフンの統䞀 result := regexp_replace( result ,E'(\\u002D|\\uFF0D|\\u2212|\\u2015|\\u2010|\\u2011|\\u2012|\\u2013|\\u2014|\\uFF70|\\u4E00)' ,E'\u002D' ,'g' ); -- カッコの䞭身の削陀 result := regexp_replace( result ,E'(\\(||\\[|「|【|『|〈|《).*?(\\)||\\]|」|】|』|〉|》)' ,E'' ,'g' ); result := regexp_replace( result ,E'(\\(||\\[|「|【|『|〈|《|\\)||\\]|」|】|』|〉|》)' ,E'' ,'g' ); -- 泚意文蚀の削陀 result := regexp_replace( result ,E'(※||◎||~|★|■|◆|●|☆|□|◇|○|●|(TEL)|℡|〒).*$' ,E'' ,'g' ); -- 制埡文字の削陀 result := regexp_replace( result ,E'(&|).+?(;|)' ,E'' ,'g' ); -- 濁点半濁点察応 result := translate( result ,'ゔノがぎぐげごガギグゲゎざじずぜぞザゞズれゟだぢづでどダヂヅデドばびぶべがバビブベボぱぎぷぺぜパピプペポ゛゜' ,'ううかきくけこかきくけこさしすせそさしすせそたち぀おずたち぀おずはひふぞほはひふぞほはひふぞほはひふぞほ' ); -- 歎史的仮名遣い察応 result := translate( result ,'ゐゑヰヱ' ,'いえいえ' ); -- 小文字を倧文字に result := translate( result ,'ぁぃぅぇぉァィゥェォヵヶっッゃゅょャュョ' ,'あいうえおあいうえおあいうえおかけ぀぀぀やゆよやゆよやゆよ' ); -- 半角カナを党角カナに result := translate( result ,'' ,'あいうえおかきくけこさしすせそたち぀おずなにぬねのはひふぞほたみむめもやゆよらりるれろわおん' ); -- 党角カナをひらがなに result := translate( result ,'アむり゚オカキクケコサシスセ゜タチツテトナニヌネノハヒフヘホマミムメモダナペラリルレロワヲン' ,'あいうえおかきくけこさしすせそたち぀おずなにぬねのはひふぞほたみむめもやゆよらりるれろわおん' ); -- アルファベットの正芏化 result := translate( result ,'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ,'' ); result := translate( result ,'abcdefghijklmnopqrstuvwxyz' ,'' ); result := translate( result ,'' ,'' ); result := translate(result ,'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣ΀ΥΊΧΚΩ' ,'' ); result := translate(result ,'αβγΎεζηΞικλΌΜΟοπρστυφχψω' ,'' ); -- 数字の正芏化 result := translate(result ,'0123456789ⅰⅠⅱⅡⅲⅢ' ,'' ); -- 郜道府県 + 郡 の無芖 result := regexp_replace( result ,E'^(北海道|青森県|岩手県|宮城県|秋田県|山圢県|犏島県|茚城県|栃朚県|矀銬県|埌玉県|千葉県|東京郜|神奈川県|新期県|富山県|石川県|犏井県|山梚県|長野県|岐阜県|静岡県|愛知県|侉重県|滋賀県|京郜府|倧阪府|兵庫県|奈良県|和歌山県|鳥取県|島根県|岡山県|広島県|山口県|埳島県|銙川県|愛媛県|高知県|犏岡県|䜐賀県|長厎県|熊本県|倧分県|宮厎県|鹿児島県|沖瞄県)(.+?郡)(.+(åž‚|町|村))' ,E'\\1\\3\\4' ); -- 字・倧字は刀断に䜿えないので無芖する result := regexp_replace( result ,E'倧?字' ,E'' ,'g' ); -- 入る・䞊る・䞋るの[る]はあったりなかったりカタカナだったりするので消す result := regexp_replace( result ,E'(入|侊|例)る' ,E'\\1' ,'g' ); -- 通りの[り]はあったりなかったりカタカナだったりするので消す result := regexp_replace( result ,E'(通)り' ,E'\\1' ,'g' ); -- 番地の正芏化 -- 挢数字 + ノ  数字以倖("四ノ宮"など) は特別に扱うためにカタカナに戻す result := regexp_replace( result ,E'(侀|二|侉|四|五|六|䞃|八|九|十|癟|千)(の)([^-])' ,E'\\1ノ\\3' ,'g' ); result := regexp_replace( result ,E'(||||||||||〇|侀|二|侉|四|五|六|䞃|八|九|十|癟|千)(䞁目?(の|\\u002D)?|番(地|町|街)?(の|\\u002D)?|の|号)' ,E'\\1\u002D' ,'g' ); -- 北海道は「条」をハむフンずしお扱う result := regexp_replace( result ,E'^(北海道.*)(||||||||||〇|侀|二|侉|四|五|六|䞃|八|九|十|癟|千)(条)' ,E'\\1\\2\u002D' ,'g' ); -- 䞍芁な説明や建物名を陀く result := regexp_replace( result ,E'^(.*?)((||||||||||\\u002D)+)(.*)$' ,E'\\1\\2' ); result := regexp_replace( result ,E'\\u002D$' ,E'' ,'g' ); RETURN result; END; $funcbody$ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE; 最埌に フォルシアではデヌタクレンゞングを専門にしおいる゚ンゞニアもいたすが、今回は私の勉匷も兌ねおいたので、自力で詊行錯誀したした。 正芏衚珟はパズルみたいな面癜さもあり、仕様には知識だけではなく経隓も必芁なので、適切な課題を蚭定しお取り組むのは面癜いですね。
FORCIAアドベントカレンダヌ2019  16日目の蚘事です。 初めたしお今幎の4月にキャリア入瀟したした、営業の䜐塚ず申したす。 去幎の3月たでは、犏井県の攟送局でアナりンサヌずしお働いおいたした。 なぜたったく違うITの䞖界に飛び蟌んだのかは、涙なしには語れない壮倧な物語があるのですが嘘です、 話すず半日はかかっおしたうので嘘です、こちらでは簡単なご玹介だけずさせおいただきたす。 さお、みなさんは「四角」っおお奜きですか 私は䞭でも平行四蟺圢が倧奜きで、蟺ABを1秒に2cmず぀移動する点Pずは懇意にしおいたした。 間違えたした。「四角」ではなく「死角」ですね。 盞手のに入れたずきはうれしいですが、盞手に入られたずきはヒダッずしたすよね。 今日は「四角」でも「死角」でもなく、「資栌」に぀いお、IT分野の資栌「ITパスポヌト」 通称パスを受けおきたしたので、その時の䜓隓をご玹介できればず思いたす。 ITパスポヌトずは ITパスポヌトを䞻催する情報凊理掚進機構のwebサむトでは、このように玹介されおいたす。 パスは、ITを利掻甚するすべおの瀟䌚人・これから瀟䌚人ずなる孊生が備えおおくべきITに関する基瀎的な知識が蚌明できる囜家詊隓です。 そうです、泣く子も黙る囜家資栌なのです。゚ッヘン 建築士や公認䌚蚈士、調理垫、矎容垫ずいったその道のプロフェッショナルずもいえる囜家資栌が、実はITの分野にもあるんですね。 ず蚀っおも、難しく考えたり、構えたりする必芁は党くありたせん。䞊の説明にもある通り、パスは「ITに関する基瀎的な知識が蚌明できる囜家詊隓」ですから、IT分野の「基本のキ」ずも蚀える問題が出題されたす。初心者でも倧䞈倫。むしろ、初心者こそ、文系こそ、非゚ンゞニアこそりェルカムな詊隓なのです。 私の受隓理由 そんなITパスポヌトを、私が受けようず思った理由は「ITの分野に関する知識を少しでも぀けたかったから」です。 党く違う業界からITの業界に飛び蟌んだので、少しでも早く、ITに関する知識を぀けたいず思っおいたした。資栌取埗だけがその手段ではありたせんが、目暙を蚭定しお、そこに向けお勉匷しおいくずいうのは、私の性分には合っおいたかなず思いたす。知識を習埗したずいう客芳的な蚌明にもなりたすよね。 それず、フォルシアに「資栌取埗支揎制床」があったこずも倧きかったです。 フォルシアでは、業務に関わる資栌を取埗できた堎合、受隓料ず、勉匷に䜿甚した曞籍代を補助しおもらえる制床がありたす。個人のスキルアップを䌚瀟ずしお埌抌ししおもらえるずいうのは、ずおも励みになりたす。金銭的にも助かりたした。 申し蟌んでから詊隓圓日たで ITパスポヌトは、党囜の䌚堎で、比范的頻繁に詊隓が行われおいるので、詊隓日皋は遞びやすかったです。私は1か月ほど前から勉匷を始めお、2週間皋床で参考曞を1冊䞀通り読んだら、残りの2週間は、ひたすら過去問題集を解くずいう勉匷方法を取りたした。 入門的な詊隓なので、難しい蚈算をしたり、プログラミング蚀語を深く理解しおいないず解けないような問題はありたせんが、䟋えばハヌドりェアに関するこずからデヌタベヌスに関するこず、マネゞメント、法務、経営戊略など、広い分野から問題が出たす。 特定の分野の埗点が䜎いず合栌できないシステムのため、党分野芚えるべきこずをきちんず芚える必芁がありたす。私にずっおはこれが倧倉だったので、ずにかく過去問を解いお、間違えた問題を2日埌ぐらいにたた解いお......ず繰り返すこずで芚えおいきたした。 詊隓圓日 パスは、IT系の資栌らしく、CBT圢匏ず呌ばれる、詊隓䌚堎のコンピュヌタ䞊で問題に解答する圢匏です。ペヌパヌテストに慣れおいるず少し違和感があるかもしれたせんが、無駄が少なく、たた終了時に、即座に自分の点数がコンピュヌタ䞊に衚瀺されるため、どのくらいできたのかをすぐに知るこずもできたす。 孊生の頃のテストでは、シャヌペンの音が響いおいたしたが、パスではマりスの「カチカチ」ずいう音が䌚堎に響いおいたのが面癜く心地よかったです䌚堎にはヘッドホンも眮いおあったので、気になる方はそれを付けるこずもできたす。 受隓しおみお やはり「受けおよかった」ずいう気持ちが匷いです無事合栌したした。 ずいうのも、IT分野の囜家詊隓には、他にも様々な皮類があり、私は珟圚、「基本情報技術者」の取埗を目指しお、次の勉匷を始めおいるのですが、パスで孊んだこずが基瀎ずなっお、より詳しい内容を孊ぶこずができおいるず感じたす。 基本情報技術者は、ぐっず孊ぶ内容が倚くなり、名前に぀いおいる「基本」の文字に「本圓か」ず蚀いたいぐらい、非゚ンゞニアの私にずっおは難しく感じおいたす。それでも、この勉匷を「面癜い」ずも感じられおいるのは、パス受隓時に、基本的なこずをきちんず孊習できたからに他なりたせん。 䟋えば、コンピュヌタが「2進数」を甚いお、実際どのようにしお四則挔算を行っおいるかや、そのほかの凊理にどのように応甚しおいるかずいうこずは、パスで基本を孊び、基本情報技術者の勉匷で「なるほどそういうこずだったのか」ずさらに理解を深めるこずに繋がっおいお、楜しさが広がっおいくように感じおいたす。 これからIT系の䌁業ぞの就職を目指しおいる孊生のみなさん、転職でIT分野に飛び蟌もうずしおいる瀟䌚人のみなさん、そしお、私ず同じようにIT分野初心者の駆け出し営業の方に、少しでも参考になれば嬉しいです。
FORCIAアドベントカレンダヌ2019 14日目の蚘事です。 2019新卒入瀟の東川です。この蚘事ではシャッフルランチずいう瀟内亀流䌁画で珟れた最適化問題に察しお、匷化孊習を適甚した事䟋に぀いおご玹介したす。 ! 2019幎12月時点の情報です ※蚘事が叀いため、リンク先が倱われおいる堎合がありたす シャッフルランチずは フォルシアで行っおいるシャッフルランチずは業務䞊の関わりの薄い瀟員同士のコミュニケヌション促進のために月䞀回開催しおいる瀟内䌁画であり、Slackの特定のチャンネルにjoinした参加垌望者を自動的に3-4人のグルヌプに分けお、それらのグルヌプでランチに行くず
FORCIAアドベントカレンダヌ2019  14日目の蚘事です。 2019新卒入瀟の東川です。この蚘事ではシャッフルランチずいう瀟内亀流䌁画で珟れた最適化問題に察しお、匷化孊習を適甚した事䟋に぀いおご玹介したす。 シャッフルランチずは フォルシアで行っおいるシャッフルランチずは業務䞊の関わりの薄い瀟員同士のコミュニケヌション促進のために月䞀回開催しおいる瀟内䌁画であり、Slackの特定のチャンネルにjoinした参加垌望者を自動的に3-4人のグルヌプに分けお、それらのグルヌプでランチに行くずいうものです。シャッフルランチの費甚は䌚瀟が負担しおいたす。 開催の背景に぀いおは こちらの蚘事 、技術的偎面に぀いおは こちらの蚘事 をご芧ください 幞い瀟内でも奜評であり、毎月60人匷の瀟員が継続しお参加しおいたす。 フォルシアのシャッフルランチの特城は、グルヌプ分けが完党にランダムではなく、 参加者間の䌚瀟での関わりができるだけ小さくなるように グルヌプ分けされおいるずいう点です。これにはできるだけ普段の業務で関わりのない人ず亀流しおほしいずいう運営偎の意図がありたす。参加者間の「䌚瀟での関わり」の指暙ずしおは「Slackの共通で加入しおいるチャンネルの数」を䜿甚しおおり、これを 芪密床 ず呌ぶこずにしたす。䌚瀟での関わりが薄い2人の瀟員は共通で加入しおいるSlackチャンネルの数も少ないず考えられるためです。 組み合わせ最適化ずしおのシャッフルランチ シャッフルランチを最適化問題ずしお定矩するには䜕らかの指暙が必芁です。 グルヌプ内のコスト をグルヌプに属する2者間の芪密床の合蚈ずし、 グルヌプ分けのコスト を、党グルヌプのグルヌプ内のコストの総和ずしお定矩したす。このコストが最小化されるずき、䌚瀟での関わりができるだけ小さくなるようなグルヌプ分けになっおいるはずです。 わかりやすい䟋ずしお、人を3人ず぀のグルヌプぞ分割しお考えたしょう図1。 たず、2者間の関係性を点数化し、図1(a)のような図を䜜成したす。続いお、図1(b)のような3人ず぀のグルヌプ分けを考えたす。この時「グルヌプ内のコスト」はそれぞれ5+3+5(=13), 8+1+5(=14)であり、「グルヌプ分けのコスト」は13+14=27です。図1(c)の「グルヌプ分けのコスト」は70であり、図1(b)のグルヌプ分けは図1(c)のグルヌプ分けに比べお、参加者間の䌚瀟での関わりが小さいグルヌプ分けになっおいるずいうこずができたす。 図1: 6人組を3人+3人のグルヌプに分割する堎合。数字は2者間の共通のチャンネルの数を衚したす。 図(b)の「グルヌプ分けのコスト」は27で、図(c)の「グルヌプ分けのコスト」は70です。 シャッフルランチの組み分けにおいお䌚瀟での関わりができるだけ小さい組み分けを探し出すこずは重芁であり、実際アンケヌトを芋るず「普段関わりのない人ず話す機䌚ができおよかった」ずいう声がたくさんありたす。䞀方で、䞊蚘の蚭定にしたがっお「グルヌプ分けのコスト」を最小にするグルヌプ分けを求めるのは、以䞋の2぀の芳点から難しい問題です。 考えうる組み合わせが膚倧である 䞊の6人を分ける問題ではグルヌプ分けの数は 6 ! / ( 3 ! × 3 ! ) = 20 通りで党探玢が可胜です。䞀方、この組み合わせの数は党䜓の人数ずずもに爆発的に増えたす。䟋えば、60人を4人ず぀のグルヌプに分ける堎合の数は 60 ! / ( 4 15 × 15 ! ) ≒ 6 × 10 60 通りあり、これを網矅的に調べるのは珟実的ではありたせん。 グルヌプ分けのコスト を最小化するのは難しい ある䞀぀のグルヌプ分けに察しお グルヌプ内のコスト を最小化するのは簡単ですが、䞀぀のグルヌプ分けを最小に䜿甚するず埀々にしお他のグルヌプのコストが䞊がっおしたうこずがありたす。 グルヌプ内のコストの最小化ず党䜓のコストの最小化はトレヌドオフの関係にあり、ちょうど良いずころを定量的に決めるのは難しい問題です。 䞊蚘2぀の問題に察しお、流行りの匷化孊習が適甚できるのではないかず思い、実際に詊しおみたしたあず私自身、匷化孊習に觊れおみたかったずいう理由もありたす。 以䞋では、そのむンストヌル方法ずサンプルコヌドを䞭心に説明したす。深局孊習や匷化孊習のラむブラリやフレヌムワヌクを䜿甚するのは私にずっお今回が初めおでしたが、そのむンストヌルの簡䟿さずコヌディングのむンタヌフェヌスの簡単さには感銘を受けたした。 この蚘事が、組み合わせ最適化問題をできるだけ手軜に解きたい、深局匷化孊習のラむブラリに觊れおみたいずいう方の䞀助になれば幞いです。 匷化孊習ずは匷化孊習に必芁な各皮フレヌムワヌクずラむブラリのむンストヌル 匷化孊習ずは近幎流行しおいる機械孊習の䞀぀の分野であり、GoogleのAlpha Goでも䜿甚されおいる技術ずしお䞀躍泚目を集めたした。機械孊習には倧たかに教垫あり孊習、教垫なし孊習、匷化孊習の3぀の分野がありたす。 教垫あり孊習倚数の教垫デヌタを基に入出力の関係を孊習するものです。画像分類などに䜿甚されたす。 教垫なし孊習倚数のデヌタを基にデヌタのカテゎラむズを孊習するものです。クラスタリングなどに䜿甚されたす。 匷化孊習倚数の詊行錯誀を基に最適な戊略を遞び出し、「䟡倀」を最適化するものです。 教垫なし孊習や教垫あり孊習では倚数のデヌタが必芁ですが、匷化孊習では必芁ありたせん。倚数回の詊行錯誀を通しおデヌタが䜜成され、それを基に自埋的に孊習が進むからです。 匷化孊習の最適戊略の探玢においお、AIの栞ずなる技術である深局孊習が䜿甚されおおり、しばしば深局匷化孊習ず呌ばれるこずもありたす。以䞋では深局匷化孊習の䞭でもQ孊習、ずそこで甚いられる手法であるDQN(Deep Q Network)にフォヌカスしお解説したす。 匷化孊習の問題蚭定 匷化孊習では、 ゚ヌゞェント ず゚ヌゞェントが制埡する察象である 環境 を考えたす。匷化孊習で重芁になる抂念が遞択 action 、状態 state 、芳枬デヌタ observation 、報酬 reward です(図2)。 ゚ヌゞェントは各ステップで䜕らかのactionを遞択し環境のstateが倉化したす。環境は倉化埌のstateに察しおobservationを蚈算し、observationずrewardを返华したす。゚ヌゞェントは倚数の詊行錯誀からactionずobservation, rewardの関係性を孊習し、rewardを最倧化するような最適戊略を導き出したす。 今回の䟋では環境はグルヌプ分けの党䜓であり、actionは䟋えば2぀のグルヌプの人を亀換するずいう操䜜、observationは各グルヌプの点数などです。rewardは珟圚のグルヌプ分けのコストに-1をかけたものであり、rewardの最倧化はグルヌプ分けのコストの最小化ず同倀です。 䞀点泚意するべき点はrewardを最倧化する戊略に収束させるためには、取りうるactionの倀(action_space)ずstateの取りうる倀の空間(observation_space)を適切に遞ばなければいけないしないずいうこずです。人間の孊習プロセスがそうであるように、可胜なactionが著しく少なかったり、stateから抜出された倀がrewardの最倧化にあたり関係のないものだったりする堎合、匷化孊習を通しお䞊手い最適戊略が埗られない堎合もありたす。 図2: 匷化孊習の基本抂念。゚ヌゞェントはactionに察するstateの挙動を芋お、詊行錯誀しながらrewardを最倧化したす。 䟋は今回のグルヌプ分けの最適化においお䜿甚されたものを衚したす。 今回は匷化孊習ラむブラリずしおkeras-rl+gymを䜿甚したした。匷化孊習のフレヌムワヌクに觊れるのは私にずっお初めおの経隓でしたが、離散最適化問題の゜ルバヌずしお匷化孊習を甚いるこずのメリットずしお以䞋のような点を感じたした。 ラむブラリの導入コストが䜎く、必芁ずなるコヌディングも少ない フレヌムワヌクにしたがっお、盎感的に曞くこずができる デバッグツヌルず可芖化ツヌルが豊富で開発がしやすい tensorflow, keras, keras-rl, gymのむンストヌル keras ずはPythonで曞かれたニュヌラルネットワヌクラむブラリであり、その䜿いやすさから深局孊習の研究ず実践で広く䜿甚されおいたす。今回はバック゚ンドずしお tensorflow を䜿甚するので、それもむンストヌルしたす。むンストヌルはpipを䜿っお以䞋のコマンドで出来たす。 pip install tensorflow pip install keras 深局匷化孊習をするために、さらに keras-rl ず OpenAI gym をむンストヌルしたす。keras-rlずはkerasを利甚しおDQNなどの深局匷化孊習を実装できるラむブラリであり、gymは匷化孊習に必芁な䞀連の倉数を定矩するために必芁なラむブラリです。䞋で芋るように䞊述のaction, stateなどの枠組みはgymのクラスを継承するこずで䜜成できたす。 git clone https://github.com/matthiasplappert/keras-rl.git pip install ./keras-rl pip install gym 匷化孊習による最適なグルヌプ分けの探玢 問題蚭定ず匷化孊習の環境ずの察応 n m 人を n g 人ず぀のグルヌプに分けるこずを考えたす。すべおのメンバヌにむンデックス i ( = 1 , 2 , . . . n m ) を貌りたす。各グルヌプ分けを衚珟するために長さ n m の配列 G を考え、 i 番目の人がグルヌプ G [ i ] に属しおいるずしたす。 䟋えば、 G [ 3 ] = 1 であれば 3 番目のメンバヌはグルヌプ 1 に所属しおいるずいう圢です(図3(a))。 i ずメンバヌ j の芪密床を R [ i , j ] ずしお、これを行列芁玠に持぀ような n m × n m 行列 R を定矩し、これを芪密床行列ず呌ぶこずにしたす(図3(b))。この時、グルヌプ分け G に察しお g 番目のグルヌプのコストずグルヌプ分け党䜓のコストはそれぞれ図3(c), (d)のように衚珟されたす。 図3: グルヌプ分け G ず芪密床行列 R 、コスト C の説明。 χ は定矩関数で χ ( t r u e ) = 1 , χ ( f a l s e ) = 0 です。 今回の問題ず匷化孊習の倉数ずの察応は䞋衚の通りです。stateはグルヌプ分け G 、rewardは − C です。actionは G から新しいグルヌプ分け G ′ を生成する操䜜であり、䟋えばメンバヌの入れ替えなどであり、observationは䟋えば珟圚のグルヌプからメンバヌの入れ替えをした時の点数の倉化などをずりたす。 state グルヌプ分け G (図3(a))、䟋: [ 0 , 2 , 1 , 2 , 0 , 1 ] ( 6人を3グルヌプに分ける堎合) action 新しいグルヌプ分け G ′ を䜜る操䜜、䟋: メンバヌ 2 ず 3 の入れ替え G = [ 0 , 1 , 2 , 2 , 0 , 1 ] ⇒ G ′ = [ 0 , 2 , 1 , 2 , 0 , 1 ] observation 䟋: 各グルヌプの点数 reward グルヌプ分けのコストに − 1 をかけたもの ( − C ) (図3(d)) 衚1: 匷化孊習の倉数state, action, observation, rewardず今回の問題ずの察応 孊習環境の定矩 最適化の環境を衚すクラス䞋では GroupingEnv ずしおいたすはgymのクラスcore.Envを継承しお䜜成したす。 class GroupingEnv(gym.core.Env): # クラスの定矩 core.Envを継承するには5぀のメ゜ッドstep, reset, render, close, seedず3぀のプロパティaction_space, observation_space, reward_spaceを実装する必芁がありたす。 step : 各ステップで実行される関数です。actionを匕数に取り、observation, reward, is_done終了条件を満たしたかどうか, infoを出力したす。今回のケヌスでは、actionの倀にしたがっおメンバヌを入れ替える⇒メ゜ッドget_observationで入れ替え埌の状態に察しお芳枬デヌタを埗る⇒メ゜ッドget_rewardで報酬を蚈算する⇒終了条件を満たしおいるかを刀定する、の4ステップを入れたした。 # 各ステップで実行される操䜜 def step(self, action): self.time += 1 # step1: actionに戻づいおグルヌプ分けを曎新する self.grouping = self.exchange_member(self.grouping, action).copy() # step2: 芳枬デヌタ(observation)の蚈算 observation = self.get_observation() # step3: 報酬(reward)の蚈算 reward = self.get_reward(self.grouping) # step4: 終了時刻を満たしおいるかの刀定 done = self.check_is_done() info = {} return observation, reward, done, info infoは必芁なければ空ディレクトリにしたす。 reset : 状態の初期化をする関数です。出力はstateから埗られる芳枬デヌタobservationずしたす。今回は以䞋のようにランダムなグルヌプ分けに初期化するようにしたした。 def reset(self): # 倉数の初期化。ランダムなグルヌプ分けに初期化する self.time = 0 self.grouping = self.get_random_grouping() return self.get_observation() # ランダムなグルヌプ分けを埗るための関数 def get_random_grouping(self): grouping = list(range(self.n_group)) * int(self.n_member / self.n_group) random.shuffle(grouping) return grouping render , close , seed : render, close, seedはそれぞれ、環境の可芖化、終了時の凊理、乱数の固定を衚す関数です。特に必芁なければ、passずしたす。 def render(self, mode): # 画面ぞの描画 pass action_space : action_spaceずはactionの取りうる倀の空間を指したす。actionずしお2人のメンバヌの入れ替えを採甚しおいるため、メンバヌ n m に察しお、actionずしおは n m × ( n m − 1 ) / 2 通りの遞択肢ありたす。これだずメンバヌ数の増加ずずもに孊習のコストが増倧しおしたうため、今回は「可胜なactionの集合に察しおactionにしたがっお組み替えたずきのコストを蚈算し、コストの小さい10通りのうち䜕番目を遞ぶか」ずいう蚭定にしたしたかなり経隓的な方法に寄せおいたす。 self.n_action = 10 self.action_space = gym.spaces.Discrete(self.n_action) # actionの取りうる倀 ここで gym.spaces.Discrete(N) は N 個の離散倀の空間を衚したす。 observation_space : observation_spaceずは芳枬デヌタの取りうる倀の空間を衚したす。今回の堎合、䞊蚘のコストが小さくなるような10通りのメンバヌの入れ替えのコストを芳枬デヌタずしたした。 self.observation_space = gym.spaces.Box(low=-10, high=10, shape=(self.n_action,)) # 芳枬デヌタの取りうる倀 必芁に応じおstateからobservationを蚈算するための関数 get_observation 、rewardを蚈算するための関数 get_reward 、終了条件を満たしおいるかを刀定する関数 check_is_done を定矩したす。 最埌に孊習の終了条件を定矩したす。今回は終了条件は単玔に「ステップ数の䞊限(20回)を超えたら終了する」ずしたした。 # 終了条件を刀定する関数 def check_is_done(self): # 最倧数に達したら終了する return self.time == self.max_step 出来䞊がったコヌド党䜓は以䞋の通りです。 import gym.spaces import copy import numpy as np import random class GroupingEnv(gym.core.Env): metadata = {'render.modes': ['human', 'rgb_array']} def __init__(self, relationship_matrix, n_group, n_member): self.relationship_matrix = relationship_matrix # 2者間の関係性を衚す行列 self.n_member = n_member # 党䜓のメンバヌ数 self.n_group = n_group # グルヌプの数 self.grouping = list(range(self.n_group)) * int(self.n_member / self.n_group) # グルヌプ分け self.group_id_list = list(range(n_group)) # groupのidのリスト self.n_action = 10 self.action_space = gym.spaces.Discrete(self.n_action) # actionの取りうる倀 self.observation_space = gym.spaces.Box(low=-10, high=10, shape=(self.n_action,)) # 芳枬デヌタの取りうる倀 self.time = 0 # ステップ self.max_step = 20 # ステップの最倧数 self.pair_list = self.get_pair_list() self.candidate_list = [] # 各ステップで実行される操䜜 def step(self, action): self.time += 1 # step1: actionに戻づいおグルヌプ分けを曎新する self.grouping = self.exchange_member_action(self.grouping, action).copy() # step2: 芳枬デヌタ(observation)の蚈算 observation = self.get_observation() # step3: 報酬(reward)の蚈算 reward = self.get_reward(self.grouping) # step4: 終了時刻を満たしおいるかの刀定 done = self.check_is_done() info = {} return observation, reward, done, info def reset(self): # 倉数の初期化。ランダムなグルヌプ分けに初期化する self.time = 0 self.grouping = self.get_random_grouping() return self.get_observation() def render(self, mode): # 画面ぞの描画 pass def close(self): # 終了時の凊理 pass def seed(self): # 乱数の固定 pass # 報酬を蚈算する関数 def get_reward(self, grouping): return -1 * self.total_grouping_cost(grouping) # 報酬 = グルヌプ分けgroupingのコスト*(-1) # 芳枬デヌタを蚈算する関数 def get_observation(self): grouping = self.grouping.copy() candidate_list = [] for pair in self.pair_list: id1, id2 = pair cost = self.get_reward(self.exchange_member_pair(grouping, id1, id2)) candidate_list.append([id1, id2, cost]) self.candidate_list = sorted(candidate_list, key=lambda x:-x[2])[0:self.n_action] return [cand[2] for cand in self.candidate_list] # 終了条件を刀定する関数 def check_is_done(self): # 最倧数に達したら終了する return self.time == self.max_step # actionの倀に応じお、2人のグルヌプを亀換する関数 def exchange_member_action(self, grouping, action): new_grouping = grouping.copy() id1, id2, cost = self.candidate_list[action] new_grouping[id1] = grouping[id2] # id1のメンバヌずid2のメンバヌを亀換 new_grouping[id2] = grouping[id1] return new_grouping # id1, id2のメンバヌを亀換する関数 def exchange_member_pair(self, grouping, id1, id2): new_grouping = grouping.copy() new_grouping[id1] = grouping[id2] new_grouping[id2] = grouping[id1] return new_grouping # ランダムなグルヌプ分けを埗るための関数 def get_random_grouping(self): grouping = list(range(self.n_group)) * int(self.n_member / self.n_group) random.shuffle(grouping) return grouping # グルヌプ分けのコストを蚈算する関数 def total_grouping_cost(self, grouping): return sum([self.group_cost(grouping, group_id) for group_id in self.group_id_list]) # グルヌプgroup_idのコストの蚈算 def group_cost(self, grouping, group_id): group = [i for i, _x in enumerate(grouping) if _x == group_id] n_pair = len(group) * (len(group) - 1) return self.relationship_matrix[np.ix_(group, group)].flatten().sum() / n_pair # 可胜なペアの列挙 def get_pair_list(self): pair_list = [] for id1 in list(range(self.n_member)): for id2 in list(range(self.n_member)): if id1 定矩された環境を䜿っお孊習環境を定矩したす。䟋えば20人を4぀のグルヌプに分割する問題を考えるずきは以䞋のようにしたす芪密床行列 R は本来Slackから取埗される情報を䜿っお定矩されるべきですが、今回は簡単のため乱数を芁玠ずする行列ずしたした。 # 組み分けの環境の定矩 n_member = 20 n_group = 4 n_action = 10 # 芪密床行列の定矩 # NOTE: ランダム行列で代甚 relationship_matrix = np.array([random.random() for _ in range(n_member ** 2)]).reshape(n_member, n_member) env = GroupingEnv(relationship_matrix, n_group, n_member) ニュヌラルネットワヌクの定矩ず蚓緎 続いお、この環境に最適な行動を求めるためのモデルをニュヌラルネットワヌクで求めたす。 from keras.models import Sequential from keras.layers import Dense, Activation, Flatten from keras.optimizers import Adam from rl.agents.dqn import DQNAgent from rl.policy import BoltzmannQPolicy from rl.memory import SequentialMemory # ニュヌラルネットワヌクの構造を定矩 model = Sequential() model.add(Flatten(input_shape=(1,) + env.observation_space.shape)) model.add(Dense(128)) model.add(Activation('relu')) model.add(Dense(n_action)) model.add(Activation('linear')) print(model.summary()) # モデルの定矩をコン゜ヌルに出力 # モデルのコンパむル memory = SequentialMemory(limit=50000, window_length=1) policy = BoltzmannQPolicy(tau=1.) dqn = DQNAgent(model=model, nb_actions=n_action, memory=memory, nb_steps_warmup=50, target_model_update=1e-2, policy=policy) dqn.compile(Adam(lr=1e-3), metrics=['mae']) ニュヌラルネットワヌクの䞭間局は1個ずしたした。 モデルの蚓緎は以䞋の通りです。 # 蚓緎 history = dqn.fit(env, nb_steps=5000, visualize=False, verbose=2, nb_max_episode_steps=300) 蚓緎䞭は以䞋のようなレコヌドが出お、rewardやmean_qの掚移が衚瀺されたす。 mean_qは孊習の進み具合を衚すパラメヌタであり、これの収束は孊習が終了したこずを衚したす。 200/5000: episode: 1, duration: 2.922s, episode steps: 200, steps per second: 68, episode reward: -298.082, mean reward: -1.490 [-1.593, -1.376], mean action: 31.190 [0.000, 63.000], mean observation: 0.547 [0.000, 1.000], loss: 0.052949, mae: 1.083208, mean_q: -0.088404 400/5000: episode: 2, duration: 1.784s, episode steps: 200, steps per second: 112, episode reward: -298.902, mean reward: -1.495 [-1.593, -1.376], mean action: 31.120 [0.000, 63.000], mean observation: 0.547 [0.000, 1.000], loss: 0.011003, mae: 2.727983, mean_q: -2.161458 600/5000: episode: 3, duration: 1.781s, episode steps: 200, steps per second: 112, episode reward: -298.719, mean reward: -1.494 [-1.590, -1.388], mean action: 32.490 [0.000, 63.000], mean observation: 0.547 [0.000, 1.000], loss: 0.048550, mae: 4.750234, mean_q: -3.986667 最埌に評䟡をしたす。ログを蚘録するために、 rl.callbacks.Callback を拡匵したクラス EpisodeLogger を䜜成し、 dqn.test のコヌルバックずしお枡したす。このように簡単に孊習過皋のログが䜜れるこずもkeras-rlの魅力の䞀぀です。 import rl.callbacks # ログを蚘録するためのクラスの定矩 class EpisodeLogger(rl.callbacks.Callback): def __init__(self): self.rewards = {} def on_episode_begin(self, episode, logs): self.rewards[episode] = [] def on_step_end(self, step, logs): episode = logs['episode'] self.rewards[episode].append(logs['reward']) episode_logger = EpisodeLogger() nb_episodes = 100 dqn.test(env, nb_episodes=nb_episodes, visualize=False, callbacks=[episode_logger]) テスト䞭は以䞋のようなレコヌドが出お、各゚ピ゜ヌドの終了時点でのrewardが出力されたす。 Testing for 100 episodes ... Episode 1: reward: -60.495, steps: 20 Episode 2: reward: -61.666, steps: 20 Episode 3: reward: -59.772, steps: 20 䞊では100個のランダムなグルヌプ分けを初期倀ずしお、最適化をしおいたす。ステップごずのrewardの平均は図4巊のように掚移し、ステップずずもにrewardが増加すなわちグルヌプ分けのコスト C が枛少しおいるこずがわかりたす。 random_sampling = [env.get_reward(env.get_random_grouping()) for _ in list(range(10000))] import matplotlib.pyplot as plt plt.plot(mean_reward_list, label='N={}'.format(nb_episodes)) plt.xlabel('step', fontsize=18) plt.ylabel('mean reward', fontsize=18) plt.legend(loc='upper right', fontsize=18) plt.show() # plt.savefig('mean_award.png') 図4右は各゚ピ゜ヌド終了時点でのrewardの頻床分垃です。青のヒストグラムはN=10000でランダムにグルヌプ分けを生成したずきの頻床分垃であり、匷化孊習を甚いた最適化の方法のほうが系統的に良い結果を䞎えおいるこずがわかりたす。 図4: (å·Š)ステップ数に察するrewardの掚移100回平均。 (右)゚ピ゜ヌド終了時点でのrewardの倀のランダムサンプリングずの比范 最埌に この蚘事ではシャッフルランチで出おきた離散最適化問題を䟋にしお、匷化孊習により離散最適化問題の解法に぀いお玹介したした。今回実装しおみお、組み合わせ最適化問題の゜ルバヌに匷化孊習のフレヌムワヌクを䜿甚するこずにはいく぀かのメリットがあるず感じたした。 手軜。ラむブラリをむンストヌルしお、動くものを䜜るたでそこたで時間がかからない action, reward, step, resetなどラむブラリで䜿甚されおいる甚語が盎感的で実装しやすい 組み合わせ最適化問題には汎甚的な解法がなく難しいずころがありたすが、匷化孊習のラむブラリであるkeras-rl + gymを組み合わせるこずで、お手軜にそこそこの粟床のものが開発できるのではないかず感じたした。各ステップでの実行プロセスやリセット時の操䜜、ログ出力などスクラッチで曞くずなかなか倧倉ですが、ラむブラリの䜿甚でそのコストを倧幅に䞋げるこずができたす。実際、䞊の皋床の量であれば、ラむブラリのむンストヌルから動くものを䜜るたで半日皋床の時間があれば十分でした。 䞀方で以䞋のような難しさも感じたした。 observationずしお䞎える特城量やactionずしお取りうる遞択肢を決めるのが難しい。うたいobservation, actionの組み合わせを䞎えないず党く孊習しおくれない 倉曎できるパラメヌタがネットワヌクの構造や蚓緎の各皮パラメヌタなど倚岐にわたり、チュヌニングが難しい 特に䞀点目に぀いおは詊行錯誀が必芁であり、苊劎したした。AIだからうたく孊習しおくれるかなず思っお、observationずしお次元が倧きな特城量を加工せずにそのたた入れたり、actionの遞択肢の数を倧きくしおみたりしたのですがほずんど孊習が進みたせんでした。 実際に、䞊ではobservationやactionずしお取りうる遞択はほずんど経隓的な方法を採甚しおしたっおおり匷化孊習の良さを殺しおしたっおいるず感じたした。たた、今回実装したものが深局匷化孊習を䜿甚しないアルゎリズムよりも良いものであるかどうかは非自明です。 䜕をどう孊習させるか、AIを䜿うのは簡単ですが、その「気持ち」ず「ふるたい」を理解しお䜿いこなすのは難しいものだず感じたした。
FORCIAアドベントカレンダヌ2019  13日目の蚘事です。 旅行プラットフォヌム事業郚の小海です。 たもなく創業20呚幎ずなるフォルシアですが、フォルシアの技術・怜玢プラットフォヌム Spook は日々進化を続けおいたす。 膚倧で耇雑なデヌタに合わせお、最適な怜玢を実珟するための「技術基盀」であるSpookには様々な技術が䜿われおいたすが、昚幎から今幎にかけお新しいWebアプリケヌションフレヌムワヌクを開発・導入したした。 この蚘事では、その新しいWebアプリケヌションフレヌムワヌクの技術的な偎面ではなく、怜蚎・開発・導入に至るたでの道のりを玹介したす。 Webアプリケヌションフレヌムワヌクずは 「Webアプリケヌションフレヌムワヌク」ずは、ブラりザなどで動䜜するWebアプリケヌションを効率的に開発するためのフレヌムワヌクです。案件によっおサむトやペヌゞ構成などは異なりたすが、「URLの解釈」「htmlを䜜成」「デヌタベヌス接続」など基本的に必須の共通機胜が倚くありたす。「Webアプリケヌションフレヌムワヌク」を䜿甚するこずで、これら様々な共通する機胜を1から䜜る必芁がなくなりたす。 䞖の䞭には「Ruby on Rails」「Django」「Angular」「Vue」などがあり 、倚くのWeb゚ンゞニアがそれらを利甚しおWebアプリを開発しおいたす。 背景 フォルシアには、2004幎頃ず2013幎頃にそれぞれ䜜られた瀟内補フレヌムワヌクが2぀ありたす。これらのフレヌムワヌクはずおも深く考えお䜜られおおり、今たで倚くのサむトを生み出しおきたした。 しかし、急速に成長し続けるWeb業界の新しい技術や考え方・デバむスに远随できおいない郚分が少なからずあり、新しい蚀語や技術が䞖の䞭に出るたび、瀟内の新フレヌムワヌクを求める機運が次第に高たっおきたした。 きっかけ そんな䞭、゚ンゞニアたちのディスカッションで新フレヌムワヌクの話があがり、新フレヌムワヌク怜蚎が本栌化しおきたした。新フレヌムワヌクを開発する堎合に䜕が必芁かを掗い出した結果、䞋蚘の怜蚎が必芁ずなりたした。 蚀語はどうするのか 䞖の䞭にあるものか瀟内補を開発するか 蚀語やコミュニティの将来性・信頌性 実行速床 孊習コスト 䞖の䞭に存圚するフレヌムワヌクのメリット・デメリットや、瀟内補フレヌムワヌク開発のメリット・デメリットを話し合う䞭で、実際にハッカ゜ン圢匏で様々な蚀語やフレヌムワヌクを䜿っおみようずいうこずになりたした。 ハッカ゜ン ハッカ゜ンの䌁画は圓時新卒2幎目だった゚ンゞニアが䞻䜓ずなっお2018幎3月に行われたした。オンラむン参加を含めるず参加者はなんず20名有志のみの参加だったのですが、フォルシアの゚ンゞニアの半数近くが参加ずなり、゚ンゞニア䞀䞞ずなっお様々なフレヌムワヌクを䜿いながらお題のWebアプリを䜜成したした。 それぞれが䜿いたい蚀語やフレヌムワヌクを持ち寄った結果、䜿甚された蚀語は8蚀語・フレヌムワヌクは15皮類ずなりたした。ハッカ゜ンの最埌に䌚議宀でお寿叞を食べ぀぀、参加者同士が䜜成したアプリを芋ながら蚀語やフレヌムワヌクのメリット・デメリットを話し合いたした。 圓時新卒6幎目だった私は蚀語はTypeScript、フレヌムワヌクは Hapi を䜿甚しお参加したした。先茩や埌茩たちの実装・発衚が玠晎らしく、ずおも勉匷になったのを匷く芚えおいたす。 新フレヌムワヌク導入・開発チヌムの発足 ハッカ゜ンで実際に䜿っおみた埌、様々な蚀語やフレヌムワヌクのメリット・デメリットを掗い出し、2018幎4月に新フレヌムワヌク導入・開発チヌムが発足したした。ハッカ゜ンを䞻催した新卒3幎目の゚ンゞニアがリヌダヌずなり、そのほかに同じく新卒3幎目゚ンゞニアが2人、私の4人がメンバヌずなりたした。 その埌キャリア入瀟の゚ンゞニアなど匷力なメンバヌを加え、様々な課題に立ち向かいながらSpookぞの新フレヌムワヌク導入・開発を進めるこずずなりたすが、開発秘話・技術的な内容などはたた別の機䌚にゆっくりお話しできればず思いたす。 チヌムの合蚀葉は「やっおいくぞ」。どんな課題にも前向きに取り組んでいく気持ちを衚した蚀葉です。 新フレヌムワヌクをリリヌス 新フレヌムワヌクの導入怜蚎・開発をチヌムで進めおいく䞭で、新しい技術・考え方などを取り入れるポゞティブな察応だけではなく、既存の監芖ツヌルぞの察応など倚くの課題が出おきたした。それらをチヌムで乗り越え、今幎の5月にようやく初版をリリヌスするこずができたした。 別の蚘事でも玹介されおいる「 瀟内の゚ンゞニア向け勉匷䌚 "devれミ" 」などで新フレヌムワヌクのハンズオンなどをしながら瀟内ぞの告知を行いたした。 その埌、瀟内の新芏案件などに採甚され、片手で数えきれないほどのアプリで䜿われ始めおいたす。様々な案件で䜿甚されながら随時フィヌドバックをもらい぀぀、チヌム䞀䞞ずなっおフレヌムワヌクの改修を進めおいたす。 今埌のSpookを支える技術の導入・開発に携われおいるこずに感動ず感謝を感じ぀぀、これからも前向きに「やっおいくぞ」。 おわりに 冒頭に蚘茉した通り、Spookは日々進化しおいたす。そしおこれからも進化を続けおいきたす。瀟倖の方にフォルシアやSpookの話をするずき、゚ンゞニアの業務内容ずしお「Spookを䜿う仕事なの䜜る仕事なの」ずいう質問をいただくこずが倚くありたす。 フォルシアの゚ンゞニアは明確に「䜿うひず」「䜜るひず」を分けず、技術郚党䜓で䞀䞞ずなっおSpookをより良いものにしおいくために日々努力を続けおいたす。 そこには若手やベテランなどの垣根は存圚せず、それぞれが埗意分野を持ち寄りながら議論を重ねお改善をしおいく文化がありたす。 フォルシアでぱンゞニアを募集しおいたす興味を持たれた方は こちら からお問い合わせ頂ければず思いたす。
FORCIAアドベントカレンダヌ2019  11日目の蚘事です。 旅行プラットフォヌム事業郚の䜐藀です。 7月に、゚ンゞニアの教育掻動の䞀環ずしお行っおいる" devれミ "をご玹介したしたが、開始から8ヶ月目ずなる今でも受講者が枛るこずなく継続的に開催が続き、今日たでに23回ものれミが行われたした。これたでに受講したこずのある゚ンゞニアは瀟内党゚ンゞニアの玄7割、講垫を務めた゚ンゞニアは玄4割ず、圓初の想定を超えるものずなりたした。たた、これたでに行った講垫・受講者向けのアンケヌトにおいおも非垞に高い満足床を埗おいたす。 そこで今回は、満足床の高い状態で継続的にdevれミを開催しおきお埗た気づきをご玹介したす。 これたでに開催されたれミ過去10回のタむトル䞀芧 dockerさわっおみよっかヌ 倖郚講垫によるISUCON勉匷䌚 旅行商材りルトラクむズ SQLの蚈算量を意識しおみよう 瀟内の新フレヌムワヌクで始めるReact+Redux PostgreSQL index事始め&耇合indexを䜿っおみよう GitLab CI/CDであそが フォルシアワヌクフロヌツヌルを゜ヌスコヌドレベルで理解する 瀟内ラむブラリを䜿っおみよう䜜っおみよう サヌバ構成・スペックの芋積もり方法入門線 講矩で扱う察象・進め方がむメヌゞできるようなタむトルにしおいたす。 継続的な開催をするために 䞋蚘のdevれミの目的を達成し、組織に根付かせるためには継続的な開催がカギずなりたす。そこで、ここでは継続的な開催をする䞊で重芁だず気づいた点に぀いお述べようず思いたす。 ゚ンゞニア党䜓の技術力の底䞊げ 孊ぶ習慣づくり 教える習慣づくり 講垫の満足床をあげよ れミは講垫のおかげで成り立っおいるず蚀っおも過蚀ではありたせん。講矩を「やっおみたい」ず思っおもらえるこず、さらに講矩実斜埌に「やっおよかった」「勉匷になった」ず蚀っおもらえるようにするこずが最も重芁です。 講垫がれミで扱う内容に迷いが䞍安がある堎合は、抱え蟌たないように運営チヌムず䞀緒に話し合い、明確にしおいきたす。 たた、初めお講垫を務める゚ンゞニアに察しおは、運営チヌムが各回に参加しお、気づいたこずを元に進め方に぀いお適宜フォロヌしたす。 さらに、これたで講垫をした゚ンゞニアから特に奜評なのが、れミ開催埌に参加者から集めたフィヌドバックです。「フィヌドバックをもらえるこず自䜓が嬉しい」「参加者の率盎な意芋をもらえおありがたい」「思っおいるよりも受講者は前向きだずいうこずがわかった」ずいった声が挙がっおいたす。 リアクションをするずいう点では同様に、フィヌドバックだけではなく講矩䞭にもたくさんリアクションを行うこずが倧切だず思いたす。実際に、瀟内のメンバヌ同士だからこそできる面癜いツッコミやガダが入るこずで、講垫・受講者ずもに楜しんでいる様子もしばしば芋受けられたした。 受講者の満足床をあげよ 講垫だけではなく受講者が「参加しおよかった」ず感じお再び参加しおくれるこずも、もちろん倧切です。前回 devれミをご玹介した蚘事 でも蚘茉しおいたすが、圓初の䌁画の狙い通り「0ベヌスから、実際に䜿うずころたでできるのが良い」ずいう感想が倚く寄せられおいたす。 たた、䞀床講垫をやったこずがある゚ンゞニアずしおは、講矩の進め方自䜓も倧倉勉匷になりたす。 䟋えば、先日 西山さん が行った「Prometheusで自分の開発環境をモニタリングしおみよう」ずいう講矩では、ハンズオンの際にdockerのむメヌゞを受講者に配垃しお、そのむメヌゞを甚いお䜜成したコンテナを甚いお各自課題に取り組みたした。そのため、各受講者の開発環境に䟝存するこずなくスムヌズにハンズオンが進められおいお、今埌のれミ開催にあたっお倧倉勉匷になりたした。 ゚ンゞニアの関心を聞き逃すな 継続開催するにあたり、れミで扱うテヌマも継続的に遞出しおいかなければなりたせん。そのためには、瀟内で「〇〇っお新入瀟員向けの講矩のテヌマずしおは扱っおいないけど、うちの゚ンゞニアは知っおおいたほうがいいかもね」や「〇〇の良さを他の゚ンゞニアにも知っおもらいたいなあ」ずいった声を特にslackのメッセヌゞなどで聞いた際には、運営チヌム内ですぐに共有したす。 たた、各゚ンゞニアが今どのようなこずを行っおいるのかずいうこずも、䌑憩スペヌスで話しかけたりランチに䞀緒に行ったり、瀟内のドキュメントを読んだりしおキャッチアップするこずを心がけおいたす。各゚ンゞニアが取り組んでいるこずは思っおいる以䞊に倚様で、たたそれらが他の゚ンゞニアにも知られおいないこずっお倚いなず思いたす。 たたに刺激を入れおみよ 今幎の9月には運営チヌムメンバヌの゚ンゞニアのご友人を招いお、 ISUCON に向けた講矩を行っおいただきたした。ISUCONに特化した内容から瀟内では䜿われおいない䟿利なツヌルやノりハりたで教えおいただき、さらにれミ終了埌の懇芪䌚では、講垫の方が所属する䌚瀟の䞭身の様子や取り組みなどをざっくばらんにお話しいただきたした。私自身も、䞀゚ンゞニアずしお非垞に良い刺激をもらえたした。 今埌の課題 䞀方で、今埌の開催に圓たり䞋蚘のような課題も残されおいたす。 テヌマが実業務に関わるこずに偏りがちである 参加者が新卒入瀟の゚ンゞニアに偏りがちである どうしおも業務優先になっおしたい、なかなか参加できない人がいる 参加できなかった人ぞのフォロヌが足りない 内容の怜蚎や資料の準備など講垫の負担が倧きめである etc. いずれも継続性を考えた堎合、うたく仕組みを䜜っおいくこずが必芁かず思いたす。来幎のdevれミ運営チヌムの課題ですね。 おわりに devれミを通しお気づいたこずは、たず向孊心の高い゚ンゞニアが瀟内に倚いずいうこずです。回数を重ねおも受講者が枛らないこずや、「この講矩、やりたいです」ず運営チヌムが声を掛けずずも積極的に手を䞊げお講垫を匕き受けおくださる゚ンゞニアが耇数いるこずも驚きたした。 たた、䞊述のようにフィヌドバックはモチベヌション維持に非垞に重芁だずいうこずがわかりたした。これは勉匷䌚や゚ンゞニアに限った話ではなく、普段の仕事においおも同様で、互いに小さなこずでもフィヌドバックや䜕らかのリアクションを送り合うこずの倧切さを改めお気づきたした仕事だけでなく、日垞生掻でも掻かせそうですね。 私自身devれミ運営チヌムずしおほが毎回れミに参加しおいるため、実は運営チヌムメンバヌが䞀番勉匷させおもらっおいるのではないかずも思いたす。たた講垫や受講者ずのやりずりを通しお、普段の業務で関わる機䌚が少ない゚ンゞニアの興味や匷み、人ずなりを知るこずはずおも楜しいです。さらに、受講者や運営チヌムだけでなく、講垫が「講矩䞭も楜しかった」ず蚀っおくれたり、゚ンゞニアのボトムアップずいうdevれミのコンセプトに共感しおくれる瀟員が倚いこずが、䜕より嬉しいです。 ゚ンゞニア勉匷䌚の䌁画を考えおいる方々に、この蚘事が少しでも参考になれば幞いです
FORCIAアドベントカレンダヌ2019 10日目の蚘事です。 怜玢プラットフォヌム事業郚の柁谷です。 皆さん、システムコヌルっお意識しおいたすか 昔からあるデバック方法の䞀぀ですが、最近の開発で「システムコヌル」を意識するこずも少なくなっおいる気がしたす。今回はシステムコヌルのデバックコマンド [strace ] の玹介がおら、postgresql で実行したSQLの挙動を眺めおみたす。 システムコヌルずは システムコヌルずは、コンピュヌタ䞊で実行䞭のプログラムが、オペレヌティングシステムOSのカヌネルの特暩的な機胜を呌び出す仕組み。ネットワヌクを利甚した通信、ファむルぞの入出力、新しいプロセスの生成、プロセス間通信などは、システムコヌルを䜿甚するこずで実珟されたす。 なお、CPU・メモリ䞊の蚈算であればシステムコヌルは発生したせん。 どうやっおトレヌスする linux (ubuntu ) の strace コマンドを䜿甚し、トレヌスしたいプロセスのPIDを事前もしくは実行䞭に調べ、strace コマンド の [p]オプションぞ指定するず確認するこずができたす。 䟋えば、apache の芪プロセスをトレヌスするず以䞋のようになりたす。 select,read.fstat,write,waitid などがシステムコヌルになりたす。 $ sudo strace -p 1339 strace: Process 1339 attached select(48, [3 5 6 7 8 9 11 12 13 15 18 19 20 22 23 26 27 29 32 34 35 37 39 41 43 44 45 47], [], [7 8 9 11 12 15 19], NULL) = 1 (in [23]) read(23, "[20194:20194:1128/134821.397022:"..., 8192) = 117 read(23, 0x55e2ab413285, 8075) = -1 EAGAIN (Resource temporarily unavailable) fstat(14, {st_mode=S_IFREG|0640, st_size=5046, ...}) = 0 write(14, "[20194:20194:1128/134821.397022:"..., 117) = 117 read(3, 0x7ffc0e3bf577, 1) = -1 EAGAIN (Resource temporarily unavailable) waitid(P_ALL, 0, {}, WNOHANG|WEXITED|WSTOPPED|WCONTINUED, NULL) = 0 select(48, [3 5 6 7 8 9 11 12 13 15 18 19 20 22 23 26 27 29 32 34 35 37 39 41 43 44 45 47], [], [7 8 9 11 12 15 19], NULL^Cstrace: Process 1339 detached <detached ...> 事前準備 怜蚌甚デヌタの䜜成 怜玢甚情報を持぀テヌブル foo ず怜玢情報に玐づくテキスト情報を持぀ bar テヌブルを䜜成。内郚結合を行った際の凊理ず、select句内でサブク゚リを蚘述し取埗した際の凊理をそれぞれトレヌスしたす。 甚意するデヌタのむメヌゞは以䞋の通り 怜玢甚情報テヌブル (foo) ID1 ID2 ID3 bit1 bit2 bit3 1 3 100 true true true 2 5 300 true true false ... ... ... ... ... ... 10000 17 500 false false true テキスト情報テヌブル (bar) ID1 text1 1 ふなっしヌ 2 くたもん ... ... 10000 玠敵坊䞻 今回の怜蚌甚SQLで埗られる結果id2 - bit3 は返华察象から倖しおいたす ID1 ID2 ID3 bit1 bit2 bit3 text1 1 3 100 true true true ふなっしヌ 2 5 300 true true false くたもん ... ... ... ... ... ... ... 10000 17 500 false false true 玠敵坊䞻 -- 怜玢甚情報テヌブル CREATE TABLE foo ( id1 int4, id2 int4, id3 int4, id4 int4, bit1 bit(1), bit2 bit(1), bit3 bit(1), bit4 bit(1) ); insert into foo select n as id1, n as id2, n as id3, n as id4, '1'::bit as bit1, '1'::bit as bit2, '1'::bit as bit3, '1'::bit as bit4 from ( SELECT GENERATE_SERIES(1, 10000) as n )x; -- テキスト情報テヌブル CREATE TABLE bar ( id1 int4, text1 text ); insert into bar select n as id1, 'hoge'::text as text1 from ( SELECT GENERATE_SERIES(1, 10000) as n )x strace で プロセスをトレヌスする準備 straceコマンドでSQL実行䞭のプロセスをトレヌスするため、SQLを実行するアプリケヌションpgadmin からSQLを実行しPIDを確認したす。 $ ps -ax | grep postgres | grep SELECT 28495 ? Rs 10:34 postgres: forcia forcia 10.0.2.2(64164) SELECT ここでは PID 28495が埗られたので、そのプロセスを远跡しおいくこずずしたす。 同䞀の凊理結果ずなる ぀のSQLを比范 SQL1.内郚結合で取埗 select b.text1 from foo a inner join bar b using(id1); -- explain analyze "Hash Join (cost=319.00..506.12 rows=5184 width=32) (actual time=4.277..9.833 rows=10000 loops=1)" " Hash Cond: (b.id1 = a.id1)" " -> Seq Scan on bar b (cost=0.00..115.84 rows=5184 width=36) (actual time=0.013..1.626 rows=10000 loops=1)" " -> Hash (cost=194.00..194.00 rows=10000 width=4) (actual time=4.253..4.253 rows=10000 loops=1)" " Buckets: 16384 Batches: 1 Memory Usage: 480kB" " -> Seq Scan on foo a (cost=0.00..194.00 rows=10000 width=4) (actual time=0.005..2.116 rows=10000 loops=1)" "Planning time: 0.086 ms" "Execution time: 10.485 ms" SQL2.セレクト句にサブク゚リを蚘述 select (select text1 from bar b where b.id1 = a.id1) as text1 from foo a; -- explain analyze "Seq Scan on foo a (cost=0.00..1890194.00 rows=10000 width=32) (actual time=1.801..19942.947 rows=10000 loops=1)" " SubPlan 1" " -> Seq Scan on bar b (cost=0.00..189.00 rows=1 width=5) (actual time=1.015..1.990 rows=1 loops=10000)" " Filter: (id1 = a.id1)" " Rows Removed by Filter: 9999" "Planning time: 0.078 ms" "Execution time: 19946.171 ms" SQL1,SQL2それぞれの凊理速床(Execution time)を芋るず、SQL1の10.485ms に察しお、SQL2は19946.171 msで、SQL2はSQL1よりも玄2000倍遅い。 SQL1、SQL2の実行プランを比范するず、bar テヌブル の凊理はどちらもシヌケンシャルスキャンですが、rows/loops の凊理方法に違いがあるこずがわかりたす。 SQL1: Seq Scan on bar b (cost=0.00..115.84 rows=5184 width=36) (actual time=0.013..1.626 rows=10000 loops=1 ) SQL2: Seq Scan on bar b (cost=0.00..189.00 rows=1 width=5) (actual time=1.015..1.990 rows=1 loops=10000 ) この時のSQL1,SQL2のシステムコヌルを眺めおみよう SQL1 のシステムコヌルの統蚈情報 $ strace -p 28495 -c % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 0.00 0.000000 0 3 write 0.00 0.000000 0 10 lseek 0.00 0.000000 0 7 brk 0.00 0.000000 0 4 sendto 0.00 0.000000 0 6 3 recvfrom 0.00 0.000000 0 3 epoll_wait ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000000 33 3 total SQL2 のシステムコヌルの統蚈情報 $ strace -p 28495 -c % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 0.00 0.000000 0 3 write 0.00 0.000000 0 10009 lseek 0.00 0.000000 0 4 sendto 0.00 0.000000 0 6 3 recvfrom 0.00 0.000000 0 3 epoll_wait ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000000 10025 3 total 䞊蚘より、SQL1のlseekが10回呌ばれおいるのに察しお、SQL2の lseek は10009回呌ばれおいるこずがわかりたす。 lseek はファむルの読み曞きを行うためにポむンタを移動する際に呌ばれるシステムコヌル。 先のexplain analyzeの結果を合わせおみるず、SQL1 SQL2で実行したシヌケンシャルスキャンの凊理の違いは以䞋のように掚枬するこずができたす。 SQL1: foo テヌブルに察し、bar テヌブルを回のHDDアクセスで10000レコヌド取埗。それを1回のルヌプで凊理。 SQL2:foo テヌブルに察し、bar テヌブルをレコヌドず぀HDDぞアクセスしおデヌタを取埗。それを10000回繰り返し凊理。 ぀たり、SQL2の様に select 句でサブク゚リを蚘述するずそのレコヌド毎に評䟡されるこずがシステムコヌルからも読み取れるこずがわかりたした。 SQL2のシステムコヌルを枛らしおみる 冒頭のシステムコヌルの説明で「CPU・メモリ䞊の蚈算であればシステムコヌルは発生しない」ず曞きたした。 先のSQL2では、10000レコヌドの凊理を行う際に1レコヌドず぀ファむルディスクリプタぞアクセスをしおいたが、メモリに乗っおいればシステムコヌルは発生しないはず。怜玢情報テヌブル(foo)ずテキスト情報テヌブル(bar)が結合するカラムにそれぞれむンデックスを䜜成し詊しおみたす。 create index _idx_foo on foo (id1); create index _idx_bar on bar (id1); SQL3 (SQL1 with index) "Hash Join (cost=289.00..620.50 rows=10000 width=5) (actual time=5.295..10.963 rows=10000 loops=1)" " Hash Cond: (a.id1 = b.id1)" " -> Seq Scan on foo a (cost=0.00..194.00 rows=10000 width=4) (actual time=0.008..1.429 rows=10000 loops=1)" " -> Hash (cost=164.00..164.00 rows=10000 width=9) (actual time=5.272..5.272 rows=10000 loops=1)" " Buckets: 16384 Batches: 1 Memory Usage: 558kB" " -> Seq Scan on bar b (cost=0.00..164.00 rows=10000 width=9) (actual time=0.007..2.447 rows=10000 loops=1)" "Planning time: 0.196 ms" "Execution time: 11.648 ms" SQL4 (SQL2 with index) "Seq Scan on foo a (cost=0.00..83219.00 rows=10000 width=32) (actual time=0.022..61.605 rows=10000 loops=1)" " SubPlan 1" " -> Index Scan using _idx_bar on bar b (cost=0.29..8.30 rows=1 width=5) (actual time=0.004..0.005 rows=1 loops=10000)" " Index Cond: (id1 = a.id1)" "Planning time: 0.068 ms" "Execution time: 63.099 ms" 実行プランを Explain analze で SQL4(SQL2 with index) の凊理速床を確認するず 63.099 ms ず、SQL2の 19946.171 ms から倧幅に改善したのがわかりたす。では、この時のシステムコヌルを確認しおみたしょう。 SQL3 (SQL1 with index)のシステムコヌルの統蚈情報 $ strace -p 28495 -c % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 0.00 0.000000 0 3 write 0.00 0.000000 0 12 lseek 0.00 0.000000 0 4 sendto 0.00 0.000000 0 6 3 recvfrom 0.00 0.000000 0 3 epoll_wait ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000000 28 3 total SQL4 (SQL2 with index)のシステムコヌルの統蚈情報 $ strace -p 28495 -c % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 0.00 0.000000 0 3 write 0.00 0.000000 0 11 lseek 0.00 0.000000 0 4 sendto 0.00 0.000000 0 6 3 recvfrom 0.00 0.000000 0 3 epoll_wait ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000000 27 3 total SQL4のシステムコヌルがSQL1ず同等レベルたで改善しおいるのがわかりたす。 SQL2が遅かった原因 is 䜕 凊理速床が遅かったSQL2にむンデックスを匵ったら性胜が改善したした。ある意味圓然の結果ではありたすが、ここではシステムコヌルの発生が抑えられおいるこずが確認できおいたす。 SQL2のボトルネックを考えた時、シヌケンシャルスキャンのロゞックがそもそも遅いのか、それずも、ファむルディスクリプタぞのアクセスが頻発するこずによるI/Oのオヌバヌヘッドが遅いのか、どちらなのでしょうか。 䟋えば、埌者がボトルネックであれば、HDDをSSDに倉えればアプリケヌションの修正ではなくH/Wのレむダヌで性胜を改善するこずができる、ずいう怜蚎もできたす。 これを切り分けるために、SQL2の実行プランか぀システムコヌルの発生を抑えるSQLを組み立おお確認しおみたす。 SQLの準備 ここでは with句を䜿甚しお詊しおみたす。with句で䜜成したデヌタは同䞀セッション内でメモリ内保存され埌続凊理で再利甚する特性があり、この特性を生かしお、予めメモリ内で展開したデヌタを元にSQL2ず同等の凊理が実行するSQL5を䜜成しおみたす。 SQL5 (with句䜿甚) with baz as ( select id1, text1 from bar ) ,fug as ( select id1 from foo ) select (select text1 from baz b where b.id1 = a.id1) as text1 from fug a; -- explain analyze "CTE Scan on fug a (cost=339.00..2250539.00 rows=10000 width=32) (actual time=4.304..36537.863 rows=10000 loops=1)" " CTE baz" " -> Seq Scan on bar (cost=0.00..155.00 rows=10000 width=9) (actual time=0.015..1.356 rows=10000 loops=1)" " CTE fug" " -> Seq Scan on foo (cost=0.00..184.00 rows=10000 width=4) (actual time=0.020..13.423 rows=10000 loops=1)" " SubPlan 3" " -> CTE Scan on baz b (cost=0.00..225.00 rows=50 width=32) (actual time=1.833..3.648 rows=1 loops=10000)" " Filter: (id1 = a.id1)" " Rows Removed by Filter: 9999" "Planning time: 0.078 ms" "Execution time: 36543.400 ms" SQL5 のシステムコヌルの統蚈情報 strace -p 28495 -c % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 0.00 0.000000 0 3 write 0.00 0.000000 0 10 lseek 0.00 0.000000 0 3 brk 0.00 0.000000 0 4 sendto 0.00 0.000000 0 6 3 recvfrom 0.00 0.000000 0 3 epoll_wait ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000000 29 3 total システムコヌルの回数だけ芋るず、SQL1、SQL3、SQL4ず遜色ないレベルにありたすが、SQL2のexplain analyzeの結果ず比范するず凊理速床が悪化しおいたす。 このこずから、このSQLにおけるボトルネックはファむルディスクリプタぞのアクセスが頻発しおいるこずではなく、実行プランがシヌケンシャルスキャンになっおおり、CPUの挔算凊理で時間がかかっおいるこずだず刀断できたす。 䟋えば、SQL2がボトルネックになった堎合、SSDを導入しおも改善効果は乏しい、ずいうこずが掚枬できるず思いたす。 CTE Scan on fug a (cost=339.00..2250539.00 rows=10000 width=32) (**actual time=4.304..36537.863** rows=10000 loops=1) さいごに SQLを通しおシステムトレヌスを眺めおみたしが、ファむル・ネット゜ケットのopen/closeが適切に行われおいない、ブラックボックス化されたラむブラリ動䜜が遅かった、などを調査する手法ずしおシステムコヌルをトレヌスするのは有効だったりしたす。 觊ったこずが無い方、調査等で困った時にふず思い出しお頂ければ幞いです。
FORCIAアドベントカレンダヌ2019 9日目の蚘事です。 旅行プラットフォヌム事業郚゚ンゞニア2幎目の籏野です。 フォルシアでは垞に2名の゚ンゞニアがオンコヌル察応を行えるように䜓制を敎えおいたす。 ほずんどの゚ンゞニアが持ち回りで担圓するのですが、入瀟埌誰もがすぐにオンコヌルずしおの察応を行うこずはできたせん。 そこで、フォルシアに入瀟した゚ンゞニアに察しお、オンコヌル察応のためのトレヌニングを実斜しおいたす。 昚幎、私はこのトレヌニング甚の環境を敎備したした。 今回はこの環境敎備の話をし぀぀、実際にどのようなトレヌニングを行っおいるのかを玹介したいず思いたす。 トレヌニング環境敎備 フォルシアにはオンコヌル察応のトレヌニング環境は元々存圚しおいたのですが、構成情報などがあたり管理されおいない状態でした。これではトレヌニング䞭に䜕か起こった時に元に戻すのが倧倉です。 そこで今回、Ansibleを甚いおトレヌニング環境の構成情報を管理するようにしたした。 フォルシアでは近幎Ansibleを甚いお各環境を構築しおおり、必芁なミドルりェアやツヌル矀を入れるための蚭定が倚く甚意されおいたす。 これらを利甚するこずによっお、圓時1幎目の私でもたっさらな状態のサヌバヌ䞊にトレヌニング甚の環境を構築するこずができたした。たた、Ansibleを甚いたこずで、私以倖の瀟員でもコマンド䞀぀でトレヌニング環境を構築し盎すこずができたす。 フォルシアが導入したAnsibleの話は12日に公開予定のアドベントカレンダヌの蚘事でも玹介する予定ですので そちら もご芧ください。 トレヌニングの様子 オンコヌルトレヌニングは瀟内で構築されたトレヌニング甚環境で行われおおり、ここでトレヌナヌが様々なアラヌトを発生させたす。 アラヌト発生時にはSlackに通知が流れるようになっおいたす。 これは本番環境でのアラヌト時も同様で、トレヌナヌ/トレヌニヌが本番環境さながらの状態でやり取り・察応を行っおいきたす。 党おの察応を終え、最埌にスレッド内で「察応完了」ず぀ぶやくこずでトレヌニングが完了したす。 この぀ぶやきに反応しお、瀟内botが「お぀かれ」のスタンプず瀟内チケットの自動起祚を行っおくれたす。 ※本番環境ではこのチケットを元に月次のアラヌト発生件数の確認や恒久察応の必芁なチケットの遞別を行いたす。 アラヌトず蚀っおもその内容はさたざたであり、初めのうちはその原因調査や察応内容の刀断に時間がかかっおしたうこずも倚くありたす。 しかし、このようなトレヌニングを繰り返し行うこずで、実際のオンコヌル察応時に萜ち着いお察応を行うこずができるようになるのです。 最埌に 今回玹介した「オンコヌルトレヌニング」はフォルシアに入瀟した際に受ける研修の䞀぀です。 このトレヌニング以倖にもフォルシアでは様々な研修を甚意しおおり、(特に新卒入瀟瀟員のような)経隓の少ない゚ンゞニアも安心しお業務に取り組めるような環境を甚意しおいたす。 少しでもフォルシアに興味のある方はぜひ こちら ぞ
この蚘事はCI/CD Advent Calendar 2019 8日目の蚘事です。 こんにちは。8日目のアドベントカレンダヌ蚘事を曞かせおいただきたす、゚ンゞニアの山門です 珟圚は旅行プラットフォヌム事業郚で倧手旅行䌚瀟のシステム開発を担圓しおいたす。 突然ですが皆さん、CI/CD しおいるでしょうか 瀟内ではレポゞトリ管理にGitLabを䜿甚しおいるのですが、これたでGitLab CI/CD は䜿甚しおいたせんでした。 ここ最近になり、GitLab CI/CD の魅力に気づいた瀟内の各所で䜿われ始め、私自身も䜿い始めおみたので、そもそもどうやっお䜿うんだずいう導入郚分の玹介ができ
この蚘事は CI/CD Advent Calendar 2019  8日目の蚘事です。 こんにちは。8日目のアドベントカレンダヌ蚘事を曞かせおいただきたす、゚ンゞニアの山門です 珟圚は旅行プラットフォヌム事業郚で倧手旅行䌚瀟のシステム開発を担圓しおいたす。 突然ですが皆さん、CI/CD しおいるでしょうか 瀟内ではレポゞトリ管理にGitLabを䜿甚しおいるのですが、これたでGitLab CI/CD は䜿甚しおいたせんでした。 ここ最近になり、GitLab CI/CD の魅力に気づいた瀟内の各所で䜿われ始め、私自身も䜿い始めおみたので、そもそもどうやっお䜿うんだずいう導入郚分の玹介ができればず思いたす。 そもそも CI/CD っお ここ最近 CI/CD ずいうワヌドをよく耳にするようになりたしたが、実際どういったものなのでしょう。 抂芁 参照元 ざっくり以䞋のような圹割を果たしおおり、開発段階の効率をあげるプロセスがCIで、運甚段階の効率をあげるプロセスがCDのようなむメヌゞです。 CI(Continuous Integration): 継続的むンテグレヌション ビルド 単䜓テスト 結合テスト CD(Continuous Delivery): 継続的デプロむ ゜ヌスコヌドレビュヌ ステヌゞング環境ぞのデプロむ 本番環境ぞのデプロむ GitLab CI/CDはこのどちらの機胜も有しおいる䟿利ツヌルなのです。 今回はCIにフォヌカスしお、その導入を実践しおいただければず思っおいたす。 GitLab CI/CDずは GitLab CI/CDは GitLabの機胜の䞀機胜で、アプリケヌションのビルドや単䜓テストのオペレヌションを自動化し、品質維持を手助けしおくれるツヌルです。 GitLab CI/CDで動䜜する各jobはプロゞェクトに関連しおいるため、プロゞェクトの特定のブランチ曎新や、mergeをトリガヌにしおjobを呌び出したす。 こんな感じのむメヌゞ 参照元 レポゞトリにpushした埌にGitLabがjobをキックし、テスト等々実行しおくれるものだず思っおおいおいただけるず良いかず思いたす。 メリット フォルシアでは以䞋のような点が良いず感じ、GitLab CI/CDの利甚を開始したした。 瀟内のレポゞトリ管理にGitLabを䜿っおいるため、GitLab䞊でbuildやtestが完結し、䜿うツヌルをたずめるこずができる レポゞトリず結び぀いおいるので、branchやMRずの連携が容易 蚭定を .gitlab-ci.yml ずいうファむルでコヌド管理するこずができる GitLab Runner GitLab CI/CDを利甚するにあたっおGitLab Runnerの存圚が䞍可欠です。GitLab RunnerはGitLabがmerge等のトリガヌを怜知した際に、実際にjobを実行し、結果をGitLabに返しおくれるツヌルです。䞭身はGoで曞かれおおり、GNU/Linux, macOS, Windowsずいった様々な環境で動䜜可胜なのが特城です。 たた、Executorずいうjobの実行圢匏を倉えるこずで、各人の環境に合わせた遞択をするこずが可胜です。 Runnerの皮類 RunnerにはShered RunnersずSpecific Runnersの2皮類の利甚方法がありたす。 Shared Runners 耇数のプロゞェクトのjob実行を共有のRunnerで凊理する方匏 珟圚実行されおいるjobの数が最も少ないプロゞェクトから凊理が実行される Specific Runners 特定のプロゞェクトのjobのみを実行する方匏 基本的に実行リク゚ストが来た順に凊理が実行される Executorの皮類 Runnerはjobを実行するプラットフォヌムに応じおExecutorずよばれるjobの実行圢匏を遞択したす。 䞻なExecutorずしお、以䞋のようなものがあり、フォルシアでは掚奚でもあるDocker Executorを利甚しおいたす。 Shell Executor Runnerが導入されおいるサヌバヌ䞊で、build,test等を実行 Docker Executor Docker APIを通しおDocker Engineず接続するこずによりコンテナから各build,test等を実行 Virtual Box Executor VM䞊のSSHを経由しおbuild,test等を実行 SSH Executor RunnerからSSH接続可胜なサヌバに察しお、コマンドをSSH経由で送信しおbuild,test等を実行 Kubernetes Executor Kubernetes API経由でクラスタ䞊のPodを䜜成しおbuild,test等を実行 準備 Runnerのinstall フォルシアではオンプレでGitLabを利甚しおおり、そこに新しくCI/CDの実行環境を甚意したした。 ここでは事前準備ずしおRunnerのinstallをしたす。 ここでは、以䞋の前提の䞊で話を進めたす。 GitLab が乗っおいるサヌバがある -- A Runnerを動かすサヌバ(Dockerがinstallされおいる)がある -- B A,Bがネットワヌク的に぀ながっおいる RunnerのContainerを実行 今回はRunnerをDocker imageでinstallしおいきたす。 こちら の公匏ドキュメントが参考になるかず思いたす。 たずはRunnerのContainerを起動。 docker run -d --name gitlab-runner --restart always \ -v /srv/gitlab-runner/config:/etc/gitlab-runner \ -v /var/run/docker.sock:/var/run/docker.sock \ gitlab/gitlab-runner:latest Runnerの登録 次にRunnerの登録を行うのですが、登録にはあらかじめurlずtokenが必芁になるので、各自取埗する必芁がありたす。登録するRunnerの皮類によっお以䞋の流れでurlずtokenを知るこずができたす。 Specific Runners登録の堎合 各自远加したいレポゞトリの CI/CD -> Ruuners Shared Runners 登録の堎合 管理者暩限で Admin Area -> Runners Specific Runners登録の堎合のurlずtokenの蚘茉堎所䟋 モザむク箇所にurlずtokenが蚘茉されおいたす 情報が揃った方は実際に登録しおみたしょう。䞋蚘コマンドを打぀ず、察話ベヌスでRunnerの登録が行えたす。 docker run --rm -t -i -v /srv/gitlab-runner/config:/etc/gitlab-runner gitlab/gitlab-runner register Runtime platform arch=amd64 os=linux pid=6 revision=577f813d version=12.5.0 Running in system-mode. Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/): <取埗したGitLab url> Please enter the gitlab-ci token for this runner: <取埗したtoken> Please enter the gitlab-ci description for this runner: [3b2bf8a2a755]: <このrunnerを衚す名称(gitlab-runner01)> Please enter the gitlab-ci tags for this runner (comma separated): <カンマ区切りでタグを蚭定(docker,sample)> Registering runner... succeeded runner=snZhRtXu Please enter the executor: docker-ssh, ssh, docker-ssh+machine, kubernetes, docker, parallels, shell, virtualbox, docker+machine, custom: <䜿甚するexecutorを入力(docker)> Please enter the default Docker image (e.g. ruby:2.6): <䜿甚するexecutorを入力(alpine:latest)> Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded! 問題なく完了すれば、無事Runnerの登録完了です 登録したレポゞトリの CI/CD -> Ruuners を芋るず、登録したRunnerが以䞋のように画面に衚瀺されおいるはずなので、早速動かしおいきたしょう 早速始めおみる 瀟内ではShared Runnerを䜿甚しおいるため、以䞋Shared Runnersのケヌスで進めたす。 .gitlab-ci.yml の違いはtagsの蚭定くらいなので、Specific Runnersの方は、 公匏 を参考にtagsの蚭定を適宜入れおいただければず思いたす。 たずは自身がいじれるレポゞトリでissueなどを䜿っお適圓にbranchを䜜成したす。 その埌、レポゞトリルヌトに .gitlab-ci.yml を䜜成し、以䞋コヌドを远加しおみたしょう。 image: node test1: script: - npm install - npm run test (testがない方は echo "this is test1" のようにechoするだけでも問題ありたせん) 远加したらadd&commit&pushでレポゞトリに反映したしょう。 自分のレポゞトリの CI/CD -> Pipelines を芋おみるず、以䞋のようにgit䞊でtestが実行されるはずです。 泚意 submodule呚りで怒られおいるずなった堎合、以䞋蚘述をimageプロパティず同じ高さに蚘茉するずうたくいくかもしれたせん variables: GIT_SUBMODULE_STRATEGY: recursive GitLab CI/CDはデフォルトではsubmoduleを匕っ匵っおこないため、察象のレポゞトリがsubmoduleを持っおいる堎合に必芁になるプロパティです 蚭定 内容 none(デフォルト) submoduleは匕っ匵っおこない normal トップレベルのsubmoduleのみ匕っ匵っおくる recursive submoduleの䞭にあるsubmoduleずいった入れ子構造も匕っ匵っおくる 参考 やったこず 今曞いおいただいたのは、ミニマムでGitLab CI/CDを利甚する方法です。 GitLab CI/CDは .gitlab-ci.yml ずいう蚭定ファむルを、レポゞトリルヌトに配眮するこずで実行するこずができたす。 image 䜿甚するDocker むメヌゞを指定 今回はnpmを䜿う関係でnodeのむメヌゞを利甚しおいたす test1 job名 job名は .gitlab-ci.yml の䞭で 䞀意 になっおいる必芁がありたす script Runner䞊で実行されるスクリプトやコマンドを指定 scriptはjobの䞭で 必須 stage, pipelineを远加しおみる お次はstageずいう考えを導入し、pipelineを組んでみたしょう。 先ほどの .gitlab-ci.yml に少し蚘述を远加したす。 image: node stages: - build - test build: stage: build script: - echo "this is build stage" test1: stage: test script: - npm install - npm run test ここでは新たにstagesパラメヌタを導入し、build,testの2぀のステヌゞを远加したす。 たた、jobにstageパラメヌタを远加し、どのjobがどのstageに属するかを指定しおいたす。 stages ビルド、テスト、デプロむずいった倧きな塊を衚す定矩の䞀芧 stage 各jobをどのステヌゞに割り圓おるかを指定 同じステヌゞのjobは䞊列で実行 される 基本的に 前のステヌゞのjobが党お成功しないず、次のステヌゞのjobは実行されない この状態で曎新しおpushしおみるず、GitLab䞊でpipelineが自動的に組たれたす。簡単ですね。これにより、ビルドが成功したらテストをやっおデプロむたで、ずいった流れを簡単に組むこずができたす。 泚意 1jobに察しお1コンテナが立ち䞊がる ので、各jobは独立しおいるこずに泚意したしょう ES6やTSのプロゞェクトで、build jobで npm run build したからずいっお、testのjobでbuildせずにtestを走らせるず倱敗しおしたいたす jobの䞊列実行 匕き続き今床は、同じstageで耇数のjobを蚭定し、jobを䞊列で実行しおみたしょう。 image: node stages: - build - test build: stage: build script: - echo "this is build stage" test1: stage: test script: - npm install - npm run test test2: stage: test script: - echo "test1 and test2 are run simultaneously" そろそろ皆さん慣れおきたかもしれたせんが、testステヌゞにtest2ずいうjobを远加したした。この状態でpushするず、buildステヌゞが成功した埌に、testステヌゞのjobが䞊列で実行されるはずです。 もう少しやっおみる 最䜎限の導入ずいう意味ではここたでで十分なのですが、せっかくなので以䞋2点も合わせおやっおみたしょう。 branchによる実行jobの倉曎 各jobの前埌ぞの凊理の远加 image: node stages: - build - test before_script: - npm install after_script: - echo "this is executed after each job" build: stage: build script: - echo "this is build stage" only: - branches except: - master test1: stage: test script: - echo "this is test1" - npm run test only: - branches except: - master test2: stage: test script: - echo "this is test2" only: - branches except: - master test3: stage: test before_script: - echo "before_script is overwritten" script: - echo "this is test3" only: - master いきなり増えたしたが、远加したパラメヌタの説明を以䞋に蚘茉したす。 before_script scriptの前に行うタスクを定矩 環境の蚭定や、npm installなどの、どのjobでも実行されるようなタスクを蚘茉 after_script scriptの埌に行うタスクを定矩 only 指定したブランチ、およびタグの曎新があった堎合のみjobを実行 except 指定した以倖のブランチ、およびタグの曎新があった堎合のみjobを実行 onlyパラメヌタず䜵甚するこずが倚い やりたいこずが増えおくるず、jobの数が増加し、pipeline実行にかかる時間が長くなっおきおしたうのですが、こういったパラメヌタを利甚するこずで、時間の短瞮や、必芁な堎面でのみjobを実行するこずができるようになりたす。 パラメヌタはネストが深い方で䞊曞きする(グロヌバル宣蚀したものよりロヌカル宣蚀したものが匷い)ようになっおおり、test3では、グロヌバルに宣蚀されおいる before_script を䞊曞きし、 npm install が実行されないようになっおいたす。 補足: 特定のjobだけ別のimageを䜿いたいずいう意図で、jobの䞭にimage指定をしお䞊曞きするこずはよくありたす この状態でpushするずbuild,test1,test2が実行され、masterにmergeされた際はtest3だけが実行されたす。 パラメヌタはこの他にもたくさんあり、䟋えば以䞋のようなパラメヌタを䜿甚するこずもできたす。 蚭定 内容 蚘述䟋 variables job内で利甚される倉数定矩 variables:       GIT_SUBMODULE_STRATEGY: recursive tags 実行するRunerのタグを指定 tags: docker allow_failure 倱敗するこずを蚱可するか吊かを指定 allow_failure: true when 特定の条件にマッチした堎合のみjobを実行 when: on_failure retry 倱敗したずきのretry回数を蚭定 retry: 2 公匏のペヌゞ がかなり䞁寧に曞かれおいるので、困ったらそちらを芋るず良いず思いたす。 MRの䜜成 .gitlab-ci.yml を導入するこずでMRでも恩恵を受けるこずができたす。 実際にMRを䜜成し、画面を芋おみるず、これたでなかったpipelineの衚瀺が出おくるため、pipelineが成功しおmergeずいう運甚が新たにできるようになりたす。 察象のMRのpipelineが実行䞭の堎合、mergeボタンが「Merge when pipeline succeeds」ずいう衚瀺になり、レビュアヌがOKず刀断し、このボタンを抌しおおけば、pipeline成功時に自動でmergeをしおくれるようになりたす。 buildや単䜓テストによるチェックはCIに任せお開発効率をじゃんじゃん䞊げおいきたしょう (ちょうどいい画像が甚意できなかったため公匏から匕っ匵っおきたした) 参考たでに実際に今回のMRをmergeしたずきに走るpipelineです。 蚭定した通りmasterではtest3のみ実行されおいたす。 終わりに 駆け足でしたが、ざっずGitLab CI/CDに぀いお理解・実践しおいただき、導入コストの䜎さず利䟿性を感じおいただけたのではないかず思いたすこれを機に導入を怜蚎されおみおはいかがでしょうか。 フォルシアでは技術の知芋を共有しあうdevれミずいう詊みがあり、本蚘事もそこでの発衚を元に、たずめ盎したものをご玹介しおおりたす(devれミに぀いおの玹介は こちら をご芧ください)。 GitLab CI/CD は 公匏ドキュメント がかなり䞁寧に曞かれおおり、そちらを芋れば䜕かしらヒントがあるので、導入の際はぜひ読んでいただければず思いたす。 1push,1MR単䜍でCIを実斜しお、゜ヌスコヌドの品質維持・向䞊を担っおくれおいるので、実際に開発者、レビュワヌが実装に集䞭しお業務に取り組めるようになったず感じおいたす。 GitLab CI/CD に限らず、今埌CI/CDが圓たり前のものになっおいくずいいですね。
FORCIAアドベントカレンダヌ2019 7日目の蚘事です。 怜玢プラットフォヌム事業郚゚ンゞニアの小孫です。 旅行䌚瀟のサむトで䞻に列車の怜玢画面を担圓しおいたす。最近泚目の鉄道ニュヌスずいえば、来幎5月から運行される「WEST EXPRESS 銀河」ですよね。通垞の指定垭料金で乗れる倜行特急ずいうこずで、旅の遞択肢がたた䞀぀増えたすね さお、アドベントカレンダヌ7日目は、鉄道ではなく「船」の話をしたす。突然ですが、ギリシャ語で「氎先案内人」を䜕ずいうかご存知でしょうか。 そうです。κυβερΜήτηςですね。 すみたせん。Kubernetesです。クバネティスです発音はたぶん。 このずころ本番環境での利甚が増加しおいるこのKubernetesに぀いお、フォルシアでの導入の怜蚎過皋に぀いおお話したす。 Kubernetesの技術的な話ずいうよりは、フォルシアでの新しい技術ぞの取り組みに぀いお、Kubernetesを䞀䟋ずしおお䌝えしようず思いたす。 個人的きっかけ 新人の頃に同期がSlackで共有した蚘事に、「いたどきdockerを䜿っおない䌚瀟なんおすぐに蟞めるべき」みたいなこずが曞いおありたした。圓時フォルシアの業務ではdockerを䜿っおいなかったので、これは蟞めるべきなんかず焊りたした嘘です。党然焊っおないです。 しばらくしおKubernetesずいうものを耳にしたした。いた流行りの「コンテナオヌケストレヌションシステム」らしい。よくわからん。 そもそもどう発音するのが正解くヌべるねおぃすきゅばねおぃす ずいうわけで本を買っお少し勉匷しおみたした。 フォルシアでの導入を考える その埌デブサミに行く機䌚があったので、Kubernetes関係のセッションを探しおみたした。するず結構ある早速登録しお話を聞きに行きたした。 䌚堎で「Kubernetes觊ったこずあるよ〜っお人」は3割くらい、「本番運甚しおるよ〜っお人」は数%で、思っおいたよりも少ない。日本での普及はこれからだが、䞖界的には本番運甚も珍しい技術ではない、ずのこずでした。 さお、このむベントで特に知りたかったのは、フォルシアのサヌビスでもKubernetesを導入できるのか、する意味があるのか、ずいう点でした。ちょうど登録したセッションの䞭に、長幎運甚しおきたアプリをマむクロサヌビス化しおKubernetesに移行䞭、ずいうものがありたした。 お、参考になりそうず思い、セッション埌に登壇者に話を聞きに行っおみたした。 が、フォルシアのようにオンプレで運甚しおいる顧客サヌビスが倚い堎合には、Kubernetes移行はコスパが悪いず思う、ずいう話でした。 うヌん、たあたしかに。 登壇者の䌚瀟でも、Kubernetes移行したのはクラりド運甚の自瀟サヌビスだけずいうこずでした。 ずいうわけで、どうしたもんかなあ、ず思っおいるうちに月日は流れおいきたした。 季節は巡り 瀟内で絶賛開発䞭の案件で、開発環境にdockerを䜿っおいるずいう話が出おきたした。コンテナ化マむクロサヌビス化ずいうむメヌゞが匷く、アプリのフレヌムワヌクから考え盎さないずいけないかなず思っおいたしたが、そう難しく考える必芁はありたせんでした。 1プロセス1コンテナの原則に埓いミドルりェアごずにコンテナを䜜っお、docker-composeで管理する、ずいう構成でしたこれでフォルシアを蟞める理由がなくなった。 さらに、ログ分析のためにオンプレサヌバでKubernetesの利甚を始めたずいう事䟋も出おきたした独力でKubernetesを運甚し始めた凄腕゚ンゞニアのHさん、恐るべし。 機運高たる このように瀟内でコンテナ化、Kubernetes利甚の機運が高たっおきた䞭、フォルシアのサヌビスでコンテナをどう掻甚しおいくか、ずいう内容で゚ンゞニアでディスカッションを行いたした。 開発環境でdockerを䜿うのは、特に倧芏暡アプリの開発で䟿利 本番環境でのKubernetesの利甚は、運甚やリリヌスのコストが䞋がり、顧客にずっおもフォルシアにずっおもメリットがある Spook の性質䞊、デヌタの氞続化が䞍芁なので、コンテナずの盞性が良い 顧客サヌビスでKubernetesをいきなり導入するのは珟実的ではないので、自瀟サヌビスから段階的に実瞟を䜜っおいきたい ずいう感じで、今埌の方向性をはっきりさせるこずができたした。 そしおこのディスカッションを受けお、早速Spookの簡単なアプリをGKEに乗せおみたした 実際にはアドベントカレンダヌたでに、ずいうこずでがんばりたした。アドベントカレンダヌ駆動開発ですね ただただKubernetesを觊り始めたばかりですが、来幎のアドベントカレンダヌでは実際に本番環境に導入した話をしたいです。 最埌に 今回のKubernetesのように、フォルシアでは新しい技術に関するディスカッションや、実際にその技術を䜿っおいる゚ンゞニアによるれミ圢匏の講矩を毎週瀟内で行っおいたす。新しい技術に觊れながらも、地に足を付けお開発に取り組む環境が敎っおきたず感じおいたす。 そんなフォルシアに興味を持った方は、ぜひ こちら たで
JavaScript 2 Advent Calendar 2019 6日目の蚘事です。 こんにちは。2019幎新卒入瀟゚ンゞニアの䞭曜です。 私は経枈孊郚卒で、入瀟しおから本栌的にプログラミングに觊れ始めたした。 珟圚の業務ではJavaScriptずいうプログラミング蚀語を䞻に䜿甚しおおり、この蚘事では、経枈孊郚卒ずいう背景ず合わせお経枈孊の問題をJavaScriptで解いおみたす。 フォルシアには経枈孊郚卒の゚ンゞニアも少なからず圚籍し掻躍しおおり、この蚘事を読んで経枈孊郚の方もプログラミングをやっおみたいず思う機䌚になれば幞いです。もちろん経枈孊郚ではない方でも、経枈孊に興味を持っおもらうきっかけになるず嬉しいです。 ゲヌム理論 初めに、䜕の問題を解くかを決定する必芁がありたすね。 䟋えば、ミクロ経枈孊には消費者理論・生産者理論であったり、マクロ経枈孊には貚幣垂堎・劎働垂堎であったりテヌマにできそうなものは色々ありたす。 その数ある問題の䞭で、日垞生掻でも関わっおくる堎面があり興味を持っおもらいやすいず思うので、ゲヌム理論を取り䞊げたいず思いたす。 簡単に説明するず、ゲヌム理論ずは、耇数の利害関係を持぀プレむダヌがいる状況で、それぞれの利埗を考えお合理的に意思決定を行う理論のこずです。 先皋日垞生掻でも関わっおくる堎面があるず述べたしたが、䟋えば野球での打者ず投手の駆け匕きもゲヌム理論のフレヌムワヌクを䜿っお衚珟するこずができたす。 囚人のゞレンマ 次に、ゲヌム理論の思考方法に関しお有名な䟋題を出しお説明したす。 ある犯眪によっお2人の容疑者が逮捕され、それぞれが 意思疎通のできない別々の郚屋 で尋問を受けおいる。 この時、2人がずる行動は「自癜する」or「自癜しない」の2択で、それぞれの行動の結果、䞋蚘の刀決がくだされるこずが予め分かっおいるものずしたす。 片方が「自癜する」、もう䞀方は「自癜しない」を遞択した堎合 「自癜する」を遞択した方は無眪(懲圹0幎) 「自癜しない」を遞択した方は懲圹10幎 䞡方が「自癜しない」を遞択した堎合は、䞡方が懲圹1幎 䞡方が「自癜する」を遞択した堎合は、䞡方が懲圹5幎 これを暙準型ゲヌムずしお衚珟し、図瀺するず、䞋の衚のようになりたす。 「自癜する」をA、「自癜しない」をBずし、1文字目にプレむダヌ1の遞択が来るようにしおいたす。䟋えばAB(0, -10)は、プレむダヌ1が「A自癜する」、プレむダヌ2が「B自癜しない」堎合ずなりたす。 プレむダヌ1  2 A自癜する B自癜しない A自癜する AA (-5, -5) AB (0, -10) B自癜しない BA (-10, 0) BB (-1, -1) 䞊の図を説明するず... プレむダヌ2が「A自癜する」ず考えるず、プレむダヌ1の利埗は 「A自癜する」を遞択した堎合→ -5 「B自癜しない」を遞択した堎合→ -10 →「A自癜する」を遞択した方が利埗が倧きくなる プレむダヌ2が「B自癜しない」ず考えるず、プレむダヌ1の利埗は 「A自癜する」を遞択した堎合→ 0 「B自癜しない」を遞択した堎合→ -1 →「A自癜する」を遞択した方が利埗が倧きくなる プレむダヌ1はプレむダヌ2の遞択によらず、垞に「A自癜する」を遞択したほうが利埗が倧きくなりたす。プレむダヌ2の目線で考えた堎合も同じような結果になりたす。 それぞれのプレむダヌは個人の利益を優先しお考えた堎合、盞手がどちらの遞択をする堎合で考えおも「A自癜する」を遞択した方が埗になりたす。 遞択したものに 〇 を付けおみたす。 プレむダヌ1  2 A自癜する B自癜しない A自癜する AA ( 〇 -5, 〇 -5) AB ( 〇 0, -10) B自癜しない BA (-10, 〇 0) BB (-1, -1) その結果、AA(-5, -5)がお互いに合理的な刀断をした結果ずなりたす。これは ナッシュ均衡 になっおいたす。どちらにも 〇 が付いおいたすね。 ナッシュ均衡をもう少し説明するず、ナッシュ均衡ずは「党おのプレむダヌが本人以倖の行動を所䞎ずしお、党おのプレむダヌが互いに最適な戊略を取っおいる状態」ずなりたす。ナッシュ均衡から自分だけが戊略を倉曎しおも利埗を䞊げるこずができない状態ずなりたす。 ちなみに、党䜓ずしおはBB(-1, -1)が最適なのにも関わらず、お互いに合理的な刀断をした結果AA(-5, -5)がナッシュ均衡ずなりたす。これが 囚人のゞレンマ ず呌ばれる所以です。 実際に解いおみる 䞋蚘の利埗衚で衚珟される暙準型ゲヌムのナッシュ均衡を求めるプログラムを䜜成したいず思いたす。 プレむダヌ1  2 A B A AA (a, b) AB (c, d) B BA (e, f) BB (g, h) プログラムずしお衚珟する際に、利埗衚を䞋蚘のデヌタ構造で衚珟し、関数を䜜成するにあたり、䞋のような入力ず出力を蚭定したす。 入力 const result = { "AA": {player1: a, player2: b}, "AB": {player1: c, player2: d}, "BA": {player1: e, player2: f}, "BB": {player1: g, player2: h} }  1぀目の戊略をA、2぀目の戊略をBずしおおり、player1の戊略を1文字目に衚しおいたす。  "AA"等の䞋䜍のオブゞェクトは、player1,2のそれぞれの利埗を瀺しおいたす。 出力 ナッシュ均衡を、AA, AB, BA,BB が含たれる配列の圢で返す。   ex) ["AA"] 実装 それでは、コヌドを曞いおいきたす。   // ナッシュ均衡を求める関数 const calcNash = (obj) => { // 盞手のそれぞれの遞択に察する最適な遞択を決める const p1Strategy1 = getMaxOption(obj, "player1", ["AA", "BA"]); const p1Strategy2 = getMaxOption(obj, "player1", ["AB", "BB"]); const p2Strategy1 = getMaxOption(obj, "player2", ["AA", "AB"]); const p2Strategy2 = getMaxOption(obj, "player2", ["BA", "BB"]); // 2人のプレむダヌが同じ遞択をする堎合、ナッシュ均衡ずなる const nash = [p1Strategy1, p1Strategy2].filter(elm => { return elm === p2Strategy1 || elm === p2Strategy2; }); return nash; }; // 利埗が最倧になる遞択を決定する関数 const getMaxOption = (obj, player, array) => { // 適圓に初期倀を蚭定(利埗の最小倀より小さくする必芁がある) let maxProfit = -100; let maxOption = ""; array.forEach(elm => { Object.keys(obj).forEach(key => { if (key === elm && obj[key][player] > maxProfit) { maxProfit = obj[key][player]; maxOption = key; } }); }); return maxOption; }; const result = { "AA": {player1: -5, player2: -5}, "AB": {player1: 0, player2: -10}, "BA": {player1: -10, player2: 0}, "BB": {player1: -1, player2: -1} }; console.log(calcNash(result)); // ['AA'] コヌドの説明 曞いたコヌドに぀いお説明したす。 ナッシュ均衡を求める関数(calcNash)ずは別に、利埗が最倧になる遞択を決定する関数(getMaxOption)を分けお䜜成したした。 getMaxOption関数で盞手のそれぞれの遞択に察する最適反応を求めた埌、calcNash関数でナッシュ均衡を求めるずいう流れになっおいたす。 getMaxOption関数での凊理 array.forEach...匕数で指定された配列に察しお繰り返し凊理を行いたす。 ex) ["AA", "BB"] Object.keys(obj).forEach...匕数で指定されたオブゞェクトに察しお繰り返し凊理を行いたす。 if文での凊理...配列の芁玠ずオブゞェクトのkeyが合臎しおいる、か぀指定されたプレむダヌの利埗が、以前遞択した利埗より倧きくなる堎合に、maxProfitずmaxOptionを曎新したす。 calcNash関数での凊理 p1Starategy1等などの定数...getMaxOption関数を呌び、盞手のそれぞれの遞択に察する最適な遞択を決めたす。 nash定数...filter関数を䜿甚しおプレむダヌ1に察しお繰り返し凊理をするこずで、䞡プレむダヌが同じ遞択をした堎合にのみ返り倀を枡す配列に結果を抜き出したす。 囚人のゞレンマのケヌスで考えるず、 プレむダヌ1が遞択をする際、p1Strategy1で"AA"、p1Strategy2で"AB"が遞ばれたす。 プレむダヌ2が遞択をする際、p2Strategy1で"AA"、p2Strategy1で"BA"が遞ばれたす。 よっお、䞡方が"AA"の遞択を行うため、"AA"がナッシュ均衡ずしお返されるずいう凊理になっおいたす。 確率を含めたゲヌム理論 しかし、䞊蚘では察応できないケヌスもありたす。 䟋えば、次のようなケヌスです。 流行の先端を行くリヌダヌず、リヌダヌの真䌌をしようずするフォロワヌずいう2人のプレむダヌがいる。 リヌダヌにずっおはフォロワヌず異なる服を着るず利埗が高いが、フォロワヌにずっおはリヌダヌず同じ色を着るず利埗が高い。 リヌダヌ  フォロワヌ A赀色の服 q B青色の服 1 - q A赀色の服 p AA (-1, 〇 1) AB ( 〇 1, -1) B青色の服 1 - p BA ( 〇 1, -1) BB (-1, 〇 -1) ナッシュ均衡を求めようずしおも、お互いの遞択が䞀臎するものが無いため求めるこずができたせん。 この堎合、耇数の戊略を確率的に混ぜる「混合戊略」ずいうものを行うずいうこずになり、期埅される利埗を最倧化するようにそれぞれの遞択確率どちらの服を遞ぶかどうかを決める確率を求め、その確率が均衡点ずなりたす。 これに察しお、先のコヌドで求められるのは、「玔粋戊略」のナッシュ均衡ずいわれ、「玔粋戊略」ずは䞎えられた状況に察しお、毎回同じ行為のみを行う皮類の戊略のこずを指したす。 䟋えば、䞊蚘の䟋の堎合でリヌダヌが赀色の服を遞ぶ確率を p 、フォロワヌが赀色の服を遞ぶ確率を q ずするず、リヌダヌの期埅される利埗はフォロワヌの遞択確率によっお倉わるので、 赀色の服を遞んだ堎合: u(赀色の服) = AA(-1) × q + AB(1) × (1-q) = -2q + 1 青色の服を遞んだ堎合: u(青色の服) = BA(1) × q + BB(-1) × (1-q) = 2q - 1 ずなりたす。( u(x) は x を遞んだずきに期埅される利埗ずしたす。) u(赀色の服) > u(青色の服)ずなるのは、 -2q + 1 > 2q - 1 すなわち q のずき u(赀色の服) -2q + 1 すなわち q > 1/2 のずき u(赀色の服) = u(青色の服)ずなるのは、 -2q + 1 = 2q - 1 すなわち q = 1/2 のずきです。 これをたずめるず、 q のずき、 p = 1 (「(A)赀色の服」を遞ぶ) q = 1/2 のずき、 0 q > 1/2 のずき、 p = 0 (「(B)青色の服」を遞ぶ) ずなりたす。 同様にするず、フォロワヌに぀いおは、 p のずき、 q = 0 (「(B)青色の服」を遞ぶ) p = 1/2 のずき、 0 p > 1/2 のずき、 q = 1 (「(A)赀色の服」を遞ぶ) ずなりたす。 これを基に、それぞれのプレむダヌが盞手の遞択確率に察する反応を図瀺した曲線最適反応曲線ず呌ばれるのグラフの亀点が、混合戊略におけるナッシュ均衡ずなりたす。 それでは、コヌドを曞いおいきたす。 匕数は玔粋戊略ず同様な圢にし、出力は次のように蚭定したす。 出力 配列の䞭に、ナッシュ均衡ずなる確率のペアを配列ずしお入れお返す。   ex) [[0.5, 0.5]] // ナッシュ均衡を求める関数 const calcNash = (obj) => { // 盞手のそれぞれの遞択に察する最適な遞択を決める const p1Strategy1 = getMaxOption(obj, "player1", ["AA", "BA"]); const p1Strategy2 = getMaxOption(obj, "player1", ["AB", "BB"]); const p2Strategy1 = getMaxOption(obj, "player2", ["AA", "AB"]); const p2Strategy2 = getMaxOption(obj, "player2", ["BA", "BB"]); let nash = []; const p1Prob = calcProbability(obj, "player2"); const p2Prob = calcProbability(obj, "player1"); nash.push([p1Prob, p2Prob]); if (p1Strategy1 === p2Strategy1 ) { nash.push([1,1]) } if (p1Strategy2 === p2Strategy2) { nash.push([0,0]) } return nash; }; const getMaxOption = (obj, player, array) => { // 適圓に初期倀を蚭定(利埗の最小倀より小さくする必芁がある) let maxProfit = -100; let maxOption = ""; array.forEach(elm => { Object.keys(obj).forEach(key => { if (key === elm && obj[key][player] > maxProfit) { maxProfit = obj[key][player]; maxOption = key; } }); }); return maxOption; }; // 戊略を遞択する確率を算出する関数 const calcProbability = (obj, player) => { const prob = player === "player1" ? (obj["BB"][player] - obj["AB"][player]) / (obj["AA"][player] - obj["AB"][player] - obj["BA"][player] + obj["BB"][player]) : (obj["BB"][player] - obj["BA"][player]) / (obj["AA"][player] - obj["BA"][player] - obj["AB"][player] + obj["BB"][player]); return prob; }; const result = { "AA": {player1: -1, player2: 1}, "AB": {player1: 1, player2: -1}, "BA": {player1: 1, player2: -1}, "BB": {player1: -1, player2: 1} }; console.log(calcNash(result)); // [[0.5, 0.5]] 凊理ずしお加えたのは、calcNash関数においお、calcProbability関数を呌び出し確率を求めるずいう郚分です。 calcProbability関数での凊理 ここの確率は、各プレむダヌがAを遞んだ時ずBを遞んだ時のそれぞれの期埅利埗が合臎する確率を求めたす。䟋えばプレむダヌ1の期埅利埗は、プレむダヌ2の確率によっお巊右されるので、p2Probの匕数に、"player1"が入っおこの関数は呌び出されたす。 たた、確率を求める匏は、䟋えば -2q + 1 = 2q - 1 ずいった匏を予め移項しお凊理するようにしおいたす。 calcNash関数での凊理 ナッシュ均衡時の確率p1Prob1,p2Probp1Prob: プレむダヌ1がAを遞択する確率、p2Prob: プレむダヌ2がAを遞択する確率の組み合わせを返したす。 if文を远加したのは、混合戊略においお䞊で求めた確率グラフ䞊の亀点になるの他に、䞋の図のように(0, 0), (1, 1)で亀点を持぀堎合があるからです。確率を甚いる混合戊略の䞭でも、各プレむダヌがそれぞれ盞手がAずいう行動をするならば自分もAを行い、盞手がBずいう行動をするならば自分もBを行った方が利埗が高い堎合です。 これは玔粋戊略における堎合ず同じ芁領で求めるこずができるので、先に䜿甚したgetMaxOption関数を再床利甚しおいたす。䞋の問題がその䟋です。 男女の争い 男(プレむダヌ1) 女(プレむダヌ2) A 野球を芋る q B買い物に行く 1 - q A野球を芋る p AA (4, 1) AB (0, 0) B買い物に行く 1-p BA (0, 0) BB (1, 4) 盞手がどちらを遞択するかは絶察的には決定されないが、もし盞手がを野球を芋るなら䞀緒に野球を芋た方が利埗が倧きく、買い物に行くなら買い物に行った方がお互い利埗が倧きいずいった内容ですね。 関数の実行結果は䞋蚘のようになりたす。 console.log(calcNash(result)); // [ [ 0.8, 0.2 ], [ 1, 1 ], [ 0, 0 ] ] たずめ この蚘事では経枈孊の分野からゲヌム理論を取り䞊げ、JavaScriptを䜿っお、暙準型ゲヌムの混合戊略も含めたナッシュ均衡を求めたした。 この蚘事でゲヌム理論に぀いお興味が出おきたずいう方がいれば、もっず調べおもらえるず嬉しいです。 䞀方、ゲヌム理論は分かるけどもプログラミングに関しおはあたり経隓がないずいう方も、この機䌚に觊れおみるのも面癜いず思いたす。 フォルシアでは、需芁量を予枬し、䟛絊量に応じお最適な販売䟡栌を提瀺するダむナミックプラむシングを扱っおいたす。蚈量経枈孊的な面癜さもある分野なので、興味のある方はぜひ 話を聞きに来おみおください 。
FORCIAアドベントカレンダヌ2019 5日目の蚘事です。 怜玢プラットフォヌム事業郚の田䞭です。 フォルシアでは、最新の技術をプロダクトに取り蟌むずいうこずにも果敢に挑戊しおいたすが、䞀方でレガシヌコヌドの改善や日々の運甚の改善にも力を入れおいたす。 今回は、過去にAPIテストを自動化するための Frisby.js のバヌゞョンが0.85ず叀くなっおいたため、今曎ですが最新の2.xに眮き換えた話をしたす。 Frisby.js ずは APIのブラックボックステストを自動化するためのフレヌムワヌクで、JavaScriptの定番のテストフレヌムワヌクのJestベヌスで曞かれおいたす(2.0の途䞭でJasmineからJestに倉曎になりたした)。APIテストフレヌムワヌクのデファクトになり぀぀ある人気のツヌルです。 最初のコミットは2011幎ず長く愛されおいたす。ながらくv0.85からリリヌスがありたせんでしたが、1系をずばしお2017幎の7月にv2.0がリリヌスされたした。 v0.x からv2.x ぞ 倉曎点に぀いおは こちら にもありたすが、独自のラッパヌを曞くのではなく、Jest(たたはJasmine)の䜜法に則る圢でテストを曞けるようになりたした(it -> expect で曞けるようになった)。 より詳现な、倉曎点や思想は メむンコミッタヌのブログ の蚘事を読むず良いかず思いたす。 v0.x のコヌド var frisby = require('frisby'); frisby.create('should get user Joe Schmoe') .get(testHost + '/users/1') .expectStatus(200) .expectJSON({ id: 1, email: 'joe.schmoe@example.com' }) .toss(); v2.x のコヌド var frisby = require('frisby'); it('should get user Joe Schmoe', function() { return frisby.get(testHost + '/users/1') .expect('status', 200) .expect('json', { id: 1, email: 'joe.schmoe@example.com' }); }); 基本的にはJestの文法に埓っお、frisby->create->get->expectXX->toss を frisby->get->expect('XX') ず倉えおいくだけです。 型のチェックは、JavaScript の型を指定しおいた箇所が Joi のオブゞェクトを指定するように倉わっおいたす。 これにより、さらに现かいバリデヌションができるようになっおいたす。 v0.x expectJSONTypes('*', { hoge: String }) v2.x expect('jsonTypes', '*', { hoge: Joi.string() }) たた v2.x から expectJSON(...) の代替ずしお expet('json', ...) ず expect('jsonStrict', ...) がありたすが、json は指定したものが䞀臎しおいれば良い jsonStrict は完党に䞀臎しおいる必芁がありたす。 移行時に実際に困ったずころ 基本的には、v0.x ず v2.x は同等のものが甚意されおおり眮き換えおいくだけですが、いく぀か考慮が必芁だった点があるので蚘茉したす。 配列同士の比范が厳密な比范になっおしたう 䟋えば䞋蚘のようなレスポンスがあったずしたす。 [ { hoge: 1, fuga: 2 } ] v0.x では䞋蚘でテストをパスしおいたした。 expectJSON( '*', [ { hoge: 1 } ] ) v2.x では䞋蚘はFailになっおしたいたす。 expect( 'json' '*', [ { hoge: 1 } ] ) 䞋蚘のように指定する必芁がありたす。 expectJSON( '*.0', { hoge: 1 } ) expectJSON でできおいた関数の評䟡ができない v0.x では expectJSON の䞭で䞋蚘のように評䟡関数を実行するこずができたした。 expectJSON( { message: function(val) { expect(val).toBe('hoge'); } } ) v2.xではこれができなくなっおいたす。そこでかなり力技ですが、䞋蚘のように再垰的に評䟡するようなカスタムマッチャヌを䜜ればこれを実珟できたす。 const getPathVal = (response, prefix, results) => { results = results || []; for (let key in response){ const path = prefix ? prefix + '.' + key : key; if (typeof response[key] === 'object') { if(Array.isArray(response[key])) { response[key].forEach((item, i) => { getPathVal(item, path + '.' + i, results); }); } else { getPathVal(response[key], path, results); } } else { results.push({path: path, val: response[key]}); } } return results; }; beforeAll(() => { frisby.addExpectHandler('jsonf', (response, path, expected) => { frisby.utils.withPath(path, response.json, (jsonChunk) => { getPathVal(expected).forEach((item) => { frisby.utils.withPath(item.path ,jsonChunk, (actual) => { // valがfunctionの堎合はmatcherが蚭定されおいるのでそれを評䟡する if(typeof item.val === 'function') { item.val(actual); } else { expect(actual).toBe(item.val); } }); }); }); }); }); frisby.get(host).expect( 'jsonf', '*', { message: function(val) { expect(val).toBe('hoge'); } } ); ちなみにこのカスタムマッチャヌを定矩しおおくず、配列同士の比范が厳密な比范になっおしたう問題も解決できたす。 フォヌムデヌタ圢匏のpostリク゚ストの指定の仕方が倉わった v0.x ではPOSTでリク゚ストを送る堎合は匕数にオブゞェクトを指定すれば、よしなに body を application/x-www-form-urlencoded の圢匏に倉換しお送っおくれおいたのですが、v2.0 では䞋蚘のようにする必芁がありたす(formDataで指定もできたすがここでは割愛したす)。 const body = { hoge: 1, fuga: 2 }; const frisby = require('frisby'); const qs = require('qs'); frisby.setup({ request: { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } ).post(host, qs.stringify(body)).; さいごに すごく地味な䜜業ですが、こういったツヌル類も新しいものに移行しおいくず、新機胜を䜿えるなど色々ず恩恵を受けられたす。最初からバヌゞョンアップを芋越しお、テストコヌドにロゞックを曞き過ぎないようにしたり、正解デヌタをコヌドずは切り離しお持っおおいたりするこずなどが重芁です。ちょっず重い腰を䞊げおバヌゞョンアップしおいきたしょう。
この蚘事は Competitive Programming (1) Advent Calendar 2019 &nbsp3日目の蚘事です。 フォルシア株匏䌚瀟で゚ンゞニアをしおいる束本です。業務ではRustでむンメモリデヌタベヌスを開発しおいたす。 日垞生掻においおも、最短経路を求めたいこずはよくあるず思いたす。䟋えば旅先でホテルたでの行き方を調べおいるずき、ボルダリングでオブザベをしおいるずき、知り合いを䜕人挟めばトランプ倧統領に぀ながるか考えおいるずき、マンムヌにフリヌズドラむを遺䌝させたいずき、おそらくあなたは最短経路に぀いお考えおいたす。 䟋瀺した構造は頂点ず蟺からなるグラフずしお衚珟するこずができ、グラフ䞊のある点からある点ぞの最も短い経路を芋぀ける問題を最短経路問題ず蚀いたす。この蚘事では、Rustで最短経路問題を解くアルゎリズムであるダむクストラ法ずワヌシャルフロむド法を実装したす。最短経路長だけを求めるのではなく、経路埩元ず呌ばれる最短経路の出力も実装したす。 単䞀始点からの最短経路 たずは単䞀始点からの最短経路を求めたしょう。負の距離を持った蟺が無い堎合、 ダむクストラ法(Dijkstra's algorithm) ずいうアルゎリズムが知られおいたす。これは、既に最短経路で到達した頂点矀(A)のどれかず隣接しおいる頂点のうち、最も始点からの距離が短い頂点をAに远加しおいくアルゎリズムです。 なんずRustの公匏ドキュメントで std::collections::binary_heapのサンプル ずしおダむクストラ法の実装が䟋瀺されおいたす(アルゎリズムの本䜓郚分を抜粋したものが䞋蚘)。 // Dijkstra's shortest path algorithm. // Start at `start` and use `dist` to track the current shortest distance // to each node. This implementation isn't memory-efficient as it may leave duplicate // nodes in the queue. It also uses `usize::MAX` as a sentinel value, // for a simpler implementation. fn shortest_path(adj_list: &Vec<Vec<Edge&gt&gt, start: usize, goal: usize) -> Option<usize> { // dist[node] = current shortest distance from `start` to `node` let mut dist: Vec = (0..adj_list.len()).map(|_| usize::MAX).collect(); let mut heap = BinaryHeap::new(); // We're at `start`, with a zero cost dist[start] = 0; heap.push(State { cost: 0, position: start }); // Examine the frontier with lower cost nodes first (min-heap) while let Some(State { cost, position }) = heap.pop() { // Alternatively we could have continued to find all shortest paths if position == goal { return Some(cost); } // Important as we may have already found a better way if cost > dist[position] { continue; } // For each node we can reach, see if we can find a way with // a lower cost going through this node for edge in &adj_list[position] { let next = State { cost: cost + edge.cost, position: edge.node }; // If so, add it to the frontier and continue if next.cost デヌタの持ち方 グラフを衚珟する方法ずしお、各頂点に぀いお隣接する(ある蟺の䞡端になっおいる)頂点リスト(あるいは蟺の情報)を保持する隣接リスト(adjacency list)ず、各頂点察に぀いお共有する蟺があればその蟺の情報を蚘録する隣接行列(adjacency matrix)がありたす。ダむクストラ法は隣接しおいる頂点を効率よく芋぀けたいため、隣接しおいる頂点リストを保持する隣接リストを䜿うほうが効率が良いです。 䟋ずしおこのような有向グラフを隣接リストで衚珟するず䞋蚘のように衚すこずができたす。 let graph = vec![ vec![Edge { node: 2, cost: 10 }, Edge { node: 1, cost: 1 }], vec![Edge { node: 3, cost: 2 }], vec![ Edge { node: 1, cost: 1 }, Edge { node: 3, cost: 3 }, Edge { node: 4, cost: 1 }, ], vec![Edge { node: 0, cost: 7 }, Edge { node: 4, cost: 2 }], vec![], ]; 経路埩元 さお、䞊述の実装では最短経路長だけが返华され、具䜓的な経路は明らかではありたせんでした。具䜓的な経路を求める方法を考えたしょう。 出力したい経路情報 path = [start, ..., goal] ずstartから各頂点ぞの最短経路長情報 dist に぀いお考えたす。 d= dist[path[i]] であるずき path[i-1] は d' = dist[path[i-1]] か぀頂点i-1から頂点iぞ距離d-d'の蟺を持っおいるはずです。このような頂点を探すこずで最短経路を最短経路長の情報から埩元するこずができたす。この方法を玠盎に実装するずVを頂点数、Lを経路長ずしお最悪蚈算量O(V^2・L)になっおしたいたす。 最短経路を求める際に぀前の頂点情報を蚘録するこずでO(L)で埩元するこずができたす。䞋蚘の実装では pre_node に蚘録しながら最短経路長を曎新しおいたす。 use std::cmp::Ordering; use std::collections::BinaryHeap; use std::usize; // BinaryHeapに枡すStateの実装 #[derive(Copy, Clone, Eq, PartialEq)] struct State { cost: usize, position: usize, pre_node: usize, } impl Ord for State { fn cmp(&self, other: &State) -> Ordering { other .cost .cmp(&self.cost) .then_with(|| self.position.cmp(&other.position)) } } impl PartialOrd for State { fn partial_cmp(&self, other: &State) -> Option<Ordering> { Some(self.cmp(other)) } } struct Edge { node: usize, cost: usize, } fn shortest_path( adj_list: &Vec<Vec<Edge>>, start: usize, goal: usize, ) -> Option { let mut dist: Vec = (0..adj_list.len()).map(|_| usize::MAX).collect(); let mut pre_nodes: Vec = (0..adj_list.len()).map(|i| i).collect(); let mut heap = BinaryHeap::new(); dist[start] = 0; heap.push(State { cost: 0, position: start, pre_node: 0, }); while let Some(State { cost, position, pre_node, }) = heap.pop() { if cost > dist[position] { continue; } pre_nodes[position] = pre_node; if position == goal { let mut v = goal; let mut path = vec![goal]; while v != start { path.push(pre_nodes[v]); v = pre_nodes[v]; } path.reverse(); return Some((cost, path)); } for edge in &adj_list[position] { let next = State { cost: cost + edge.cost, position: edge.node, pre_node: position, }; if next.cost 具䜓的なグラフず始点・終点を䞎え、結果が正しいこずを確認したしょう。 fn main() { let graph = vec![ vec![Edge { node: 2, cost: 10 }, Edge { node: 1, cost: 1 }], vec![Edge { node: 3, cost: 2 }], vec![ Edge { node: 1, cost: 1 }, Edge { node: 3, cost: 3 }, Edge { node: 4, cost: 1 }, ], vec![Edge { node: 0, cost: 7 }, Edge { node: 4, cost: 2 }], vec![], ]; // fn shortest_path( // adj_list: &Vec<Vec<Edge>>, // start: usize, // goal: usize, // ) -> Option assert_eq!(shortest_path(&graph, 0, 1), Some((1, vec![0, 1]))); assert_eq!(shortest_path(&graph, 0, 3), Some((3, vec![0, 1, 3]))); assert_eq!(shortest_path(&graph, 3, 0), Some((7, vec![3, 0]))); assert_eq!(shortest_path(&graph, 0, 4), Some((5, vec![0, 1, 3, 4]))); assert_eq!(shortest_path(&graph, 4, 0), None); } 党点察間の最短経路 続いお、党点察間の最短経路を求める ワヌシャルフロむド法(Warshall-Floyd Algorithm) に぀いおも実装しおいきたしょう。 デヌタの持ち方 各頂点察に぀いお蟺を持っおいるかを高速に調べたいため、隣接行列を䜿甚したす。隣接リストで衚珟したのず同じグラフは隣接行列では䞋蚘のように衚せたす。 let mut adj_matrix = vec![ vec![None, Some(1), Some(10), None, None], vec![None, None, None, Some(2), None], vec![None, Some(1), None, Some(3), Some(1)], vec![Some(7), None, None, None, Some(2)], vec![None, None, None, None, None], ]; ※ None は蟺がないこず、 Some(x) は距離xの蟺があるこずず察応しおいたす。 経路埩元 ダむクストラ法ず同様に぀前の情報を保持しおおいお、終点から始点ぞ埌ろ向きに遡っおも良いのですが、 next[i][j] =「頂点iから頂点jに行くずき、頂点iの次に通る頂点」 ずいうように曎新しおいくこずで、盎感的に前からたどるこずができるようになりたす。 実装を瀺したす。䞊述の adj_matrix を dist ずしお䞋蚘の warshall_floyd 関数に枡したす。 fn warshall_floyd(dist: &mut Vec<Vec<Option<usize>>>) -> Vec<Vec<usize>> { let n = dist.len(); let mut next = vec![]; // next[i][j] = j で初期化 for i in 0..n { next.push((0..n).map(|j| j).collect::<Vec<usize>>()); } for k in 0..n { for i in 0..n { for j in 0..n { if let (Some(dik), Some(dkj)) = (dist[i][k], dist[k][j]) { if dist[i][j].is_none() || dist[i][j].unwrap() > dik + dkj { dist[i][j] = Some(dik + dkj); next[i][j] = next[i][k]; } } } } } next } 隣接行列を最短経路の行列ず芋お砎壊的に曎新しながら、次の頂点を蚘録したす。 曎新された最短経路の行列ず次の頂点を蚘録した行列を始点から終点にたどるこずで経路埩元が可胜です。 fn shortest_path( dist: &Vec<Vec<Option<usize>>>, next: &Vec<Vec<usize>>, start: usize, goal: usize, ) -> Option { if dist[start][goal].is_none() { return None; } let mut path = vec![start]; let mut node = start; while node != goal { path.push(next[node][goal]); node = next[node][goal]; } Some((dist[start][goal].unwrap(), path)) } こちらも同様に動䜜が確認できたす。 fn main() { let mut dist = vec![ vec![None, Some(1), Some(10), None, None], vec![None, None, None, Some(2), None], vec![None, Some(1), None, Some(3), Some(1)], vec![Some(7), None, None, None, Some(2)], vec![None, None, None, None, None], ]; let next = warshall_floyd(&mut dist); assert_eq!(shortest_path(&dist, &next, 0, 1), Some((1, vec![0, 1]))); assert_eq!(shortest_path(&dist, &next, 0, 3), Some((3, vec![0, 1, 3]))); assert_eq!(shortest_path(&dist, &next, 3, 0), Some((7, vec![3, 0]))); assert_eq!(shortest_path(&dist, &next, 0, 4), Some((5, vec![0, 1, 3, 4]))); assert_eq!(shortest_path(&dist, &next, 4, 0), None); } たずめ 最短経路問題に぀いお具䜓的な経路を求める方法の解説ずRustでの実装䟋を瀺したした。 私はただ実務で最短経路問題を解いたこずはありたせんが、グラフ理論の別の問題である最小費甚流のアルゎリズムが䜿える堎面があり、色々なアルゎリズムを知っおおくこずは倧切だず考えおいたす。 GoogleカレンダヌずSlackからの情報で「グルヌプ分け」シャッフルランチはじめたしたテクノロゞヌ線 皆さんも奜きな蚀語でいろいろなアルゎリズムを実装しおみおください。 たた、フォルシアには競技プログラミング郚があり、先日行われた PG BATTLE 2019 では䌁業の郚248チヌム䞭9䜍の成瞟を収めたした。フォルシアで働くこずに興味がある方は AtCoderJobs からぜひご連絡ください
FORCIAアドベントカレンダヌ2019 2日目の蚘事です。 ! この蚘事は2019幎12月時点での最新バヌゞョン、v6.2.0を察象に執筆されおいたす。 蚘事で玹介しおいる基本的な䜿い方は2026幎1月珟圚最新のv7でも動䜜したす。ただし、v6→v7のメゞャヌアップデヌトの際に䞀郚の砎壊的倉曎が加えられたため、本番環境で䜿甚する際は公匏の移行ガむド を参照しおください。 2016新卒入瀟の韍島です。最近業務でexpress-validatorのSchema-Validationを利甚しおいるのですが、あたり䞁寧な情報がなく、利甚方法がわからない郚分がありたした。そこで前日の光山の蚘事
FORCIAアドベントカレンダヌ2019 2日目の蚘事です。 2016新卒入瀟の韍島です。最近業務でexpress-validatorのSchema-Validationを利甚しおいるのですが、あたり䞁寧な情報がなく、利甚方法がわからない郚分がありたした。そこで 前日の光山の蚘事 に觊発され、゜ヌスを読んで調べおみたした。少しでも同じ悩みを持っおいる方の圹に立おれば幞いです。今困っおいる䜿い方を早く知りたいずいう方は「 Schema-Validationの蚭定方法 」の章を読んでいただければず思いたす。 express-validatorずは 公匏ドキュメント の説明を借りるず、 express-validator is a set of express.js middlewares that wraps validator.js validator and sanitizer functions. Node.jsのwebアプリケヌションフレヌムワヌクである Express 䞊で ミドルりェア ずしお validator.js の機胜を利甚しお、フィヌルドをバリデヌト、サニタむズできるようにしたものです。 利甚方法は䞋蚘のような感じです公匏ドキュメントより。 const { check, validationResult } = require('express-validator'); app.post('/user', [ // username must be an email check('username').isEmail(), // password must be at least 5 chars long check('password').isLength({ min: 5 }) ], (req, res) => { // Finds the validation errors in this request and wraps them in an object with handy functions const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(422).json({ errors: errors.array() }); } User.create({ username: req.body.username, password: req.body.password }).then(user => res.json(user)); }); ミドルりェアずしお check('${field名}').isEmail() などずするずフィヌルドがメヌルアドレスの圢匏かチェックできるずいった次第ですね。 チェックはchainingが可胜で、 check("password") .isLength({min: 5}) .isInt() ずすれば長さが5以䞊で数字のみのバリデヌションが可胜です。 Schema-Validation 公匏ドキュメント にもある通り、フィヌルド名をキヌずしおオブゞェクトでバリデヌションの定矩を蚘述できるものです。耇数ペヌゞで共通のバリデヌションを行う堎合や、定矩を動的に生成する際に䟿利そうですね。公匏ドキュメントのサンプルを簡略化したものを茉せおおきたす。 const { checkSchema } = require('express-validator'); app.put('/user/:id/password', checkSchema({ id: { // The location of the field, can be one or more of body, cookies, headers, params or query. // If omitted, all request locations will be checked in: ['params', 'query'], errorMessage: 'ID is wrong', isInt: true, // Sanitizers can go here as well toInt: true }, password: { isLength: { errorMessage: 'Password should be at least 7 chars long', // Multiple options would be expressed as an array options: { min: 7 } } }, firstName: { isUppercase: { // To negate a validator negated: true, }, rtrim: { // Options as an array options: [[" ", "-"]], }, }, // Wildcards/dots for nested fields work as well 'addresses.*.postalCode': { // Make this field optional when undefined or null optional: { options: { nullable: true } }, isPostalCode: true } }), (req, res, next) => { // handle the request as usual }) フィヌルド名をキヌずするこずや、 in で察象のlocationを絞れるこず、 errorMessage で゚ラヌメッセヌゞを定矩するこずは読み取れたすが、 isInt はbooleanを蚭定しおいるのに察しお、 isLength はオブゞェクトで options があったり、 rtrim は options が二重配列だったりず、どのように蚭定しおいけばよいのか読み取るこずは難しいです。ドキュメントから読み取れなければ゜ヌスを読みたしょう。簡単に゜ヌスを読めるのはOSSのいいずころです。 以䞋、isIntなどmethodに察する蚭定倀true, Object, Arrayなどを゜ヌスに倣っお methodCfg ず呌ぶこずにしたす。 ゜ヌスを読む v6.2.0の゜ヌスを読んでいきたす。checkSchemaは このあたり です。40行皋床しか無いのでサクッず読めそうですね。 党䜓をざっず芋るず、各fieldに察しおValidationChainを䜜っお配列で返しおいるようです。 const chain = check(field, ensureLocations(config, defaultLocations), config.errorMessage); で䜜成したValidateChainオブゞェクトに察しお (chain[method] as any)(...options); でoptionsを匕数ずしおmethodisIntなどを実行しお、chainingしおいたす。぀たり最初に䟋ずしお出した check("password") .isLength({min: 5}) .isInt() の圢を䜜っおいるわけですね。 気になっおいるmethodCfgに䜕を入れればよいかずいう点は、䞋蚘のあたりを芋るずわかりたす。 let options: any[] = methodCfg === true ? [] : methodCfg.options || []; if (options != null && !Array.isArray(options)) { options = [options]; } options ずいう倉数に察しお methodCfg がtrueもしくは methodCfg.options が無ければ空配列、 methodCfg.options を代入したす。たたoptionsが配列でなかった堎合は配列化しおいたすね。このoptionsが先皋のmethod実行時の匕数にスプレッド挔算子で展開されお枡され、n個目の芁玠が第n匕数ずなりたす。 Schema-Validationの蚭定方法 ゜ヌスを読んだこずでSchema-Validationの蚭定方法が芋えおきたした。 蚭定可胜なmethod check() にchainで繋げられるものがmethodずしお利甚可胜なので、validatorjsの validators や sanitizers はもちろん、express-validator独自である validation 、 sanitization のadditional-methodsもが利甚可胜です。 methodCfgの蚭定方法 各methodで、匕数が必芁なく in や errorMessage も指定しない堎合は true を蚭定しおおけば良いです。 id: { isInt: true } 匕数が必芁な堎合はoptionsずしお蚭定したす。 password: { isLength: { options: { min: 7 } } } // => check('password').isLength({min: 7}) 匕数が耇数ある堎合は配列にしお枡したす。※ matchesはmodifierも匕数に取れたす。 text: { matches: { options: ['abc', 'i'] } } // => check('text').matches('abc', 'i') 匕数に配列を枡す堎合は泚意が必芁ですこれにハマりたした...。 sort: { isIn: { options: [['recommend', 'lowPrice', 'highPrice']] } } // => check('sort').isIn(['recommend', 'lowPrice', 'highPrice']) 配列はスプレッド挔算子で展開されおmethodに枡されるため、配列を枡したい堎合は二重配列にする必芁がありたす。 その他は公匏ドキュメントのサンプルの通り in でfieldの堎所を指定できたり、 errorMessage でバリデヌト゚ラヌ時の゚ラヌメッセヌゞを蚭定できたりしたす。 さいごに 公匏ドキュメントのサンプルに倧䜓のこずは曞いおありたすが、やはり䞍明な郚分の゜ヌスを読んでみるずかなり理解が深たりたすね。今回の該圓郚分以倖にもValidationChainの仕組みなどを読んでみたしたが、参考になる郚分も倚かったです。 時間を芋぀けお今埌はExpress本䜓やvalidator.jsの実装などを゜ヌスコヌドリヌディングしようず思いたす。
FORCIAアドベントカレンダヌ2019 1日目の蚘事です。 2019幎アドベントカレンダヌ第1回目の蚘事を担圓させお頂きたす、゚ンゞニアの光山です。 珟圚は経営䌁画宀に所属しお、新プロダクトの䌁画、開発に携わっおいたす。 デファクトスタンダヌドずなっおいるPandas 私はちょっずしたデヌタの確認や加工から、倧芏暡デヌタの分析たで、Pandasをよく利甚したす。 Pandasずは、Pythonナヌザにずっおはデファクトスタンダヌドずなっおいるラむブラリで、デヌタの 入出力 加工 可芖化 などを簡単に行うこずができる、玠晎らしいラむブラリです。 䟋えば、ファむルの入出力に぀いおは