Hatena::ブログ(Diary)

やねうらお−ノーゲーム・ノーライフ このページをアンテナに追加 RSSフィード

GT-Rの買取ならここですわ。どこよりも高く買取ってもらえるはず。お勧め!GT-R 買取
電王戦出場記念! 書籍化されたで! 監修したで!(`ω´) 絶版なってしもた Kindle版で復活!! 記事書いたで!
解析魔法少女美咲ちゃん マジカル・オープン!

YaneuLabs / やねうら王公式 / やねうらおにメール / twitter / プロフィール

 | 

2009-11-26 広く知られているinsertion sortのコードは駄目すぎる

[] 広く知られているinsertion sortのコードは駄目すぎる  広く知られているinsertion sortのコードは駄目すぎるを含むブックマーク  広く知られているinsertion sortのコードは駄目すぎるのブックマークコメント


insertion sortは「挿入ソート」と訳される。(Wikipediahttp://ja.wikipedia.org/wiki/%E6%8C%BF%E5%85%A5%E3%82%BD%E3%83%BC%E3%83%88 )


■ 日本語


Wikipedia日本語のページのコード引用すると次のようになっている。

for (i = 1; i < n; i++) {
    tmp = data[i];
    for (j = i; j > 0 && data[j-1] > tmp; j--) {
        data[j] = data[j-1];
    }
    data[j] = tmp;
}

上のコードでは、内側のループでinsertの必要がなかった場合でも最後にdata[j] = tmpでtmp変数をwrite backしており、しかも、insertの必要のなかった場合でもj=iが1回実行される。それらの意味において(速度的な観点からすれば)とても無駄コードである。


write backすることの弊害は、このコードでは読み出したばかりのところに書き出すだけなので、そのメモリは(いまどきのアーキテクチャなら)L1 cacheに載っているだろうから、大きな損だとは言えないが、例えば、t[X].Order に従って t[X] 自体を昇順ソートしたいような場合、write backのコスト馬鹿にならない。(sizeof(T[X])==12ぐらいだとして)


どこの馬鹿アルゴリズム教科書から引用してきたのかは知らないが、こんなものをサンプルとして掲載しないでもらいたい。


■ 英語版・イタリア語


次いでWikipediaのinsertion sortの英語で書かれたページのコードも見ておこう。

insertionSort(array A)
begin
    for i := 1 to length[A]-1 do
    begin
        value := A[i];
        j := i - 1;
        while j >= 0 and A[j] > value do
        begin
            A[j + 1] := A[j];
            j := j - 1;
        end;
        A[j + 1] := value;
    end;
end;

こちらはPascalで書かれている。さきほどのコードの変形だが、さきほどのコードよりさらに悪い。jにi-1を入れているが、これが無駄で、j にはiを入れて、"j + 1 "を"j"に、" j "を "j - 1"にしたほうがコンパイラはいいコードを生成するだろう。


j >= 0の判定も初回は不要なわけで、insertの必要がない場合でも1回無駄な j >= 0の判定が必要になる。これもダメダメコードと言える。


■ フランス語


同じく、フランス語のほうも見てみよう。

#define MAX 100
 
void insertion(int t[MAX]) 
{
    /* Tri du tableau t par insertion sequentielle */
    int i,p,j;
    int x;
 
    for (i = 1; i < MAX; i++) 
    {
        /* Stockage de la valeur en i */
        x = t[i]; 
 
        /* Recherche du plus grand indice p inferieur a i tel que t[p] <= t[i] */
        p = i-1;
        while (t[p] > x && p-- > 0) {}
 
        /* Il faut inserer t[i] juste apres cette case */
        p++;
 
        /* Decalage avant des valeurs de t entre p et i */         
        for (j = i-1; j >= p; j--) {
            t[j+1] = t[j]; 
        }   
 
        t[p] = x; /* Insertion de la valeur stockee a la place vacante */
    }
}

こちらは、英語版のページに書かれていたコードをCで書き直したものだ。ループカウンタのj以外に、無駄なpという変数が導入されている。insertする地点pを求めて、そこ以降の要素をひとつ前にずらすコードなのだが、無駄変数pが必要になる上に、そのあとのループではループカウンタをダウンカウントして、配列の後ろから前に向かってアクセスしている。


せっかくinsertする地点が求まっているのにわざわざreverse memory copyで書いて、コンパイラ最適化を阻害し、かつメモリハザードを起こす意味がわからない。こんなことは馬鹿のすることだ。


■ ドイツ語


INSERTIONSORT(A)
1 for j = 2 to length(A)
2     do key = A[j]
          //Fuge A[j] ein in die sortierte Folge A[1 .. j - 1]
3         i = j - 1
4         while i > 0 and A[i] > key
5             do A[i + 1] = A[i]
6                 i = i - 1
7         A[i + 1] = key

Fortranで書かれた疑似コードなのでjのindexが2から始まるが、アルゴリズムとしては英語版と同じだ。これも話にならない。


■ 中文

void insertion_sort(char array[], unsigned int first, unsigned int last)
 {
 	int i,j;
 	int temp;
 	for (i = first+1; i<=last;i++)
 	{
 		temp = array[i];
 		j=i-1;
 
 		//与已排序的数逐一比?,大于temp?,?数移后
 		while((j>=first) && (array[j] > temp))
 		{
 			array[j+1] = array[j];	
 			j--;
 		}
 
 		array[j+1] = temp;	
 	}
 }
這個更好:
 void InsertSort(char array[],unsigned int n)
 {
    int i,j;
    int temp;
    for(i=1;i<n;i++)
    {
      temp = array[i];//store the original sorted array in temp
      for(j=i ; j>0 && temp < array[j-1] ; j--)//compare the new array with temp
      {
          array[j]=array[j-1];//all larger elements are moved one pot to the right
      }
     array[j]=temp;
    }
 }

中段に書いてある「這個更好」は、「以下のように書いたほうがいいよ」の意味だと思う。


前者は英語版のコード後者日本語版のコードとほぼ同じだ。さきほど私が解説したように後者のほうがいいコードが生成されるだろう。しかし、ループの最後でwrite backしているのはいただけない。


■ 誰がこんな駄目なコードを広めたのか


以上で見てきたようにWikipediaのinsertion sortのサンプルはひどすぎる。おそらく書籍か何かの丸写しで、これがinsertion sortとして広く信じられているコードなんだろうけど、だとしたらとても悪いコードが広まっていると言わざるを得ない。


insertion sortは配列の要素が少ないときや、ある程度整順されていることがわかっているときにはとても有用なアルゴリズムであり、この関数高速化はとても重要なのだ。(高速化が不要ならそもそも何も考えずに最初から言語ライブラリにあるqsort関数でも呼び出しておけばいいわけだし)


ひょっとしてinsertion sortを提案した奴(誰かは知らん)が、こんなひどいコードを広めたのかも知れない。だとしたらそいつはかなり罪深いと思う。


例えば、Introduction to Algorithms, Second Editionを見てみると(第3版は最近出たようだが私は持っていない)、次のコードが掲載されている。

INSERTION-SORT(A)
1 for j ← 2 to length[A]
2 do key ← A[j]
3 	> Insert A[j] into the sorted sequence A[1 .. j - 1].
4 	i ← j - 1
5 	while i > 0 and A[i] > key
6 		do A[i + 1] ← A[i]
7 			i ← i - 1
8 	A[i + 1] ← key

Wikipedia英語版のコードと同じアルゴリズムだ。一連の劣悪なコードはこいつが犯人かも知れない。


また、アルゴリズム本で有名なROBERT SEDGEWICKの著書の何冊かには次のコードが掲載されている。

procedure insertion;
    var i, j, v: integer;
    begin
    for i:=2 to N do
        begin
        v:=a[i]; j:=i;
            while a[j-1]>v do
                begin a[j] :=a[j-1]; j:=j-1 end;
            a[j] :=v
        end ;
    end ;

こちらはWikipedia日本語版のコードとほぼ同じだ。こちらのコードはまだ救いはあるが、お世辞にも高速とは言い難い。ついでに言えば、このコードだけ見ればwhileのところの条件式に「j > 1 &&」が忘れてあるように思えるが、a[0]には番兵を置くと本文中にある。


■ まとめ


結局、insertion sortは最初に発案した奴が、きっとこんな駄目なコードを書いたんじゃないかと思う。(よく知らん) そしてそれがほとんどすべてのアルゴリズム教科書採用されている。Wikipediaのinsertion sortは言わずもがなである。


私ならinsertion sortは次のように書く。これは、上で見てきたinsertionコードより典型的には3割程度速い。何より無駄なwrite backや"j = i - 1"などをしていないのでコードがわかりやすい。

  // yaneurao's insertion sort
  for (int i = 1; i < MAX; i++) 
  {
    int tmp = t[i];
    if (t[i-1] > tmp)
    {
      int j = i;
      do {
        t[j] = t[j-1];
        --j;
      } while ( j > 0 && t[j-1] > tmp);
      t[j] = tmp;
    }
  }

※ whileの条件を (t[j] > tmp && j > 0) と 書き間違えてました。コメント欄でご指摘をいただいたので修正しました。

また、昇順ではなく降順ならば、配列の要素をひとつ多く確保して、先頭にINT_MAXか、その末尾の要素にINT_MINを番兵(sentinel)として入れておき、jの終端チェックを省略することでさらに数割速くなるだろう。


昇順ソートの場合もt[0]は事前に空けておき、t[0] = INT_MINなどを番兵として代入しておき、j > 0 を省略することで同じく数割速くなるだろう。

hogehoge 2009/11/26 00:36 速くなるのは分かるけど、とても分かりやすくなったとは思えない。
例ってのはエッセンスを伝えるものだから、
遅くても分かりやすいものを載せるのは正解だと思う。
そもそも後置 while はどの言語にもあるわけじゃないし、
無限ループ+if-break に置き換えるとますます読み辛くなる。
「こういう高速化法がありますよ」ってのは載せてもいいとは思うけど。

yaneuraoyaneurao 2009/11/26 00:55 > とても分かりやすくなったとは思えない。

元のコードのように挿入しないときも値をwrite backするコードのほうが非直観的で、このinsertion sortの形を見慣れていないと何をしているのか理解しにくいです。対して私のコードは、t[i - 1] <= t [ i ] である限りは、ifが成立せず tに対して何も影響を与えませんから、すなわちすべての i に対して t[ i - 1 ] <= t[ i ] になるようにしようとしていることは明白です。

> 例ってのはエッセンスを伝えるものだから

insertion sortに限って言えばそれは間違いです。これは、要素数が少ないときのために特化されたソートであって、もともとの狙いが高速化にあります。insertion sortで書いて高速化しないなら何の利用価値もないのです。

この速度的に見て駄目駄目なコードがあまねく広く知られていて、コピペしていたるところに使われているのです。それはとても大きな実害を招いているのです。

> そもそも後置 while はどの言語にもあるわけじゃないし、

後置whileが無い言語ならばgotoで良いのです。そもそも疑似コードで示しているだけなのですから、後置whileであろうが何であろうが使って良いのです

YuichirouYuichirou 2009/11/26 02:03 私はyaneuraoさんのコードの方が可読性にも優れていると思います。むしろ日本語版Wikipediaのコードは脱出条件が複雑な内側のループを無理にfor文で書いているため、可読性が落ちています。
yaneuraoさん、ぜひその最後のコードをWikipediaに掲載すべきだと思いますがいかがでしょうか。

kilreykilrey 2009/11/26 02:10 - } while (t[j] > tmp && j > 0);
+ } while (t[j-1] > tmp && j > 0);
ですよね。

ingktingkt 2009/11/26 02:15 j = iである初回のdo whileループの終了条件チェックが、ループに入るかどうかを判定するif文と重複しています。そこをif文にするなら、その分ループ展開もするべきではありませんか?

yaneuraoyaneurao 2009/11/26 02:32 > kilreyさん

> + } while (t[j-1] > tmp && j > 0);

あっ、ingktさんの指摘と同じ意味ですね。まずingktさんの指摘は正しいです。私は、番兵を置くコードを普段書いているので、このチェックは今回の記事のために追加したものでそのときに書き間違えたようです。

それで、その場合 j > 0ではなく j > 1にしないと[j - 1]が j==0のときアクセス違反になります。よって、この部分は、
while (j > 0 && t[j-1] > tmp);
が正しいのではないかと思うのですが、どうでしょう?

yaneuraoyaneurao 2009/11/26 02:44 ブクマのコメントで気になるのがあったので返信しておきます。

> kilrey 技術, ネタ Atom/gcc -O3だと、Wikipedia版は最内ループをj=i-1から始まるように変えてNが大きければ5%くらいやね版より速くなった。これはコンパイラの最適化傾向に由来する誤差のような気がする。

ひとつ上のコメントにあるように私のコードが間違えていたためかも知れません(汗) ご迷惑おかけします。

実験されたコードを是非私にメールしてください。私のほうでも追試したいです。

仮にそれで本当に私のコードより速くなるとしたら、それはgccが、do〜while + ループ内メモリコピー に対する最適化が甘いのかも知れませんね。

また、アセンブラで書けば確実に私のコードのほうが高速なのは明白なのですから、これは決して「コンパイラの最適化傾向に由来する誤差」ではありません。ゆえにアルゴリズム的には(高速化できる伸びしろの大きさとしては)、私のコードのほうが断然優れていると思います。

実際Visual C++2008コンパイルした場合、N == 1000ぐらいでも私の書いたコードのほうが圧倒的に速いです。(配列の中身がある程度まばらな数字であるなら)

ingktingkt 2009/11/26 03:00 >while (j > 0 && t[j-1] > tmp);
これが正しいと思います。展開する必要はありませんでしたね。

yaneuraoyaneurao 2009/11/26 03:04 ↑了解です。コメントありがとうございます。

yaneuraoyaneurao 2009/11/26 03:15 Yuichirouさん
> yaneuraoさん、ぜひその最後のコードをWikipediaに掲載すべきだと思いますがいかがでしょうか。

Wikipediaよりは、まずはアルゴリズム本関係の著者に連絡すべきかも知れません…が、うまく伝える自信がないので私はパスで(´ω`)人

shiroshiro 2009/11/26 05:11 おもしろいです。TAOCPを見てみたら、やっぱり書き戻ししてました (Second Edition, Volume 3 p.80)。ただし、こちらの議論でのj>0チェックはループ最後になってるのと(同書ではiとjが入れ替わってるのでi>0チェック) 、実行時間の検討ではレコードサイズは1ワードと仮定してます。Knuthはこの時点ではMIXインストラクションでのステップ数最小を強調していて、メモリアクセスのペナルティは考えてないみたいなんで、ステップ数優先でこうしたのかもしれません。時代によってアルゴリズムのコンスタントファクターに影響を与える要素の重みが大きく変化したということなのでしょう。(あと、コンスタントファクタなのであまり重視されてこなかったという面もあるかも。)
Third Editionは持ってないですがどうなっているでしょうね。

yaneuraoyaneurao 2009/11/26 06:50 TAOCP(The Art Of Computer Programming)、2nd Ed.Vol.3の内容、いま確認しました。

> 実行時間の検討ではレコードサイズは1ワードと仮定してます

そうですね。実際はレコードサイズが3ワードとか4ワードでinsertion sortしたいケースも多々あるので、そういったケースについてもきちんと検討して欲しいところですね。

このKnuthのコードは、MIXインストラクションとして最小だとは思うのですが、最速かというと集合がworst case(すなわちソートしたい逆の順番で値が並んでいるとき)ではそうかも知れませんが、そうではない場合は、i←j-1を実行している時間が丸々無駄になるので、最速じゃないような気がします。(MIXインストラクションは、私はよくわからないので、もう少し注意深く読まないといけませんが)

あと、同書、演習問題33の解答では、番兵を置くアルゴリズムのMIXコードが掲載されていますね。

あと、insertion sortの発案者が誰なのかはこの本には書かれてないですね。

ff 2009/11/26 11:12 ソート済みのデータ列に対して追加するアルゴリズム。
挿入ソートってのは「配列」じゃなくて「Linked List」に対してもっとも効率的に動くものじゃないかな。
配列に対して挿入ソートを使う場面が思い浮かばない…。

名無し名無し 2009/11/26 14:17 日本語版の元ネタは珠玉のプログラミングかな、と思います。
確認したところ、変数名dataがxなだけで載ってる擬似コードそのままなので。

yaneuraoyaneurao 2009/11/26 18:19 > 配列に対して挿入ソートを使う場面が思い浮かばない…。

5個程度しかデータが無いことがわかっている配列を高速にソートしたいときにはいつでも使えると思いますよ。

> 日本語版の元ネタは珠玉のプログラミングかな、と思います。

確認しようと思ったら、その本、友達にあげたのでした・・。

yaneuraoyaneurao 2009/11/26 18:22 はてブで気になるコメントがあったので、返信。

> nanakoso 高々数割の高速化のためにアルゴリズムの例示サンプルコードにけちをつける人々

何度でも言いますが、数割遅くてもどうでもいいという程度の処理ならそもそもinsertion sortを使う必要はないのです。それくらい高速化が要求されるところにinsertion sortは使うのです。

ボトルネックになっているところが数割違えば、XeonとCorei7ぐらい違ってくるのです。お金に換算すれば、それこそ何十万とか何百万とか変わってくるのです。そういうシビアなところに使うのです。そういうシビアなプログラムを書いたことがないか、もしくは書くつもりがなら、しょーもないコメントをつけずに黙ってお引き取りください。

反復何とか反復何とか 2009/11/26 21:18 挿入ソートの使いどころは、もし昇順でない要素を見つけたらスゲー得するのでラッキー、という類の問題の一部な場面ですね

yaneuraoyaneurao 2009/11/26 21:30 はてブに対する返答。

> shuji_w6e 「馬鹿すぎる」とか「駄目すぎる」とか何様なんだろ?調べて回ったついでに全部書き換えてくればいいのに。

アホか。「調べて回ったついでに全部書き換え」るなんてこと、なんで調べた人間がいちいちしなきゃならんのだ。お前こそ何様のつもりだ。

yaneuraoyaneurao 2009/11/26 22:05 はてブに対する返答。

> akimotokenichi Wikipediaは事典なので効率を要求するのはちょっと…。物事の仕組みを知ったり考え方のヒントを得るためのものだと思うので、理解しやすいコードの方がよいと思う。パフォーマンスについては注記してあれば十分では。

何度でも言いますが、insertion sortはsortの効率を追求するための手法であって、高速でないなら何の意味もないのです。

また理解しやすさで言えば(見ようによっては)私のコードのほうが理解しやすいでしょう。bellbindさんがはてブで指摘しているように(私のコードで)「ソート済みのときはメモリをいじらない特徴を主張するのであれば、内側ループの直前に "if (data[i - 1] <= tmp) continue;"を入れるだけにしとくのがわかりやすいと思う」は確かにそうで、そう書き直せば、さらに私のコードのほうが元のコードよりさらにわかりやすいでしょう。

「パフォーマンスについては注記してあれば十分」かも知れませんが、insertion sortについては、Wikipediaに書かれているコードがすでに広くコピペされて使われています。あるいはアルゴリズム本のコードがすでにコピペされて広く使われています。Wikipediaの問題ではなく世間に氾濫しているアルゴリズム本のほうにそもそもの問題があります。それがコンピュータサイエンスの授業で使われて、悪いコードを覚えて、いまだにそのコードを使っている人がたくさんいます。また、Wikipediaのinsertion sortのサンプルコードは何も考えずに広くコピペして使われています。そういう意味では、これは、かなり根の深い問題なのです。どこに"注記"すれば、そういった人たちまでに伝わるでしょうか?

また、shuji_w6eさんのように「調べて回ったついでに全部書き換えてくればいいのに」などと無責任に言う人がいますが、Wikipediaのすべての言語のものを書き換えるのは一個人では不可能で、また、速度的に良いコードかどうかを判断するには、ターゲットとするコンピュータアーキテクチャが存在して、かつそのアーキテクチャについて精通していないと判断できません。要するに、他の人とコンセンサスを確立するのが非常に難しく、Wikipediaを編集する他の人たちを説得するには並々ならぬ労力が必要です。

反復何とか 反復何とか 2009/11/26 22:37 批判ついでにWikipediaを書き換えればいいと言う人はWikipediaの編集方針を理解していないと言わざるおえない
・ウィキペディアの掲載基準は、「真実であるかどうか」ではなく、「検証可能かどうか」です。
・独自研究を書いてはいけません
・書く事柄には検証可能な出典(刊行物等)が必要です

というわけで、yaneurao氏がまた本を書いて技術書売り上げのクリーンヒットを放てばすぐ解決する問題

yaneuraoyaneurao 2009/11/26 23:01 > yaneurao氏がまた本を書いて技術書売り上げのクリーンヒットを放てばすぐ解決する問題

自分で本に書いて、それを出典としてWikipediaの記事に追記したりするのは、すごく自作自演かつ捏造くさいですね…(´Д`)

すがーすがー 2009/11/27 00:46 >検証可能かどうか
無意味な処理でありバグってるのが自明なので、現在の記述は不適切であると検証可能です。

第一それは話の根拠の事であって、文章やソースコードの出展元を制約するものではありません。というか、既出のコードのみ掲載可能とか言い出すとライセンスで死にます。

あくまでWikipediaで許されている引用か、執筆者が定義に従って執筆したものであるべきです。
アルゴリズム全体の実装例は引用の範囲に収まるかは疑問ですし、執筆者がその定義から実装したコードを執筆する事には何の問題もありません。

yaneuraoyaneurao 2009/11/27 01:09 横からですが..
> 無意味な処理でありバグってるのが自明

?? 現在のWikipediaの挿入ソートのコードは別にバグってはいませんよ。パフォーマンス上の問題だけです。

通りすがり通りすがり 2009/11/27 16:52 yaneurao氏のコードが無駄なload/storeを減らした最適化されたコードであり、その差が挿入ソートを用いるべきとこでは無視できないコスト差が発生するという主張は分かります。

ただ『挿入ソートを知らない人』に示す最初のサンプルコードとしてはyaneuraoさんのは最適化のエッセンスも混ざってる分敷居が高いと思います。

かといって、各種本やウェブの記事、wikipediaのがそのままにしとくのが最良なのかというと
それをそのまま実践投入しちゃう人が量産されてしまうのも事実なので導入の後の発展として
実際に使えるコードを示すのがbetterなのではないかと思います。


shellやquickやheapなどにページ割いてる本はあれどinsertionにページ割いてる本ってあるんですかね?
学生の頃sedgewick先生の本を読みながら勉強してたんですけど、その本ではquickとかは分割がある程度進んだら
途中でinsertionに切り替えた方が速くなるよって教えてくれてるんだからinsertionにももうちょっとページ割いて
発展のコード例を示してくれてもいいのにとはコメントを書きながら思う今日この頃です(´Д`)

等価なコードに落とすだけの簡(ry等価なコードに落とすだけの簡(ry 2009/11/27 21:57 ループ実行時の統計を考慮しないコンパイラが教科書版挿入ソートからyaneurao版挿入ソートを導くことはほぼあり得ない
が、、
i=jや--jを挟んだt[j±定数]やt[i±定数]同士の同値性を見抜くぐらいには賢いコンパイラがyaneurao版挿入ソートを見たらば
クリティカルパス(if (t[i-1] > tmp) 非成立時のマシンサイクル)を短縮しようするあまり教科書版に変形してしまったりして、、

yaneuraoyaneurao 2009/11/29 02:40 > だからといって、最適化後「のみ」を掲載するのでは事典的意味が無いと思います。

もちろんそうです。そのことは↓で詳しく書いています。
http://d.hatena.ne.jp/yaneurao/20091127

hogeloghogelog 2009/12/06 04:35 > 挿入ソート
そこに書かれているコードは講義で挿入ソートを習った僕がWikipediaを見て「これ挿入ソートじゃないじゃん」と思い書き換えたものだったと思います。「Wikipediaに載せるコードはコピペとかじゃライセンスとかそういうのでダメなのでは?」と思ったので講義資料などのアルゴリズムの記述とかだけ参考にしつつ適当に。
http://ja.wikipedia.org/w/index.php?title=%E6%8C%BF%E5%85%A5%E3%82%BD%E3%83%BC%E3%83%88&oldid=8899642
当時気づいていませんでしたが変数名間違ってました。で、修正がはいったわけですが
http://ja.wikipedia.org/w/index.php?title=%E6%8C%BF%E5%85%A5%E3%82%BD%E3%83%BC%E3%83%88&oldid=10226327
挿入ソートじゃなくなってました、と。


で2007年頃に修正されてるけど間違ってることに気づきました。
http://d.hatena.ne.jp/hogelog/20070704/p1
その元になったコードが間違ってたのが恥ずかしかったので↑のことには言及しなかったんだったと思います。


で2008年頃にあらためてそれをWikipediaに書いた、と。
http://twitter.com/hogelog/status/755298012
http://ja.wikipedia.org/w/index.php?title=%E6%8C%BF%E5%85%A5%E3%82%BD%E3%83%BC%E3%83%88&oldid=18212514


ここ見て「そういえば」とWikipediaの履歴見ながら辿ってわかりましたが、完全に僕の仕業ですね。

> Wikipediaに書かれているコードがすでに広くコピペされて使われています。
なかなか恐ろしい話です。

yaneuraoyaneurao 2009/12/06 07:37 ↑真相発覚(゜Д゜)!?

 | 

1900 | 01 |
2004 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2005 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2006 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2013 | 01 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2014 | 01 | 02 | 03 | 04 | 06 | 08 | 10 | 11 | 12 |
2015 | 01 | 02 |


Microsoft MVP
Microsoft MVP Visual C# 2006.07-2011.06