検索を作らない、検索エンジニアw

おととい26歳になりました。それもあって、はてなダイアリーから、最近新しくできたはてなブログに移行してみました。自分は、はてなのサービス好きなので、応援の意味もこめて。

 

さて、Wantedlyに入社してはや2ヶ月経ちました。前より働いているのに給料が落ちたのが気のせいではない気がするのですが、正直今楽しいです!

素直に転職して良かったなと思っています。

 

今楽しいと思っている理由はいろいろありますが、その中で一つ、ここで働いて得た気づきをシェアしたいと思います。

 

 

それは、本当は、

 

「人を雇うということは、すごく嬉しいこと」

 

だということです。

 

 

よく考えれば当たり前のことかもしれません。

人を雇うということは、人が足りないせいでやりたいけれどできていない何かがあるはずなんです。それをできるようにするため、新しい何かに挑戦するために、一緒に同じ目標に向かってくれる仲間を探します。そんな仲間がやってきてくれる!なんて、すごく幸せなことじゃありませんか!?

 

でも、世の中そういうふうになっていないことも多いような気がします。

 

その証拠に、以前の自分はこの事実に気づいていませんでした。それは、やっぱり新卒の就職活動の経験が大きいのかもしれません。

採用面接では、今までで一番頑張ったことは何ですかというような、個人の過去に探りを入れるような質問がされ、大して個人が具体的にどんな仕事をやるのかの説明はしていないのに、何故この会社に入りたいのかというような質問がなされます。

 

そこで受かれば良いですが、そうでない場合、少し人格が否定されたような気がするでしょう。受かった側も、第一志望であればハッピーですが、それ以外の場合は、不本意な気持ちが残ったり、いくつも落とされていた後だった場合、ただほっとした気持ちだったりするかもしれません。

でも本当はそういう感情じゃないはずなんです。

「新しく入る会社の人は、自分が入ってきてくれることを望んでいる。早く貢献したい!」というような感情がもっと起こっていいと思うんです。

 

そのためには会社側ももっと、「今こういう問題を抱えているから、その問題を解決できるような人が欲しい、本気で一緒に取り組んでいる人が欲しい」「うちには、いまこんな人達がいる、今いる人達と息を合わせてやっていける人がほしい」というべきなんだと思います。さらに、ちゃんと入ってくる人と一緒に働く人が事前に会い、お互いに一緒に働きたいと思うかどうかを確かめておく必要もあると思います。

 

 

でも、世の中そういうふうになっていないし、理想論を語っても何にもならないです。自分たちが変えれる何かで変えるしか無いわけです。

 

会社側も、応募数が採用できる人数の100倍のようなことになってしまえば、効率的に人を篩にかけるような方法を使わなければ業務が回らなくなってしまいます。逆に人がほとんど応募してこなければ、チームフィットも見ずに即採用してしまって、不幸なマッチングが起きやすくなってしまいます。これじゃ理想論はいつまでも理想論です。

 

自分は、実は、検索など情報化社会がこの良くない流れを加速したと思っていて、

  1. 余りにも多くの選択肢を目にできるため、企業ランキングなどの安易な情報で物事を判断しやすくなっている
  2. 検索は基本的に名前でしかできないため、名前の知っている企業ほどより情報を入手でき、より良く見えてしまう

ということが起きている気がしています。

 

だからWantedlyでは、全員にとって同じで、1年ずっと固定したようなあからさまなランキングや、会社名を検索するようなことはできないようになっています!!

 

というか僕ができるようにするのを必死に邪魔してるんだと思います(笑)検索したいという意見はもちろん聞くので。

ただ、自分にあった会社なんて、単純なキーワードを使った検索では判断できないと信じているし、それがやりたいならGoogleにでもやってもらえばいいわけだから。

 

その2つの方法を封印しつつ、面白い!と思ってもらえるような会社を提示してあげることは、かなり難しいなと思っているのですが、それでも、少しずつそういうことが実現できてきています。ぜひ見てみてください。

 

https://www.wantedly.com/

 

そして、ぜひ意見をください!

 

まだまだ今感じている問題を解決するために改善できる点はいろいろあるだろうし、僕は、自分の持てる力でより良いものを提供していこうと思っています。

もしかしたら問題は、技術で解決できる部分は少ないのかもしれません。でも、その部分は、今いる仲間と未来の仲間が変えていってくれると信じています。

 

 

Thanks,

awakia

GoogleからWantedlyに転職しました

今日から正式にWantedlyというスタートアップに勤めることになりました。
なぜWantedlyに決めたのかを書こうと思うのですが、やはりこの決断に至ったのは前職の影響が大きいので、前職のGoogleの話をさせていただければと思います。


僕はGoogleでは、検索のソフトウェアエンジニアをやっていました。ただ1年2ヶ月で辞めてしまったので、これからGoogleについて書くことは、すごく狭いところしか見ていない部分的な意見であることをご了承ください。

自分と検索とGoogle

Googleは、入社する前からとても尊敬する企業でした。僕は、僕が生まれてから認知している中で、Googleの「検索」というプロダクトは、最も世の中を変えたインパクトのあるものだと思っていました。今まで分からないものがあったら、親や先生に聞くか図書館で調べるといった方法しかなかったところを、Googleはキーワードを入力するだけで知りたいことが知れる世の中に変えてしまいました。この結果、学校で出される課題への取り組み方を始め一人でできるものごとの量、ビジネスのあり方など様々なものが変わったと思います。もちろん他の様々な要素の貢献はあると思いますが、根本はGoogleの創業者の一人が考えだしたPageRankという検索結果の順位付けに関する一つのアイデアを形にしたことが始まりだと思います。
少数の力で速いペースで世の中を良くすることができること、これが、政治家など他の職業ではなく、エンジニアであることの一番の喜びだと僕は思っています。


そんなわけで、僕は大学4年生の頃から検索っ子で、検索を研究テーマの一つとしている早稲田の山名研究室で研究生活を送り、その間に、検索・推薦エンジンを作っているPreferred Infrastructureでアルバイトを経験させていただき、検索やその周辺課題を研究しているMicrosoft Researchの酒井さんの元でインターンをさせていただきました。


そんな僕が結果的にGoogleにエンジニアとして採用されたわけですが、Googleは割とジェネラルにプログラムが書ける人を採用するので、実は、検索を専門にしていた学生が新卒としてGoogleに入ることは珍しいのではないかと思います。僕がGoogleに受かった理由も、そういった経歴の部分よりも、ICPCなどのプログラミングコンテストに多数出場経験があったため、採用試験のコーディングクエッションに素早く正しく答えられたというのが大きいと思います。

Googleで何をやりたかったか

さて、ここからGoogleの検索部門に入って、僕が何をしたかったかということを話そうかと思います。まず、検索を良くするためには、今何ができてないかちゃんと答えられることは大事ですよね。


そこで、皆さん、「今の検索の問題点、不満な点はどこですか?」と聞かれたらなんと答えますか?ちょっと考えてみてください。






僕が導き出した現状の検索の一番の問題は、「今自分が本当に困っていて解決したい問題」は全く検索しない、検索したとしても大して役に立つ結果が得られないという事でした。


正直僕たちは、もう何か知りたい"もの"が調べられなくて困ることって殆ど無いんですよね。でも、自分や自分の周りの人が困ってることとか聞いてみると、みんなそれぞれ困ってることはあるわけです。


彼女ができない、就職できない、人間関係がうまくいかない、他人に流されやすい、今日暇すぎる、いじめにあってる、定年して何したらいいかわからない、大切な人が重病、宇宙飛行士になりたいけど今何すればよいかわからない、新しく仕事を任されたけど何から手をつけていいかわからないなどなど。


あれ、これの解決法って実は重要な情報で、大きな意味で知りたいものなんじゃないかなと思いました。

解決案

問題提起だけして解決案を提示しないのは、エンジニアとしてあるべき姿ではないので、次は解決法を考えてみようと思います。


まず、こういう問題を解決できてる人はどうやって解決してるんだろうと考えると、人に相談すると本を読むことの2つがメジャーな方法だと思います。その他にも、自己解決、時間が解決、問題自体を無視するなどあると思いますが参考にならないかベストでは無さそうです。


で、この人に相談、本を読むが解決策なら、検索エンジンとして何もしなくて良いかというとそんなことは無いはずで、きっちり相談すべき人、読むべき本を提示してあげるべきです。だって、問題に対して相談すべき相手や読むべき本を見つけ出せている人は消して多くないはずですから。
人に相談するパターンでの方法としては、その分野の専門家か、同じ問題に直面したけれど切り抜けた人を見つけ出し、その人に低いコストで質問できるようにする、もしくはその人が過去に書いた答えを表示することが考えられます。
特に、レアな病気にかかった場合などは良い例で、質問者も回答者もお互いに質問する気、回答する気は起きる気がしています。Google Moderatorのようなものを使って質問を管理すれば、相談すること自体のハードルを下げることや回答を再利用することもできるかもしれないわけです。


例えば「いじめ」に関しては、専門家はもちろん、いじめから抜けだした人はいくらでもいるし、死ぬ直前まで行ったけど生き残っている人もいるわけです。
彼女ができない」問題に関しては、検索するとそれなりの結果は出るのですが、ナンパやパーティーなど抵抗ある人にはできないし、バイトで周りに女の子がいても、女の子に声かけられない人は声かけられないんです。でもそんな感じの人でも最終的に結婚してる人はいるだろうし、そういう人が実際どうできたかを聞ける機会があっても良いと思うんです。


本に関しては、具体的に、「サーバーを管理する方法」「起業する方法」などで検索するとわかるのですが、どれも確かに正しい内容は書いてあるのですが網羅性にかけ、その記事だけを読んでも具体的なアクションに結びつかないようなものしか出て来ません。それに対し、書籍であれば、実際に1冊よめばかなり網羅的に知識がつき出来るようになるものが存在します。こういった時に本を提示すれば、実際に、なりたい自分になることや、やりたいことの実現によりつながるのではないでしょうか。正直、現在のような中途半端に人を満足させるような検索結果しか出ないことは、検索がなかったころよりも人をダメにしてるとすら思います。


暇過ぎて困る」など、やることやりたいことが見つからない系の問題に関しては、今流行のソーシャルゲームをやらせるなどでも良いですが、例えば、最近は予定を全部Googleカレンダーなどクラウド上で管理している人も多いので、今日同じく暇であるはずの友人を教えてあげる事が考えられます。他にも、同年代の人がよくやっているアクティビティの一覧とその始め方を出す、興味がありそうなもしくは学習教材、本、音楽、動画などを推薦してあげるなどいくらでも方法はあるわけです。


このように検索する際にある程度コンテンツを作ってあげる事により、人が今本当に困っている問題に答えを出してあげるということは僕はやるべきなのではないかと思います。検索には「Web上に無いコンテンツは検索できない」という根本的な問題もあるので。

なぜ実現できなかったか

さて、こういうアイデアをなぜ実現できなかったかという部分ですが、正直、僕の力不足です。それはまず認めておきます。世の中、優秀な人が本気で頑張れば不可能なことなんてないので。その上で何故難しかったかを書くことにします。ただ、これは社内の仕組みに触れる部分なので、そこら辺は、NDA違反にならないレベルで、端折りつつ、さらに抽象的に書きます。


一般に、皆を説得してプロジェクトとしてすすめるためには以下の3つの方法があると思います。僕はこれらの方法をそれぞれ試みましたが、挫折しました。
1.皆が良さを実感するようなデモを作ってを見せる
2.データで示す
3.偉い人に直訴


1.の方法は自分の提案にはそぐわないものでした。そもそもあれだけ大規模になったシステムの上で複数の物を組み合わせてデモを作るというのはかなりコストが高いということもありますが、僕の場合、人に相談するという提案も、適当にデータを作り何かうまいこと答えてもらっている図を作っても意味が無いし、本の提案もGoogleBooksの検索結果を間に挟むことくらいなら簡単にできるけれども、それを見せたところで、自分が口で話す以上の感動は何も起きない類のものでした。


2.に関しては、Googleはデータ主義であることは割と知られている事実だと思います。
僕がやりたいことは今ない使われ方を提供することでした。それ現状の結果しかないデータから自分がやろうとしてることの有用性を見出して証明する方法は僕には想像もつきませんでした。ユーザーが知りたいことが全て現状の行動に表れているとすると、何で検索される文字列はあんな不自然な空白で区切られたものばかりなのか僕は疑問に思いますが。
もう一つ感じたのは、視聴率重視の番組を作ると、重いけれど価値ある良い番組は駆逐されていくテレビ業界で言われていることと同じ事が起こっているということでした。ただ、これは感覚的なもので、この事自体を証明している資料は無いということと、何を良いと定義するかは自由であることが問題をより難しくしていることだけ付け加えておきます。


3.もやってみましたが、QAサイト的なものを今更やってもうまくいかないだろう、本の結果を出してみる話も、ユーザーは数分で見てすぐに知れる結果の方を求めているのではないかというような話に終わりました。僕はこの時スティーブジョブスの独裁の重要性と必然性を感じました。


けれども、実際冷静になって考えると、自分は今与えている仕事に対し、こんな事やっててもダメだーと言いつつ、上手くいくかもわからないアイデアを主張してくる、正直迷惑な新人極まりないと思います。特にチームの上司には迷惑をかけたなと思います。
これが「現状の問題をコツコツ解決している」現場と「新しい問題を見つけ、それの解決法を探る」研究の違いなのかもしれず、僕が研究的なマインドに染まってしまっていたのが不幸な部分だったのかもしれません。それでも、Googleはユーザーの利便性は考えられてもユーザーの幸せについてはあまり考えられてないんじゃないかなという漠然とした思いは自分の中からは消えませんでした。

Googleを退社することを思い立った経緯

先程も述べましたが、自分が言ったことを社内で実現するのも不可能ではなかったと思います。与えられた仕事でGoogleの先輩社員以上の成果を出して、更に空いた時間でやりたいことの企画書、デモ、プレゼン資料を作り、本社にいる凄く偉い人の前で流暢な英語で説得力のある発表をすることが出来れば。でも残念ながら僕はそんなにスーパーマンではありませんでした。


ある時、いろいろな可能性を模索していく中で、自分がやりたいことは社内じゃないとできないのか?と疑問を持ち、社内よりも外に出て100%の時間を使って自分で作った方が早いのではないかと考えるようになりました。自分が考えた案の幾つかは社内でやらないと意味がないけれども、幾つかは外でもできるものでしょう。本当にそれがGoogleの検索にとっても価値があるなら、価値を示した後で買収してもらって使ってもらえるユーザー数を劇的に増やすことだってできるわけです。何がOKで何がダメかも固定された上の人が決めるのではなく、どこかからお金が集まって、ちゃんとサービスを運営していくことが出来たらOK、できなくて会社が潰れてしまえばダメだったことがはっきりします。


ただ自分は、一人でやってもモチベーションが続く人間でないことは自分で理解していました。そこで仲間を集めて会社を作るという案が生まれ、そうすると必然的に会計や登記関係に関する懸念が生まれ、自分で起業する方法と共にある程度自由のきく小さい会社(僕は勝手に5人以下と数値を決めていました)を見てみるという方針に発展して行きました。


ただ、それでもやはりGoogleの給与や福利厚生などの待遇は捨てがたいものがあります。そんな中でもやはりGoogleを去る決断ができたのは、何かをやりたいという未来に対するポジティブな感情だけでなく、現状に対するネガティブな感情もあったことは告白しておきたいと思います。


まず、僕がこれまで言ってきたことというのは、どれだけ独自性があるのでしょう?そしてどれだけ誰にも受け入れられないような間違ったことを言っているのでしょうか?僕は社内で僕と同じようなことを言っている人に一人も会いませんでした。少し先に辞めていった年上の同期の言葉を借りると、正直、自分だけ頭がおかしいのかと思った(Google 辞めました - アスペ日記)ことがありました。


もう一つ。本当にGoogleは環境がよく、与えられた仕事も、やる価値が全く無いわけではもちろんないので、やっていたら現状に満足していってしまうことにも気づきました。自分が何かを変える前に、自分が変えられてしまう。僕を退社に踏み切らせたのは、そういう恐怖感でした。


これが社内の異動などの選択肢もあったにもかかわらず、社外に出る決断をした理由です。
Googleの話だいぶ長くなっちゃいました。

そんなこんなでWantedlyに決めた理由

もうここまで書けば、Wantedlyが自分がGoogleで抱いた解決したい問題の幾つかにマッチしている事がわかるかと思います。特にその中でも就職に関する問題は、自分自信や、自分の親しい友達やいとこに対して起こっているので凄く当事者意識があります。「就職できない」はもちろん、「就職したけど、全然楽しそうじゃない」「学生時代自分がやってきたことややりたかったこととは全く違うところに就職してしまって後悔している」などなど。
僕はこういう身近な人が感じている問題を解決していきたいなと本気で思います。ちなみにこのマッチングの問題は、検索・推薦という技術的な文脈でも面白い分野なのでこのあたりはまた別の機会に書きたいなと思います。


やりたいことがそこにあるというのはもちろん大きな理由ですが、ただここに決めた一番の理由はそれではないです。


自分は、アルバイトなどで、小さい会社を幾つか経験してきているし、そういう経験をしている友達も多いです。そういう人達と話している中で行き着くのは、小さい会社で一番大切なのは、何をやるかでも、資金でも、ビジネスモデルでもなく、メンバーの人間関係です。


小さい会社であればあるほど、メンバーと密に議論するし、人間関係は必然的に深く濃くなります。気の合う仲間と仕事していたほうが楽しいというのはもちろんありますが、こういう時は良くない状況の時も考えた方が良くて、会社がうまく回っていない時はあるでしょうし、さらにその状況下で、意見の対立だってあるでしょう。そんな中で、誰かと口も聞きたくないぐらいの人間関係になっては本当におしまいです。大きい会社なら、上司に気を使ってもらったり、最悪、人事異動をしてもらったり対応はいくらでもありますが、小さい会社だと辞める以外の選択肢がなくなるのは想像がつくでしょう。


だから、自分がそれぞれのメンバーを尊敬できるか、意見が対立してもちゃんと折り合いが付けられそうか、間違ったことを言っても許しあえる雰囲気があるかというのは見ておいた方が良いと思います。もし、小さい会社への就職を考えているという人がいれば覚えておいてほしいなと思います。


この辺は、CEOの仲さんもインタビューで「何をするかより誰とするか」と言っているので考えは同じだと思います。ただ、何をするかもすごく大事だと僕は思っていて、その中でどちらが上かをつけるならという意味です。多分、仲さんもそうなんじゃないかなと思います。


ただ、なんだかんだ言いつつ、僕はこのメンバー凄く面白いって思ったのが決定打です(笑)

とりあえず、メンバーは他にもいるのですが、正社員の三名だけを紹介すると、


CEOの仲さん。もう何度も会っているのに、何度あっても、僕はこの人の10%もわかっていないなぁと思わされるすごい人。多才だし、すごいしっかりしたことを言うなと思えば、急にぶっ飛んだことを言い始めるのがすごく面白い。簡単に言えば、こういう記事が書けつつ、こういう記事も書けちゃうところがとても好きです。


CFOの萩原さん。オフィスに始めて遊びに行った時、いきなりチーフ残飯(雑用)オフィサー(CZO)と紹介され、ファイナンスはもちろん何でもやっちゃう、いじられキャラです。ちなみに、ピアノも弾けるし駐車もできます。それでも、皆(特に仲さんw)がぶっ飛んだ時にが最後に締めてくれる頼りになる人です。


CTOの川崎さん。決めゼリフが、ゆるい表情で( ー`дー´)キリッ。もともとFacebookで友達の友達としてつながっていてWantedly的に僕が採用されるに至ったキーパーソンです。エンジニアとしても信頼出来る上、マネージングがうまいなと感じています。


Wantedlyの目標は「究極の適材適所」なのですが、これは少なくとも社内ではできているなと感じます。それぞれがそれぞれに合った役をきちんとできています。


実は、Wantedlyは、僕と会う2回目に、社内の戦略を話しあうようなブレインストーミングに僕を参加させてくれました。

この時、僕は、ここでは、「正しい意見だけじゃなくて、馬鹿な意見や間違った意見」もいっぱい言えることに気づきました。そして、間違っていたら誰かが正してくれる雰囲気があるのが凄く安心感があって、これが、僕がWantedlyを一番気に入っているところです。これからもこの雰囲気はずっと続いて欲しいなと思っています。




ちなみに、要素として次に大切だと思っているのは、何をやるか、次が資金で、次がビジネスモデル(どう収益化するか)です。


何をやるかに関しては、そもそもやりたいことが違っていたらその会社に入ることは無いだろうから、大して書くことはないです。ただ、何をするかなんて小さい会社だと状況によって簡単に変わるし、顧客(や出資者)の要求にも左右されるので、変わることが無いであろう大きいレベルでの目標に共感できれば良いという感じで、自分は捉えています。あと、そもそも何をやるかなんて大きい会社と比べて、より自分次第だと思いますし。


資金に関しては、そもそも資金の少ない会社に移っておいて何言ってるんだという感じかもしれませんが、小さい会社であればあるほどお金がなくなると、資金を確保するために本来やりたいことではなかった、下請け的な仕事をやらざるを得なくなり、そのまま下請けをやり続けるみたいなことにもなりかねないので、気をつけないと行けないなと思ってます。


収益化については、あえて書くとすれば、収益化後回しでもうまくいっているところは、特にアメリカで多いんだよと言うことは、FacebookやFreebaseなどを見て、最近気づいてきた事実です。

まとめ

それぞれの道

恋愛は就職に似ている。


僕が付き合っていた彼女は、皆の人気者で高嶺の花的な存在だった。美人だったし、芸能活動をやっていて結構お金も持っていた。手料理も美味しかった。特に彼女が時折見せるイタズラっぽい笑顔はそれを見た皆を魅了する力があった。僕は彼女のことを誇りに思ったし、そんな彼女と付き合っている自分も誇りに思ってたのかもしれない。


それでも、そういう幸せは長くは続かなかった。人は長いこと一緒にいると見た目やお金よりももっと深いところの価値を見いだし始めるからだ。付き合う前には考えられないぐらいいろんな話をした。お互いの将来の話、人生で何が重要だと思い、何に価値を感じるかという話。こういうことをするとお互いの絆がより深まったりするのかもしれないけれど、僕達の場合はより互いの違いに気づいていく結果になってしまった。

まず、彼女は友達を数で判断するところがあった。周りにいる友達の数が多い人を慕い、そうでない人を軽蔑した。僕は少ない友達でもそれが信頼できるものであればそれで良いと思った。また、親が厳しいらしく、どこに遊びに行く時も何する時も親に許可をとっていた。別に悪いことだとは思わなかったけれど、やっぱり窮屈だとは思った。

そういうことが原因で喧嘩をしたこともあった。けど大抵はお互いを傷つけないために話に火がつく前に言葉を飲み込むようになっていた。ちゃんともっと時間をとって、話しあう時間を作ればよかったのかもしれない。諦めずに歩み寄る努力をし続ければ良かったのかもしれない。ただもう自分にはどうすれば彼女に僕の言葉が届くのかわからなくなっていた。


彼女の魅力は今でも変わらない。ただ僕は、少し距離を置いて彼女に接し、彼女と付き合ったことで気づいた自分の価値観を基に、新しい出会いを探したほうが良いと思った。



・・・ということで、僕は昨日Googleを辞めました。



これから何をするかは、ある程度考えはあるが、まだ決まってはいない。時間はたっぷりある。ただ、Googleで感じた、Googleではできない世の中の問題に本気で立ち向かえるような場所に行きたいと思っている。

上海交通大学のICPC事情

今いる北京のMSRAで、今年のACM-ICPC世界大会で優勝したチームのメンバー1人とそのアシスタントコーチがたまたまインターンに来ていて、いろいろICPCにまつわる話を聞いたので報告します。特にアシスタントコーチ(といっても学部3年生)の人は自分と同じグループにいるので主にその人に聞いた内容です。


聞いた話の中で、特に上海交通らしいと思われる3つの要素が

  • 徹底したコーチによるチーム管理
  • 夏休みの週5での練習会
  • リーダー制のチーム戦略

です。以下これに絡めて、聞いた話をうまくまとめていければいいなぁと思います。

コーチによるチーム管理

上海交通チームに何度かあってる人なら、よく目にする、たまにはしゃぎ過ぎている年配の男性をご存知なのではないでしょうか。そう、その人がコーチです。そして、その下に何人かのアシスタントコーチ(1年か2年で変わるのが普通)がついて出場者全員を管理しています。

入部に数学のテストがあり点が悪いと参加すらできないとか言うのも聞く話ですが、コーチ管理の一番の目玉はやはりチーム編成でしょう。

  • トップチームは完全に実力だけで、本人達の意見も聞かずに決める
  • 2位以下のチームはあえて実力を均等にしつつ割り振る
    • 全員の希望(誰と組みたいか、リーダーをやりたいか等)をとっているのでここではそれも参考にする

と言うのが方針のようです。2位チーム以下を均等に割り振るのは、2位チーム以下同士で競わせて士気を高めるのと、最後の最後に世界大会に何処が出るかという争いを避けるためでしょう。ただ、これにはいろいろ人間関係的な問題があることはアシスタントコーチも思っているそうです。例えば、世界大会優勝チームは1,2,3年生のそれぞれのトップ。2,3年生は世界大会出場2回目。この中の2人は実は仲が悪いそうです。

そのほかのコーチ管理の例としては、参加者は大会に出たらなぜ解けなかったかなどの反省も含めた結果報告をしなければならないらしく、悪い成績だと結構きつく言われるようです。


ちなみに、アシスタントコーチは、海外旅行にただでいけるとはいえ、あまりに他人のために時間をつぶさなければならないので、結構人気ないらしいです。

夏休みの練習会

まず、「この学生はICPC夏季合宿をする」ということをそれぞれの教授に通達をし、他の課題や夏季授業などを免除してもらうようです。これが許されるのが、さすが上海交通といったところでしょうか。まぁこれは実は中国の文化な気もしますが。何かに秀でている人は相当優遇される雰囲気は感じます。

さて、合宿の内容ですが、基本はチーム練習です。各国のサイトの5時間の問題を5時間もしくは6時間で解きます。その後解けたチームが問題の解説をしていくという流れのようです。

まぁ、別に誰でも考え付く、ありきたりなメニューかとは思いますが、以下の3つの上海交通らしい特徴があると感じました。

  • チーム編成をコーチが管理しているので、ある日はこのチーム編成を試してみようなどと簡単にチームを変えられること
  • 基本的に問題は自分達では作らず、解説も選手達でやるので、コーチ側の手間が減ること
  • いくつかの問題は毎年決まったセットで、過去のトップチームとバーチャル対戦し、今年のチームの実力を量れること

1つ目は、チーム練習だけで個人練習をしなくても、個々の力を伸ばしやすくしているのではないかと思います。チームの練習ばかりになると、実はメインのコーダーだけができるようになっていて、実はチームの中での下の人が伸びていないとなりがちですが、チームが変わるので個人の能力も明らかになるのでしょう。まぁ全体で下であればチーム変わっても関係ないかもしれませんが。また、チームを変えることでどうやるとチームとしてうまく動けるのかをより意識できそうな気がします。

2つ目は、やっぱりできる人たちが集まっているところだからできるのだなぁと言う感覚です。最初の方は時間を6時間でやるので大半の問題の回答例をカバーできてしまうのでしょう。

3つ目に関しては、これが成り立つのは、大抵のICPC参加者は2年か3年で辞めてしまうという事実もあるからかもしれません。2回目ぐらいなら多少同じ問題を解くことになっても、1年前のセットならまぁもう一度練習する意味もあるでしょう。


これを夏休みの5週間で25セットやるのでまぁ成長することは間違いないでしょうね・・・。

チーム戦略

上海交通大では、「1+1+1」と呼ばれる3人が別々の問題をやる方法を使うこともあるらしいのですが、伝統的に「リーダー制」がとられているようです。

「リーダー制」とは、あらかじめ決められたリーダーが、誰がどの問題を解くか、誰がパソコンを使うかなどコンテスト中の采配を全て担うととうものです。あらかじめリーダーに従うことが決まっているため、その他の人は問題の情報や自分が何ができそうかなどできる限りの判断材料をリーダーに与えなければなりません。

リーダーのする仕事は一言で言えばコンピュータの割り当てです。誰が読んだどの問題から解くか、バグったときに交代させるかそのまま続けさせるか、などが大きいようですね。まぁそれを割り振ると言うことは、他の人は何をするみたいなことももちろん指示出すようですが。

リーダーに与えるべき最も大切な情報であると言われたのが、「自分がこの問題を何分あれば解けるか」という情報です。つまり、上海交通の人は解けるかだけではなく、何分で解けるかをできる限り正確に見積もる意識を常に持ち訓練をしています。

その他、戦略的な話としては、以下のことを聞きました。

  • 基本方針としては、「パソコンはできる限り遊ばせておかない」
  • 簡単な問題はできる限りチームの最弱者に解かせる。できる人が解いた方が10分程度早くても全体としてみれば損。
    • あらかじめ、最弱者が問題A(簡単であると予想される問題)を読むことを決めておき、基本的にはそのまま解く。
    • 日付とか、ただだるいだけの問題も然り。最弱者かわいそすw
  • リーダーが割り当てるべきタスクとして、問題を読む、アルゴリズムを考える、コードを書くのほかに、テストデータを作るも含まれる。(もちろんそれが必要そうな場合だけ)
  • リーダーは最初の1時間はコーディングはせずに、プランニングをする。(つまりこの問題セットで何問解くかを1時間目の時点でほとんど決めてしまう)
  • 1時間半前(通常、2問解けるぎりぎりの時間らしい)になったら、戦略を切り替え、現状と分析し、1問集中するか、2問通すか、1位じゃないとダメな場合は無理しても3問別々に考えるか、などを判断する。

注意として、これは基本側であり、さらに世界大会の戦略として聞いた話です。例えば日本の国内予選程度のレベルならいちいち律儀にリーダーに報告するみたいなことはやらないと思います。レベルが上がり、一瞬で解ける問題が数問で初速があまり関係ない時、上位で問題数で勝敗が決まるレベルのとき、より有効に働くのでしょう。

ちなみに、リーダーは基本的に年長者(さらに希望者)がなります。さらに、リーダーはコーディング力はチーム内でトップではない方がうまくいくようです。まぁ最近の日本でもそうですが、IOIなどの関係もあり年少者の方が大抵一人のコーディング能力はあるようなので、意識しなくてもうまくまわっているみたいです。


その他、詳しくは分かりませんでしたが、各選手の強みなどをより意識しているように感じました。リーダー制でうまく割り振るためには必要ですし、チーム編成をするときにも強みがかぶらないよう気をつけているみたいでしたし。何かそういうデータも取ってるのかも知れませんね。

まとめ

やはり、上海交通の強さは良くも悪くも、徹底された管理システムによるところが大きいようです。日本もそれをやれば「東大予選」なるものがなくなるのかもしれませんが、面白くないですし、現実的に不可能でしょうしね。
ただ、日本も全く参考にすべきとこがないかと言えばそうでもなく、

  • チーム内での徹底した役割分担
  • お互いの長所の把握
  • 解ける時間を意識した練習

などは十分取り入れるべき点だなと思いました。
どのレベルでも、この人の方がコード書けるというのはあって、大抵そういう人がリーダーっぽくなり、仕切ってしまいがちなので、それを敢えて避けるようなチームの役割分担を考えてみるといいのかもしれませんね。


これは完全に個人的な余談ですが、OB1年目と言うOB会を主体的に回していく立場にありながら、海外にいるなどの事情がありOB会に全然貢献できていないので、せめてもとの思いをこめ、聞いた話を記事にしてみました。
現役の誰かの参考になることを願っています。

C++の多倍長整数クラス

Google Code Jamの関係で、多倍長演算クラスを作ってみた。


JavaのBigIntegerと比べて格段に見劣りするし、確実にgmpxxを使った方がいいし、自分は本番Python使って全く困らなかったけど、それでもライブラリなしでコピペしてC++で解きたいときに。


constとか&書く場所には気をつけたつもり。C++くだらないところでむずす。これ、こうした方がいいよとかあればぜひコメントとかTweetとかくださいな。


ちなみにGCJ2010QualのProblemBではVerifyしたけど、間違ってる可能性も多々あるし、インプットの実装適当だし、割り算とかもう少し高速な実装ができそうな気がする。修正案も募集中です。


bigint.cpp

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

class bigint {
private:
  static const int BASE = 100000000, LEN = 8;
  bool negative;
  std::vector<int> a;
  bigint& normalize();
public:
  bigint(int x = 0);
  bigint(const std::string& s);
  bigint& operator = (const bigint& x);
  bigint& operator = (int x);
  bigint& operator = (const std::string& s);
  const bool operator < (const bigint& x) const;
  const bool operator > (const bigint& x) const;
  const bool operator <= (const bigint& x) const;
  const bool operator >= (const bigint& x) const;
  const bool operator != (const bigint& x) const;
  const bool operator == (const bigint& x) const;
  bigint operator -() const;
  bigint& operator += (const bigint& x);
  bigint& operator -= (const bigint& x);
  bigint& operator *= (const bigint& x);
  bigint& operator /= (const bigint& x);
  bigint& operator %= (const bigint& x);
  const bigint operator + (const bigint& x) const;
  const bigint operator - (const bigint& x) const;
  const bigint operator * (const bigint& x) const;
  const bigint operator / (const bigint& x) const;
  const bigint operator % (const bigint& x) const;
  friend std::pair<bigint,bigint> divmod(const bigint& lhs, const bigint& rhs);
  friend std::istream& operator >> (std::ostream& is, bigint& x); //適当実装
  friend std::ostream& operator << (std::ostream& os, const bigint& x);
  friend const bigint abs(bigint x);
};
bigint& bigint::normalize() {
  int i = a.size()-1;
  while (i >= 0 && a[i] == 0) --i;
  a.resize(i+1);
  if (a.size() == 0) negative = false;
  return *this;
}
bigint::bigint(int x) : negative(x<0) {
  x = abs(x);
  for (; x > 0; x /= BASE) a.push_back(x % BASE);
}
bigint::bigint(const std::string& s): negative(false) {
  int p = 0;
  if (s[p] == '-') { ++p; negative = true; }
  else if (s[p] == '+') { ++p; }
  for (int i = s.size()-1, v = BASE; i >= p; --i, v*=10) {
    int x = s[i]-'0';
    if (x < 0 || 9 < x) {
      std::cerr<<"error: parse error:"<<s<<std::endl;
      exit(1);
    } 
    if (v == BASE) {
      v = 1;
      a.push_back(x);
    } else a.back() += (x)*v;
  }
  normalize();
}
bigint& bigint::operator = (const bigint& x) {
  negative = x.negative;
  a = x.a;
  return *this;
}
bigint& bigint::operator = (int x) { return *this = bigint(x); }
bigint& bigint::operator = (const std::string& s) { return *this = bigint(s); }
const bool bigint::operator < (const bigint& x) const {
  if (negative != x.negative) return negative < x.negative;
  if (a.size() != x.a.size()) return (a.size() < x.a.size())^negative;
  for(int i = a.size()-1; i >= 0; --i)
    if (a[i] != x.a[i]) return (a[i] < x.a[i])^negative;
  return false;
}
const bool bigint::operator > (const bigint& x) const { return x<(*this); }
const bool bigint::operator <= (const bigint& x) const { return !(x<(*this)); }
const bool bigint::operator >= (const bigint& x) const { return !((*this)<x); }
const bool bigint::operator != (const bigint& x) const { return (*this)<x || x<(*this); }
const bool bigint::operator == (const bigint& x) const { return !((*this)<x || x<(*this)); }
bigint bigint::operator -() const {
  bigint ret(*this);
  if (a.size()) ret.negative = !ret.negative;
  return ret;
}
bigint& bigint::operator += (const bigint& x) {
  if (negative != x.negative) return *this -= -x;
  if (a.size() < x.a.size()) a.resize(x.a.size());
  int tmp = 0;
  for (int i = 0; i < a.size(); ++i) {
    a[i] += (i<x.a.size()?x.a[i]:0) + tmp;
    tmp = a[i] / BASE;
    a[i] %= BASE;
  }
  if (tmp) a.push_back(1);
  return *this;
}
bigint& bigint::operator -= (const bigint& x) {
  if (negative != x.negative) return *this += -x;
  std::vector<int> b(x.a);
  if ((*this < x) ^ negative) {
    a.swap(b);
    negative = !negative;
  }
  for (int i = 0, tmp = 0; i < a.size(); ++i) {
    a[i] += BASE - (i<b.size()?b[i]:0) + tmp;
    tmp = a[i] / BASE - 1;
    a[i] %= BASE;
  }
  return this->normalize();
}
bigint& bigint::operator *= (const bigint& x) {
  negative ^= x.negative;
  std::vector<int> c(a.size()*x.a.size()+1);
  for (int i = 0; i < a.size(); ++i) {
    long long tmp = 0;
    for (int j = 0; j < x.a.size(); ++j) {
      long long v = (long long)a[i] * x.a[j] + c[i+j] + tmp;
      tmp = v / BASE;
      c[i+j] = (int)(v % BASE);
    }
    if (tmp) c[i+x.a.size()] += (int)tmp;
  }
  a.swap(c);
  return this->normalize();
}
bigint& bigint::operator /= (const bigint& x) {
  return *this = divmod(*this,x).first;
}
bigint& bigint::operator %= (const bigint& x) {
  return *this = divmod(*this,x).second;
}
const bigint bigint::operator + (const bigint& x) const {
  bigint res(*this); return res += x;
}
const bigint bigint::operator - (const bigint& x) const {
  bigint res(*this); return res -= x;
}
const bigint bigint::operator * (const bigint& x) const {
  bigint res(*this); return res *= x;
}
const bigint bigint::operator / (const bigint& x) const {
  bigint res(*this); return res /= x;
}
const bigint bigint::operator % (const bigint& x) const {
  bigint res(*this); return res %= x;
}
std::pair<bigint,bigint> divmod(const bigint& lhs, const bigint& rhs) {
  if (!rhs.a.size()) {
    std::cerr<<"error: division by zero"<<std::endl;
    exit(1);
  }
  bigint x(abs(rhs)), q, r;
  for (int i = lhs.a.size()-1; i >= 0; --i) {
    r = r * bigint::BASE + lhs.a[i];
    int head = 0, tail = bigint::BASE;
    if (r >= x) {
      while (head + 1 < tail) {
        int mid = (head + tail) / 2;
        if (x * bigint(mid) > r) tail = mid;
        else head = mid;
      }
      r -= x * head;
    }
    q.a.push_back(head);
  }
  reverse(q.a.begin(), q.a.end());
  bool neg = lhs.negative ^ lhs.negative;
  q.negative = neg; r.negative = neg;
  return std::make_pair(q.normalize(), r.normalize());
}
std::istream& operator >> (std::istream& is, bigint& x) {
  std::string tmp; is >> tmp;
  x = bigint(tmp);
  return is;
}
std::ostream& operator << (std::ostream& os, const bigint& x) {
  if (x.negative) os << '-';
  if (!x.a.size()) os << 0;
  else os << x.a.back();
  for (int i = x.a.size()-2; i >= 0; --i) {
    os.width(bigint::LEN);
    os.fill('0');
    os << x.a[i];
  }
  return os;
}
const bigint abs(bigint x) {
  x.negative = false;
  return x;
}

/* end of the library */

#define PRINT(x) std::cout<<#x":"<<(x)<<std::endl

int main() {
  bigint a("-100000000000000000000"), b(999999999);//*
  std::cerr<<"input bigint a>"<<std::flush;
  std::cin>>a;
  std::cerr<<"input bigint b>"<<std::flush;
  std::cin>>b;//*/
  PRINT(a);
  PRINT(b);
  PRINT(-a);
  PRINT(-b);
  PRINT(abs(a));
  PRINT(abs(b));
  PRINT(a<b);
  PRINT(a>b);
  PRINT(a<=b);
  PRINT(a>=b);
  PRINT(a!=b);
  PRINT(a==b);
  PRINT(a+b);
  PRINT(a-b);
  PRINT(a*b);
  PRINT(a/b);
  PRINT(a%b);
  return 0;
}

こんな感じです。

みなさん好きに使ってください。責任は負いませんがw

Cabocha新インターフェース用のC++サンプルコード

Cabocha 0.6 pre系列はインターフェースが変わったので、取得できるものを全部列挙する感じで組んでみた。

#include <iostream>
#include <string>
#include <cabocha.h>
using namespace std;

//How to compile?
//g++ -O2 `cabocha-config --cflags` cabochasample.cpp -o cabochasample `cabocha-config --libs`

string getBase(const CaboCha::Token* token) {
  if (token->feature_list_size > 6 && token->feature_list[6] != string("*"))
    return token->feature_list[6];
  return token->surface;
}
string getType(const CaboCha::Token* token) {
  return string(token->feature_list[0]) + "-" + token->feature_list[1];
}

int main (int argc, char **argv) {
  try {
    CaboCha::Parser* parser = CaboCha::createParser(argc, argv);
    string sentence;
    while (getline(cin, sentence)) {
      const CaboCha::Tree *tree = parser->parse(sentence.data());
      for (unsigned int i = 0; i < tree->size(); ++i) {
        const CaboCha::Token *token = tree->token (i);

        // is this token begnning of chunk ?
        if (token->chunk) {
          cout << endl;
          cerr << "* " 
               << token->chunk->link << ' '
               << token->chunk->head_pos  << ' '
               << token->chunk->func_pos  << ' '
               << token->chunk->token_size  << ' '
               << token->chunk->token_pos  << ' '
               << token->chunk->score << ' ';
          for (int k = 0; k < token->chunk->feature_list_size; k++)
            cerr << token->chunk->feature_list[k] << ' ';
          cerr << endl;
        }

        cout << '[' << getBase(token) << '/' << getType(token) << "] ";
        cerr << token->surface << '\t'
             << token->normalized_surface << '\t'
             << token->feature << '\t';
        for (int k = 0; k < token->feature_list_size; k++)
          cerr << token->feature_list[k] << '\t';
        cerr << token->ne << endl;
      }
      cout << endl;
    }
  } catch (exception &e) {
    cerr << e.what() << endl;
    return 1;
  }
  return 0;
}

こんな感じでいかがでしょうか?

標準出力の結果

[かぼちゃ/名詞-一般] [が/助詞-格助詞] 
[簡単/名詞-形容動詞語幹] [に/助詞-格助詞] 
[入る/動詞-自立] [て/助詞-接続助詞] 
[とても/副詞-助詞類接続] 
[幸せ/名詞-形容動詞語幹] 

標準出力+標準エラー出力の結果

* 2 0 1 2 0 0.545432 f_H0:かぼちゃ f_H1:名詞 f_H2:一般 f_F0:が f_F1:助詞 f_F2:格助詞 f_F3:一般 a:が B:名詞-一般 G_CASE:が f_BOS:1 
[かぼちゃ/名詞-一般] かぼちゃ	かぼちゃ	名詞,一般,*,*,*,*,かぼちゃ,カボチャ,カボチャ	名詞	一般	*	*	*	*	かぼちゃ	カボチャ	カボチャ	O
[が/助詞-格助詞] が	が	助詞,格助詞,一般,*,*,*,が,ガ,ガ	助詞	格助詞	一般	*	*	*	が	ガ	ガ	O

* 2 0 1 2 2 1.86243 f_H0:簡単 f_H1:名詞 f_H2:形容動詞語幹 f_F0:に f_F1:助詞 f_F2:格助詞 f_F3:一般 a:に B:名詞-形容動詞語幹 G_CASE:に 
[簡単/名詞-形容動詞語幹] 簡単	簡単	名詞,形容動詞語幹,*,*,*,*,簡単,カンタン,カンタン	名詞	形容動詞語幹	*	*	*	*	簡単	カンタン	カンタン	O
[に/助詞-格助詞] に	に	助詞,格助詞,一般,*,*,*,に,ニ,ニ	助詞	格助詞	一般	*	*	*	に	ニ	ニ	O

* 4 0 1 2 4 0 f_H0:入っ f_H1:動詞 f_H2:自立 f_H5:連用タ接続 f_H6:五段・ラ行 f_F0:て f_F1:助詞 f_F2:接続助詞 A:て B:動詞-自立 G_CASE:て 
[入る/動詞-自立] 入っ	入っ	動詞,自立,*,*,五段・ラ行,連用タ接続,入る,ハイッ,ハイッ	動詞	自立	*	*	五段・ラ行	連用タ接続	入る	ハイッ	ハイッ	O
[て/助詞-接続助詞] て	て	助詞,接続助詞,*,*,*,*,て,テ,テ	助詞	接続助詞	*	*	*	*	て	テ	テ	O

* 4 0 0 1 6 0 F_H0:とても F_H1:副詞 F_H2:助詞類接続 F_F0:とても F_F1:副詞 F_F2:助詞類接続 A:とても B:副詞-助詞類接続 
[とても/副詞-助詞類接続] とても	とても	副詞,助詞類接続,*,*,*,*,とても,トテモ,トテモ	副詞	助詞類接続	*	*	*	*	とても	トテモ	トテモ	O

* -1 0 0 1 7 0 F_H0:幸せ F_H1:名詞 F_H2:形容動詞語幹 F_F0:幸せ F_F1:名詞 F_F2:形容動詞語幹 A:名詞-形容動詞語幹 B:名詞-形容動詞語幹 F_EOS:1 
[幸せ/名詞-形容動詞語幹] 幸せ	幸せ	名詞,形容動詞語幹,*,*,*,*,幸せ,シアワセ,シアワセ	名詞	形容動詞語幹	*	*	*	*	幸せ	シアワセ	シアワセ	O

係り受け解析器cabochaのインストール

SVMとかを自前で作ったのかインストールがめちゃくちゃ簡単になったcabochaがあると聞いたので入れてみた。

http://chasen.org/~taku/blog/archives/2008/01/cabocha_060_pre.html

前準備

いれたもの

(リンク先は現状で最新のもの)

wget http://(ry
tar xvzf XXX.tar.gz
cd XXX
./configure
make
sudo make install

ipadicでUTF8使う場合は"./configure --with-charset=utf8"する。

cygwinなどWindows環境に入れる場合は、
Mecabのconfigure時に

./configure LDFLAGS="-liconv" --with-charset=utf8

インストール時にcommon.hで

#if defined(_MSC_VER) || defined(__CYGWIN__)
#define for if (0); else for
/* why windows.h define such a generic macro */
#undef max
#undef min
#define snprintf _snprintf
#endif

の部分をalgorithmなどいろいろインクルードする前に持ってこないと、"expected unqualified-id before ..."とかいう名前がコンフリクトしてるときによく起こるエラーが出るみたい。まじ、windows.hのmin,maxうざい。


さらに、CRF++のMakefileをいじってあげないとCabochaのインストールの際にlibtoolがらみのリンクエラーが発生する。いじり方はCXXLINKの最後あたりに-no-undefinedをいれてあげるだけ。

いよいよかぼちゃ

いれたもの

インストール法はまぁいつもどおり。tar.bz2を解凍する方法に戸惑ったけど、

bzip2 -dc ファイル名 | tar xvf -

でいけた。環境によってはtar命令のオプションだけでできるっぽいんだけど・・・。
こっちもUTF8使う時に"./configure --with-charset=utf8"する。
Macにインストールする時は"LIBS=-liconv ./configure --with-charset=utf8"にしないといけない。

完了

$ cabocha
かぼちゃが簡単に入ってとても幸せ
かぼちゃが---D
      簡単に-D
        入って---D
          とても-D
              幸せ
EOS