はてなキーワード: Sakiとは
大体言っていることは同意できるのだけど二つだけ気になる点。
1.咲って言われているほど失敗してないよ。
咲-sakiって8000枚くらいは売れていますよ。そりゃけいおんに比べたら見劣りするけど、これだけ売れれば充分合格点。けいおんについては、私の直感だから、ソースを出せと言われたら困るのだけど、女性ファンについては高校生以上の人はほとんど居なくて、小中学生以上の若くて購買力のない女の子がファンになるケースが多い気がする。実際女性ファンが多い作品ってDVDが売れる傾向が強いんだけど、ほとんど売れていないし、当たらずとも遠からずな気がする。
いつのまにか消えてたので中身はっときますね。
■字義的な意味
はてなダイアリーキーワードにおいて、2007年10月頃から始まった現象。ハーブ関連キーワードに「ハーブマイスター」という名前のネットショップへのリンクが張られ、次第にその数が増えてくるとリンクが削除されることも多くなり、12月頃には編集合戦となり過熱の一途をたどったが、2008年1月末に終息している。((id:xxxoxxx氏、id:ktuo2002-globpoint氏は削除しているが、理由は不明。キーワード「ハーブマイスター」をはじめ多数のキーワードで編集合戦があったことは事実))
はてな匿名ダイアリーにおいて、2007年11月頃から始まった現象。「ハーブマイスター」という名前のネットショップ内の語句解説URLへリンクを張った大量のハーブ名称がたまに貼り付けられる。
同名のショップがいくつかあるが、リンクされているのは西新宿の有限会社Active Officeが運営している店舗である。当然匿名であるため、投稿者は不明。
以上の部分をid:xxxoxxx氏、id:ktuo2002-globpoint氏は以下の文章に書き換えている。
同名のショップがいくつかあるが、リンクされているのは有限会社Active Officeが運営している店舗である。リンクをしている人間とこの会社は無関係であり、id:ktuo2002氏、id:globpoint氏らの決め付けである。
((リンクしている人間とリンク先の会社の関係について「不明」を「無関係」と断定、さらにid:ktuo2002氏、id:globpoint氏がなにか事実と異なる主張をしているような表現をしている。しかしなにを「決め付け」ているのか書かれていない。リンク先が有限会社Active Officeが運営している店舗であることは事実であり、争いがない。))
さらに2008年2月以降「ハーブマイスター」関連キーワードを編集した特定idを「荒らし」と決め付ける同文の記事が数十回の単位で連続投稿されることが起こっている。投稿者は不明。
なお、2008年3月14日には、特定idを非難するこの種の記事中で、「愉快犯だから別に関係ないよ!」「ただ、ヒートアップするのを楽しむ手段でしかない」との声明が出された。((id:xxxoxxx氏、id:ktuo2002-globpoint氏は削除しているが、理由は不明。))
なお、(2008/03/17 10:02)付当キーワードコメント欄でid:ktuo2002-globpoint氏は「ショップに直接聞きましたので、無関係なので(復帰)」と発言している。真偽は不明だが、当然ショップは匿名日記上での大量リンクを認識したということを意味する。
Watcher((id:ktuo2002-globpoint氏は「ストーカー」と書き換えているがそれは同氏の主張にすぎない))の出現とキーワード編集合戦
はてな匿名ダイアリーにおいて、興味を持って関連情報をまとめる者が出現したほか、id:ktuo2002氏が継続して動向を追っており、はてなグループにおいてWatch活動を行っている。リンク先になっているネットショップが宣伝書き込みを行っている、リンク先によるスパム行為である、という前提に立った活動である。
同じく継続して動向を追っているid:globpoint氏も、記述が偏っている等不適切なキーワード解説文の修正を積極的に行っているほか、後述の「ハーブマイスターストーカー」登録に代表される修正活動をネガティブキャンペーンとして批判している。
以上の部分をid:xxxoxxx氏、id:ktuo2002-globpoint氏は以下の文章に書き換えている。
はてな匿名ダイアリーにおいて、こそこそと関連情報を収集し、ハーブマイスターの誹謗中傷をする者が出現したほか、id:ktuo2002氏、id:globpoint氏が継続してストーカーしており、はてなグループにおいてストーカー行為を行っている。ハーブストーカーと言われてもいる模様。リンク先になっているネットショップが宣伝書き込みを行っている、リンク先によるスパム行為である、という前提に立った活動であるが、事実は確認できていないので、単なる誹謗中傷である。id:ktuo2002氏、id:globpoint氏はこのキーワードに対し必要に執着し、事実が確認できないことをあたかも事実であるかのように編集したり、都合の悪い個所を削除したり、書き換えたりとおかしな行動が目立つ。
「こそこそ」「ストーカー」といった揶揄する形容をしながら、コメント欄での「事実と異なる」という指摘には答えず根拠も提示しない。事実が確認できないのは「事実が確認できないことをあたかも事実であるかのように編集したり、都合の悪い個所を削除したり、」という主張の方である。また記述を大きく削除しているのはid:xxxoxxx氏、id:ktuo2002-globpoint氏である。((http://d.hatena.ne.jp/keywordlog?klid=963601 , http://d.hatena.ne.jp/keywordlog?klid=963120))
一方、id:saki-ha氏、id:xxxoxxx氏、id:ktuo2002-globpoint氏はそれらの行為は行き過ぎであるとして「ハーブマイスターストーカー」と名付け、ハーブマイスターは「ストーカーにあっているネットショップである」という意味へとキーワードを修正する活動を行っている。はてな匿名ダイアリーへの書き込み自体もネットストーカーによるいやがらせであり、ショップによる宣伝書き込みではないという前提に立って活動している。また、上記の者が行っているという事実も確認できてない。id:ktuo2002氏、id:globpoint氏がハーブ関連の記事を書くことを「ストーカー行為」と呼び、キーワード化しており、(([[ハーブストーカー]]http://d.hatena.ne.jp/keywordlog?klid=965096))それに対する反論は「キーワード荒らし」であると批判している。それに伴って「globpoint」((http://d.hatena.ne.jp/keywordlog?klid=965168)),「osit」((http://d.hatena.ne.jp/keywordlog?klid=963252))の各idキーワードを登録し、キーワード「ktuo2002」((http://d.hatena.ne.jp/keywordlog?klid=963248))も含めて主張を喧伝する道具としている。
これに伴い、当キーワード「ハーブマイスター」は継続的な編集合戦の戦場となっている。
■関連事項
リンクのアドレス文字列の".jp"を".jip"に書き換える。
[http://www.herb-meister.com:title=http://www.kenko.com/product/seibun/sei_834001_D.html]ブックマークの要約文を改竄する。
■関連キーワード
d:id:ktuo2002-globpoint、d:id:xxxoxxx、d:id:saki-ha、d:id:vxxxxxvovxxxxxv、あとd:id:d-smもそうか。
これらのアカウントで日記とキーワードと匿名ダイアリーを荒らしてるのはオマエだろ。
何を今さら人のせいにしてるんだ。
リンクのアドレス文字列の".jp"を".jip"に書き換える。
編集者:xxxoxxx 編集内容:3c3 -+ハーブマイスターセンター[http://www.herb-meister.jp]のこと。 ++ハーブマイスターセンター[http://www.herb-meister.jip]のこと。
編集者:herb-meister 編集内容:3c3 -http://www.kenko.com/product/seibun/sei_834001_D.html より引用。 +[http://www.herb-meister.com:title=http://www.kenko.com/product/seibun/sei_834001_D.html] より引用。
ブックマークの要約文を改竄する。
** 編集者:xxxoxxx ** 編集内容: -><blockquote cite="http://d.hatena.ne.jp/osito/20080304/p1" title="キーワード荒らしの逆ギレ? - 「愛」も「萌え」も定義は広い。"> +><blockquote cite="http://d.hatena.ne.jp/osito/20080304/p1" title="キーワード荒らしの天才id:ositoです。よろしく">
http://anond.hatelabo.jp/20080311082458
から
http://anond.hatelabo.jp/20080311081417
(計55件)
http://anond.hatelabo.jp/20080307004725
から
http://anond.hatelabo.jp/20080307004753
(計29件)
http://anond.hatelabo.jp/20080306001533
から
http://anond.hatelabo.jp/20080306002034
(計15件)
など
キーワード「ハーブマイスター」の編集合戦が続く中、第三者が中立的視点から両論併記してくれたのに、早速偏った見方からの内容に改竄。
増田にハーブのスパム投稿をしてた連中は「会社(有限会社Active Office)とはまったく無関係で何者かの仕業」という事を、xxxoxxxは知っているらしい。不思議だね。
ktuo2002、globpointの主張は「事実は確認できない」とか言って門前払いしてるくせにね。
編集者:xxxoxxx 編集内容:5c5 7c7 10c10 -同名のショップがいくつかあるが、リンクされているのは西新宿の有限会社Active Officeが運営している店舗である。 +同名のショップがいくつかあるが、リンクされているのは西新宿の有限会社Active Officeが運営している店舗であるが会社とはまったく無関係で何者かの仕業。 -はてな匿名ダイアリーにおいて、興味を持って関連情報をまとめる者が出現したほか、id:ktuo2002氏、id:globpoint氏が継続して動向を追っており、はてなグループにおいてWatch活動を行っている。リンク先になっているネットショップが宣伝書き込みを行っている、リンク先によるスパム行為である、という前提に立った活動である。 +はてな匿名ダイアリーにおいて、興味を持って関連情報をまとめる者が出現したほか、id:ktuo2002氏、id:globpoint氏が継続して動向を追っており、はてなグループにおいてWatch活動(ストーカー)を行っている。リンク先になっているネットショップが宣伝書き込みを行っている、リンク先によるスパム行為である、という前提に立った活動であるが、この会社がスパムをしてる事実は確認できない。 -一方、id:saki-ha氏、id:xxxoxxx氏、id:ktuo2002-globpoint氏はそれらの行為は行き過ぎであるとして「ハーブマイスターストーカー」と名付け、ハーブマイスターは「ストーカーにあっているネットショップである」という意味へとキーワードを修正する活動を行っている。はてな匿名ダイアリーへの書き込み自体もネットストーカーによるいやがらせであり、ショップによる宣伝書き込みではないという前提に立って活動している。 +一方、id:saki-ha氏、id:xxxoxxx氏、id:ktuo2002-globpoint氏はそれらの行為は行き過ぎであるとして「ハーブマイスターストーカー」と名付け、ハーブマイスターは「ストーカーにあっているネットショップである」という意味へとキーワードを修正する活動を行っている。はてな匿名ダイアリーへの書き込み自体もネットストーカーによるいやがらせであり、ショップによる宣伝書き込みではないとい。
http://d.hatena.ne.jp/keywordlog?klid=955411
d:id:saki-haが増田でのスパムに関する記述を削除。
http://d.hatena.ne.jp/keywordlog?klid=956304
id:DASMがd:id:ktuo2002の誹謗中傷と、d:id:herb-meisterのスパム行為に関するアイデアのリンク(idea:18780)を追記する。
http://d.hatena.ne.jp/keywordlog?klid=956656
d:id:d-sm(saki-ha)がd:id:herb-meisterのスパム行為に関するアイデアのリンクを削除する。
http://anond.hatelabo.jp/20080217204219
http://anond.hatelabo.jp/20080217215531
http://anond.hatelabo.jp/20080217210537
http://anond.hatelabo.jp/20080217221248
なんかもうクビ突っ込む気もないんだけど、
なんでこういう人は自分の文体がここでは浮きまくってて自演がバレバレだって気付かないんだろうなぁ。
あと「ID:ktuo2002」とか、それDにリンクしないから! つうかなんでそもそもIDが大文字?
とりあえず↓の編集履歴を見てd:id:saki-haの人が必死なことはよくわかった。
「ハーブマイスターストーカー」を含む日記 - はてなダイアリー
うん。もちつけ。まずヘルプぐらい読んで、キーワードリンク記法をおぼえろ。
なんて言うかもう全体に「はてなに初めて来てはしゃいでる感」がぶりぶりだ。半年ROMれ。
しかしktuo2002の人もヒマだな。っていうかちょっと楽しいんだろうな。saki-haの人はソーシャルハッキングとか複雑なことできなさそうだから安全だし。
2007年10月-12月中旬
hiroshi-ichijyoがハーブ、ハーブティーなどのキーワードにハーブマイスターへのリンクを張る。
11月末くらいから、ハーブ関連キーワードを新規登録して、ハーブマイスターへのリンクを張るようになる。
12月中旬
ktuo2002がハーブマイスターへのリンクを消し、編集合戦が始まる。
12月下旬
hiroshi-ichijyoとherb-meisterがリンクを張り、ktuo2002とglobpointがリンクを消す。
対象キーワードが増加する。
編集合戦が過熱する。
増田にハーブ屋のスパムが投稿され始める。http://anond.hatelabo.jp/20080108002417
2008年1月中旬
AKB48など、ハーブと関係ないキーワードも対象になり、ますます編集合戦が過熱する。
多分60近いキーワードが、1日に2回とか、多い日は6,7回書き換えられていたと思う。
はてなアイデアに苦情が出る。http://i.hatena.ne.jp/idea/18780
運営側がなんらかの対処を行った模様。hiroshi-ichijyo、herb-meister、herb-meister-1はプライベートモード化し、市民権も喪失している。
2008年2月初旬
d:id:saki-haがキーワード「ハーブマイスター」「herb-meister」でktuo2002、globpointと編集合戦。
2008年2月中旬
増田にktuo2002、globpointの中傷記事が繰り返し投稿される。←いまここ
その他ハーブマイスター関連id:d:id:d-sm, http://d.hatena.ne.jp/ktuo2002-globpoint/], d:id:xxxoxxx, d:id:vxxxxxvovxxxxxv
Return-Path: <direct@mini-saki.com>
Received: from gmail-smtp-in.l.google.com ([202.75.37.206])
by mx.google.com with SMTP id v39si9729804wah.44.2008.01.29.03.18.31;
Tue, 29 Jan 2008 03:18:33 -0800 (PST)
Received-SPF: neutral (google.com: 202.75.37.206 is neither permitted nor denied by best guess record for domain of direct@mini-saki.com) client-ip=202.75.37.206;
Authentication-Results: mx.google.com; spf=neutral (google.com: 202.75.37.206 is neither permitted nor denied by best guess record for domain of direct@mini-saki.com) smtp.mail=direct@mini-saki.com
Date: Tue, 29 Jan 2008 03:18:33 -0800 (PST)
From: ジェントルマン木下 <direct@mini-saki.com>
To: 俺@Gmail
Subject: 毎日土下座していますか?
Message-ID: <8O4X33M53Z.9222@mini-saki.com>>
どうも。ジェントルマン木下です。
どのくらいジェントルマンかと言うと、デートマナーマニュアルを
12冊読破し、車のドアも女性には開けさせず自分の手で開けて乗せ、
運転席にかけて行くくらいジェントルマンです。
私のジェントル魂はそれだけでは飽き足りずテーブルマナーも覚え、
そんな苦悩に満ちた日々を送っていた時、ある人物が目に入りました。
その人物はなんと女性の目の前で土下座してセックスを懇願しているのです。
そう思いながらその光景を冷ややかな視線で傍観していたのですが、
なんと女性はOKしているじゃないですか!!
ジェントルマン改め、ジェットマンに変身して
大空に飛び出しそうなくらい驚きました。実際、飛んでましたね。
そして大空を羽ばたきながら気がついたのです。私は外国かぶれな人間だった…、
そうさ日本人じゃないか…と。日本にもこんなにもジェントルマンな作法があったのだと。
その日を境に、私はありとあらゆる土下座を試してきました。なかでも一番効果があった
土下座中もドアがバタンバタンと私の体を挟み、音を立てて演出効果を高めます。
それをされた女性は必ずOKします。
あまりのジェントルマンぶりにOKせざるを得ないからです。正直なところ女性も「セックスを
する口実」が欲しいのだと思います。「あそこまでされたら仕方ないよね」って。
そして、その成功率の一番高かったところがここです。
(URL 省略)
発祥: http://ex23.2ch.net/test/read.cgi/morningcoffee/1188654905/
Scheme という Lisp 語族の言語を用いて ℃-ute の相関関係をプログラムし、様々な角度から関係性を分析する手法を紹介していきます(ソースコードは最後に張ります)。
まずは、メンバー間の関係を「リスト」というデータ型で表現します。例えば「栞菜->愛理」という関係は
(kanna . airi)
という形で表すことができます。これに、「大好き」という情報を付加し、ついでにその関係の性質を数値化したものを加えると
((kanna . airi) (desc "大好き") (score . 1))
のようになり、関係図における一つの矢印の情報をデータ化できたことになります(暫定的に、好意は 1、良好・中立は 0、険悪は -1 の3段階で表すことにします)。
メンバー間の全ての関係性をこのデータ単位で定義し、データベース化しておくことで、色んな条件に基づいた検索やスコア計算などが可能となります。
ここで相関関係図における矢印を「リンク」と呼ぶことにして、あるメンバーから他のメンバーへどのようにリンクし、またリンクされているかを調べることができます。
(sort-nodes (number-list (from-links)))
結果:
((kanna . 6) (saki . 5) (maimi . 4) (erika . 3) (mai . 3) (chisato . 3) (airi . 2))
栞菜ちゃんがメンバー全員にリンクを張っていることが分かり、℃-ute ラブっぷりが伺えます。なっきーにも同様の事が言えます。例の「女の子が好き」発言を数値的に裏付ける結果と言えるかもしれません。
ただ、データ不足でリンク件数がまだ少ないのと、リンクの性質(好意/反感など)までは分からない点を考慮する必要があるでしょう。
同様に、リンクの終点の件数を調べてみます。
(sort-nodes (number-list (to-links)))
((chisato . 5) (erika . 5) (kanna . 4) (maimi . 4) (airi . 4) (mai . 3) (saki . 1))
えりかちゃんと千聖ちゃんが高ポイントです。メンバーからの人気や注目度の高さを示すデータですが、千聖ちゃんの場合敵対的なリンクが2件含まれている点に注意してください。
なっきーの被リンク数が極端に少ないですが、単純にデータ不足のためだと思われます。はぶら(ryとか言わないようにお願いします。
リンクに付随するスコアを計算することで、愛情の度合いを測ることができるのではないか、という考えに基づく研究です。
まず、全ての関係性を対象として、スコアがマイナスの関係を抽出してみます。
(filter-nodes (lambda (n) (< (score-relation n) 0)))
結果:
(((kanna . chisato) (desc "愛理に手出すんじゃねぇよ") (score . -1)) ((saki . chisato) (desc "愛理に手出すんじゃねぇよ") (score . -1)))
件数だけを得ると
(length (filter-nodes (lambda (n) (< (score-relation n) 0))))
2
僅か2件です。
良好・中立的な関係は
(length (filter-nodes (lambda (n) (= (score-relation n) 0))))
8
愛に満ちた関係は
(length (filter-nodes (lambda (n) (> (score-relation n) 0))))
16
非常に多いです。舞美ちゃんの「℃-ute同士でラブラブなんですよ」発言(例のラジオ)を数値的に裏付ける結果と言えるんじゃないでしょうか。
次に、メンバーごとのスコアを算出してみます。Lisp 的には以下のようにフィルタリングと畳み込み (fold) で計算することができます。例えば
(foldr (lambda (n acc)
(+ (get-score n) acc))
0
(filter-nodes (cut to? <> 'kanna)))
3
上式を一般化して一挙にメンバー全員に適用してみると
(sort-nodes (map (lambda (x) (cons x (score-loved x))) (all-members)))
結果:
((airi . 4) (kanna . 3) (mai . 2) (erika . 2) (maimi . 2) (saki . 1) (chisato . 0))
愛理ちゃんが好意を寄せられやすい傾向が伺えます。
今度は逆方向のスコアを計算してみると
(sort-nodes (map (lambda (x) (cons x (score-loving x))) (all-members)))
((kanna . 3) (maimi . 3) (chisato . 2) (airi . 2) (saki . 2) (mai . 1) (erika . 1))
まいまいとえりかちゃんが特に堅い・一途だという傾向を読み取ることができます。
今度は組み合わせ(カップリング)の評価です。
2点間相互のリンク・スコアを加算したものを「相性」と考えられるものとします。最大値 (互いに好意を寄せている場合の数値) は現在のスコアリング方式では 2 です。例えば
(score-between 'kanna 'airi)
の値は
2
となります。1 であれば一方通行と考えます。
関係性が未定義の場合もあるので 0 のものを除外して算出すると
(sort-nodes (filter (lambda (n) (not (= (cdr n) 0))) (map (lambda (n) (cons n (apply score-between n))) (all-combinations))))
(((chisato mai) . 2) ((chisato airi) . 2) ((airi kanna) . 2) ((saki kanna) . 2) ((kanna maimi) . 2) ((erika maimi) . 2) ((saki airi) . 1) ((saki erika) . 1) ((kanna mai) . 1) ((maimi airi) . 1) ((saki chisato) . -1) ((kanna chisato) . -1))
となります。若干ピンとこない部分もあるかも知れませんが、計算上は矛盾無くデータの内容を表しています。
(map (lambda (p) (find-relation (cons (caar p) (cadar p)) identity)) (filter (lambda (n) (= (cdr n) 1)) (map (lambda (n) (cons n (apply score-between n))) (all-combinations))))
(((kanna . mai) (desc "喰ってやるよ") (score . 1)) ((saki . airi) (desc "好き") (score . 1)) ((maimi . airi) (desc "良き妹") (score . 1)) ((saki . erika) (desc "彼氏にしたい") (score . 1)))
のようになります。
以上の調査を経て気になった問題点を列挙してみます。
特に最初の点に関して、「百合的」なるものの質的評価がなかなか難しいと感じました。例えば「大好き」も「良き妹」も同じ 1 と評価してしまっているのが妥当かどうか、といったことです。
また、スレにて与えられた情報を評価・分析する方法としては有効だとしても、逆方向のフィードバックの手段がなかなか見つからないというのが三つ目の問題です(技術力不足とも言います)。(注:画像化の方法が分かりました。追記参照)
最後に、プログラムのソースを示します。実行には PLT Scheme が必要です。文字コードは UTF-8 で保存した上で、(load "c-ute.ss") としてください。文字化けする場合はターミナルが UTF-8 を表示できるよう設定する必要があります。がんばってください。
c-ute.ss:
(require (lib "etc.ss") (lib "list.ss") (lib "26.ss" "srfi") (lib "delete.ss" "srfi" "1")) ;;; Utilities (define true? (compose not not)) (define (ignore _) #f) (define fif (case-lambda ((predicate consequent) (fif predicate consequent ignore)) ((predicate consequent alternative) (lambda (x) (if (predicate x) (consequent x) (alternative x)))))) (define (concat! xs) (apply append! xs)) (define (mapconcat f lst sep) (let lp ((str (f (car lst))) (lst (cdr lst))) (if (null? lst) str (lp (string-append str sep (f (car lst))) (cdr lst))))) (define (slice-string str len) (let lp ((res '()) (str str)) (if (<= (string-length str) len) (reverse! (cons str res)) (lp (cons (substring str 0 len) res) (substring str len))))) (define (break-string str len) (mapconcat identity (slice-string str len) "\\n")) ;; NOTE: input and output ports have to be either file-stream or #f ;; (i.e., cannot be a string port) (define (run exe opt in out) (let-values (((p p-i p-o p-e) (subprocess out in #f exe opt))) (subprocess-wait p) (close-input-port p-e))) ;;; Database ;; http://ja.wikipedia.org/wiki/%E2%84%83-ute (define names '((erika . "えりか") (maimi . "舞美") (saki . "早貴") (airi . "愛理") (chisato . "千聖") (mai . "舞") (kanna . "栞菜"))) (define (symbol->name sym) ((fif true? cdr) (assq sym names))) (define nodes '()) (define edges '()) (define (relate from to desc score) (let ((n (cons from to))) (or (find-relation n (lambda (r) (let ((d (assq 'desc r)) (s (assq 'score r))) (set-cdr! d (cons desc (cdr d))) (set-cdr! s (+ score (cdr s)))))) (begin (set! nodes (cons n nodes)) (set! edges (cons (cons n `((desc ,desc) (score . ,score))) edges)))))) (define (find-relation n k) ((fif true? k) (assoc n edges))) (define (related? x y) (find-relation (cons x y) (lambda (_) #t))) (define (from? n x) (eq? (car n) x)) (define (to? n x) (eq? (cdr n) x)) (define flip-relation (case-lambda ((n) (and (related? (cdr n) (car n)) (cons (cdr n) (car n)))) ((n k) ((fif true? k) (flip-relation n))))) (define (get-score n) (cdr (assq 'score n))) (define (get-description n) (cdr (assq 'desc n))) (define (describe-relation n) (find-relation n get-description)) (define (score-relation n) (or (find-relation n get-score) 0)) (define (print-node . ns) (for-each (cute find-relation <> (lambda (r) (display (format "| ~a => ~a (~a)~%" (caar r) (cdar r) (mapconcat (lambda (s) (string-append "\"" s "\"")) (cdr (assq 'desc r)) ", "))))) ns)) (define (iter-nodes k) (let lp ((nodes nodes)) (unless (null? nodes) (k (car nodes)) (lp (cdr nodes))))) (define (filter-nodes p) (let ((ns '())) (iter-nodes (fif p (cut find-relation <> (lambda (n) (set! ns (cons n ns)))))) ns)) (define (from-links) (map car nodes)) (define (to-links) (map cdr nodes)) (define (all-members) (delete-duplicates! (from-links))) (define (all-pairs) nodes) (define (ordered-pairs) (concat! (map (lambda (x) (map car (sort (filter-nodes (cute to? <> (car x))) (lambda (x y) (> (get-score x) (get-score y)))))) (sort-nodes (map (lambda (x) (cons x (score-loved x))) (all-members)))))) (define (all-combinations) (let lp ((cs '()) (ns nodes)) (if (null? ns) cs (let ((n (car ns))) (lp (if (member (list (cdr n) (car n)) cs) cs (cons (list (car n) (cdr n)) cs)) (cdr ns)))))) ;; number-list :: [a] -> [(a . Int)] (define (number-list ls) (let lp ((ns '()) (ls ls)) (if (null? ls) ns (let ((x (car ls))) (lp ((fif not (lambda (_) (cons (cons x 1) ns)) (lambda (n) (set-cdr! n (add1 (cdr n))) ns)) (assq x ns)) (cdr ls)))))) ;; sort-nodes :: [(a . Int)] -> [(a . Int)] (define (sort-nodes ns) (sort ns (lambda (x y) (> (cdr x) (cdr y))))) (define (diff-nodes ms ns) (let lp ((ds '()) (ns ns)) (if (null? ns) (sort-nodes ds) (lp (let* ((n (car ns)) (m (assq (car n) ms))) (cons (cons (car n) (- (cdr m) (cdr n))) ds)) (cdr ns))))) (define (get-total-score x p) (foldr (lambda (n acc) (+ (get-score n) acc)) 0 (filter-nodes (cut p <> x)))) (define (score-loved x) (get-total-score x to?)) (define (score-loving x) (get-total-score x from?)) (define (score-between x y) (+ (score-relation (cons x y)) (score-relation (cons y x)))) (define (-> x) (display (format "~%Links from [~a]~%" x)) (iter-nodes (fif (cut from? <> x) print-node))) (define (<- x) (display (format "~%Links towards [~a]~%" x)) (iter-nodes (fif (cut to? <> x) print-node))) (define (<-> x) (display (format "~%Reciprocal links for [~a]~%" x)) (iter-nodes (fif (cut to? <> x) (lambda (n) (flip-relation n (lambda (m) (print-node m n))))))) (define (<=> x) (display (format "~%Reciprocal matches for [~a]~%" x)) (iter-nodes (fif (cut to? <> x) (lambda (n) (flip-relation n (lambda (m) (if (ormap (lambda (x) (ormap (lambda (y) (equal? x y)) (describe-relation m))) (describe-relation n)) (print-node m n)))))))) (define (<?> x) (let ((to (assq x (number-list (from-links)))) (from (assq x (number-list (to-links))))) (display (string-append (format "~%Link statistics for [~a]~%" x) (format "| ~a => ~a (love ~a)~%" x (cdr to) (score-loving x)) (format "| ~a => ~a (love ~a)~%" (cdr from) x (score-loved x)))))) (define (info x) (for-each (cut <> x) (list <- <-> <=> -> <?>))) ;;; GraphViz (http://www.graphviz.org/) support (define graphviz "C:/Program Files/ATT/Graphviz/bin/dot.exe") (define (nodes->dot ns) (string-append "digraph cute {\n" ;;"\tordering=out;\n" ;;"\trankdir=LR;\n" "\toverlap=true;\n" "\tnode[fontname=\"msgothic.ttc\"];\n" "\tedge[fontname=\"msgothic.ttc\",fontsize=9];\n" (let lp ((str "") (ns ns)) (if (null? ns) str (let* ((n (car ns)) (s (score-relation n))) (lp (string-append str (format "\t\"~a\" -> \"~a\"" (symbol->name (car n)) (symbol->name (cdr n))) (format "[label=\"~a\",color=\"~a\"," (break-string (car (describe-relation n)) 7) (cond ((> s 0) "red") ((= s 0) "green") (else "blue"))) (format "style=\"bold~a\"];\n" (if (and (not (= s 0)) (< s 1) (> s -1)) ",dashed" ""))) (cdr ns))))) "}")) (define (write-dotfile dot file) (and (file-exists? file) (delete-file file)) (with-output-to-file file (lambda () (display dot))) file) (define (dot->png dot png) (call-with-input-file (write-dotfile dot "c-ute.dot") (lambda (in) (and (file-exists? png) (delete-file png)) (call-with-output-file png (lambda (out) (run graphviz "-Tpng" in out))))) 'done) ;;; Setup database ;; Based on: ;; http://ex23.2ch.net/test/read.cgi/morningcoffee/1188654905/116-142 (begin (relate 'maimi 'erika "大好き" 1) (relate 'maimi 'kanna "良き妹" 1) (relate 'maimi 'airi "良き妹" 1) (relate 'maimi 'mai "姉妹" 0) (relate 'erika 'maimi "一番可愛いよ" 1) (relate 'erika 'kanna "仲間" 0) (relate 'erika 'chisato "おソロパジャマ" 0) (relate 'kanna 'erika "仲間" 0) (relate 'kanna 'maimi "好き" 1) (relate 'kanna 'saki "喰ってやるよ" 1) (relate 'kanna 'mai "喰ってやるよ" 1) (relate 'kanna 'airi "大好き" 1) (relate 'kanna 'chisato "愛理に手出すんじゃねぇよ" -1) (relate 'saki 'maimi "荷物整理" 0) (relate 'saki 'erika "彼氏にしたい" 1) (relate 'saki 'kanna "興味がある" 0.5) (relate 'saki 'chisato "愛理に手出すんじゃねぇよ" -1) (relate 'saki 'airi "好き" 1) (relate 'airi 'kanna "受け入れる" 1) (relate 'airi 'chisato "最近親密" 1) (relate 'mai 'erika "保護者" 0) (relate 'mai 'maimi "姉妹" 0) (relate 'mai 'chisato "恋人" 1) (relate 'chisato 'erika "おソロパジャマ" 0) (relate 'chisato 'mai "恋人" 1) (relate 'chisato 'airi "最近親密" 1)) ;; query relations / draw graphs (if (file-exists? graphviz) (dot->png (nodes->dot (ordered-pairs)) "c-ute.png") (for-each info (all-members)))
Graphviz というソフトによって関係図を可視化できる、ということを教えていただきました(既に上プログラムを実行すると自動的に関係図画像を作成するようにしてあります)。ここでは技術的な観点から幾つか注意点を挙げておきます。
まず、Scheme プログラムから Graphviz を動かす方法について。コマンドラインからの起動のように、プログラムへのオプション文字列で入出力ファイルを指定する方法ではどうも上手く行きませんでした。調査の結果、入出力ファイルのポートを Scheme 側で用意しておく必要があるようです。処理系によって異なりますが、PLT Scheme の場合 subprocess という関数を次のように呼び出します。
(subprocess output-port input-port #f "/path/to/dot.exe" "-Tpng")
ここで output-port は png 等画像ファイルへの出力ポート。input-port は dot ファイル(グラフの定義ファイル)の入力ポートです。エラーポートは必要無いでしょう (#f)。
dot という名前の実行ファイルが、関係図のような有向グラフを描画するプログラムです。最後にオプション文字列として出力形式を指定します(png, jpeg, gif, etc.)。
次に dot ファイルを Scheme で書く方法ですが、以下の基本的な有向グラフの書式
digraph g {
A -> B;
B -> C;
C -> A;
}
を理解すれば、後は実直に Scheme のデータを当てはめて format 関数等で変換するだけです。
(string-append "digraph g {" (format "~a -> ~a;" (car node) (cdr node)) "}")
問題は、ノードを配置する順番によって出来上がる画像が変わってくる、ということです。
より見た目に分かりやすくするための工夫としては、相互にリンクするノード同士が dot ファイル上でも近接して出力されるようにすると良いでしょう。関連の強いものが画像の上でも近くに表示されるようになります。
また上述(特に例3)のスコアの概念を応用し、スコアの低いものが後に出力されるようにすることで、重力感覚に一致するような関係図を得ることができるでしょう。