Ansibleを触ってみた
Ansibleを触ったのでメモ
Ansibleとは
エージェントレス型の構成管理ツール。エージェント型とエージェントレス型では以下のような違いがある。
◯エージェントレス型
管理対象のサーバにエージェントを入れる必要がない
管理対象のサーバへの接続情報が必要になる
管理端末から構成反映の処理を行う必要がある
◯エージェント型
管理対象のサーバにエージェントを入れる必要がある
起動時などに自動で構成が適用される
複数の設定が異なるサーバを管理する場合は、管理用サーバを別で持てるエージェントレス型の方が扱いやすいような気がします。それからAnsibleはエージェント型のchefやpuppetより後発のため洗練されている部分がありますし、RedHatがAnsibleを買収したため今後も開発が続けられることが期待できます。
0.インストール環境準備
Vagrant.configure("2") do |config| config.vm.define :node1 do |node| node.vm.box = "geerlingguy/centos7" node.vm.network "forwarded_port", guest: 22, host: 2001, id: "ssh" node.vm.network "private_network", ip: "192.168.33.11" end config.vm.define :node2 do |node| node.vm.box = "geerlingguy/centos7" node.vm.network "forwarded_port", guest: 22, host: 2002, id: "ssh" node.vm.network "forwarded_port", guest: 80, host: 8000, id: "http" node.vm.network "private_network", ip: "192.168.33.12" end end
ssh接続準備
管理用端末が管理対象の端末を操作する際にはsshのプロトコルを使用するので、鍵を配置する等の事前準備は必要になると思います。
1.Ansibleインストール
# vagrant ssh node1 # sudo yum install epel-release # sudo yum install ansible
2.管理対象ホストの設定とAnsibleの疎通確認
管理対象ホストの設定
$ echo 192.168.33.12 > hosts
pingでの疎通確認
node1の方で実行 $ ansible -i hosts 192.168.33.12 -m ping 192.168.33.12 | success >> { "changed": false, "ping": "pong" }
ansibleコマンドで連携対象サーバ上で任意のコマンドを実行する
$ ansible -i hosts 192.168.33.12 -a 'uname -r' 192.168.33.12 | success | rc=0 >> 2.6.32-431.el6.x86_64
任意のコマンドを実行
ansible -i hosts 192.168.33.12 -m yum -s -a name=telnet
ansible yumは以下でドキュメントを確認できる
$ ansible-doc yum
3.Playbookの作成から実行まで
Playbookの作成
# vi simple-playbook
--- - hosts: test-servers become: yes tasks: - name: be sure httpd is installed yum: name=httpd state=installed - name: be sure httpd is running and enabled service: name=httpd state=started enabled=yes
Playbookの文法チェック
ansible-playbookに–syntax-checkのオプションをつけて実行する
ansible-playbook -i hosts simple-playbook.yml --syntax-check
Playbookのタスク一覧を確認する
–list-tasksオプションをつけてplyabookのnameを一覧表示してタスクを確認
[vagrant@localhost ~]$ ansible-playbook -i hosts simple-playbook.yml --list-tasks playbook: simple-playbook.yml play #1 (test-servers): test-servers TAGS: [] tasks: be sure httpd is installed TAGS: [] be sure httpd is running and enabled TAGS: []
Playbookの実行
[vagrant@localhost ~]$ ansible-playbook -i hosts simple-playbook.yml PLAY [test-servers] ************************************************************ TASK [setup] ******************************************************************* ok: [192.168.33.12] TASK [be sure httpd is installed] ********************************************** changed: [192.168.33.12] TASK [be sure httpd is running and enabled] ************************************ changed: [192.168.33.12] PLAY RECAP ********************************************************************* 192.168.33.12 : ok=3 changed=2 unreachable=0 failed=0
ansibleには冪等星性(ある操作を何度実行しても常に結果が同じになる)が備わっており、上記実行例ではchangedの部分が新しく実行された部分になるようです。これでnode2のサーバにPlaybookで指定した通りapacheがインストールされました。
node2内でhttpdが動作しているか確認
[vagrant@localhost ~]$ sudo systemctl status httpd
たったこれだけで、Apacheがインストールできました。chefはほとんど触ったことありませんが、それよりもシンプルで使いやすい気がします。
Playbook入門
yumを使う
tasks: - name: be sure httpd is installed yum: name=httpd state=installed
nameでインストール対象を指定する。state=installedはインスートールされているか確認し、されていなかったらインストールする。state=latestでは最新が入っているか確認し最新でなければインストールする。
コマンドを実行する
tasks: - name: disable selinux command: /sbin/setenforce 0
ファイルを編集する
文字列を置換する
tasks: - name: set selinux permisive replace: > dest=/etc/selinux/config regexp='SELINUX=enforcing' replace='SELINUX=disabled'
http://docs.ansible.com/ansible/replace_module.html
ホスト端末のファイルを送る
事前に転送するファイルを準備
echo hogehoge > copyFile
tasks: - name: copy test copy: > src=/home/vagrant/copyFile dest=/home/vagrant/copyFile
vagrant の基礎
ansibleを触る環境を準備するためにvagrantを触ったのでその時のメモ
mac環境へのインストール
1.公式サイトからインストーラをダウンロードする。
https://www.vagrantup.com/downloads.html
/usr/local/bin/vagrantにインストールされた
~ which vagrant
/usr/local/bin/vagrant
2.vagrantfileの作成
# mkdir -p ~/vagrantspace/vagrant_getting_started # cd ~/mywork/vagrantspace/vagrant_getting_started # vagrant init
vagrant initでVagrantfileが作られる。Vagrantfileはvagrantのバージョンコントロールを行うためのファイルになる。
3.boxの追加
公式の手順に従ってhashicorp/precise64のboxを追加する。
# vagrant box add hashicorp/precise64
別のboxを追加する場合はatlasが提供している以下のダウンロードサイトから探すことができる。
https://atlas.hashicorp.com/boxes/search?_ga=1.197033387.661843880.1488614682
自前でisoを用意したい場合は、以下を参考にする。
http://kan3aa.hatenablog.com/entry/2015/05/29/120212
vagrantで使用するダウンロード済みboxの一覧を確認する場合は以下を実行する。
# vagrant box list
4.vmを起動して接続する
VirtualBoxをインストールしていないのであれば、公式からインストーラーをダウンロードして入れておく。
https://www.virtualbox.org/wiki/Downloads
・vmの起動
# vagrant up
# vgrant ssh
・vm接続を解除する
# logout 又は # exit
vagrant基本コマンド
再起動 vagrant reload ステータス確認 vagrant status 一時停止 vagrant suspend 一時停止からの復帰 vagrant resume 停止 vagrant halt 起動 vagrant up boxの初期化 vagrant init boxの削除 vagrant destory
vagrantの起動状態を確認する
# vagrant plugin install # vagrant global-status
ディレクトリの共有
通常Vagrantfileはssh接続後の/vagrant/Vagrantfileにマウントされる。
# ls /vagrant/
/vagrantディレクトリ直下にファイルを作成したらvmのホスト側のVagrantfileがあったディレクトリにファイルが作られる動きをする。
# touch /vagrant/hoge # exit # ls
5.vagrant起動時のスクリプトでapacheのインストール
以下のapacheインストール用スクリプトをbootstarp.shのファイル名でVagrantfileがあるディレクトリに保存しておく。 # vi /vagrant/bootstrap.sh
#!/usr/bin/env bash apt-get update apt-get install -y apache2 if ! [ -L /var/www ]; then rm -rf /var/www ln -fs /vagrant /var/www fi
それから設定ファイルを編集し/vagrantディクトリにbootstrap.shをvagrant起動時に実行するスクリプトにす指定する。
Vagrant.configure("2") do |config| config.vm.box = "hashicorp/precise64" config.vm.provision :shell, path: "bootstrap.sh" end
次にvagrant reloadして再起動
# vagrant reload --provision # vagrant up
vagrantにssh接続している状態でapacheにアクセスできるか確認
# wget -qO- 127.0.0.1
6.ポートフォワード
ホスト端末からvagrantへアクセスできるようにポートフォワードを設定する
# vi Vagrantfile
config.vm.network "forwarded_port", guest: 80, host: 4567
ホスト端末からブラウザでlocalhost:4567にアクセスして動作確認
PyMongo覚書
Pymongoの覚書になります、と言ってもmongoのクライアントから直接実行するのとほとんど同じように使うことができます。
モジュールインポート
import json import re import pymongo import bson from bson.son import SON from bson.code import Code from pymongo import MongoClient from bson.code import Code from datetime import datetime, timedelta
DB接続
事前に外部接続を許可する等して接続できるようにしておく必要があります。
mongo_client = MongoClient('ホスト名:27017') db=mongo_client['DB名']
コレクションの一覧確認
for c in db.collection_names(): print(c) || *** インサート >|python| # インサート db.book_collection.insert_one( { 'category': 'it', 'title': '入門 python3', 'synopsis': 'Pythonが誕生して四半世紀。データサイエンスやウェブ開発、セキュリティなどさまざまな分野でPythonの人気が急上昇中です。', 'publish': 'オライリー', 'authors': ['Bill Lubanovic', '斎藤康毅', '長尾高弘'], 'created': datetime.strptime("2015-12-01", "%Y-%m-%d") } )
以下の検索、集約では上でインサートしたフィールド情報を持ったコレクションを使って行います。
検索
# 条件なし検索 for c in db.book_collection.find().limit(5): print(c) # ○条件付き created > "2016-09-01" for c in db.book_collection.find({'created': {'$gt': datetime.strptime("2016-09-01", "%Y-%m-%d")}}).limit(5): print(c) # ◯複数条件 created > "2016-09-01" と category = 'fantasy' for c in db.book_collection.find({ 'created': {'$gt': datetime.strptime("2016-09-01", "%Y-%m-%d")}, 'category': 'fantasy' }).limit(5): print(c) #◯条件 authorsに田中太郎が含まれる for c in db.book_collection.find({'authors': {"$in":[u"田中太郎"]}}).limit(5): print(c) # ◯条件指定を変数に格納する created > "2016-10-01" condition={'created': {'$gt': datetime.strptime("2016-10-01", "%Y-%m-%d")}} for c in db.book_collection.find(condition).limit(5): print(c) # ソート # ◯createdで昇順にソート for c in db.book_collection.find({},{'title': 1, 'created': 1} ).sort('created', pymongo.DESCENDING): print(c)
集約
# 集約 # ◯publishで集約した件数を表示 for c in db.book_collection.aggregate([ { '$group' : {'_id': "$publish", 'count':{ '$sum': 1 } }} ]): print(c) # ◯createdの年、月、日で集約した件数を表示 for c in db.book_collection.aggregate([ { '$group' : {'_id': {'month': { '$month': '$created'}, 'day': { '$dayOfMonth': '$created' }, 'year': { '$year': "$created" }}, 'count':{ '$sum': 1 } }} ]): print(c) #◯publichとcreatedの年で集約した件数を表示 for c in db.book_collection.aggregate([ { '$group' : {'_id': {'publish': "$publish", 'year': { '$year': "$created" }}, 'count':{ '$sum': 1 } }} ]): print(c) # ◯createdの年、月、日で集約してソートした件数を表示 for c in db.book_collection.aggregate([ { '$group' : {'_id':{'year': { '$year': "$created" }, 'month': { '$month': "$created" } }, 'count':{ '$sum': 1 } }}, { '$sort': {'_id.month':1}} ]): print(c) # ◯createdの年、月、日で集約し件数上位3件を表示 for c in db.book_collection.aggregate([ { '$group' : {'_id':{'year': { '$year': "$created" }, 'month': { '$month': "$created" } },'count':{ '$sum': 1 } }}, { '$sort': {"count":-1}}, { '$limit': 3 } ]): print(c) # ◯authorに田中太郎が含まれるものをpublichで集約して表示 for c in db.book_collection.aggregate([ { '$match': {'authors': {"$in":['田中太郎']}}}, { '$group' : {'_id': "$publish", 'count':{ '$sum': 1 } }}, { '$sort': {"count":-1}} ]): print(c) # マップリデュースによるpublishごとの集約 mapper = Code(""" function () { emit(this.publish, 1); } """) reducer = Code(""" function (key, values) { var total = 0; for (var i = 0; i < values.length; i++) { total += values[i]; } return total; } """) result = db.book_collection.map_reduce(mapper, reducer, "myresults") for doc in result.find(): print(doc['_id'], ':', doc['value'])
MongoDB覚書
MongDB
MongoDBをcliから操作時のメモです。Mngoのインストール等を試した環境はCentOS7.2になります。
CentOS7環境でのインストール
◯リポジトリ追加 公式ページを確認しyumのリポジトリを追加する(https://www.mongodb.com/download-center#community)
/# vi /etc/yum.repos.d/mongodb.repo
[mongodb-org-3.4] name=MongoDB Repository baseurl=https://repo.mongodb.org/yum/amazon/2013.03/mongodb-org/3.4/x86_64/ gpgcheck=1 enabled=1 gpgkey=https://www.mongodb.org/static/pgp/server-3.4.asc
MongoDB起動
# systemctl start mongod
mongo接続
# mongo
mongo接続終了
exit
外部アクセスを許可する(IP指定なし)
/# vi /etc/mongod.conf
net: port: 27017 #bindIp: 127.0.0.1,192.168.1.15
bindIpを未指定または"bind_ip: 0.0.0.0"にすることで接続元のIPアドレスを制限しないようにできます。
外部アクセスを許可する(IP指定あり)
/# vi /etc/mongod.conf
net: port: 27017 bindIp: 127.0.0.1,192.168.1.15
bindIpを指定することでアクセス元のIPアドレスを制限できます。
bsonのリストア
# mongorestore –host localhost –db DB名 bsonファイル
DB一覧表示
show dbs
DBの選択
use DB名
DBの作成
use DB名
コレクションの作成
db.createCollection(“test_collection”)
DBの削除
use DB名 db.dropDatabase()
コレクション一覧確認
show collections
挿入
db.コレクション名.insert({ フィールド名:値,,,, }) サンプル db.book_collection.insert({ category: 'fantasy', title: 'タイトル1', synopsis: 'あらすじ1', publish: '田中出版', authors: ['田中太郎', '佐藤賢作'], created: ISODate("2016-11-29"), })
検索
◯テストデータ db.book_collection.insert({ category: 'fantasy', title: 'タイトル1', synopsis: 'あらすじ1', publish: '田中出版', authors: ['田中太郎', '佐藤賢作'], created: ISODate("2016-11-29"), }) db.book_collection.insert({ category: 'horror', title: 'ホラー1', synopsis: ' ホラー1', publish: '竹中出版', authors: ['田中太郎', '伊藤優一'], created: ISODate("2016-08-21"), }) db.book_collection.insert({ category: 'fantasy', title: 'ファンタジー2', synopsis: ' ファンタジー2', publish: '田中出版', authors: ['加藤信之'], created: ISODate("2016-07-15"), }) db.book_collection.insert({ category: 'SF', title: 'SF1', synopsis: ' SF1', publish: 'はなまる文庫', authors: ['田中賢介'], created: ISODate("2016-09-15"), }) db.book_collection.insert({ category: 'SF', title: 'SF2', synopsis: ' SF2', publish: 'はなまる文庫', authors: ['田中賢介'], created: ISODate("2016-09-16"), })
○一度に取得する件数変更(デフォルトでは20件ずつのデータ取得になっていたはず) DBQuery.shellBatchSize = 200 ○条件なし db.コレクション名.find() ◯条件付き x > 1 db.コレクション名.find({x: {$gt: 1}}) ○条件付き created > "2011-11-01" db.コレクション名.find({created: {$gt: ISODate("2011-11-01T00:00:00+09:00")}}) ◯複数条件 created > "2011-11-01" と category = 'fantasy' db.コレクション名.find({ created: {$gt: ISODate("2011-11-01T00:00:00+09:00")}, category: 'fantasy' } ) ・sample db.book_collection.find({ created: {$gt: ISODate("2011-11-01T00:00:00+09:00")}, category: 'fantasy' } ) ◯条件 authorsに田中太郎が含まれる db.コレクション名.find({authors: {"$in":['田中太郎']}}) ◯条件 作者の名前が田中で前方一致する db.コレクション名.find({authors: {"$in":[/^田中/]}}) */をつける時はシングルクォーテーションを外さないと見つからなかった ◯表示するフィールドを指定する titleのみ表示 db.コレクション名.find({}, {'title': 1}) ◯条件指定を変数に格納する created > "2016-08-01" condition={created: {$gt: ISODate("2016-08-01T00:00:00+09:00")}} db.コレクション名.find(condition) ◯表示条件を変数に格納する titleのみ表示 view={title:1} db.コレクション名.find({}, view)
ソート
◯createdで昇順にソート db.コレクション名.find().sort({created:1})
集約
◯publishで集約した件数を表示 db.コレクション名.aggregate([ { $group : {_id: "$publish", count:{ $sum: 1 } }} ]) ◯createdの年、月、日で集約した件数を表示 db.コレクション名.aggregate([ { $group : {_id: {month: { $month: "$created" }, day: { $dayOfMonth: "$created" }, year: { $year: "$created" }}, count:{ $sum: 1 } }} ]) ◯publichとcreatedの年で集約した件数を表示 db.コレクション名.aggregate([ { $group : {_id: {publish: "$publish", year: { $year: "$created" }}, count:{ $sum: 1 } }} ]) ◯createdの年、月、日で集約してソートした件数を表示 db.コレクション名.aggregate([ { $group : {_id:{year: { $year: "$created" }, month: { $month: "$created" } },count:{ $sum: 1 } }}, { $sort: {"_id.month":1}} ]) ◯createdの年、月、日で集約し件数上位3件を表示 db.コレクション名.aggregate([ { $group : {_id:{year: { $year: "$created" }, month: { $month: "$created" } },count:{ $sum: 1 } }}, { $sort: {"count":-1}}, { $limit: 3 } ]) ◯authorに田中太郎が含まれるものをpublichで集約して表示 db.コレクション名.aggregate([ { $match: {authors: {"$in":['田中太郎']}}}, { $group : {_id: "$publish", count:{ $sum: 1 } }}, { $sort: {"count":-1}} ])
CentOS7でユーザのコマンドに制限をかける
以前特定のコマンドしか実行できないユーザを作る必要があった時にrbashを使用したのが便利だったので、その時のメモになります。確認した環境はCentOS 7.2になります。
1. rbashを使えるようにする
# ln -s /bin/bash /bin/rbash
# vi /etc/shells
/bin/rbash ←この行を追加
2. 実行シェルの変更
# su 制限するユーザ
$ cssh
新しいシェル [/bin/bash]: /bin/rbash ←「/bin/rbash」を入力
3. bash_profileの所有者変更および
rootにユーザーを切り替えてtenda_keieiのbash_profileの所有者と所有グループを変更
# chown root:root ./bash_profile
# chmod 755 .bash_profile
4. 実行可能コマンドの設定
# mkdir /home/制限するユーザ/command
# ln -s /usr/bin/java /home/制限するユーザ/java
# ln -s /usr/bin/date /home/制限するユーザ/date
# ln -s /usr/bin/mv /home/制限するユーザ/mv
5. 実行可能コマンドをpathに追加
# vi /home/tenda_keiei/.bash_profile
# settings for rbahs # PATH=$PATH:$HOME/.local/bin:$HOME/bin PATH=/home/制限するユーザ export PATH=$PATH:/home/制限するユーザ/command export PATH
javaで作ったバッチを実行するときとか
・実行するスクリプトの置き場所もpathに追加しておく
・javaコマンドでクラスパスを指定する場合、bash_profileないで事前にクラスパス用の環境変数を用意しておき、それを使うようにする(rbashでは/を含んだコマンドを実行できない!)
◯その他
コマンド制限かけたユーザでバッチを実行する場合は以下の点に注意が必要になります。
・cronはrbashを有効にする前であれば設定できる。
・cronの場合にbash_profileで設定した環境変数が使えなかったら、rootユーザとかでにcronを設定しておきsourceで読み込ませる。(制限ユーザに対してsourceコマンドを有効にすることでも対応可能かは未確認です。)
0 3 * * * source /home/ruser/.bash_profile; ◯◯◯.sh
Chainerで簡単なクラス分類をしてみる
Chainerを試してみるために簡単なサンプルプログラムを動かしてみたいと思います。
まず必要なライブラリをインポートします。
import numpy as np import chainer from chainer import cuda, Function, gradient_check, Variable from chainer import optimizers, serializers, utils from chainer import Link, Chain, ChainList import chainer.functions as F import chainer.links as L
それから、学習用データを読み込みます。今回はsklearnからアヤメのデータを使って4入力(がくの長さ、幅と茎の長さ、幅)からアヤメの種類(setosa, versicolor, virginica)を分類できるように試してみます。
# Set data from sklearn import datasets iris = datasets.load_iris() X = iris.data.astype(np.float32) Y = iris.target.astype(np.float32) N = Y.size Y2 = np.zeros(3 * N).reshape(N,3).astype(np.float32) for i in range(N): Y2[i,np.int(Y[i])] = 1.0 index = np.arange(N) xtrain = X[index[index % 2 != 0],:] ytrain = Y2[index[index % 2 != 0],:] xtest = X[index[index % 2 == 0],:] yans = Y[index[index % 2 == 0]]
モデルの定義として4入力、3出力で中間層はなしのものを定義しています。入力に対して出力が簡単に結びつくような今回のケースでは中間層は必要なさそうに思います。多クラスの分類としてはソフトマックス関数を使用し誤差関数として二乗誤差を使用しています。ソフトマックスを利用した多クラス分類等では交差エントロピーを使うように思っていたのですが、この辺りはちゃんと学習して使いこなせるようにしたいと思います。
# Define model class IrisRogi(Chain): def __init__(self): super(IrisRogi, self).__init__( # 入力4軸(がくの長さ、幅と茎の長さ、幅) 出力3クラス分類(irisの種類setosa, versicolor, virginica) l1=L.Linear(4,3), ) def __call__(self,x,y): # 順伝番した結果に対してmean_squared_errorで二乗誤差求めている return F.mean_squared_error(self.fwd(x), y) def fwd(self,x): # 多クラス分類(irisの種類)のための各ユニットの出力としてソフトマックスを使用する return F.softmax(self.l1(x)) # Initialize model model = IrisRogi() # パラメータの最適化で比較的高速に良い値を出すadamを使用する optimizer = optimizers.Adam() optimizer.setup(model)
それから学習を行います。
# Learn n = int(index.size / 2) bs = 25 for j in range(5000): sffindx = np.random.permutation(n) accum_loss = None for i in range(0, n, bs): x = Variable(xtrain[sffindx[i:(i+bs) if (i+bs) < n else n]]) y = Variable(ytrain[sffindx[i:(i+bs) if (i+bs) < n else n]]) model.zerograds() # 勾配を初期化 loss = model(x,y) # 順方向に計算し誤差を算出 loss.backward() # 逆伝番で勾配の向きを計算 optimizer.update() # 逆伝番で得た勾配からパラメータを更新する
学習で得たパラメータを利用しテストをしてみます。
# Test xt = Variable(xtest, volatile='on') yy = model.fwd(xt) ans = yy.data nrow, ncol = ans.shape ok = 0 for i in range(nrow): cls = np.argmax(ans[i,:]) # print( ans[i,:], cls) if cls == yans[i]: ok += 1 print (ok, "/", nrow, " = ", (ok * 1.0)/nrow)
自分の環境で動かしてみたところ、73/75の精度で正解していたのでちゃんと動いてることは確認できます。
Scalaのfor式とflatMapについて
自分は職場では良くjava7でプログラミングするのでjava8のStream APIなどを触ることがないのですが、そのような状態だとscalaのfor式やflatMapに抵抗があったので簡単に使い方だけでもおさらいしてみたいと思います。
まず、動作確認で使うクラスは以下になります。Todo管理用のクラスを用意し、特定の作業者のタスクを検索するプログラムを試します。
case class Todo( TodoId: Int, CategoryId: Int, Title: String, Text: String, Order: Int, worker: List[String]){ override def toString = Text } case class TodoCategory( CategoryId: Int, Category: String, Order: Int, TodoList: List[Todo])
それから動作確認ようデータは以下になります。
val collecting_doc = Todo(1, 1, "collect data", "資料を集める", 1, List("sato", "nakata")) val prepare_tool = Todo(2, 1, "prepare tool", "ツールを準備する", 2, List("kato", "nakata")) val investigate = Todo(3, 2, "investigate", "調査する", 1, List("sato")) val summarize_result = Todo(4, 2, "summarize result", "調査結果をまとめる", 2, List("kato", "nakata")) val review = Todo(5, 2, "review", "レビューを受ける", 3, List("sato", "nakata")) val presentation = Todo(6, 3, "presentation", "発表する", 1, List("sato")) val improve = Todo(7, 3, "improve", "改善する", 2, List("sato")) val done = TodoCategory(1, "完了", 1, List(collecting_doc, prepare_tool)) val doing = TodoCategory(2, "実施中", 2, List(investigate, summarize_result, review)) val next = TodoCategory(3, "待ち", 3, List(presentation, improve)) val allTodo: List[TodoCategory] = List(done, doing, next)
for式を使い特定の作業者のタスクを検索する場合は以下のようになります。
def workerTaskFor(name: String): List[Todo] ={ for( category <- allTodo; todo <- category.TodoList; tw <- todo.worker if tw eq name ) yield todo }
"category <- allTodo;"の部分でallTodoの一つずつの要素がcategoryには入ります。同様にtw <- todo.workerではtodoに設定されている一人一人のwoerkerが入り if tw eq nameにより名前での絞り込みを行っています。最後にyield todoの部分で絞り込まれたtodoでListを作成しています。
次にflatmapを使った場合は以下のようになります。
def workerTaskFlatmap(name: String): List[Todo] = { allTodo flatMap(category => category.TodoList flatMap( todo => todo.worker withFilter (worker => worker eq name) map( worker => todo ) ) ) }
やっていることはfor式と同じなのですがworkerの絞り込みにwithFilterというコレクションAPIを使用しています。コレクションAPIはTraverable を実装するコレクションクラスで利用することができ、これに慣れておくとデータの操作周りがすごい快適になると思います。自分の実感からもこの辺りになれるかどうかでScalaに感じる抵抗がぐっと変わってくるのかなという気がします。
最後にプログラムの全体は以下のようになりました。
object MyStudy { case class Todo(TodoId: Int, CategoryId: Int, Title: String, Text: String, Order: Int, worker: List[String]){ override def toString = Text } case class TodoCategory(CategoryId: Int, Category: String, Order: Int, TodoList: List[Todo]) val collecting_doc = Todo(1, 1, "collect data", "資料を集める", 1, List("sato", "nakata")) val prepare_tool = Todo(2, 1, "prepare tool", "ツールを準備する", 2, List("kato", "nakata")) val investigate = Todo(3, 2, "investigate", "調査する", 1, List("sato")) val summarize_result = Todo(4, 2, "summarize result", "調査結果をまとめる", 2, List("kato", "nakata")) val review = Todo(5, 2, "review", "レビューを受ける", 3, List("sato", "nakata")) val presentation = Todo(6, 3, "presentation", "発表する", 1, List("sato")) val improve = Todo(7, 3, "improve", "改善する", 2, List("sato")) val done = TodoCategory(1, "完了", 1, List(collecting_doc, prepare_tool)) val doing = TodoCategory(2, "実施中", 2, List(investigate, summarize_result, review)) val next = TodoCategory(3, "待ち", 3, List(presentation, improve)) val allTodo: List[TodoCategory] = List(done, doing, next) def main(args: Array[String]): Unit ={ workerTaskFor("nakata") foreach(println) workerTaskFlatmap("nakata") foreach(println) } def workerTaskFor(name: String): List[Todo] ={ for( category <- allTodo; todo <- category.TodoList; tw <- todo.worker if tw eq name ) yield todo } def workerTaskFlatmap(name: String): List[Todo] = { allTodo flatMap(category => category.TodoList flatMap( todo => todo.worker withFilter (worker => worker eq name) map( worker => todo ) ) ) } }