「転職しました」

2011年12月22日木曜日

あるいは、@muo_jpの頭の中。

技術の話だと思った?残念、さやかちゃんでした!
本エントリは「Android Advent Calendar 2011」への参加エントリです。12/22担当の@muo_jpと申します(ぺこり

「転職しました」というのは、ひょっとすると今年一番多く見たblog記事タイトルですね。転職アドベントカレンダーとか裏の裏の裏の裏の裏ぐらいまで開催出来るんじゃないかという勢いです。

イントロ
AndroidやiOSはエコシステム(生態系)を構成する要素です。OS本体だけで完結するものではなく、それを載せる端末、そして上位で動作するアプリケーション、その他サーバサイドなど数多の要素が複雑に絡み合ったものです。今回は、Androidエコシステムの中でも実際にアプリやハードウェア、サービスを開発している技術者にフォーカスした話を書いてみます。つまり、技術者の転職という話です。OSバージョンアップの話もADKの話も全部すっ飛ばして、転職の話です。
とにかく今年はAndroid界隈を中心に、自身の周囲で転職する人が多い年でした。そんな年の終わりに、技術者の転職というものと、これからの技術者における個人と会社のありかたみたいなのを考えてみた次第です。書いてるのは至って普通なことで、新しいとしたらAndroid好きなエンジニア(私)がAndroid方面の人々の転職を一年間眺めてきた結果を書いてるというあたりでしょうか。
技術者としての人生の考え方、転職の考え方などについて最近だと転職エージェント系の視点で書かれたのは多いけど、エンジニア視点ってのはあんまりないかなと思い、これは今年のうちにAndroidな話のひとつとして書いておいたほうがいいと信じ技術ネタをぶん投げて書くことにしました。
用意していたエントリを当日になってごっそりキャンセルしてこの記事書き始めましたさーせん。そして、内輪向けの書き方にならないよう注意を払ったつもりですが、通りすがりに読んでイミフ、という感じだったら私の力不足です。先に謝っておきます。

注意書き
  • 技術者転職how-toでも、技術者採用how-toでもないのでご了承ください
  • ごめんね、AndroidエントリなんだけどAndroid技術エントリじゃなくてごめんね

2011年、ソーシャル、スマートフォン転職の一年間
明らかに、転職の年だったという話。年の後半、秋口あたりから転職者がかなり多かった。最近だと@tomo_watanabeさんとか@adamrockerさんとか@zaki50さんとか。
少し引いて考えてみると、ソーシャル系とスマートフォン系、共に人の動きが激しい一年だった。
ソーシャル系は昨年の頭あたりからずっと。ソーシャル系では、サービス開発志向の人が多くSAPへ、より大規模なデータやインフラ/ミドルウェアへの興味が強い人が大手プラットフォームへ集中という構図。
スマートフォン系(iOS/Android)は今年に入ってから激しくなった。とはいえ、まだマネタイズが難しい。iTunes Store / Android Marketで展開するアプリにおいて、課金での売上を読むのは依然として難しい状況。スマートフォンの中でもB2Cでお金になっているのはソーシャル(特にゲーム)が多いという状況。
このへんは一定は産業化されてるという話を少々。ある程度新規参入しやすい領域というよりは産業化/大規模化が進行している。つまり規模の経済が成り立つようになりつつある。ゲーム系だと従来の大手コンソール向けゲームベンダーが強くなってる。
B2B(2C)の観点だと、ユーザのスマートフォン移行が急激に進行しているために従来提供してきたサービスをスマートフォンへ「対応させざるを得ない」状況が生じたことでの仕事が多いように見える。端末の販売に直結するAndroid本体周辺のプリインアプリケーションなども一定ボリューム投資されている状況ではあるが、支配的な発注額は占めていない(はず)。となると新規投資フェイズの会社へ行くか、ソーシャル系で筋の良さそうな会社へ行くか、B2B(2C)の発注者または受注者側へ行くか、はたまた全く別の軸を選ぶか、という流れ。

なんで転職の舞台がスマートフォン(特にAndroid)って話。単純に考えると、そのエリアに強い人の転職市場における市場価値上昇という話。これはAndroid端末の種類・出荷台数を見れば明らか。 だけどそれって本当にお金になってるの?って話は先程書いたとおり。ただ、アプリ/サービス開発需要が高まった結果、技術者の転職しやすい状況にはなった。
では、人の流動性って上がったのかな?というのは当然気になる疑問だけど、調べてない。同じく人材市場として観た際の流動性についても、上昇したのか否かという数字を持っていないので確実なことは言えない。
ここで成長してる市場における人材の流動性が高まるというのは、平均勤続年数が低下しつつ、平均給与が比較的変動しないないしは上昇する状況と考えられる。少なくとも自身の周囲では起きているように思うけれど、サンプルが少なすぎるのでなんとも言えない。

転職者の簡単なプロファイル的なもの。直感的にはこんな感じ、というのをまずぶん投げておく。
エンジニアに限って(というのが、他の方面の人をあんまり知らないから)言えばわりと独身者が多い。年齢的に20代半ばから30代前半辺りの人が多い。ちょうど企業の中での自身の成長というのを強く意識する頃かなという感じ。
リスクに対する許容度を考えてみる。リスクの許容度は、当然結婚や子供によって左右される。リスクを取ってでも新たな行動を取ろうとするモチベーションについて考える際に参考にしたいのはマズローの欲求階層仮説。彼は人の欲求を以下の5段階に分け、このより高いレベルに向かって希求するものだとした。
  1. 生理的欲求 / 生命維持のための食欲・性欲・睡眠欲等の本能的・根源的な欲求
  2. 安全の欲求 / 衣類・住居など、安定・安全な状態を得ようとする欲求
  3. 所属と愛の欲求 / 集団に属したい、誰かに愛されたいといった欲求
  4. 承認の欲求 / 自分が集団から価値ある存在と認められ、尊敬されることを求める欲求
  5. 自己実現の欲求 / 自分の能力・可能性を発揮し、創作的活動や自己の成長を図りたいと思う欲求
5段階の中でフェイズの壁を感じた時に、自身の状況を強く考えるんじゃなかろうかというのが、直感的なところ。けどそれだけだとなんだかうそくさいので、いろんな人のケースを考えつつもう少し検討してみる。

転職していったひとびと
私の周りには、前向きな転職をしてる人が多い。いやまあ、後ろ向きな理由を好んで話すようなもんじゃないだろうからそう見えてるだけかもしれんけど。
これには時期的な事情が多分にあると考えられる(このへんは2節目で書いた)。もちろんクリエイターに認められない部分はあるけど、Androidは常用端末として一定以上の地位を占めるようになってるのは事実。
前向きな転職する人が多い一方で「Androidなんていう、まだ誰も知らないけどきっとこの先大きなうねりを生みそうな技術に賭けるために完全に畑違いだけど行くぜ」って感じのは、もうないんだろうな。ちょっと寂しい。まあいいや。
それはさておき。
エンジニアな人(特に、Android界隈に居る人々)の頭の構造ってどういう感じだろうと考える。
実際にどんな転職した人が居るっけ。1年前ぐらいから転職してる人多い。具体名を挙げるのは控えたい。数例を挙げるとこのような感じ。
  • メーカー勤務でわりと好き勝手にやれて給料も良かったけれどスタートアップへ転職した人
  • メーカー勤務で技術的な挑戦エリアの狭さを感じ、またその組織を変えていくことが非常に困難だと感じてスタートアップへ転職した人
  • メーカーの研究所勤務で、実際に新しい技術を使った開発を行いたくてスタートアップへ転職した人
  • 新卒からSIer勤務で自身の技術的な興味範囲と周囲のギャップに悩みスタートアップへ転職した人

かくいう私も転職組でね。あんまり長々と自分語りをしたいわけではないのでサラッと書いておくと私のバックグラウンドは経営学、生き方は主にテンションベース。身の回りに凄い人が多くて、そういう人と話すと、めっちゃテンション上がりながらめっちゃ憂鬱感じるという驚異の重ね合わせ状態を体感出来て楽しい、という感じ。
さて、そう言うほどAndroid方面の方々の転職話をしっかりと知っているわけでもないので、以下の分析では他に私が話を聞く機会があった方の中でマインド近そう、と感じた人の分も含めてみる。
皆がどういうポイントを重視して転職していったのかという話
  • 十分なお金を稼ぐことが出来るか
    そして、それが本源的に持つ金銭的価値と比較して適切と考えられるか(バブルならバブルで、期間の読みは妥当か)
  • 人間関係
    自分の求める距離感とマッチしているか(ある意味、会社の空気)
  • 会社から受けるストレス
  • マネージャとの関係
    エンジニアはエンジニア出身のマネージャなど、技術面で何かしらの尊敬を出来たりする人の言うことしか聞かない(ってことが多い)ので、マネージャが技術に対して一定以上の認識を持っているかどうか
  • 新しいことが出来る環境か
    AndroidやiOSなどに特化したスタートアップへ転職した人にはここが大きい感
  • 自身の人生の中でのステップ
    技術に深く関わり、自ら実装するフェイズからマネジメントフェイズへの移行なども
  • 会社の未来
    時代の変化にあわせて仕事の中身を変えていくことが出来るか
  • 自身でどこまでをコントロールすることが出来るか
    まずいところがあったとしても、自分の力で変えていくことが出来るか
    これをコントロールしたい度が強いほど、初期スタートアップへ行ってる感
  • 自身が会社のメインストリームに居らるかどうか
    メインストリームに居るべきと考えられるのか(むしろ新規投資系の事業に居たほうがいい場合もある。このへんは会社のスタイルによる)
  • 尊敬できる人が居るか
逆に、このへんの全てではないけれど一定の領域で満足を得られている人は転職していないし、当分転職しなさそうな人も多い。

転職していったAndroiderってこんな人かなー

  • 会社以外の人と触れる機会が多い人
  • 社外の勉強会や定例会での発表を行なう人
  • 積極的に情報を取り込み、自分の中で処理して発信する人
  • 飲み会好きな人
  • なんか新しいことやってみるのが好きな人
  • ヘンな人
    • 尖った分野を持ってる人
    • いくつかの分野を合わせてヘンなことを出来る人
  • 技術に対して取り組む姿勢(ここはあんまり一致する像が浮かばない。むしろいろいろ)
    • 技術がとことん好きな人も居る
    • 技術を使ってなにかを作ることが好きな人も居る
    • 人を巻き込んでサービスを作るのが好きな人も居る
  • Android好き?
    • 必ずしもAndroid大好きじゃない人も
これからのAndroid界隈の転職の形
業界全体としてマネタイズ策がもっとうまく出来て行かないと、早晩限界が来そう。当然単価は下がるしオフショア化もある程度は出来るようになっていくから(特に、新しいことをやるよりも既存の仕組みをルーチン回して新サービス/アプリ化する仕事から順次)。
大手志向って出てくるの?これ、微妙なところ。
個人(フリーランス)でやるか、会社でやるか。個人のほうが自由を確保出来るか?という話。会社で一定安定した収益基盤を保持しているほうが、積極的な投資を行える場合もある(これは会社の文化にもよる)。
個人対会社という構図。
技術者を集める構造とか分かる?技術者にとって、よりよい会社のありかたってどんなものだろう。技術者が会社に対して持つ要望(先ほど挙げたのが一例)を、全てではないにしても可能な範囲で満たすこと。難しい?難しいですたぶん。けど、他の会社が技術者に対する魅力を一定以上訴求するようになれば、ここを頑張らなきゃどうしようもなくなるので、この先はもう少し頑張る会社が増えると読んでる。
どういう会社に転職するとこの先幸せになれるんだろう。人によって全然違う。しかし、やっぱ凄い人が居るところに人は集まる。会社は自社の魅力をこれまでよりももっとオープンにすれば、割と伝わりやすい時代にはなってる。

会社と個人がうまく付き合っていくには
個人対会社というのは敵対関係じゃないし、片側が搾取する関係でもないべき。どっちかというと補完的なもの。会社だと厄介だけど個人だとやりやすいってエリアは、ここ数年かなり増えてる。例えばOSSの利用ひとつとっても、会社でGPL'edなソフトウェアのソースを利用して何かを作ってリリースというのは、現実問題結構大変。個人なら、当然自分自身の判断でなんとかなる。稟議の行列がしんどいと思うものはどんどん個人化してしまうのが良いはず。
その結果「会社で出来ないから個人でやる」というものがどんどん増え、開発者の会社に対するコミット度合いがどんどん低下するようなら会社のあり方を考えないとまずい。この意味でも「時間を100%コミットして、仕事終わったらヘロヘロになって自分の時間で何も出来ないような状態が恒常的である会社」などでは、その業務自体から得られる成長や満足が一定以上なければ人の流出は止まらない。
どういう形であれば個人の成長と会社でのアウトプットを両立出来るんだろう。ここには割と考えられることがあって
この1年間とか3年間とかを振り返って自分は成長することが出来た?出来たならそれは会社のおかげ?自分のおかげ?出来なかったならそれは会社のせい?自分のせい?それって多分一歩引いて考えると自分のせいなんだけど会社を変えることで改善しそう?
という問いを自分に対して投げかけてみて、会社での成長(または金銭面を含めた満足)と個人での成長(または金銭面を含めた満足)がバランスしているかを見ると、そこそこ分かる気がする。それと、関連してこれからの技術者像ってのを考えてみる
  • 社内・社外及び社内+社外でのコラボを多めに
    自身の視点を広げ、現時点で自身の持つ価値を比較的正確に把握するため
  • 技術だけでない、せめてもう一つの視点を持つべき
    経営視点?サービス視点?デザイン視点?自身が既に持っているバックグラウンドなどをほどよく活かしつつ、従来とは異なるエリアへ取り組むことで継続的な成長を習慣付けることが出来そう
こういう感じだと、会社と個人というのをうまくバランスさせて成長していくことが出来そうな感じ(自分の周囲とか見ていて感じるものなのであんまり客観性無いけど)。
一方、会社としては、人を囲い込むのが良いかというとそんなことはない。人間関係でも会社対個人でも、ヘタな囲い込みをするほど相手をなめちゃいけない。例えば、人材流出を防止する目的で勉強会参加禁止措置を取るなどすると、どんどん人は辞めていく(出来る人からやめていく)。

この1年半のうちに転職した人間として
作りたいと思った瞬間がアプリ/サービスの作りどき、端末は買いたいと思った時が買いどき、転職しようと思った瞬間が転職どき。
となると、自分がどういう状況になったら転職したいと思うのか、ということを想像しておくと兆候を掴んだ時に動きやすくなる。自分の場合は、だいぶ前にITをやるということだけ決めて仕事を始めたので、前職がITやめると決まった瞬間に転職活動始めた。確か、電話で話し終わった瞬間にDoDA開いてた。
twitterや各種Android関連の集まりで会える人って、面白いバックグラウンド持ってる方が多い。なんせ、Android界隈って起源が全然違うめっちゃ多様な人々が集まってる。そしてエンジニアがいかに大切な存在かということを分かってる人がいっぱいいる。だからこそたまに異様に心強いんじゃないかなー。生き方とかも参考になる。
つい先日も「これからの年収の話をしよう」という話があったりと、先輩に学ぶことも多いしね。割と損得抜きに思いをぶつけられる相手が多いような気がする。もやもやしたら(問題無い範囲で)ぶつけてみたらいいんじゃないかな!
人生は考えたほうが断然楽しい!

ということで
私、中澤 慧は12/31付でKLab株式会社を退職しません
私、来年はたぶんPS Vitaで遊びます。SDK欲しいです。
実は、@vvakameに「みゅおの頭の中みてみたい」と言われたので書いてみたというのも少しあります。小ネタの仕込みから、アニメネタへ偏ってるかが分かると思います(攻殻/まどマギ/ピンドラ)。
あ、そうそう。最近読んだ中では「先延ばし屋の予定帳」が秀逸でした。締め切りすぎないとパフォーマンスが上がらん的な話。締め切りを2倍過ぎてから作業を始めると、どんなに悪くても締め切りを4倍遅れることはないという素晴らしい理論です(白目)。

ではではこんなところで。長文お付き合いありがとうございました。来年も楽しい一年になりますように、そして(省略されました…続きを読むにはご自身で書いて下さい)

TopCoder SRM練習 - SRM 525 DIV 2 Easy/Medium

2011年12月4日日曜日

次回のSRMで復帰予定なのでそろそろ感覚取り戻していくべく練習。
以前とは違ってSRM同様75分の中で解くのを重視。

今回やったのはSRM 525 Div 2

まずはEasy問題。
(0, 0) - (N-1, 1) の 2 x N な通路中を(0, 0)からスタートして(N-1, 0)まで到達出来るかどうかを判定して返せ
という問題。で、条件としては「1点以上を共有しているマスには移動できる」の中で制約に「Wとマークされているマスは水浸しで通れない」ってのがある。
少し図に書いて考えてみると
□□
□□
ok
□■
■□
ok
■□
□■
ok
□■
□■
ng
■□
■□
ng
ということで、縦にFが2つ並んでいるものを除けば良いということになる。で、これはうっかり忘れてたのだけれどゴール地点がW(水浸し)ならそもそも到達不可能として弾かなきゃいけない。後から問題の制約条件読むと「道の最初と最後は水浸しじゃない」と明記されてたので救われた形。
public class RainyRoad {
 public String isReachable(String[] road) {
  int width = road[0].length();
  for (int i = 0; i < (width - 1); i++) {
   if (
     (road[0].charAt(i) == 'W' && road[1].charAt(i) == 'W') ||
     (road[0].charAt(i + 1) == 'W' && road[1].charAt(i + 1) == 'W')
     ) {
    return "NO";
   }
  }
  return "YES";
 }
}
コードはこのとおり。無駄に2列ずつやってるのは、最初のうち問題を読み間違って「上下左右にしか動けない」と勘違いしてた状態で書いたコードの残骸(非効率だけど間違いではない)が含まれるから。システムテスト通ってスコアは212.31 / 250。

続いてMedium問題。
読んだ瞬間には「うわ、GDD DevQuiz系?」と探索アルゴリズムに弱いのを思い出したけど、まずはしばしごにょごにょ考えた。
問題をざっくり訳すと
指定サイズ(1辺は1-30マス)の盤面のいくつかのマスにコインが置かれてる。このとき、コイン群を盤面全体に対して上下左右いずれかに動かし、盤面の端から適当な数のコインを落として指定された(K枚)のコインのみを盤面に残すための最短手数を返せ
というもの。
単純に考えると1回のオペレーションで4通り、探索すると最大30x2=60手なので2^120ぐらい読む必要がある。これは2秒の制限中では明らかに無理。
ということでしばらく図を眺めてごにょごにょと考え「移動順はともかく、元盤面の中でサブの盤面を定義してその中にK枚のコインが残るようなケースのうち最も手数の少ないものを探しだす」んだと思いついた(ちょい遅かった)。
こうすると始点と終点の選び方で30^4、選んだエリアの中に含まれるコインの枚数を数えるのに最大30^2(実際はサブセットに限定される分、もうちょい小さい)の計30^6、ループは7億回ちょいとなって2秒制限の中ではやばい感じがするけどひとまず実装してみた。
ちなみに実装中に考えていたループ回数の低減策は「指定エリアに含まれるコイン枚数を辞書として持つ」で、計算量的には30^4+30^2と相当効くはず30^4+30^4となって半分程度に出来る(辞書の辿り方によってはむしろ悪化する)。ただ、その分81万要素を保持する必要が出てくるので変なパックするとメモリ足りなくなる(64MBしか使えないらしい)。まあこのあたりは一旦HashMapあたりで作って不足するならcharの配列あたりに落としていけばいいかなと。
で、そんなことをほんのり考えつつひとまず書いたナイーブな実装がこれ。
public class DropCoins {
 public int getMinimum(String[] board, int K) {
  int min = 9999;
  int width = board[0].length();
  int height = board.length;
  for (int i = 0; i < width; i++) {
   for (int j = 0; j < height; j++) {
    for (int k = i; k < width; k++) {
     for (int l = j; l < height; l++) {
      // (i, j) -> (k, l)
      int tmpCnt = 0;
      for (int m = j; m <= l; m++) {
       for (int n = i; n <= k; n++) {
        if (board[m].charAt(n) == 'o') {
         tmpCnt++;
        }
       }
      }
      if (tmpCnt == K) {
       int left = i;
       int top = j;
       int right = width - k - 1;
       int bottom = height - l - 1;
       int steps = 0;
       if (left < right) {
        steps += left * 2 + right;
       }
       else {
        steps += left + right * 2;
       }
       if (top < bottom) {
        steps += top * 2 + bottom;
       }
       else {
        steps += top + bottom * 2;
       }
       if (steps < min) {
        min = steps;
       }
      }
     }
    }
   }
  }
  return (min == 9999 ? -1 : min);
 }
}
時間切れになるかなーと思いつつシステムテスト(この適当さがあんまり良くないんだけど)走らせたところ、最も時間のかかるケースで411msかかりつつも時間内に処理出来てパス。スコアは265.44 / 600。
例によって道筋が立ってからはほとんど一本道でいけたけど危なくてしょうがない。
tmpCntのカウント中にカウントがKを超えた場合には途中で諦めるというのも手なんだけど、そのぶん分岐が増えるわけで微妙。

Hard問題は開いてないし当分開かないでいいと思う。

アーロンチェア買ったら家に入らなかった

2011年11月23日水曜日

前からずーっと欲しかったアーロンチェアをついに買ったわけです。
14万円弱するにはするんですが、軽く12年保証がついてるので年数で割るとMacよりも格段に安いなと思い。。
ちなみに日曜注文し火曜の午前中に到着という素晴らしい対応(どのお店とは書きませんが)。

で、朝に「大きめの荷物届いてますがご在宅ですか?」という電話連絡が来て「YES!YES!YES!YES!」と歓喜。そして数分後

( ゚д゚)ポカーン

噂には聞いていたけど予想以上に箱がデカく、配達に来てくれたクロネコなお兄さんも「いやー、そのまま入れるの無理だと思うんで表でバラしてください」とのブン投げっぷり。ナイス。

で、到着嬉しいので身体にフィットするよう諸々調整。そして座ってみた感覚はさすがだなぁ、という感じ。前傾チルトモード多用しそう。


残念ながら、我が家にはワーキングデスクが無い(コタツになるちゃぶ台しか無い)ので、しばらくの間はチェア単体+膝の上にPCで運用しようと考えてた。

けれど仕事から帰ってきてふと目の前を見るとやたら背が高く頑丈な段ボール箱がある。

「これ切って丁度いい高さに調整して、それに似た机買えばいいじゃん」


ということでまずハサミ使ってちょうどいい高さに詰めた。

次に足を格納して作業スペースとするべくゴリゴリとハサミで段ボールを切り、

なんか机っぽくなった。チェアを収めてみると
という感じ。これは満足度高い。しかも、梱包でビクともしてなかった段ボールなので

これ案外このまま使えるんじゃね?

と欲が湧いてくる。天板頑丈だし好き勝手に汚せるしと失うものは特になさげだったので


普通に実戦投入しました。天板がヘタるまでは使うつもり(ちなみに天板までの高さは62cmが狙った高さみたい)。 blog更新再開のための肩ならし的に、またしてもものを買った系の記事書いてみました。次からは方向結構変わります。

MacBook Air 13" Mid 2011 開封の儀

2011年7月21日木曜日

MacBook Air 13インチ、Core i5 1.7GHz、4GB MEM、256GB SSDなモデルを買ったので開封の儀など。
といっても写真をひたすら貼っていくだけ。

午前10時、50人ほどに膨らんでいたアップルストア銀座の行列に告げられたのは「本日、MacBook Airの入荷はありません」という残念すぎる言葉。ダメもとで近くの有楽町ビックカメラへ行ってみると、平然と展示しているし在庫もあるということだった。

ただ店員さんからの注意点が2つ
「Officeはまだ対応してないようです」
「不具合かどうか確認出来てない状況ですが、電源延長用ケーブルの先端が通常の二又+アース線ではなく三又のものになっています。仕様なのか誤混入なのか不明な状況ですが…」
まあいいや、っと購入。

ん?2.95kg?

なんかシールの貼り方的に、梱包完了は7/9だった模様。

外箱あけると子箱が中に

取り出す

わくわく

なんか注意書きなどされているけどあまり気にしない。


箱あけたところ。こう見るとやっぱり2010年秋モデルとさほど変わらない。

注意書きが書かれた紙で本体保護シートが封されてる形

やぶりかけ

本体開いたとこ

液晶保護用のシートを外した

電源投入!テンション上がる背景

言語選択

地域選択

キーボード選択

入力方法選択

Wi-Fi設定

他Macからの情報インポート。今回はスルー

アカウントピクチャ設定。当然ジンジャーブレッドマン

パスワード入れてアカウント作成

ユーザ登録。けっこうしっかり入力させられる

使用許諾契約

ジェスチャーの練習的なもの

ついにログイン画面

多少さわってみたところ

触ってみた感想ですが、CPUが強化された分のサクサク感、前に使っていたMBAと比べて2倍のメインメモリを積んでいる安心感、同4倍容量のSSDによる「容量気にせずAndroidのソースでもなんでも来い」感が素晴らしいです。Lionも1日使えばだいたい慣れるし、買ってよかったと思ったのでした。

TopCoder SRM練習 - SRM 511 DIV2 Easy

2011年7月13日水曜日

今日もちょいちょいと。
GameOfLifeDivTwo

・ライフゲームの変形版
・ドットが円形に並んでる
・状態Tでの各ポイントの生存状態は、その前(T-1)で自身プラスマイナス1つの計3つのうち2つ以上が生存していた(=>true)か否か(=>false)で決まる

ということで愚直に端っこをループさせてこういうふうに解いた。計算量的には、-1にあたる場所とNにあたる場所をもうちょい特殊に扱って毎回境界外をつなぐ処理を行わないようにすれば減らせそう。まあ、Top Submission見てもこんな感じ。途中喋りながらやってたというのはあるけど、さらっと書いてから配列のコピーで微妙にハマりprintfデバッグ繰り返した結果159.31。うーん、イマイチ。正答率91%、平均点183。

public class GameOfLifeDivTwo {

 public String theSimulation(String init, int T) {
  int setLen = init.length();
  boolean stateList[] = new boolean[setLen];
  boolean stateTmpList[] = new boolean[setLen];
  for (int i = 0; i < setLen; i++) {
   stateList[i] = init.charAt(i) == '1';
  }
  for (int i = 0; i < T; i++) {
   for (int j = 0; j < setLen; j++) {
    int liveCnt = 0;
    int nextIdx = j + 1;
    if (nextIdx == setLen) {
     nextIdx = 0;
    }
    if (stateList[nextIdx] == true) {
     liveCnt++;
    }
    int prevIdx = j - 1;
    if (prevIdx == -1) {
     prevIdx = setLen - 1;
    }
    if (stateList[prevIdx] == true) {
     liveCnt++;
    }
    if (stateList[j] == true) {
     liveCnt++;
    }
    stateTmpList[j] = 2 <= liveCnt;
   }
   for (int k = 0; k < setLen; k++) {
    stateList[k] = stateTmpList[k];
   }
  }
  String retStr = "";
  for (int i = 0; i < setLen; i++) {
   retStr = retStr.concat(stateList[i] == true ? "1" : "0");
  }
  return retStr;
 }

}

TopCoder SRM練習 - SRM 450 DIV1 Easy

2011年7月12日火曜日

昨日に続いて、少しずつやる。
今日やったのはOrdered Nim

・AliceとBobの2人がゲームをしてる
・複数のヒープ(インデックスが振られている)のうち1つから、1つ以上の石を交互に取り出す
・あるヒープから石を取り出せる条件は、そいつよりインデックスの小さなヒープが全て空の場合だけ
・最初に取り出すのはAlice
・各ヒープに入ってる石の数は10億個以下
・お互いが最も効率的にゲームをプレイする場合、勝つのはAliceかBobか返せ

先日落としたトラウマ化しつつある問題と少々似てるところがある。いくつかケースを紙に書いてみて
・Aliceが必ず勝てる場合以外は、Bobが必ず勝てる(直感的にこうだけど未証明)
・n-1番目へたどり着いたら全部取って勝てるので、n-2(境界注意)までが勝負
・"Aliceが勝つかどうか"をn-2番目のヒープから逆順でたどって検証していけばよさげ
・あるヒープに石が最初から1個しかなければ、勝者判定をひっくり返す
・あるヒープに石が最初2個以上あれば、その次(ループ上はひとつ前)のヒープにある石が1個であれば勝者判定をひっくり返し、2個以上であればAliceの勝ち判定とする(総取りか1つ残すかのどちらかで、Bobに不利条件を押し付けられるから)
とたどり着いた。

テストケースを自前で増やしてやばそうなパターンを拾えるようにしたけれど、それでも抜けがあって一度failed system test。その後訂正し再提出して75.0pt(実際だったら0pt)。なかなか凹みますな。avg=205 / 正答率74%。この回なら文句なしに落ちてる。

public class OrderedNim {

 public String winner(int[] layout) {
  String ALICE = "Alice";
  String BOB = "Bob";
  if (layout.length == 1) {
   return ALICE;
  }

  boolean aliceWins = true;
  boolean oneStoneFollows = false;
  for (int i = layout.length - 2; 0 <= i; i--) {
   if (oneStoneFollows == true) {
    if (layout[i] == 1) {
     aliceWins = !aliceWins;
    }
    else {
     aliceWins = true;
     oneStoneFollows = false;
    }
   }
   else {
    if (layout[i] == 1) {
     aliceWins = !aliceWins;
     oneStoneFollows = true;
    }
   }
  }
  return aliceWins ? ALICE : BOB;
 }

}

"both players play optimally"な問題はまあまあ苦手だなぁ。

TopCoder SRM練習 - SRM 450 DIV2 Easy

2011年7月11日月曜日

簡単な問題でもいいので、出来るだけ毎日解くキャンペーン中。
今回やったのはStrangeComputer

・指定位置のメモリを書き換えると、そこから右(概念的に)が全て指定した値でフィルされてしまう、変なコンピュータが与えられてる
・入力(文字列)で指定されたメモリ状態にするための最短手数を返せ
・なお、初期状態は0埋めされてる
public class StrangeComputer {

 public int setMemory(String mem) {
  char prevChar = '0';
  int opCnt = 0;
  for (int i = 0; i < mem.length(); i++) {
   if (mem.charAt(i) != prevChar) {
    opCnt++;
    prevChar = mem.charAt(i);
   }
  }
  return opCnt;
 }

}
こんだけ。同じ値が連続すれば、その分は前オペレーションで指定済みとなるので手数増やさずに設定したことに出来る。230.98、passed system test。平均スコアが213.15で正答率93%なのでこんなもん。

Google Chart Tools / Image Chartsで時系列折れ線グラフを作ってみる

2011年6月6日月曜日

さきのエントリに続いて脱デ部で使ってるもののお話。

やっぱり、計測する度にどの程度目標達成したかを視覚的に掴みたいところ。というわけで、

Google Chart Tools / Image Charts (aka Chart API)を使って適当な折れ線グラフを出力してみる。基本的にカンマ区切りでデータを渡してやればあとは適当にやってくれるのでとても楽。Chart Wizardでインタラクティブに項目の調整なども出来るし。

ということで
こんな感じのグラフがさっくり作れた。けど、このまま使うと「データがある分だけ等間隔に表示」ということになり、時間軸を正しく反映はしない。とはいえ今回のサービスって「毎日あるタイミングで体重を測定する」ってのを想定してるので、実用上そんなに問題は無いはず。と思ってたところ突っ込みを食らったので少々真面目に考えることに。

やること
・指定した時間レンジ内での変化を平滑化する
・指定した時間レンジよりもデータ量が少なければ空データを補完する

ここで、「過去1週間分を表示するけど未参加時期の分は表示しない」という仕様にする場合は一旦出力用のリストの各要素を'0'で初期化しておけば後が楽そう。
時間レンジの切り方には色々あると思うけれど今回は「最新の計測日時から24時間ずつ逆にたどっていき、その中で得られたデータについては平均値を取る」ということにした。極端に平滑化されたグラフが欲しいわけじゃないので移動平均とかは取らない。
Pythonのコードだとこんな感じ。
def getDailyHistoricalData(self, periodsToBack):
        info = self._fetchWeightLog()
        normalizeFor = 86400
        info.sort(key=lambda x:x['ts'], reverse=True)
        lastBoundary = info[0]['ts'] - normalizeFor
        avgList = list()
        cnt = 0
        sum = 0
        for v in info:
            if lastBoundary < float(v['ts']):
                sum += float(v['wt'])
                cnt += 1
            else:
                avgList.append(('%.2f' % (sum / cnt)) if cnt != 0 else '0')
                lastBoundary -= normalizeFor
                cnt = 0
                sum = 0
                if pos == periodsToBack:
                    break
        # catch last one
        if cnt != 0:
            avgList.append(('%.2f' % (sum / cnt)) if cnt != 0 else '0')
        if len(avgList) < periodsToBack:
            avgList.extend(['0' for i in range(periodsToBack - len(avgList))])
        avgList.reverse()
        return avgList
最新データから逆に古いデータへ辿っていく形で、データが足りなければ最後のほうで追加してる。リスト長が固定なのでインデックス指定でいじってもいいのだけど、appendでペチペチやっていくのが楽だったので最後にreverseかけてる。 これで無事
一日でデータ投入しまくっても平均値のみ1日1回分反映されるようになった。けれど多分これは不具合ある。1日・2日と計測をサボった際、この実装だとその区間データは0になるはずなんだけど、どっちかというと補完処理してほしいだろうなぁ。

で、Google Image Chartsは http://chart.apis.google.com/chart?chs=220x110&cht=lc&chco=3072F3&chm=B,DDDDFF,0,0,0&chd=t:1,2,3,4 というようなURLでimgタグを貼るとグラフ描画してくれるよという程度。あとはChart Wizardのギャラリーとか見ながら頑張るのがいい。使える記法のリファレンスは結構長い。

Python初心者がGoogle App Engineでソーシャルダイエットサービス作るよ

先日お邪魔したGoogle I/O報告会 in 東京でApp Engineなネタを発表したりしましたが、実はApp Engineをちゃんと使ったことは無かったりします。以前にやったことはといえばアカウントを作って試しに静的なファイルをデプロイしてみて終了というなかなか悲しい感じで、きっと当時の私には必要なかったのだと思います。

が、Google I/Oでセッションに触れ、また今後betaを卒業していったり諸々の(全文検索含め)機能強化がされていくことを考えると、いくらPython 2.5という今からするとちょっぴり古い言語がメインであっても(Javaで書く気はあんまり無くて、せっかくApp Engine使うならPythonで書きたい)App Engine使いたい気持ちが高まってきた今日この頃。そんなところに丁度ネタ振りがあったので今回はApp Engineで書いてみようという次第。ちなみにPythonはそれなりに思い通りのものを書ける程度でPythonistaではない。このへんもどんどん晒しつつ勉強していきたいところ。

やり始めてから途中丸一週間停滞という、旬が大事なプロジェクトとしては割と致命的なアクシデントもあった(これは色々言い訳してもしょうがない)んですが、サービスをさっくりとApp Engine上で作ってみました。以下はそのメモです。

----
メモ:
今回、基盤としてはtwitterの情報を利用するのでOAuthベースの認証/認可といくつかの周辺機能で構成することになる。

目的はOAuthの仕組みを作ることでもなければtwitterのAPIを知ることでもないので、既に作成/公開されているライブラリをありがたく使う。
Google App Engineで手軽にOAuthアプリを作成!(Twitterとか!) - AppEngine-OAuth
これを使い、twitter IDでのログインまではさっくりと進んだ。ちなみにApp Engineへのデプロイは結構遅めなので、どんどんコードを書き換えながら進めるには不向き。今回はtwitterの連携アプリ設定で"localhost"を許可リストへ追加し、"localhost:8080"な環境から直接twitterでの認証が使えるようにして開発を効率化した(実装としては、あまりホスト名を見て処理切り分けるのが好きでないのでLOCAL_TESTINGというグローバル変数を用意)。これに伴いOAuth用のライブラリを一部拡張した。コードはgithubあたりに置くつもりなので必要な方はそちらから拾ってもらえばいいのだけど、やってることは認証時のリクエストパラメータに'oauth_callback'を指定してlocalhostへ戻ってこれるようにしただけ。

さて、ログインまでは出来たけれどセッション情報をどのように保持しよう。ちょっと調べてみた感じ、App Engine自体でセッションの仕組みは提供されてなくて、http://gaeutilities.appspot.com/sessionこのへんの実装を使うというのが割と定石っぽい。バックエンドにはDatastoreとMemcachedが使えるのだけど、Memcachedで利用出来る容量は公表されておらずHow much memory of Memcache is available to a Google App Engine account?あたりを読んだ感じでは、アプリでの利用方法やトラフィックに依るということでなかなか恐ろしい。ここで設計を最大限パフォーマンス寄りに倒す必要は現時点で無いので、(Memcachedに保存したほうが当然爆速なんだろうけど)ひとまず安全側に倒してDatastoreでの実装としておく。
Google App Engine Pythonでセッションらしきものを実装するを参考に、Cookieの読み書き部分+Datastoreで管理するようにした。

データ構造としては揮発性のセッションと不揮発性のユーザデータを基盤とする。
揮発性のもの:
・ユーザのログインCookie

不揮発性のもの:
・twitterのOAuthトークン
・ユーザの開始時体重、目標体重
・ユーザの日々の変化情報
・ランキング情報

RDBでのスキーマ設計と異なり、1:1なものはほどほどまとめて1つのデータモデルにする。この例外は容量がやたらデカいものを切り出すとかかな(lazy-loading相当のことをやる方法があるのか分からないため)。1:Nなものは、数量が読めて検索上困らないならJSONなりカンマ区切りなりで1カラムに集約しても良さそう。数が読めなかったり後から最新の一部のみ切りだして利用する必要があるなどの事情があれば大人しく別モデルにしておくべきだろうなぁ。M:Nなものは、一旦別モデルにしておいて集約出来そうなら後から集約するのが良さそう。RDBで言うところのJOINが必要になるケースを避けたほうがパフォーマンス的に有利なはずなのでそのように計らう。
セッションをモデルとして分割するか否かについて少々悩んだ結果、大した容量にならないことだしセッションキーもセッションデータ(これはJSONで)も永続ユーザデータ側モデルの中へ入れておくことにした。小さなサービスで、ユーザからの入力パターンがほとんどないのでデータを階層化したセッションハンドラとかは要らない。
日々の計測データも分量が知れているので同様に。けど、日々の計測データはApp Engine的な作法(コードはなるべくそのままでも多ユーザ・多データにスケールさせられるようにする)としては別モデルに切っておくのが多分正解。

さて、ここで少々ややこしいのが、twitterのOAuthトークン自体の寿命とユーザのログインCookieの寿命が必ずしも一致しないし、ユーザのログアウトがtwitterのOAuthトークン無効化を意味するわけでもないというところ。今回のサービスでは、ユーザに代わってチーム内での日々の順位をツイートする機能を実装するので、ユーザログアウト(これはkeep-logged-inで共有PCなどにおいて他の人が元ユーザに成り代わって操作するのを防ぐためのもの)後でもtwitterのOAuthトークンを維持するのが正しい実装。このへんはサービスがどのような機能/使い勝手を提供するかによって変わるとこ。


スキーマのアップデートとか後からきっと必要になるのだけど、このへんはユーザの新レコードを作成した際にきちんと処理しないと面倒なことになるかも。世代ごとにデフォルト値管理するとか結構めんどい。

まさかテンプレートでincludeが使えないなんて…部分render→結果を食わせて部分renderかぁ、このへんは半分気分の問題なので、ほどよくラップ出来ればあまり気にならないかも。

あと今のところほぼ気にしないで良いレベルだけど、OAuthベースの通信に必要なUrlFetchにはQuotaがかけられてる(回数で1日あたり657,084、容量で1日あたり4GB in/out)。このへんは必要に応じてサブアプリを登録してそいつとの間でメッセージをやり取り(当然これ自体がAPI消費するので集約性重要)することである程度回避が可能な感じ。
----
まとめというか感想というか:
・App Engineはなかなか幸せな環境っぽい
・少なくとも、ある程度の使い方は把握出来たので今後App Engine上でのアプリプロトタイプ開発は加速出来そう。というか、考えたものはどんどん出していけるようにしなきゃまずい
・今度はJavaも使ってみたい。Slim3ちょっと調べてみた感じ結構イケてる。Eclipse含めた環境としてのJavaは書いててそんなに苦痛無いしJava6対応してるし
----
今後勉強したりするnotes:
http://code.google.com/intl/en/appengine/docs/python/tools/webapp/requestclass.html
リクエスト送信されてくる内容はこのへんを参考にする。

・ライブラリ内でApp Engine特有な部分はurlfetchぐらいかな、httplib2と大差ない気がするので今度違いを調べる。というか、google.appengine.extの中身はひとまず全部見ておいたほうが後々よさそうなので見る
・Datastoreで不要になったデータをexpireさせる方法(多分タイムスタンプを用意しておいてcronで範囲指定削除)を用意する。データ容量的にも効率的にも必要
・Datastoreに所謂PKっぽいものを指定する方法、インデックスを作成する(自動作成されない場合)方法
・db.Modelからlazy-loading(一部カラムのデータだけ、fetch時に取得せず具体的コール時に取得する)の仕組みがあるか調べる

SRM 507 DIV1

2011年5月29日日曜日

DIV1昇格後初のSRM。気付いたら2ヶ月ぶりで結構焦る。あっちこっち出かけたり飲みに行ったりしてたらこんなに空いてた。
差し当たりはDIV2と同様、easyを確実に取るのを目標にしつつアリ本などで勉強、過去問やる方針。

easy:
立方体の塗り分け。
複数の色が与えられてそれらで立方体を塗り分けることが出来るか否かを返す。
隣接する面は同じ色で塗っちゃダメ。つまり向かい合う面を同じ色で塗れたら必要な色数を1つ減らせる。
けど当然3色より小さな色数で塗り分けるのはムリなので一応弾いておく。

実装も単純で、与えられた色をどんどんMapに放りこんで(それぞれの数も大事なのでMap)、2つ以上見つかった
色の数を使って塗り分けに必要な色数を減らす。最後に、必要な色数<=見つかった色数かどうかを判定して返す。
import java.util.HashMap;

public class CubeStickers {

    public String isPossible(String[] sticker) {
        HashMap<String, Integer> mapping = new HashMap<String, Integer>();
        for (String target : sticker) {
            mapping.put(target, mapping.containsKey(target) ? (mapping.get(target) + 1) : 1);
        }
        int colorsNeeded = 6;
        for (int cnt : mapping.values()) {
            if (2 <= cnt) {
                colorsNeeded--;
            }
            if (colorsNeeded == 3) {
                break;
            }
        }
        return colorsNeeded <= mapping.size() ? "YES" : "NO";
    }
}
medium: 指定された立方体群(片方は1x1x1、もう片方はLxLxL)を敷き詰めるのに必要な直方体の最小体積を求める。 立てた方針は 1.Nb個の各辺Lな立方体を詰め込むのに最小限必要な領域(領域1)を調べる 2.前述の領域にNs個の各辺1な立方体を、領域1の空き空間に詰め込む→埋めきったらここで終了 3.Nb個の立方体とNs個の立方体の体積を一緒くたにして立方体にしてみる→埋められたらここで終了 4.(2)で埋めて残った分を、面積が最小となる方向に辺を伸ばすのに使って直方体つくる →終了 どのように組めば体積が最小となると言えるのか、をちゃんと検討する余裕が無かったのでこう適当な感じ。 立方体作ってちょいちょい拡張すればいいんじゃないかとなんとなく浮かんだ策で進めてみたけど 案の定うまくいかない。 なかなかやけっぱちなコードである。
public class CubePacking {

    public int getMinimumVolume(int Ns, int Nb, int L) {
        int x, y, z;
        int startSize = (int)Math.floor(Math.pow(Nb, 1.0 / 3.0));
        System.out.println("start=" + startSize);
        x = y = z = startSize;
        int bRemain = (int)(Nb - Math.pow(startSize, 3));
        System.out.println("bre=" + bRemain);
        while (0 < bRemain) {
            int px = y * z;
            int py = z * x;
            int pz = x * y;
            if (px <= py && px <= pz) {
                x++;
                bRemain -= y * z;
            }
            else if (py <= px && py <= pz) {
                y++;
                bRemain -= z * x;
            }
            else if (pz <= px && pz <= py) {
                z++;
                bRemain -= x * y;
            }
        }
        System.out.println("x=" + x + ", y=" + y + ", z=" + z);
        // fill vacant area first
        int sRemain = Ns + bRemain * (L * L * L);
        if (sRemain <= 0) {
            System.out.println("----");
            return x * y * z * L * L * L;
        }
        int sPointSize = (int)Math.floor(Math.pow(Ns + Nb * L * L * L, 1.0 / 3.0));
        int tx = x * L;
        int ty = y * L;
        int tz = z * L;
        sRemain -= sPointSize * sPointSize * sPointSize - (tx * ty * tz);
        if (sRemain <= 0) {
            System.out.println("----");
            return sPointSize * sPointSize * sPointSize;
        }
        System.out.println("point size=" + sPointSize);
        tx = ty = tz = sPointSize;
        while (0 < sRemain) {
            int px = ty * tz;
            int py = tz * tx;
            int pz = tx * ty;
            if (px <= py && px <= pz) {
                tx++;
                sRemain -= ty * tz;
            }
            else if (py <= px && py <= pz) {
                ty++;
                sRemain -= tz * tx;
            }
            else if (pz <= px && pz <= py) {
                tz++;
                sRemain -= tx * ty;
            }
        }
        System.out.println("tx=" + tx + ", ty=" + ty + ", tz=" + tz);
        System.out.println("----");
        return tx * ty * tz;
    }

}

hard:
開いてない

SRM 406 DIV1 easy

2011年5月28日土曜日

過去問の記録を書いていったら多少成長するかなとか。

今日やったのはこれ。
http://www.topcoder.com/stat?c=problem_statement&pm=8784&rd=12178

まず、ワンコは全然関係無いのでスルー。ある数値群で50を作れるのがポイントかなーとか思ったけど順列の組み合わせはたかだか5万程度(8要素しか無いから)なので全部ナイーブに見ちゃえ。
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;


public class SymmetricPie {
    public class Permutation implements Iterable<int[]> {
        private final int size;

        /**
         * <p>順列を表すクラスのコンストラクタ。反復子が返す配列の要素数を指定する。
         * @param size 順列の要素数(10程度が妥当な最大値)
         * @throws IllegalArgumentException 引数(順列の要素数)が0以下の場合
         */
        public Permutation(int size) {
            if (size <= 0) throw new IllegalArgumentException();
            this.size = size;
        }

        public Iterator<int[]> iterator() {
            return new Iter(size);
        }

        private class Iter implements Iterator<int[]> {
            private final int[] source;
            private boolean isFirst = true;

            private Iter(int size) {
                source = new int[size];
                for(int i = 0; i < size; ++i) {
                    source[i] = i;
                }
            }

            /**
             * <p>反復子がまだ順列を返しうるか調べる。
             * 本メソッドは{@link Iter#next()}呼び出し前に必ず1回ずつ実行する必要がある。
             * @return 順列が存在する場合はtrue
             */
            public boolean hasNext() {
                if (isFirst) {
                    isFirst = false;
                    return true;
                }

                int n = source.length;
                for (int i = n - 1; i > 0; i--) {
                    if (source[i - 1] < source[i]) {
                        int j = n;
                        while (source[i - 1] >= source[--j]);
                        swap(source, i - 1, j);
                        reverse(source, i, n);
                        return true;
                    }
                }
                reverse(source, 0, n);
                return false;
            }

            /**
             * <p>次の順列を表すint型配列を返す。
             * <p>このメソッドが返す参照は常に同じ配列に対する参照である。
             * このため配列の要素を変更したり次回の{@link Iter#next()}呼び出し後も参照する場合は、
             * クラスの呼び出し側が配列のクローンを生成して利用する必要がある。
             * @return 順列を表すint型配列
             */
            public int[] next() {
                return source;
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

            private void swap(int[] is, int i, int j) {
                int t = is[i];
                is[i] = is[j];
                is[j] = t;
            }

            private void reverse(int[] is, int s, int t) {
                while (s < --t) swap(is, s++, t);
            }
        }
    }

    public int getLines(int[] dogs) {
        int cnt = dogs.length;
        int maxLines = 0;
        Permutation permutations = new Permutation(cnt);
        int[] combi = new int[cnt];
        int l = 0;
        for (int[] permutation : permutations) {
            for (int k = 0; k < permutation.length; k++) {
                combi[k] = dogs[permutation[k]];
            }
            HashSet<Integer> combiSet = new HashSet<Integer>();
            int total = 0;
            int lines = 0;
            for (int j = 0; j < cnt; j++) {
                total += combi[j];
                if (total == 100) {
                    break;
                }
                if (total == 50) {
                    lines++;
                }
                if (combiSet.contains(total - 50)) {
                    lines++;
                }
                combiSet.add(total);
            }
            
            if (maxLines < lines) {
                maxLines = lines;
            }
            l++;
        }
        return maxLines;
    }

}
==
permutationsのロジックを思いつかずにハマった。ちゃんと身につけるToDo行き。

今回は http://topcoder.g.hatena.ne.jp/eller/20090831/1251723649 の「IterableなJava版next_permutation」を使わせてもらいました。

bloggerに移動してきたよ

2011年5月17日火曜日

多分VALUE DOMAIN系(というかCore Servers)を有効に使うことはこの先無さそうだなぁと思い、移動しました。

コンテンツはかなり古くなったものばかりなので、一新しちゃいましょうっと。

--
古いコンテンツもある程度参照していただいてるようなので、見えるようにします。
旧サーバへのログイン方法を思い出すまでしばらくお待ちください。。
--
その後、http://www.muo.jp/android/ などでアクセスされた際には http://old.muo.jp/android/ などへリダイレクトするよう設定しました。比較的直接リンクされてる場合にはうまくいくと思います。