第12回北海道開発オフに参加した
今日は第12回北海道開発オフでした。
開発オフはいつも通り、勉強会というよりはサークルのようなものなので、やってる事はいつも通り「各個人が黙々と開発して、ランチして、成果発表する」なのですが、今回はなんと過去最多の20人の参加!すごい!人が増えてもやることは変わりませんけど(笑)でもこの「もくもくやる」という事の楽しさが、少しでも多くの人に伝わってきたのかなぁ、なんて思います。
さて、今日の僕の成果ですが、しばらく中途半端になっていた、我が愚息@dabesaの教育です。結構作り込みを変えたのですが、大きくは以下のような構造をしています。
---
・Twitter絡みの部分は全てTwitter4Rを使用。
・@smokeymonkeyのFriend Timelineから最新200ポストを取得。
・元となるデータファイルに、最新200ポストをマージ。このデータファイルは常に新しいほうから5000個のポストを保持。
・5000個のポストをMeCabで形態素解析して、マルコフテーブルを生成。このとき、各ポストの一番最初の単語にフラグをつけておく。
・フラグをつけられた「各ポストの一番最初の単語」をランダムに抽出し、そこから文章を生成。
・生成された文章のうち、一部を北海道弁に置換。
・Twitterにポスト。
・Replyは、Replyでもらったポストから名詞・動詞を抽出し、その単語を元に文章を生成して返す。ただし文章を生成出来なかった場合は複数パターンの応答を返す。
・一日2回(9時・21時)、Google TrendsからHottrendキーワードをポストする。
---
ソース自体は素人の日曜大工なので腐ってるのですが、まぁ以前よりはちゃんと日本語っぽい言葉を発することが出来てるんじゃないかな、と思います。今後も改善していきたいですね。
人様にさらせるほど立派なものでは無いですが、一応ソースは以下に。
開発オフはいつも通り、勉強会というよりはサークルのようなものなので、やってる事はいつも通り「各個人が黙々と開発して、ランチして、成果発表する」なのですが、今回はなんと過去最多の20人の参加!すごい!人が増えてもやることは変わりませんけど(笑)でもこの「もくもくやる」という事の楽しさが、少しでも多くの人に伝わってきたのかなぁ、なんて思います。
さて、今日の僕の成果ですが、しばらく中途半端になっていた、我が愚息@dabesaの教育です。結構作り込みを変えたのですが、大きくは以下のような構造をしています。
---
・Twitter絡みの部分は全てTwitter4Rを使用。
・@smokeymonkeyのFriend Timelineから最新200ポストを取得。
・元となるデータファイルに、最新200ポストをマージ。このデータファイルは常に新しいほうから5000個のポストを保持。
・5000個のポストをMeCabで形態素解析して、マルコフテーブルを生成。このとき、各ポストの一番最初の単語にフラグをつけておく。
・フラグをつけられた「各ポストの一番最初の単語」をランダムに抽出し、そこから文章を生成。
・生成された文章のうち、一部を北海道弁に置換。
・Twitterにポスト。
・Replyは、Replyでもらったポストから名詞・動詞を抽出し、その単語を元に文章を生成して返す。ただし文章を生成出来なかった場合は複数パターンの応答を返す。
・一日2回(9時・21時)、Google TrendsからHottrendキーワードをポストする。
---
ソース自体は素人の日曜大工なので腐ってるのですが、まぁ以前よりはちゃんと日本語っぽい言葉を発することが出来てるんじゃないかな、と思います。今後も改善していきたいですね。
Ruby 逆引きレシピ すぐに美味しいサンプル&テクニック 232 (PROGRAMMER’S RECIPE)
posted with amazlet at 09.09.13
島田 浩二 設樂 洋爾 村田 賢太 前田 智樹 谷口 文威
翔泳社
売り上げランキング: 168012
翔泳社
売り上げランキング: 168012
人様にさらせるほど立派なものでは無いですが、一応ソースは以下に。
#! /usr/bin/ruby -Ku
require 'rubygems'
gem 'twitter4r'
require 'time'
require 'twitter'
require 'pit'
require 'MeCab'
require 'kconv'
require 'uri'
require 'net/http'
require 'rexml/document'
require 'cgi'
class Hash
def to_http_str
self.map{|key,value| "#{key}=#{CGI.escape(value.to_s)}"}.join("&")
end
end
# ************************************************
# データを取得するところ
# ************************************************
def getData
data = Array.new
fdata = Array.new
ndata = Array.new
# smokeymonkeyのアカウントを使う
config = Pit.get("Twitter")
cl = Twitter::Client.new(config)
# 元となるデータを取得
timeline = cl.timeline_for(:friend, :count=>200) do |status|
post = Kconv.kconv("#{status.text}",Kconv::UTF8)
if /[!-~]/ !~ post then
data.push(post)
end
end
# データファイル読み込み
f = File.open("data.txt",'r')
fdata = f.readlines
f.close
# データファイルの内容に最新データを追加
fdata.concat(data)
# データファイルの最新5000行を取得
i=0
fdata.reverse_each do |post|
ndata.unshift post
if i > 5000 then
break
end
i = i+1
end
# データファイルに書き込み
f = File.open("data.txt",'a')
f.puts ndata
f.close
# 分析用データを返す
return ndata
end
# ************************************************
# マルコフしちゃうところ
# ************************************************
def makeMarkov(data)
ary = Array.new
tbl = Array.new
c = MeCab::Tagger.new(ARGV.join(" "))
# データからマルコフテーブル作成
data.each do |line|
line.chop!
node = c.parseToNode(line)
node = node.next
i = 0
while node do
hash = Hash.new
hash["0"] = i
hash["1"] = node.surface
node = node.next
if node then
hash["2"] = node.surface
else
hash["2"] = nil
end
ary.push(hash)
# 各文章の最初の単語だけのArrayを作成
if hash["0"] == 0 then
tbl.push hash
end
i = i+1
end
end
return ary,tbl
end
# ************************************************
# マルコフしちゃうところ
# ************************************************
def execMarkov(ary,tbl)
mess = ""
# 連鎖する
post = tbl[rand(tbl.size)]
mess.concat(post["1"])
# 継続する単語が無くなるまでループ
while post["2"] != nil do
# 候補を抽出
rndline = Array.new
i = 0
ary.each do |line|
if post["2"] == line["1"] then
rndline.push(line)
i = i + 1
end
end
# 候補からランダムに接続
post = rndline[rand(i)]
mess.concat(post["1"])
# 候補が以下3種類だった場合はEnd
s1 = Kconv.kconv("。",Kconv::UTF8)
s2 = Kconv.kconv("!",Kconv::UTF8)
s3 = Kconv.kconv("?",Kconv::UTF8)
if post["1"] == s1 or post["1"] == s2 or post["1"] == s3 then
post = Hash.new
end
end
# ポストするメッセージを戻す
return mess
end
# ************************************************
# 北海道弁変換するところ
# ************************************************
def subDabesa(mess)
mess.gsub!(/すごく/,'なまら')
mess.gsub!(/すげえ/,'なまら')
mess.gsub!(/とても/,'なまら')
mess.gsub!(/バカ/,'はんかくさ')
mess.gsub!(/酷い/,'わやい')
mess.gsub!(/ひどい/,'わやい')
mess.gsub!(/落ち着かない/,'あずましくない')
mess.gsub!(/交換した/,'バクった')
mess.gsub!(/いじった/,'ちょした')
mess.gsub!(/バイバイ/,'したっけ')
mess.gsub!(/冷たい/,'しゃっこい')
mess.gsub!(/疲れた/,'こわい')
mess.gsub!(/大変だ/,'ゆるくない')
mess.gsub!(/痒い/,'いずい')
mess.gsub!(/ヤバイ/,'やくい')
mess.gsub!(/ヤバい/,'やくい')
mess.gsub!(/やばい/,'やくい')
mess.gsub!(/格好悪い/,'がさい')
mess.gsub!(/カッコ悪い/,'がさい')
mess.gsub!(/頑張/,'けっぱ')
mess.gsub!(/捨て/,'なげ')
mess.gsub!(/本州/,'内地')
mess.gsub!(/小さい/,'ちゃんこい')
mess.gsub!(/ちいさい/,'ちゃんこい')
mess.gsub!(/止めろ/,'止めれ')
mess.gsub!(/やめろ/,'やめれ')
mess.gsub!(/だな/,'だべな')
mess.gsub!(/だね/,'だべね')
return mess
end
# ************************************************
# ポストするところ
# ************************************************
def putPost(mess)
# Twitterにポスト
config = Pit.get("dabesa")
cl = Twitter::Client.new(config)
cl.status(:post,mess)
# Wassrにポスト
wmess = 'status=' + URI.escape(mess)
req = Net::HTTP::Post.new("/statuses/update.json?")
req.basic_auth(config["login"],config["password"])
Net::HTTP.start('api.wassr.jp',80) {|http|
response = http.request(req,wmess)
}
end
# ************************************************
# RepliesからReplyを返信する
# ************************************************
def SendReplies(ary,tbl)
data = Array.new
c = MeCab::Tagger.new(ARGV.join(" "))
config = Pit.get("dabesa")
cl = Twitter::Client.new(config)
flg = 0
sid = nil
idfile = open("repid.txt",'r')
idfile.each do |id| sid = id.chop end
idfile.close
# Twitterのreplies APIを取得
req = Net::HTTP::Get.new("/statuses/replies.xml")
req.basic_auth(config["login"],config["password"])
xml = Net::HTTP.start('twitter.com',80) {|http|
http.request(req).body
}
# API(XML)を解析しstatusがある間ループ
doc=nil
doc=REXML::Document.new xml
doc.elements.each('/statuses/status') do |elm|
scname = elm.elements['user/screen_name'].text.to_s
# dabesa自身のポストなら無視
if scname == "dabesa" then next end
# 最新Replyのidをファイルに書き込み
if flg == 0 and elm.elements['id'].text.to_i >= sid.to_i then
idfile = open("repid.txt",'w')
idfile.puts elm.elements['id'].text.to_i
idfile.close
flg = 1
end
# 取得していた最新Replyまで到達したらBreak(既にRelyしたポストは無視)
if elm.elements['id'].text.to_i <= sid.to_i then
break
end
#Replyのテキストを取得し、名詞と動詞を探す
post = elm.elements['text'].text
# 半角全角を削除
while /^\.*\/*@[A-Za-z0-9_]*/ =~ post do
post.sub!(/^\.*\/*@[A-Za-z0-9_]*/,'')
post.sub!(/\s*/,'')
end
post.chop!
n = c.parseToNode(post)
n = n.next
word = Array.new
while n do
ftr = n.feature.split(",")
if ftr[0] == "名詞" or ftr[0] == "動詞" then
word.push n.surface
end
n = n.next
end
# Replyの名詞or動詞がマルコフテーブルにあればそこから文章を作る。
# マルコフテーブルに無ければ疑問を返す。
wflg = 0
ary.each do |line|
if word[0] == line["1"] then
post = line
wflg = 1
end
end
mess = String.new
if wflg == 1 then
mess.concat(post["1"])
else
mflg = rand(10)
case mflg
when 0,5,10
mess = word[0].to_s + "ってなんだべか。"
when 1,3
mess = word[0].to_s + word[0].to_s + "はんかくさい。"
when 2,6
mess = "そったら" + word[0].to_s + "とか興味無いし。"
when 4,7,8,9
post = tbl[rand(tbl.size)]
mess.concat(post["1"])
end
post = Hash.new
end
# 継続する単語が無くなるまでループ
while post["2"] != nil do
# 候補を抽出
rndline = Array.new
i = 0
ary.each do |line|
if post["2"] == line["1"] then
rndline.push(line)
i = i + 1
end
end
# 候補からランダムに接続
post = rndline[rand(i)]
mess.concat(post["1"])
# 候補が以下3種類だった場合はEnd
s1 = Kconv.kconv("。",Kconv::UTF8)
s2 = Kconv.kconv("!",Kconv::UTF8)
s3 = Kconv.kconv("?",Kconv::UTF8)
if post["1"] == s1 or post["1"] == s2 or post["1"] == s3 then
post = Hash.new
end
end
pmess = "@" + scname + " " + mess
cl.status(:post,pmess)
end
end
# 実行部
data = Array.new
ary = Array.new
tbl = Array.new
data = getData()
ary,tbl = makeMarkov(data)
mess = execMarkov(ary,tbl)
pmess = subDabesa(mess)
putPost(pmess)
SendReplies(ary,tbl)
exit