[[20170724204331]] 『配列のソートをわざわざシート上でやる意義』(中途B) ページの最後に飛ぶ

[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]

 

『配列のソートをわざわざシート上でやる意義』(中途B)

 「そんなの好きにすればいいじゃん」という結論が最初から予想される内容です。
 予めご了承ください。

 ちょっと先輩と意見衝突しておりまして、
 配列のソートを自前でやろうとしている自分に対し、
 わざわざクイックソート組んだところで、結局Sortメソッドには勝てない
 というのが先輩の主張だったのですが、
 いくら高速だからと言っても、

 1.並べ替え用のシートを追加
 2.配列を(必要ならTransposeして)シートに貼り付け
 3.並べ替え実行
 4.シートの値を(必要ならTransposeして)配列に戻す
 5.シートを破棄

 どう考えても処理が増えてます。
 シート上での並べ替えはそれらを加味しても尚ってほど高速なのか?

 と思って、試しに計ってみました。

 計測にはtimeBeginPeriod(1)した上でのtimeGetTimeを使用
 配列の内容はすべて0以上のランダムな整数です。

                       |    配列のサイズ(行数x列数)   |
 1回目                 |256x3|4096x3|65536x3|1048576x3|
 クイックソート        |    0|    10|    149|     2807|
 クイックソート(安定版)|    0|     9|    173|     3210|
 シート上で並べ替え    |   96|   101|    444|     6093|
  内)Sort部分のみ     |    8|    15|     95|     1789|
 マージソート          |    1|    19|    382|     7419|

 2回目                 |256x3|4096x3|65536x3|1048576x3|
 クイックソート        |    0|    10|    164|     2840|
 クイックソート(安定版)|    0|    10|    172|     3247|
 シート上で並べ替え    |   65|    85|    417|     6145|
  内)Sort部分のみ     |    1|     4|     92|     1798|
 マージソート          |    2|    18|    378|     7652|

 予想通り、Sortメソッドそのものは高速ですが、前後処理でロスしてしまいます。
 「そもそもシート上で並べ替えできるように設計すべきだ」と言われたらそれまでかもですが、
 それでも「元データがシートに無く、配列自体をシートに書き出したい訳じゃない場合も?」
 と思ってしまいます。
 しかも手順4以外の操作ではぞれぞれイベントが投げられる訳でしょ?
 Excelの立場からすれば本筋とは無関係な処理をも要求される事になってますよね。
 それこそ「わざわざシートに書き込まなくても」って思いません?

 「配列 ソート VBA」で検索したら参考記事が山ほど引っ掛かるメジャーな手法だと思ってます。
 しかしながら、Sortメソッドを推奨する記事もたくさん引っ掛かります。

 先輩はそれなりのExcel歴をお持ちなので、おかしな事を言っておられる訳ではないのでしょう。
 しかし今回のは自分的にどうにも釈然としないのです。
 先輩に個人的なこだわりがあっただけ? とも考えられますが
 もはや先輩とこの話題を蒸し返せる雰囲気でもなかったりして^^;

 Excelのベテランである方々の感覚として、
 配列の並べ替えを自前でやるのは不都合であったり無駄であったり
 何かしらマイナス要因や違和感を感じる事なのでしょうか?

 ご意見お聞かせ下さい。

< 使用 Excel:Excel2010、使用 OS:Windows7 >


 私には難し過ぎるので、別の切り口で・・

 >配列のソートを自前でやろうとしている自分に対し

 「自前でやろう」とは何ですか?

 「個人的に作成・使用」と言う意味なんですか?
 「会社として作成・全員で使用」と言う意味なんですか?

 その先輩が辞め、新しい後輩が入って来て、新発想のソートメソッドが
 クイックソートより高速そうだから「自前でやりたい」と言ってきた場合、
 中途Bさんは先輩として、なんとアドバイスすることになりますか?

 >「元データがシートに無く、配列自体をシートに書き出したい訳じゃない場合も?」
 そういうケースが現実に頻発するんですか?

 シート方式の処理速度で実際困ったことが生じているんですか?

 > 2.配列を(必要ならTransposeして)シートに貼り付け
 仮に1048576行あるとするなら、Transposeは使えないから、どっちがいいなんて言う余地はないのでは?

 因みに、どっちか1本に絞らないといけないのですか?(少なくとも先輩はシートオンリー?)

(半平太) 2017/07/24(月) 22:07


 1) なぜTansposeが必要なのか理解できません。
 2) どちらかと言われたら、Sort Methodの方が間違いなく応用性が高い。
    一方配列内で複雑な並べ替えをするならそれなりの仕掛けをしないと無理。

    例えば、一般に配列内ソートでは大文字・小文字を同等に扱ってくれないし、同じ値でも元の出現順に
    ならないケースが多い、等を調整する必要があるケースもある。

    Sort codeのアルゴリズム自体は大して難解なものではないけど、期待した結果にソートしようとするには
    それなりの工夫をする必要がありますよ?
( seiya) 2017/07/24(月) 23:16

 こんばんわ。

 >「元データがシートに無く、配列自体をシートに書き出したい訳じゃない場合も?」

 すいません、これだけですけど、こんな使い方をする時ってどんな時なんですか?
 私も業務ツールは幾つか作ったけど、データをこんな使い方した時が1度も無いので。

 元データがシートに無いのは、まぁテキストデータやAccessのデータとかを直接読込んだり、
 CSVやExcelデータでもファイルを開きたくないとか想像できますけど、
 最後出力はどうするんですか?
 配列のまま保持していたら、何かでエラーが起きたら中身全部消えますよ。

 後このパターン以外では、初めか最後には必ずデータはシート上にあると言うことなので、
 ソートメソッドの方が有効ですよね。

 それと並べ替え条件が複数の時は、書き戻したとしてもソートメソッドの方が早そうな気がしますよね?
 これに関しては、自分でコード書いて試す気力が沸かず推測だけなので遅かったらすいません。

(sy) 2017/07/25(火) 00:18


ども。
趣味で遊んでるだけの万年初心者のど素人の雑感です。
面白いテーマなので、考察してみました。

 >どう考えても処理が増えてます。
20年前から使われているソートメソッドと、
あなたが書いたソートするサブルーチンと、
どちらのほうが品質が担保されていると思いますか?

品質を確認する工数がもったいないと思います。

「俺はちゃんとアルゴリズムを学んで使いこなせる、プログラマーだぜ!」
「エクセルの機能に頼るのは、ただのマクロじゃん?」
っていうへんなプライドがあるのは、中途Bさんのほうでは?

使うほうは、どんなやり方でも気にしませんよ?
処理速度もそうですが、品質が確かなのが一番じゃないですか?
作り手が気にしなければならないのは使ってる道具の良しあしではなく、完成品の品質でしょう。

あぁ、そうか。。。。
エクセルの使い方の、コンプレックスがあるのかも???

(まっつわん) 2017/07/25(火) 07:07


 >「元データがシートに無く、配列自体をシートに書き出したい訳じゃない場合も?」

 たまにありますかね
 データではないですが
 例えば出荷明細等のファイルを履歴として保存しているフォルダで 二週間以上前なら捨てる
 そういう場合はシートに吐き出さないですね

 ただデータ処理のソートメソッドは速いしとても良い機能だと思ってよく使っています

 >中途Bさんは先輩として、なんとアドバイスすることになりますか?

 私は半平太さんのこの質問がとても良い質問に思えます

 グロスで考える(と云う事を良く話しますが)

 クイックソートでも何でもいいのですが

 そのアルゴリズムを理解し使いこなす為のスキルアップ時間が はぶかれていませんか?

 (まあ コピペしてわけわからず使用してるなら別ですが)

 そのあたりの消費時間はどうお考えなのでしょうか

 それと 中途Bさんの心内はここで論争をしても変わらない様な気がします

 >わざわざシート上で

 という先入観があるかぎり 自前(?)でやる事ですっきりするのでは?

 この際 配列ソートのスペシャリストになってみては如何でしょうか

 その時に はじめてエクセルのソート機能がよいものにみえるかもしれません

(通りすがり) 2017/07/25(火) 07:21


純粋な疑問なんですが、今回のソートは1列のみですか?
よく、1列目、2列目、3列目と複数の列で昇順の並び替えをすることがありますが、
そのような場合で比較するとどんな結果になるんでしょうか?

ちなみに「どっちでもいい」とは思いません。
どちらでもできる方が幅が広がって良いのでは?

(K) 2017/07/25(火) 09:10


 自分では組めませんが、そのソートコードが再利用できるかどうかじゃないですかねぇ。
 レベルの追加とか簡単にできるなら使いやすいと思うのですが、毎度コード書いてデバッグするなら
 ある程度結果がわかりやすいソートメソッド使ったほうがメンテナンスは簡単だろうとは思います。

 よろしければ中途Bさんが考案されたソートコード見せてもらえませんか?
(稲葉) 2017/07/25(火) 09:32

 半平太さん、seiyaさん、syさん、まっつわんさん、通りすがりさん、稲葉さんレスありがとうございます。
 愚痴に付き合っていただいて恐縮です^^;

 背景についてちゃんと説明できていない点とか、
 今更ながら読み返してみて、稚拙で乱暴な投げかけをしてしまったとちょっぴり反省してます。

 みなさんのレスに対する返事は午後にでも正式に返させて頂きますが、
 とりあえず今思った事だけ簡単に申し上げますと、

 私はソートメソッドそのものを否定している訳ではありません。むしろ良く使います。
 但し今回のケースに関しては、クイックソートが妥当だと判断しただけです。
 状況に応じて使い分けしてるだけなんです。
 それに対してソートメソッドに固執しているのは先輩の方だと感じています。

 という感じです。
 取り急ぎお礼迄

(中途B) 2017/07/25(火) 09:45


私の意見も書いておきますね。

何故クイックソートよりExcelシートのソートを薦めるか? その主目的は、独自ロジックをコーディングしデバッグするには時間がかかるだろう、しかしExcelのソートならコーディングは簡単であり、デバッグは殆ど不要。 性能面は多少劣るかも知れないか、簡単で短時間で作れるということは、後から他の人が機能変更するのも容易。 処理速度より、開発時間効率を優先した意見なのではないか、と思います。

更に、マクロはインタプリンタ動作なので、1行、1文字毎に時間がかかります。それに対して、Excel自身の機能ならば、C言語からコンパイルされたバイナリで動作するので、よほど高性能なロジックでない限り、内部処理の方が高速です。どっちが速いかは実際にコーディングして比較してみないと判りませんが、どっちが速いにせよ、せいぜい数倍程度の差にしかならないかと思います。 それに対して、開発時間の差は圧倒的に内部処理利用の方が短い。 だったら性能より、工数のかからないほうを選ぶべき、と思いますね。

ちなみに、ソート対象が1列しかないならば、.NETのArrayListを利用するという手もありますよ。簡単さと処理速度の両方を満足させる案です。CreateObject("System.Collections.ArrayList") について調べてみてください。
(???) 2017/07/25(火) 09:59


 Kさん大変失礼しました。朝は慌てていてレスを見逃しておりました。
 ???さんもご意見やArrayListの情報ありがとうございます。

 ちょっと一気には書ききれないので途中までになりますが返事していきます。

 半平太さんへ
 >「自前でやろう」とは何ですか?
 うーん・・・。
 その業務がはじき出す「結果」の立ち位置としては「会社として作成・全員で使用」に近いですが、
 「方法」については割と好き勝手にアレンジが許容される部分です。
 完全に「使う人」イコール「メンテする人」ですから
 代々引き継がれていくものというよりは、業務を引継いだ側が自分でメンテしながら改変に対応、
 何ならイチから作り直してもOKって感じです。
 という意味では「個人的に作成・使用」と言ってしまってよいと考えます。

 従って
 >その先輩が辞め、新しい後輩が入って来て、新発想のソートメソッドが
 >クイックソートより高速そうだから「自前でやりたい」と言ってきた場合、
 >中途Bさんは先輩として、なんとアドバイスすることになりますか?
 については「そこは思った様に変更していいよ。むしろそれ教えてクダサイ」になります。

 > >「元データがシートに無く、配列自体をシートに書き出したい訳じゃない場合も?」
 >そういうケースが現実に頻発するんですか?
 >シート方式の処理速度で実際困ったことが生じているんですか?

 エクセルで行う業務全体から言えば今回のは「特殊なケースなのかもな」とは感じております。
 シート方式の処理速度で「困っている」とまでは言いませんが、
 前後処理も踏まえて「トータルでより高速なもの」を追い求めた結果です。
 今回は上述の様な背景の中での事であり
 「一般論として、というならいざ知らず、今回このシーンであえてソレなの?」と思う訳です。

 >Transposeは使えないから、どっちがいいなんて言う余地はないのでは?
 はい。それも私が
 >>わざわざシートに書き込まなくても
 と思う理由の一つです。
 (まぁ実際のところTranspose自体は回避可能ですから、それを理由に先輩に意見はしてませんが)

 >どっちか1本に絞らないといけないのですか?
 これも上述の様な背景の中、結果的に私が自身の業務においてクイックソートを採用したというだけです。
 誰かに逆らって強引にクイックソートに変えた。というのではなく、
 先輩と意見衝突したのは出来上がった後の話ですね。
 もちろん、今後また前後処理を見直すことで更に高速化出来そうならクイックソートは切り捨てです。

 >少なくとも先輩はシートオンリー?
 わかりません。
 口ぶりからするとクイックソート自体はちゃんと理解した上でのアドバイスだと受け取っています。

 seiyaさんへ
 >1) なぜTansposeが必要なのか理解できません。
 配列を作成する段階から見直す場合であれば、Tansposeしないで済む様に配列を設計しますが、
 「配列があります」の部分から課題スタートする場合、
 しかもそれをシートに転記するのであれば、配列の第2次元がExcelの列数を超過するケースを想定して
 行列を入れ替える必要があります・・・という意味です。
 私は極力使わない様に心掛けてますが、可変長配列だったりすると不可避な場合もありますよね。
 まあ今回に関しては完全に蛇足です。すみません。

 >2) どちらかと言われたら、Sort Methodの方が間違いなく応用性が高い。
 >   一方配列内で複雑な並べ替えをするならそれなりの仕掛けをしないと無理。
 同感です。
 クイックソートにアレンジ加えてたりしてると
 自分でも「これ、めっちゃ無駄なことしてる?・・・やめよかな」と疑問を持ったりしました。
 ただ本件から脱線する個人的な感覚ですが、
 配列をソートするという観点からすると、ソートメソッドの挙動は特殊だと思います。
 空白セルの取り扱い方とか、デフォルトでふりがな情報使うとか。
 それを加味してもソートメソッドの方が「簡単に何とでも対処できる」ので
 一般的には実際勝負にもならないのでしょう。
 でも今回、時間短縮を狙った特定の課題の中で
 >それなりの工夫をする必要
 を承知の上で「これはやるべきだ」と判断し、何パターンも検証を重ね、結果的に時間短縮ができたものです。
 一般論振りかざして否定するシーンではないと思います。>先輩

 syさんへ
 >こんな使い方をする時ってどんな時なんですか?
 今回のは、元データを複数のテキストから読み込み、
 複数パターンの集計結果だけが最終的なレポートとしてシート上に出力される
 という一連の流れの中の「元データ」の配列と「最終的なレポート」の出力前の配列が対象です。

 ・・・なんか、字面だけみたら「なんでシート上でやらないんだ?」って私でも思ってしまいますね^^;
 集計をシートでしないなんてオイ・・・って
 いえ、
 それを元々すべてシート上で行っていたのが、過去何世代かの改良の中で処理時間短縮を狙って
 徐々に配列内での処理割合を増やしていった経緯を持ってるヤツなんですね。
 その経緯は先輩もご承知のはずなのに
 時間短縮の為にシート上の処理を取りやめたものをまたシートに戻すことになる、というか
 えーと、これちょっと説明するのむずかしいですね。

 問題の本質はソート自体よりその前後処理にあるのは私も感じています。
 そこを改善すべきじゃないの?
 というツッコミだったら、私も「そうですよね」と思うんですが、
 既にある配列の在り方には言及せず「配列のソートはシート上で」って言われても
 「この状況でなんで?」となってしまう訳です。
 その拘りは何なのだろうかと。

 >後このパターン以外では、初めか最後には必ずデータはシート上にあると言うことなので、
 >ソートメソッドの方が有効ですよね。
 すみません。ちょっと私が勘違いしてるかもですが、
 「今回の件以外の場面ではソートメソッドの方がいいよね」だと解釈して、もちろんYesです。
 今回の様な時間短縮に的を絞ったケースでなければ、間違いなくシート上で処理します。

 >それと並べ替え条件が複数の時は、書き戻したとしてもソートメソッドの方が早そうな気がしますよね?
 あ、ヤバいかも。
 その部分に関して先輩と私の認識に齟齬があったかも知れません。
 先輩は従来通り4回ソートすると思っての発言だった可能性があります。

 すみません。とりあえずここまで。続きは夜になります。

(中途B) 2017/07/25(火) 17:42


 > 一般論振りかざして否定するシーンではないと思います。>先輩

 この意味が分からないけど、
 複雑(数値文字混合等)なデータを複数列に対して的確なソート(昇順・降順)が出来て、
 尚且つ後継者がそれを継承できるならいいんじゃないの?
( seiya) 2017/07/25(火) 18:32

 中途Bさんの会社の方針は分からないですが・・

 syさん言う「業務ツール」の位置づけにして、
 複数人で検討・製作・利用上の注意のまとめをやり
 全員で利用と言うレベルのものにすればいいと思います。

 以後、利用者は利用上の注意を守れば(チェック無しで)安心して使える。

 それなら会社の資産になります。

 会社の資産が増える事に反対する人はいないと思いますが、
 その先輩があくまでネガティブなままなら、まぁ実現は無理ですけどね。

 中途Bさんが先輩格になったら、後輩たちをこの方針でリードするしかないです。

(半平太) 2017/07/25(火) 19:25


 まっつわんさんへ
 背景を説明できていなかった私が悪かったと思います。
 私が自分でメンテして自分で使うものなんです。
 >品質を確認する工数がもったいないと思います。
 通常業務に支障が出ない範囲ではありますが、空き時間見つけてチマチマと時間掛けて確認しましたね。
 うーん確かにもったいない事をしたのかも知れません。
 勉強になったという意味ではムダだとは思いませんが、それは個人的な部分ですからね・・・

 >へんなプライドがあるのは、中途Bさんのほうでは?
 プライドが無いと言ったらウソになりますが、
 私はただの経理担当ですからプログラマーの「へんなプライド」は持ってません。・・・よくあるパターンなんですね?
 むしろ社内を見渡して、かろうじて自信持ってもいいかも? と思えるのはエクセルだけです。(肝心の簿記がまだダメという^^;)
 今回に関しては先輩の「ソートメソッドへの拘り」を感じている立場です。

 >使うほうは、どんなやり方でも気にしませんよ?
 >処理速度もそうですが、品質が確かなのが一番じゃないですか?
 >作り手が気にしなければならないのは使ってる道具の良しあしではなく、完成品の品質でしょう。
 今回の課題が処理速度だった。という背景が説明できていなかったので申し訳ありませんでした。
 仰ることは分かります。最も気を付けるべき事ですよね。

 >あぁ、そうか。。。。
 >エクセルの使い方の、コンプレックスがあるのかも???
 痛いところです〜。先輩に対してはめっちゃ持ってます。コンプレックス^^;

 通りすがりさんへ
 > そのアルゴリズムを理解し使いこなす為のスキルアップ時間が はぶかれていませんか?
 >(まあ コピペしてわけわからず使用してるなら別ですが)
 > そのあたりの消費時間はどうお考えなのでしょうか
 楽をする為の苦労は惜しむべきではない。とは件の先輩の談ですが、私もそう思います。
 また今回に関わらず、コピペしてそのまま使うとか、まず考えられないです。
 結果的に一語一句違わず同じものになろうが、絶対にイチから自分で書き直します。
 今回書いてるクイックソートも多分現時点で7回以上は大規模な書き直しをしてます。
 だからこそ先輩と衝突してしまったのでしょうけど・・・
 多分先輩に言わせればまだまだヒヨッコの付け焼き刃コードなんでしょう。
 そこは認めるしかないです。

 > それと 中途Bさんの心内はここで論争をしても変わらない様な気がします
 > >わざわざシート上で
 > という先入観があるかぎり 自前(?)でやる事ですっきりするのでは?
 そうなんですよね^^;
 やっぱこのスレの書出しに戻っちゃいます。

 > この際 配列ソートのスペシャリストになってみては如何でしょうか
 > その時に はじめてエクセルのソート機能がよいものにみえるかもしれません
 さすがにこれはまだまだ道程は長そうです。
 勇気付けられるコメントありがとうございます。

 Kさんへ
 > よく、1列目、2列目、3列目と複数の列で昇順の並び替えをすることがありますが、
 > そのような場合で比較するとどんな結果になるんでしょうか?
 いやー、syさんにも言われてハッとしたんですが、
 そこの所で先輩と私で考えてる事が違ってたかも知れないんですよね。
 私はソート1回での処理を思ってたんですが、先輩は4回ソートするつもりだったかもなんです。
 明日ちょっと先輩に確認してみなければと思ってます。
 複数回ソートするのなら確かにソートメソッドが処理全体では速くなるかも知れません。

 > どちらでもできる方が幅が広がって良いのでは?
 私としてはこのスタンスです。先輩は「クイックソートなんてイラネ」派ですね。

 稲葉さんへ
 > 自分では組めませんが、そのソートコードが再利用できるかどうかじゃないですかねぇ。
 一応、そこらへんは意識して組んだつもりなんですが・・・
 > よろしければ中途Bさんが考案されたソートコード見せてもらえませんか?
 オェwwww めっちゃコワイwwwww   素人丸だしコードwwwww
 あぁ・・・でも、せっかく上級者に診て貰えそうなチャンス...

 ぁ、ぁあとで貼ります。 お・・・お手柔らかに、ぉオネガイシマスぅ...

 ???さんへ
 > 処理速度より、開発時間効率を優先した意見なのではないか、と思います。
 > 性能より、工数のかからないほうを選ぶべき、と思いますね。
 うーん。そうであれば良いのですが・・・
 今回処理速度を課題とした取り組みだった中でのそれとなると・・・ちょっとモヤモヤしますが、
 でも、先輩のお立場的にもソコは重視されて当然ですよね。

 やはり皆様に頂いたご意見から察するに
 「ソートコード作るヒマがあったら仕事しろ」的なメッセージだったととるべきでしょうね。
 (でも言い方ってもんがあるでしょうに)
 実際に速度短縮が出来た身としては、正直素直になれない部分もありますが、
 まぁ、先輩に再度確認すべき懸案もある事ですし。

 先輩の真意は分からないですが、Excelのベテランである方々の感覚として、
 クイックソートそのものやスピードがどーこーというのではなく、開発に掛かるコスパと品質の面で。ね?
 というのが一般的な見識なのだという事は理解できました。
 ありがとうございます。

(中途B) 2017/07/25(火) 21:39


 >  この意味が分からないけど、
 すみません。つい。 先輩に対する愚痴です。たいへん失礼しました。

 >  複雑(数値文字混合等)なデータを複数列に対して的確なソート(昇順・降順)が出来て、
 >  尚且つ後継者がそれを継承できるならいいんじゃないの?
 あ、いや、そこまでとなるとハードル高いです。
 でもそうか。
 そうじゃなかったら却下されても文句言えないですね。

 > 「業務ツール」の位置づけにして、
 > 先輩格になったら、後輩たちをこの方針でリード
 ありがとうございます。ま〜だまだ先の話ですねー。

 ここの回答者は皆さん親切で温かいですね。
 殆んど私の愚痴じゃないか。ってスレにちゃんと真面目に応じて下さいます。
 改めて。皆さんありがとうございます。

(中途B) 2017/07/25(火) 21:44


稲葉さんへ
現状のモジュールほぼそのまんまです。(検証用の変数とか一時的なコメントだけ消しました)
見てやってください。
あー緊張する。

    Option Explicit
    Rem 課題検証メモ =================================================================================================
    Rem ●再帰処理とループ処理ではどちらが高速かを検証してみたが、大して違わなかったので再帰処理を採用した
    Rem  クイックソートもマージソートも両方検証したけど、やっぱオーバーヘッドなんて無かった。
    Rem  問題はクイックソートの弱点にハマった時のスタックオーバーフローをどう回避するかだろうけど、
    Rem  それはむしろピボットの採取方法の方が工夫すべきポイントであって、非再帰を採用する理由にはならないと判断。
    Rem
    Rem ●ピボットは配列中の9箇所から採取した値のMedianを採用(ココでは挿入ソートでMedianを算出)
    Rem  配列中央の値のみをピボットする場合の弱点である「山型データ」の場合のスタック低減措置で、
    Rem  キレイな「山型データ」に対する効果は抜群だった。(おおむね乱順配列と同程度の時間になった)
    Rem  関数化したからスピード落ちるかと思ったけど、100万件のデータによる実測では気にするほどの差は出なかった。
    Rem  本来は[前半][中半][後半]それぞれからMedianを取って更にそのMedianを採用すべきなんだろうけど面倒くなった。
    Rem
    Rem ●再帰レベルが上限を超えたら、そこから下位の部分はマージソートに切り換え
    Rem  イントロソートではヒープソートを使っているが、既存のマージソートを流用した。
    Rem  配列サイズから対数取って、なるべくクイックのスピードを活かしたいのでその2倍を上限にした。(←やめた)
    Rem  配列中央の値のみをピボットする場合であれば、
    Rem  最悪ケースの場合の再帰レベルは桁違いにデカいので2倍と言わず3倍4倍でも良いが、
    Rem  今回は9箇所からピボットしてるから、そうそう最悪ケースに陥ることは無いだろう。
    Rem  せいぜい1倍か1倍+1がちょうどギリ上限くらいなんじゃなかろうか。
    Rem  ちなみに最初は定数で固定値にしようと思ってて、140万件の山型データに対して上限を32して実測した結果、
    Rem  深度は27〜31くらいでギリギリオーバーしなかった。
    Rem  試しに上限を16にしたら上限オーバーでマージソートに切り換わったが、
    Rem  (このマージソートは、同じ大きさの配列に対してクイックソートの2.7倍〜3倍の時間が掛かる)
    Rem  この場合でも処理時間は32の場合とほぼ同じだったので、定数にしてしまっても割と妥当な閾値になると思うし、
    Rem  対数のままでも、対数の2倍でも結果に大差は無いんじゃないかと思う。
    Rem  1600万件の1次元配列で対数×1と対数×2で比較したら、対数×1でマージソートに切り換わっても
    Rem  掛かる時間はほぼ同じだった。データの並びに寄っては対数×1の方が微妙に速かったり(と言っても誤差範囲)。
    Rem  Log2付近になると要素数もかなり小さい範囲での処理になるから、マージソートでも充分高速なんだろうな。
    Rem  やっぱ対数×1でいこう。
    Rem
    Rem ●ソートしない方の次元のサイズが4以上の場合は外部スワップに切り換え
    Rem  1.キー列(行)と元添字をセットにした仮配列をつくり
    Rem  2.仮配列をクイックソートして
    Rem  3.ソート後の元添字に従って元配列をスワップ
    Rem  という3段階処理。
    Rem  ソートしない方の次元のサイズが4〜5あたりで、内部スワップ版の速度に追い付いたので閾を4以上にした
    Rem
    Rem ●無理やり安定ソートにするオプション付けた
    Rem  この場合はソートしない方の次元のサイズが4未満でも強制的に外部スワップに切り換える。
    Rem  1.キー列(行)と元添字をセットにした仮配列をつくり
    Rem  2.仮配列をクイックソートして
    Rem  3.仮配列の同じ値が連続している区間の元添字を更に昇順でクイックソートし
    Rem  4.ソート後の元添字に従って元配列をスワップ
    Rem  という4段階処理。
    Rem  結果、配列全体に対して2回ソートする事になるけど、実測したら時間は1割〜2割増し程度だった。
    Rem  安定ソートといえばマージソートと思ってたけど、クイックソート捨てるべきではない?
    Rem
    Rem ●配列が既にソート済みかどうかを事前チェックしたら遅くなるかどうかの検証
    Rem  2097152行で実測した時の処理時間で比較した。計測にはtimeBeginPeriod(1)した上でのtimeGetTimeを使用
    Rem   元データの並び
    Rem    同値・・・すべて整数の1
    Rem    昇順・・・整数1〜2097152(重複なし)
    Rem    降順・・・整数2097151〜0(重複なし)
    Rem    乱順・・・0以上2097151以下のランダムな整数(重複あり)
    Rem    山型・・・前半が昇順と同じで後半が降順と同じ
    Rem    波型・・・1〜10まで昇順で11〜20まで降順(9〜0)、21番目以降その繰り返し
    Rem    百色・・・0〜99の整数をそれぞれ100コずつ配置、10000番目以降その繰り返し
    Rem    二色・・・0と1を交互に配置
    Rem ▼ソート済みの事前チェックなしの場合の処理時間(秒)
    Rem |____配列サイズ____|__ソート手段___|_同値_|_昇順_|_降順_|_乱順_|_山型_|_波型_|_百色_|_二色_|
    Rem |1次元 2097152     | Quick         |  3.8 |  2.1 |  2.2 |  4.2 |  4.0 |  3.4 |  3.4 |  3.6 |
    Rem |__________________|_Merge_________|__6.4_|__6.4_|__6.5_|__8.7_|__6.6_|__8.4_|__7.4_|__7.5_|
    Rem |2次元 2097152 x 1 | Quick         |  4.5 |  2.4 |  2.5 |  4.9 |  4.8 |  3.9 |  3.9 |  4.1 |
    Rem |                  | Quick(Stable) | 12.6 |  2.8 |  3.0 |  6.7 |  7.0 | 11.4 | 10.2 | 11.8 |
    Rem |__________________|_Merge_________|__7.9_|__7.6_|__7.8_|_10.2_|__7.7_|__9.8_|__8.5_|__8.8_|
    Rem |2次元 2097152 x 2 | Quick         |  4.3 |  2.3 |  2.5 |  5.2 |  5.2 |  4.1 |  4.0 |  3.9 |
    Rem |                  | Quick(Stable) | 12.1 |  2.8 |  2.8 |  6.7 |  6.7 | 11.1 |  9.9 | 11.4 |
    Rem |__________________|_Merge_________|_10.9_|_10.3_|_10.3_|_13.5_|_10.6_|_12.7_|_12.1_|_12.4_|
    Rem |2次元 2097152 x 4 | Quick         |  7.7 |  3.1 |  3.2 |  6.8 |  6.0 |  7.2 |  6.5 |  7.1 |
    Rem |                  | Quick(Stable) | 13.0 |  3.2 |  3.4 |  7.8 |  7.3 | 12.4 | 10.7 | 12.3 |
    Rem |__________________|_Merge_________|_10.5_|_10.4_|_10.9_|_14.0_|_10.6_|_13.1_|_11.6_|_11.9_|
    Rem |2次元 2097152 x 8 | Quick         |  8.7 |  3.7 |  3.8 |  8.2 |  6.6 |  8.3 |  7.1 |  8.1 |
    Rem |                  | Quick(Stable) | 13.8 |  3.7 |  3.9 |  9.1 |  8.0 | 13.0 | 11.2 | 12.8 |
    Rem |__________________|_Merge_________|_11.2_|_11.3_|_11.9_|_16.1_|_11.8_|_15.0_|_12.6_|_12.6_|
    Rem ▼ソート済みの事前チェックありの場合の処理時間(秒)
    Rem |____配列サイズ____|__ソート手段___|_同値_|_昇順_|_降順_|_乱順_|_山型_|_波型_|_百色_|_二色_|
    Rem |1次元 2097152     | Quick         |  0.1 |  0.1 |  0.2 |  3.7 |  4.0 |  0.7 |  1.0 |  0.3 |
    Rem |__________________|_Merge_________|__0.1_|__0.1_|__6.4_|__8.0_|__3.5_|__7.4_|__3.4_|__7.4_|
    Rem |2次元 2097152 x 1 | Quick         |  0.1 |  0.1 |  0.3 |  4.4 |  4.6 |  0.9 |  1.2 |  0.4 |
    Rem |                  | Quick(Stable) |  0.1 |  0.1 |  0.7 |  6.0 |  6.4 |  6.0 |  4.9 |  3.5 |
    Rem |__________________|_Merge_________|__0.1_|__0.1_|__7.7_|__9.7_|__4.3_|__8.8_|__4.0_|__8.8_|
    Rem |2次元 2097152 x 2 | Quick         |  0.1 |  0.1 |  0.3 |  5.2 |  5.5 |  1.0 |  1.6 |  0.5 |
    Rem |                  | Quick(Stable) |  0.1 |  0.1 |  0.9 |  6.5 |  6.9 |  6.4 |  5.1 |  3.7 |
    Rem |__________________|_Merge_________|__0.1_|__0.1_|_11.2_|_12.9_|__5.9_|_12.2_|__5.7_|_12.3_|
    Rem |2次元 2097152 x 4 | Quick         |  0.1 |  0.1 |  1.3 |  6.4 |  6.0 |  2.4 |  2.4 |  1.4 |
    Rem |                  | Quick(Stable) |  0.1 |  0.1 |  1.1 |  7.1 |  6.9 |  6.8 |  5.4 |  4.2 |
    Rem |__________________|_Merge_________|__0.1_|__0.1_|_10.8_|_13.4_|__6.1_|_12.0_|__6.1_|_12.0_|
    Rem |2次元 2097152 x 8 | Quick         |  0.1 |  0.1 |  1.6 |  8.0 |  6.5 |  3.3 |  3.0 |  2.1 |
    Rem |                  | Quick(Stable) |  0.1 |  0.1 |  1.8 |  8.7 |  7.5 |  7.7 |  6.1 |  4.7 |
    Rem |__________________|_Merge_________|__0.1_|__0.1_|_11.1_|_14.7_|__6.6_|_13.0_|__6.7_|_12.6_|
    Rem ▼処理時間の削減率(△は処理時間増)
    Rem |____配列サイズ____|__ソート手段___|_同値_|_昇順_|_降順_|_乱順_|_山型_|_波型_|_百色_|_二色_|
    Rem |1次元 2097152     | Quick         |  98% |  97% |  92% |  10% |   2% |  79% |  71% |  91% |
    Rem |__________________|_Merge_________|__99%_|__99%_|___2%_|___8%_|__48%_|__11%_|__54%_|___1%_|
    Rem |2次元 2097152 x 1 | Quick         |  98% |  97% |  90% |  10% |   3% |  78% |  68% |  90% |
    Rem |                  | Quick(Stable) |  99% |  97% |  76% |  11% |   9% |  47% |  52% |  70% |
    Rem |__________________|_Merge_________|__99%_|__99%_|___1%_|___5%_|__45%_|__10%_|__53%_|_△1%_|
    Rem |2次元 2097152 x 2 | Quick         |  98% |  97% |  87% |   1% | △5% |  75% |  60% |  86% |
    Rem |                  | Quick(Stable) |  99% |  97% |  69% |   3% | △2% |  42% |  48% |  68% |
    Rem |__________________|_Merge_________|__99%_|__99%_|_△9%_|___5%_|__44%_|___4%_|__53%_|___1%_|
    Rem |2次元 2097152 x 4 | Quick         |  99% |  97% |  61% |   5% | △1% |  67% |  63% |  81% |
    Rem |                  | Quick(Stable) |  99% |  98% |  66% |   9% |   6% |  45% |  49% |  66% |
    Rem |__________________|_Merge_________|__99%_|__99%_|___1%_|___4%_|__43%_|___8%_|__48%_|_△1%_|
    Rem |2次元 2097152 x 8 | Quick         |  99% |  98% |  58% |   3% |   3% |  60% |  58% |  74% |
    Rem |                  | Quick(Stable) |  99% |  98% |  55% |   4% |   5% |  41% |  46% |  64% |
    Rem |__________________|_Merge_________|__99%_|__99%_|___7%_|___9%_|__44%_|__14%_|__47%_|___0%_|
    Rem  同値と昇順は結局1回もスワップが発生ないのでやはり効果大
    Rem  降順はクイックソートでは再帰回数が1回で済むので効果大
    Rem  肝心の乱順の削減率は小さいが逆効果にはならず、事前チェックが「余計な事」ではない証明となった
    Rem  山型は再帰レベルの上限を超えてマージソートに切り替わっている部分があるので一概には判断できないが、
    Rem  それでも逆効果の程度はほんの数%であり、マージソート単体でも効果が出ているのでやはりあった方が良い
    Rem  波型等重複の多いデータはクイックソートもマージソートもそれなりに効果が出ている
    Rem  総合的な判断としては、事前チェックはやった方がよさそうだ
    Rem ==========================================================================================================

    Rem クイックソート1次元配列用 ===============================================================================
    Public Sub QuickSort(Target() As Variant, indexFrom As Long, indexTo As Long, Optional Desc As Boolean)
    Rem オプション **********************************
    Rem     Decs --------- 降順に並べる
    Rem *********************************************
    Rem 対象区間が既にソート済みだったら何もしないで抜ける
        If IsSorted(Target, indexFrom, indexTo, Desc) Then Exit Sub
        Static LvA As Long, LvB As Long, LvLMT As Long
        Dim CurA As Long, CurB As Long
        Dim Pivot As Variant, SWP As Variant
        CurA = indexFrom
        CurB = indexTo
        Pivot = GetPivot(Target, indexFrom, indexTo)
        Do
            If Desc Then                    '降順指定の場合
                Do Until Target(CurA) <= Pivot '先頭から標本値以下の値を探索
                    CurA = CurA + 1
                Loop
                Do Until Target(CurB) >= Pivot '終端から標本値以上の値を探索
                    CurB = CurB - 1
                Loop
            Else                            '昇順指定の場合
                Do Until Target(CurA) >= Pivot '先頭から標本値以上の値を探索
                    CurA = CurA + 1
                Loop
                Do Until Target(CurB) <= Pivot '終端から標本値以下の値を探索
                    CurB = CurB - 1
                Loop
            End If
            If CurA >= CurB Then Exit Do    'A座標とB座標が交差したら終了
            If Target(CurA) <> Target(CurB) Then '←この判定が処理時間に与える影響は1%程度でデータの並びによる
                SWP = Target(CurA)              'それまではA座標とB座標の中身を交換し、
                Target(CurA) = Target(CurB)
                Target(CurB) = SWP
            End If
            CurA = CurA + 1                 '次のA座標とB座標から再度探索開始
            CurB = CurB - 1
        Loop
        If LvA + LvB = 0 Then LvLMT = -Int(-(Log(indexTo - indexFrom + 1) / Log(2)))  '再帰レベルの上限算出
        If indexFrom < CurA - 1 Then
            LvA = LvA + 1
            If LvA > LvLMT Then
                Call MergeSort(Target, indexFrom, CurA - 1, Desc) '再帰レベルの上限を超えたらマージソートに切替
            Else
                Call QuickSort(Target, indexFrom, CurA - 1, Desc) 'Aパートに対して同じ処理
            End If
            LvA = LvA - 1
        End If
        If indexTo > CurB + 1 Then
            LvB = LvB + 1
            If LvB > LvLMT Then
                Call MergeSort(Target, CurB + 1, indexTo, Desc) '再帰レベルの上限を超えたらマージソートに切替
            Else
                Call QuickSort(Target, CurB + 1, indexTo, Desc) 'Bパートに対して同じ処理
            End If
            LvB = LvB - 1
        End If
        If LvA + LvB = 0 Then LvLMT = 0
    End Sub
    Private Function GetPivot(Target() As Variant, indexFrom As Long, indexTo As Long) As Variant
        Const PIVOT_SIZE As Long = 9    '枢軸候補の採取数
        If indexTo - indexFrom + 1 < PIVOT_SIZE * 3 Then '定数×3未満の配列は配列中央の値を採用
            GetPivot = Target((indexFrom + indexTo) \ 2)
        Else 'それより大きい配列は定数分の値を採取し、そのMedianを採用(MedianOfMedianではない簡略版で)
            Dim Ary(0 To PIVOT_SIZE - 1), i As Long
            For i = 0 To PIVOT_SIZE - 1
                Ary(i) = Target(Int((indexTo - indexFrom) / (PIVOT_SIZE - 1) * i) + indexFrom)
            Next
            Call InsertionSort(Ary, 0, PIVOT_SIZE - 1)
            GetPivot = Ary((PIVOT_SIZE - 1) \ 2)
        End If
    End Function
    Rem クイックソート2次元配列用 ===============================================================================
    Public Sub QuickSort2(Target() As Variant, OrderBy As Long, indexFrom As Long, indexTo As Long _
        , Optional Desc As Boolean, Optional FirstDim As Boolean, Optional Stable As Boolean)
    Rem オプション **********************************
    Rem     Decs --------- 降順に並べる
    Rem     FirstDim ----- 第1次元をソートする
    Rem     Stable ------- 安定ソート化
    Rem *********************************************
    Rem 対象区間が既にソート済みだったら何もしないで抜ける
        If IsSorted2(Target, OrderBy, indexFrom, indexTo, Desc, FirstDim) Then Exit Sub
        Static LvA As Long, LvB As Long, LvLMT As Long
        Dim CurA As Long, CurB As Long
        Dim Pivot As Variant, SWP As Variant, IdxL As Long, IdxU As Long, j As Long
        If FirstDim Then
            IdxL = LBound(Target, 2)
            IdxU = UBound(Target, 2)
        Else
            IdxL = LBound(Target, 1)
            IdxU = UBound(Target, 1)
        End If
    Rem ソートしない方の次元のサイズが大きい(4以上)場合は外部スワップに切り換え
    Rem また安定ソート指定の場合は次元のサイズに関わらず外部スワップに切り換え
        If Stable Or (IdxU - IdxL + 1 >= 4) Then
            Call QuickSort2Ext(Target, OrderBy, indexFrom, indexTo, Desc, FirstDim, Stable)
            Exit Sub
        End If
    Rem 以下内部スワップ版の処理 _________________________________________________________________________________
        If FirstDim Then
            CurA = indexFrom
            CurB = indexTo
            Pivot = GetPivot2(Target, OrderBy, indexFrom, indexTo, True)
            Do
                If Desc Then
                    Do Until Target(CurA, OrderBy) <= Pivot
                        CurA = CurA + 1
                    Loop
                    Do Until Target(CurB, OrderBy) >= Pivot
                        CurB = CurB - 1
                    Loop
                Else
                    Do Until Target(CurA, OrderBy) >= Pivot
                        CurA = CurA + 1
                    Loop
                    Do Until Target(CurB, OrderBy) <= Pivot
                        CurB = CurB - 1
                    Loop
                End If
                If CurA >= CurB Then Exit Do
                If Target(CurA, OrderBy) <> Target(CurB, OrderBy) Then
                    For j = IdxL To IdxU
                        SWP = Target(CurA, j)
                        Target(CurA, j) = Target(CurB, j)
                        Target(CurB, j) = SWP
                    Next
                End If
                CurA = CurA + 1
                CurB = CurB - 1
            Loop
        Else
            CurA = indexFrom
            CurB = indexTo
            Pivot = GetPivot2(Target, OrderBy, indexFrom, indexTo, False)
            Do
                If Desc Then
                    Do Until Target(OrderBy, CurA) <= Pivot
                        CurA = CurA + 1
                    Loop
                    Do Until Target(OrderBy, CurB) >= Pivot
                        CurB = CurB - 1
                    Loop
                Else
                    Do Until Target(OrderBy, CurA) >= Pivot
                        CurA = CurA + 1
                    Loop
                    Do Until Target(OrderBy, CurB) <= Pivot
                        CurB = CurB - 1
                    Loop
                End If
                If CurA >= CurB Then Exit Do
                If Target(OrderBy, CurA) <> Target(OrderBy, CurB) Then
                    For j = IdxL To IdxU
                        SWP = Target(j, CurA)
                        Target(j, CurA) = Target(j, CurB)
                        Target(j, CurB) = SWP
                    Next
                End If
                CurA = CurA + 1
                CurB = CurB - 1
            Loop
        End If
        If LvA + LvB = 0 Then LvLMT = -Int(-(Log(indexTo - indexFrom + 1) / Log(2)))
        If indexFrom < CurA - 1 Then
            LvA = LvA + 1
            If LvA > LvLMT Then
                Call MergeSort2(Target, OrderBy, indexFrom, CurA - 1, Desc, FirstDim)
            Else
                Call QuickSort2(Target, OrderBy, indexFrom, CurA - 1, Desc, FirstDim)
            End If
            LvA = LvA - 1
        End If
        If indexTo > CurB + 1 Then
            LvB = LvB + 1
            If LvB > LvLMT Then
                Call MergeSort2(Target, OrderBy, CurB + 1, indexTo, Desc, FirstDim)
            Else
                Call QuickSort2(Target, OrderBy, CurB + 1, indexTo, Desc, FirstDim)
            End If
            LvB = LvB - 1
        End If
        If LvA + LvB = 0 Then LvLMT = 0
    End Sub
    Rem クイックソート2次元配列用(外部スワップ) =================================================================
    Private Sub QuickSort2Ext(Target() As Variant, OrderBy As Long, indexFrom As Long, indexTo As Long _
        , Desc As Boolean, FirstDim As Boolean, Stable As Boolean)
        Dim SWP() As Variant, IdxL As Long, IdxU As Long
        Dim Key() As Variant, r As Long, c As Long
        ReDim Key(0 To 1, indexFrom To indexTo)
        If FirstDim Then
            For r = indexFrom To indexTo
                Key(0, r) = r
                Key(1, r) = Target(r, OrderBy)
            Next
            Call QuickSort2Indexer(Key, indexFrom, indexTo, Desc)
            If Stable Then Call QuickSort2Stabilizer(Key, indexFrom, indexTo)
            IdxL = LBound(Target, 2)
            IdxU = UBound(Target, 2)
            ReDim SWP(indexFrom To indexTo)
            For c = IdxL To IdxU
                For r = indexFrom To indexTo
                    SWP(r) = Target(Key(0, r), c)
                Next
                For r = indexFrom To indexTo
                    Target(r, c) = SWP(r)
                Next
            Next
        Else
            For r = indexFrom To indexTo
                Key(0, r) = r
                Key(1, r) = Target(OrderBy, r)
            Next
            Call QuickSort2Indexer(Key, indexFrom, indexTo, Desc)
            If Stable Then Call QuickSort2Stabilizer(Key, indexFrom, indexTo)
            IdxL = LBound(Target, 1)
            IdxU = UBound(Target, 1)
            ReDim SWP(indexFrom To indexTo)
            For r = IdxL To IdxU
                For c = indexFrom To indexTo
                    SWP(c) = Target(r, Key(0, c))
                Next
                For c = indexFrom To indexTo
                    Target(r, c) = SWP(c)
                Next
            Next
        End If
    End Sub
    Private Sub QuickSort2Indexer(KeyList() As Variant, indexFrom As Long, indexTo As Long, Desc As Boolean)
        If IsSorted2(KeyList, 1, indexFrom, indexTo, Desc, False) Then Exit Sub
        Static LvA As Long, LvB As Long, LvLMT As Long
        Dim CurA As Long, CurB As Long
        Dim Pivot As Variant, SWP As Variant
        CurA = indexFrom
        CurB = indexTo
        Pivot = GetPivot2(KeyList, 1, indexFrom, indexTo, False)
        Do
            If Desc Then
                Do Until KeyList(1, CurA) <= Pivot
                    CurA = CurA + 1
                Loop
                Do Until KeyList(1, CurB) >= Pivot
                    CurB = CurB - 1
                Loop
            Else
                Do Until KeyList(1, CurA) >= Pivot
                    CurA = CurA + 1
                Loop
                Do Until KeyList(1, CurB) <= Pivot
                    CurB = CurB - 1
                Loop
            End If
            If CurA >= CurB Then Exit Do
            If KeyList(0, CurA) <> KeyList(0, CurB) Then
                SWP = KeyList(0, CurA)
                KeyList(0, CurA) = KeyList(0, CurB)
                KeyList(0, CurB) = SWP
                SWP = KeyList(1, CurA)
                KeyList(1, CurA) = KeyList(1, CurB)
                KeyList(1, CurB) = SWP
                CurA = CurA + 1
                CurB = CurB - 1
            End If
        Loop
        If LvA + LvB = 0 Then LvLMT = -Int(-(Log(indexTo - indexFrom + 1) / Log(2)))
        If indexFrom < CurA - 1 Then
            LvA = LvA + 1
            If LvA > LvLMT Then
                Call MergeSort2Indexer(KeyList, indexFrom, CurA - 1, Desc)
            Else
                Call QuickSort2Indexer(KeyList, indexFrom, CurA - 1, Desc)
            End If
            LvA = LvA - 1
        End If
        If indexTo > CurB + 1 Then
            LvB = LvB + 1
            If LvB > LvLMT Then
                Call MergeSort2Indexer(KeyList, CurB + 1, indexTo, Desc)
            Else
                Call QuickSort2Indexer(KeyList, CurB + 1, indexTo, Desc)
            End If
            LvB = LvB - 1
        End If
        If LvA + LvB = 0 Then LvLMT = 0
    End Sub
    Private Sub QuickSort2Stabilizer(KeyList() As Variant, indexFrom As Long, indexTo As Long)
        Dim CurA As Long, CurB As Long, CurVal As Variant, i As Long
        CurA = indexFrom
        CurVal = KeyList(1, CurA)
        For i = indexFrom + 1 To indexTo
            If KeyList(1, i) = CurVal Then
                CurB = i
            Else
                If CurB > CurA Then Call QuickSort2(KeyList, 0, CurA, CurB)
                CurA = i
                CurVal = KeyList(1, i)
            End If
        Next
        If CurB > CurA Then Call QuickSort2(KeyList, 0, CurA, CurB)
    End Sub
    Private Function GetPivot2(Target() As Variant, OrderBy As Long, indexFrom As Long, indexTo As Long, FirstDim As Boolean) As Variant
        Const PIVOT_SIZE As Long = 9
        If indexTo - indexFrom + 1 < PIVOT_SIZE * 3 Then
            If FirstDim Then
                GetPivot2 = Target((indexFrom + indexTo) \ 2, OrderBy)
            Else
                GetPivot2 = Target(OrderBy, (indexFrom + indexTo) \ 2)
            End If
        Else
            Dim Ary(0 To PIVOT_SIZE - 1), i As Long
            For i = 0 To PIVOT_SIZE - 1
                If FirstDim Then
                    Ary(i) = Target(Int((indexTo - indexFrom) / (PIVOT_SIZE - 1) * i) + indexFrom, OrderBy)
                Else
                    Ary(i) = Target(OrderBy, Int((indexTo - indexFrom) / (PIVOT_SIZE - 1) * i) + indexFrom)
                End If
            Next
            Call InsertionSort(Ary, 0, PIVOT_SIZE - 1)
            GetPivot2 = Ary((PIVOT_SIZE - 1) \ 2)
        End If
    End Function

    Rem マージソート2次元配列用 =================================================================================
    Public Sub MergeSort2(Target() As Variant, OrderBy As Long, indexFrom As Long, indexTo As Long _
        , Optional Desc As Boolean, Optional FirstDim As Boolean)
    Rem オプション **********************************
    Rem     Decs --------- 降順に並べる
    Rem     FirstDim ----- 第1次元をソートする
    Rem *********************************************
    Rem 対象区間が既にソート済みだったら何もしないで抜ける
        If IsSorted2(Target, OrderBy, indexFrom, indexTo, Desc, FirstDim) Then Exit Sub
        Dim IdxL As Long, IdxU As Long
        If FirstDim Then
            IdxL = LBound(Target, 2)
            IdxU = UBound(Target, 2)
        Else
            IdxL = LBound(Target, 1)
            IdxU = UBound(Target, 1)
        End If
    Rem ソートしない方の次元のサイズが大きい(4以上)場合は外部スワップに切り換え
        If (IdxU - IdxL + 1 >= 4) Then
            Call MergeSort2Ext(Target, OrderBy, indexFrom, indexTo, Desc, FirstDim)
            Exit Sub
        End If
    Rem 以下内部スワップ版の処理 _________________________________________________________________________________
        Dim EndOfA As Long, StartOfB As Long
        EndOfA = indexFrom + Int((indexTo - indexFrom) / 2)
        StartOfB = EndOfA + 1
        If EndOfA > indexFrom Then Call MergeSort2(Target, OrderBy, indexFrom, EndOfA, Desc, FirstDim)
        If StartOfB < indexTo Then Call MergeSort2(Target, OrderBy, StartOfB, indexTo, Desc, FirstDim)
        Dim SWP() As Variant
        Dim i As Long, j As Long
        Dim CurA As Long, CurB As Long
        Dim SortFlg As Boolean
        If FirstDim Then
            ReDim SWP(indexFrom To indexTo, IdxL To IdxU)
            CurA = indexFrom
            CurB = StartOfB
            For i = indexFrom To indexTo
                If CurA > EndOfA Then
                    For j = IdxL To IdxU
                        SWP(i, j) = Target(CurB, j)
                    Next
                    CurB = CurB + 1
                ElseIf CurB > indexTo Then
                    For j = IdxL To IdxU
                        SWP(i, j) = Target(CurA, j)
                    Next
                    CurA = CurA + 1
                Else
                    If Desc Then
                        SortFlg = Target(CurA, OrderBy) >= Target(CurB, OrderBy)
                    Else
                        SortFlg = Target(CurA, OrderBy) <= Target(CurB, OrderBy)
                    End If
                    If SortFlg Then
                        For j = IdxL To IdxU
                            SWP(i, j) = Target(CurA, j)
                        Next
                        CurA = CurA + 1
                    Else
                        For j = IdxL To IdxU
                            SWP(i, j) = Target(CurB, j)
                        Next
                        CurB = CurB + 1
                    End If
                End If
            Next
            For j = IdxL To IdxU
                For i = indexFrom To indexTo
                    Target(i, j) = SWP(i, j)
                Next
            Next
        Else
            ReDim SWP(IdxL To IdxU, indexFrom To indexTo)
            CurA = indexFrom
            CurB = StartOfB
            For i = indexFrom To indexTo
                If CurA > EndOfA Then
                    For j = IdxL To IdxU
                        SWP(j, i) = Target(j, CurB)
                    Next
                    CurB = CurB + 1
                ElseIf CurB > indexTo Then
                    For j = IdxL To IdxU
                        SWP(j, i) = Target(j, CurA)
                    Next
                    CurA = CurA + 1
                Else
                    If Desc Then
                        SortFlg = Target(OrderBy, CurA) >= Target(OrderBy, CurB)
                    Else
                        SortFlg = Target(OrderBy, CurA) <= Target(OrderBy, CurB)
                    End If
                    If SortFlg Then
                        For j = IdxL To IdxU
                            SWP(j, i) = Target(j, CurA)
                        Next
                        CurA = CurA + 1
                    Else
                        For j = IdxL To IdxU
                            SWP(j, i) = Target(j, CurB)
                        Next
                        CurB = CurB + 1
                    End If
                End If
            Next
            For j = IdxL To IdxU
                For i = indexFrom To indexTo
                    Target(j, i) = SWP(j, i)
                Next
            Next
        End If
    End Sub
    Rem マージソート2次元配列用(外部スワップ) ===================================================================
    Private Sub MergeSort2Ext(Target() As Variant, OrderBy As Long, indexFrom As Long, indexTo As Long _
        , Desc As Boolean, FirstDim As Boolean)
        Dim SWP() As Variant, IdxL As Long, IdxU As Long
        Dim Key() As Variant, r As Long, c As Long
        ReDim Key(0 To 1, indexFrom To indexTo)
        If FirstDim Then
            For r = indexFrom To indexTo
                Key(0, r) = r
                Key(1, r) = Target(r, OrderBy)
            Next
            Call MergeSort2Indexer(Key, indexFrom, indexTo, Desc)
            IdxL = LBound(Target, 2)
            IdxU = UBound(Target, 2)
            ReDim SWP(indexFrom To indexTo)
            For c = IdxL To IdxU
                For r = indexFrom To indexTo
                    SWP(r) = Target(Key(0, r), c)
                Next
                For r = indexFrom To indexTo
                    Target(r, c) = SWP(r)
                Next
            Next
        Else
            For r = indexFrom To indexTo
                Key(0, r) = r
                Key(1, r) = Target(OrderBy, r)
            Next
            Call MergeSort2Indexer(Key, indexFrom, indexTo, Desc)
            IdxL = LBound(Target, 1)
            IdxU = UBound(Target, 1)
            ReDim SWP(indexFrom To indexTo)
            For r = IdxL To IdxU
                For c = indexFrom To indexTo
                    SWP(c) = Target(r, Key(0, c))
                Next
                For c = indexFrom To indexTo
                    Target(r, c) = SWP(c)
                Next
            Next
        End If
    End Sub
    Private Sub MergeSort2Indexer(KeyList(), indexFrom As Long, indexTo As Long, Desc As Boolean)
        If IsSorted2(KeyList, 1, indexFrom, indexTo, Desc, False) Then Exit Sub
        Dim EndOfA As Long, StartOfB As Long
        EndOfA = indexFrom + Int((indexTo - indexFrom) / 2)
        StartOfB = EndOfA + 1
        If EndOfA > indexFrom Then Call MergeSort2Indexer(KeyList, indexFrom, EndOfA, Desc)
        If StartOfB < indexTo Then Call MergeSort2Indexer(KeyList, StartOfB, indexTo, Desc)
        Dim SWP() As Variant
        Dim i As Long
        Dim CurA As Long, CurB As Long
        Dim SortFlg As Boolean
        ReDim SWP(0 To 1, indexFrom To indexTo)
        CurA = indexFrom
        CurB = StartOfB
        For i = indexFrom To indexTo
            If CurA > EndOfA Then
                SWP(0, i) = KeyList(0, CurB)
                SWP(1, i) = KeyList(1, CurB)
                CurB = CurB + 1
            ElseIf CurB > indexTo Then
                SWP(0, i) = KeyList(0, CurA)
                SWP(1, i) = KeyList(1, CurA)
                CurA = CurA + 1
            Else
                If Desc Then
                    SortFlg = KeyList(1, CurA) >= KeyList(1, CurB)
                Else
                    SortFlg = KeyList(1, CurA) <= KeyList(1, CurB)
                End If
                If SortFlg Then
                    SWP(0, i) = KeyList(0, CurA)
                    SWP(1, i) = KeyList(1, CurA)
                    CurA = CurA + 1
                Else
                    SWP(0, i) = KeyList(0, CurB)
                    SWP(1, i) = KeyList(1, CurB)
                    CurB = CurB + 1
                End If
            End If
        Next
        For i = indexFrom To indexTo
            KeyList(0, i) = SWP(0, i)
            KeyList(1, i) = SWP(1, i)
        Next
    End Sub

    Rem マージソート1次元配列用 ←単体では絶対使うこと無いやろな ================================================
    Public Sub MergeSort(Target(), indexFrom As Long, indexTo As Long, Optional Desc As Boolean)
    Rem オプション **********************************
    Rem     Decs --------- 降順に並べる
    Rem *********************************************
    Rem 対象区間が既にソート済みだったら何もしないで抜ける
        If IsSorted(Target, indexFrom, indexTo, Desc) Then Exit Sub
        Dim EndOfA As Long, StartOfB As Long
    Rem 再帰処理で配列をAパートとBパートに2分割
        EndOfA = indexFrom + Int((indexTo - indexFrom) / 2)
        StartOfB = EndOfA + 1
        If EndOfA > indexFrom Then Call MergeSort(Target, indexFrom, EndOfA, Desc)
        If StartOfB < indexTo Then Call MergeSort(Target, StartOfB, indexTo, Desc)
    Rem 分割したAパートとBパートを比較してソート済み仮配列を作成
        Dim SWP() As Variant
        Dim i As Long
        Dim CurA As Long, CurB As Long
        Dim SortFlg As Boolean
        ReDim SWP(indexFrom To indexTo)
        CurA = indexFrom
        CurB = StartOfB
        For i = indexFrom To indexTo
            If CurA > EndOfA Then      'Aパートが処理済ならBパートの残りを仮配列に書込み
                SWP(i) = Target(CurB)
                CurB = CurB + 1
            ElseIf CurB > indexTo Then 'Bパートが処理済ならAパートの残りを仮配列に書込み
                SWP(i) = Target(CurA)
                CurA = CurA + 1
            Else                       'どちらも残ってたら比較して一方を仮配列に書込み
                If Desc Then '昇順降順の指定に従って判定
                    SortFlg = Target(CurA) >= Target(CurB)
                Else
                    SortFlg = Target(CurA) <= Target(CurB)
                End If
                If SortFlg Then '判定結果に基づき、一方を仮配列に書込み
                    SWP(i) = Target(CurA)
                    CurA = CurA + 1
                Else
                    SWP(i) = Target(CurB)
                    CurB = CurB + 1
                End If
            End If
        Next
    Rem 仮配列をそのまま元配列に転記
        For i = indexFrom To indexTo
            Target(i) = SWP(i)
        Next
    End Sub

    Rem 挿入ソート1次元配列用 ===================================================================================
    Public Sub InsertionSort(Target() As Variant, indexFrom As Long, indexTo As Long, Optional Desc As Boolean)
    Rem オプション **********************************
    Rem     Decs --------- 降順に並べる
    Rem *********************************************
        Dim CurA As Long, CurB As Long
        Dim SWP As Variant, Flg As Boolean
        For CurA = indexFrom + 1 To indexTo
            SWP = Target(CurA)
            If Desc Then
                Flg = (Target(CurA - 1) < SWP)
            Else
                Flg = (Target(CurA - 1) > SWP)
            End If
            If Flg Then
                CurB = CurA
                Do
                    If Desc Then
                        If Target(CurB - 1) >= SWP Then Exit Do
                    Else
                        If Target(CurB - 1) <= SWP Then Exit Do
                    End If
                    Target(CurB) = Target(CurB - 1)
                    CurB = CurB - 1
                Loop While CurB > indexFrom
                Target(CurB) = SWP
            End If
        Next
    End Sub
    Rem 配列が整列済みか否かのチェック関数1次元配列用 ===========================================================
    Private Function IsSorted(Target() As Variant, indexFrom As Long, indexTo As Long, Desc As Boolean) As Boolean
        Dim i As Long
        If Desc Then
            For i = indexFrom To indexTo - 1
                If Target(i) < Target(i + 1) Then Exit Function
            Next
        Else
            For i = indexFrom To indexTo - 1
                If Target(i) > Target(i + 1) Then Exit Function
            Next
        End If
        IsSorted = True
    End Function
    Rem 配列が整列済みか否かのチェック関数2次元配列用 ===========================================================
    Private Function IsSorted2(Target() As Variant, OrderBy As Long, indexFrom As Long, indexTo As Long, _
         Desc As Boolean, FirstDim As Boolean) As Boolean
        Dim i As Long
        If FirstDim Then
            If Desc Then
                For i = indexFrom To indexTo - 1
                    If Target(i, OrderBy) < Target(i + 1, OrderBy) Then Exit Function
                Next
            Else
                For i = indexFrom To indexTo - 1
                    If Target(i, OrderBy) > Target(i + 1, OrderBy) Then Exit Function
                Next
            End If
        Else
            If Desc Then
                For i = indexFrom To indexTo - 1
                    If Target(OrderBy, i) < Target(OrderBy, i + 1) Then Exit Function
                Next
            Else
                For i = indexFrom To indexTo - 1
                    If Target(OrderBy, i) > Target(OrderBy, i + 1) Then Exit Function
                Next
            End If
        End If
        IsSorted2 = True
    End Function

(中途B) 2017/07/25(火) 22:30


 >「今回の件以外の場面ではソートメソッドの方がいいよね」だと解釈して、もちろんYesです。
 はい、認識の通りです。

 >>こんな使い方をする時ってどんな時なんですか?
 >今回のは、元データを複数のテキストから読み込み、 
 〜
 >それを元々すべてシート上で行っていたのが、過去何世代かの改良の中で処理時間短縮を狙って
 >徐々に配列内での処理割合を増やしていった経緯を持ってるヤツなんですね。
 この件ですけど、ちょっと本題からは外れますが、ソート自体必要無いかも知れませんね?
[[20170723091359]] 『配列 の使い方、Dictionaryの使い方』(tata)
 此方で稲葉さんが今ちょうど解説されてますけど、Dictionaryを使えばソートしなくても集計出来るので
 配列ソートやしーとに書き出してのソートなどの工程自体が省略できれば、今よりも早くなるかも知れませんね。
 お二人の争いは主義の違い的な部分があるので、今回の件では下手すれば一生分かり合えないかも知れませんが、
 争いの種は無くなるかも知れませんよ。

 質問が今より早いコードに出来るか?と言う事でしたら、一度現在のコードと、元のデータと最終の結果の
 レイアウトを提示して頂ければ、もしかしたらアドバイスも出来たかも知れませんが。

 今回の件では、もうコードを書いていて、ご自身の業務に支障の出ないだけの検証も充分してると言うのでしたら、
 業務内容の説明もこれまでの経緯も、ここまでの話の流れでは分からないので、一般的にはソートメソッドの方が
 便利ですよくらいしか言えないですねぇ。。。

 喧嘩の原因も背景が分からない以上、中途Bさんの意見だけ聞いても判断できないのと、
 主義の違いが根本原因な気もするので、そうであれば分かり合う事は難しいので、
 後は先輩を立てるか、自身の正義を貫くかの選択になるんじゃないかと言う気がします。

 書いてる間にコードは提示されてますね。
 これはソート部分だけのコードですか?

(sy) 2017/07/25(火) 22:37


 コード拝見しました。一目見て、私は使い方すら理解できませんでした!
 すごいですね。
 これを理解して使いこなせるレベルの人の入社が約束されているんですね。
 羨ましいです。
 弊社は数式さえ怪しい人のオンパレードですよ。

 一分一秒が惜しい業務に置かれたことないので、想像でしかありませんが
 ここまで書くなら、まずはハイスペックなパソコンを買ってもらいましょう!
 勿体ないです。
 ハイスペックな人材雇うより安いですっていえば一発です!

 使い方も教えてもらっていいですか?
 優先順位は
 A列昇順、漢字込
 B列降順、数値
 C列昇順、英数、半角全角大文字小文字区別しない、
 みたいなのもできますか?
(稲葉) 2017/07/25(火) 23:03

 1次元の QuickSort 試させて頂きました。

 1〜100万までの100万件のランダムデータを作成して昇順で実行しました。
 1回目  : 4.78秒
 2回目  : 4.81秒
 3回目  : 4.81秒
 4回目  : 4.79秒
 5回目  : 4.78秒
 6回目  : 4.78秒
 7回目  : 4.78秒
 8回目  : 4.78秒
 9回目  : 4.75秒
 10回目 : 4.79秒
 平均   : 4.79秒

 凄いですね。

 ただ全く同じデータ(並び順も同じ)をシートに書き出して、ソートメソッドでソート後、配列に書き戻しました。
 1回目  : 4.08秒
 2回目  : 4.11秒
 3回目  : 4.13秒
 4回目  : 4.13秒
 5回目  : 4.13秒
 6回目  : 4.13秒
 7回目  : 4.20秒
 8回目  : 4.16秒
 9回目  : 4.26秒
 10回目 : 4.23秒
 平均   : 4.16秒

 中途Bさん作成の QuickSort は純粋に凄いと言う感想しかありませんが、
 ソートメソッドの方が遅いと言うのは、どう言うコードで検証されたんでしょうか?
 ソート用のシートなどは一々追加しなくても、それ用のまっさらなシートを一つマクロブックに
 置いておけば良いので、シートに書出し→ソート→書き戻しの手順だけで良いですよね。

 因みに私が検証したテストコードは以下です。
 A列に1〜100万までの整数データを、1行目から100万行目まで項目は無しで作成しました。

    Sub 配列ソートtest()
        Dim v As Variant
        Dim v2() As Variant
        Dim i As Long
        Dim k As Long
        Dim t As Double

        For k = 1 To 10
    '事前の配列作成
            v = Range("A1").CurrentRegion.Value
            ReDim v2(1 To UBound(v, 1))
            For i = 1 To UBound(v, 1)
                v2(i) = v(i, 1)
            Next i

    '計測はここから
            t = Timer
            Call QuickSort(v2, 1, UBound(v))
            Debug.Print " " & k & "回目 : " & WorksheetFunction.Round(Timer - t, 2) & "秒"
    'ここまで

        Next k

    'ソート結果の確認用にシートに書出し
        For i = 1 To UBound(v2)
            v(i, 1) = v2(i)
        Next i
        Range("C1").Resize(UBound(v2)).Value = v

    End Sub

    Sub シート書出しtest()
        Dim v As Variant
        Dim v2() As Variant
        Dim v3() As Variant
        Dim i As Long
        Dim k As Long
        Dim t As Double

        For k = 1 To 10
    '事前の配列作成
            v = Range("A1").CurrentRegion.Value
            ReDim v2(1 To UBound(v, 1))
            For i = 1 To UBound(v, 1)
                v2(i) = v(i, 1)
            Next i

    '計測はここから
            t = Timer
            ReDim v3(1 To UBound(v2), 0)
            For i = 1 To UBound(v2)
                v3(i, 0) = v2(i)
            Next i
            Sheets("Sheet1").Range("C:C").ClearContents
            Range("C1").Resize(UBound(v2)).Value = v3

            With Sheets("Sheet1").Sort
                .SortFields.Clear
                .SortFields.Add Key:=Range("C1"), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
                .SetRange Range("C1").CurrentRegion
                .Header = xlNo
                .Apply
            End With

            v = Range("C1").CurrentRegion.Value
            ReDim v2(1 To UBound(v, 1))
            For i = 1 To UBound(v, 1)
                v2(i) = v(i, 1)
            Next i
            Debug.Print " " & k & "回目 : " & WorksheetFunction.Round(Timer - t, 2) & "秒"
    'ここまで

        Next k

    'メソッド版は確認用の記述は必要無し

    End Sub

(sy) 2017/07/26(水) 02:16


 半分寝てたので、シート装飾してたりしてなかったり無茶苦茶ですね、
 すいません。

 でも速度は変わらないので、他のは試してないので分かりませんが、ソートメソッドの方が遅いと言うのは、
 もう一度ちゃんと検証してから主張した方が良いかも知れませんね。

(sy) 2017/07/26(水) 02:27


>今回の課題が処理速度だった。

そか、あまり興味がないテーマなので考慮してませんでしたが、

そもそも、なんでエクセルにソートさせたら速いの?って話ですよね。
VBAで大量にループするだけで処理速度的には不利ですよね?
C言語とかでソートする部分を書いて、それをVBAで使うようにすると、
処理速度があがりますよね?
逆に「自前に拘る&処理速度に拘る」ならそれくらいしてもいいのでは?

ぼく程度のレベルなら、安心・安全が気が楽でいいです。

(あと、クイックソートより速いアルゴリズムがあった気がしましたが、
見つかりませんね。。。ぼくの勘違いか^^;)
(まっつわん) 2017/07/26(水) 06:54


 2次元の QuickSort2 MergeSort2 も試してみました。
 1次元の MergeSort も試しましたが100万件では遅すぎるので途中で止めました。

 これなぜ2次元目がデータ数なんですか?
 1次元目の方が扱いやすいと思うのですが?
 (シートとの連携と言う意味ですけど)

 後各次元の中身の始まりは0からなので、1からしか受け付けないのはどうかと思います。

 これもソートメソッドの方が早いですね。
 2次元側がデータなので、シートソートの方が並べ替え分不利ですけど、提示の条件のままの配列で検証しています。

 QuickSort2 データ数20万件
 1回目  : 1.09秒
 2回目  : 1.09秒
 3回目  : 1.08秒
 4回目  : 1.09秒
 5回目  : 1.09秒
 6回目  : 1.08秒
 7回目  : 1.08秒
 8回目  : 1.09秒
 9回目  : 1.06秒
 10回目 : 1.09秒
 平均   : 1.08秒

 MergeSort2 データ数20万件
 1回目  : 1.91秒
 2回目  : 1.92秒
 3回目  : 1.97秒
 4回目  : 1.92秒
 5回目  : 1.91秒
 6回目  : 1.91秒
 7回目  : 1.9秒
 8回目  : 1.89秒
 9回目  : 1.89秒
 10回目 : 1.91秒
 平均   : 1.91秒

 シートでソート データ数20万件
 1回目  : 0.83秒
 2回目  : 0.83秒
 3回目  : 0.83秒
 4回目  : 0.85秒
 5回目  : 0.81秒
 6回目  : 0.88秒
 7回目  : 0.89秒
 8回目  : 0.84秒
 9回目  : 0.83秒
 10回目 : 0.86秒
 平均   : 0.85秒

 2次元配列で、1次元目は1つ、2次元目をデータで配列を作りました。
 中身のデータは1次元配列の時と同じです。
 100万件だと遅すぎるので20万件で検証しました。

 以下今回の検証コードです。
 MergeSort2 も呼出先が変わるだけです。

    Sub 配列ソートtest2次元()
        Dim v As Variant
        Dim v2() As Variant
        Dim v3() As Variant
        Dim i As Long
        Dim k As Long
        Dim t As Double

        For k = 1 To 10
    '事前の配列作成
            v = Sheets("Sheet1").Range("A1").CurrentRegion.Value
            ReDim v2(1 To 1, 1 To UBound(v, 1))
            For i = 1 To UBound(v, 1)
                v2(1, i) = v(i, 1)
            Next i

    '計測はここから
            t = Timer
            Call MergeSort2(v2, 1, 1, UBound(v))
            Debug.Print " " & k & "回目 : " & WorksheetFunction.Round(Timer - t, 2) & "秒"
    'ここまで

        Next k

    'ソート結果の確認用にシートに書出し
        ReDim v3(1 To UBound(v, 1), 0)
        For i = 1 To UBound(v, 1)
            v3(i, 0) = v2(1, i)
        Next i
        Sheets("Sheet1").Range("C:C").ClearContents
        Sheets("Sheet1").Range("C1").Resize(UBound(v3, 1)).Value = v3

    End Sub

    Sub シートソートtest2次元()
        Dim v As Variant
        Dim v2() As Variant
        Dim v3() As Variant
        Dim i As Long
        Dim k As Long
        Dim t As Double

        For k = 1 To 10
    '事前の配列作成
            v = Sheets("Sheet1").Range("A1").CurrentRegion.Value
            ReDim v2(0, 1 To UBound(v, 1))
            For i = 1 To UBound(v, 1)
                v2(0, i) = v(i, 1)
            Next i

    '計測はここから
            t = Timer
            ReDim v3(1 To UBound(v2, 2), 0)
            For i = 1 To UBound(v2, 2)
                v3(i, 0) = v2(0, i)
            Next i
            Sheets("Sheet1").Range("C:C").ClearContents
            Sheets("Sheet1").Range("C1").Resize(UBound(v, 1)).Value = v3

            With Sheets("Sheet1").Sort
                .SortFields.Clear
                .SortFields.Add Key:=Sheets("Sheet1").Range("C1"), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
                .SetRange Sheets("Sheet1").Range("C1").CurrentRegion
                .Header = xlNo
                .Apply
            End With

            v = Sheets("Sheet1").Range("C1").CurrentRegion.Value
            For i = 1 To UBound(v, 1)
                v2(0, i) = v(i, 1)
            Next i
            Debug.Print " " & k & "回目 : " & WorksheetFunction.Round(Timer - t, 2) & "秒"
    'ここまで

        Next k

    'メソッド版は確認用の記述は必要無し

    End Sub

(sy) 2017/07/26(水) 07:25


すぐに動かせる状態ではないので、ぱっと見だけの感想ですか、かなり綺麗なコードですよ。 コーディング技術には問題がないように見えます。 自信を持ってください。(コメントにREMを使うのは、かなり長くソフトウェアを扱ってきた方なのでしょうか? 私の経験上、こういう書き方をする人は、30年以上の大ベテランだったりしますが、更に先輩が在社されているのですよね?)

現状は機能変更等はご自身で行うので、クイックソートの運用も問題無い、とお考えのようですが、10年後、20年後でも貴方は同じ会社で同じ業務を行っているとは限らないので、判りやすさや直しやすさは大事です。 長くやっている人ほど、いつまでも自分自身がコード修正するものではない、という事を実感しているので、先輩は独自ロジックを感覚的に避けるようになっているのかもですね。

そして、徹底的に速度追求するならば、同じロジックをC言語等のコンパイラ言語で書いたら、もっと速いのではないかと思います。 Excelマクロのメリットは、入力データや出力データを見易い表の形で用意できる事だと思います。 Cで書いても、表に転記するところまで含めてExcelの内部処理と比較することになり、ソート時間より転記時間の方が大きくて不利そうですが、例えばDLLにしてExcelから呼び出す関数にしてしまえば、現状のクイックソートロジックよりは速くなる事でしょう。とはいえ、速くなると予想できはしますが、作成にかかる工数と向上する速度で得られる時間を考えると、C言語等によるDLL化は手を出さなくても良いと思います。

あと、クイックソートした結果の検証は、どのように行いましたか? おそらく、Excelのソート結果と比較する数式でも書いたのかと思いますが、それはExcelの処理を全面信用し、自身のコードにはバグがある可能性を想定したものかと思います。 この信頼性の違いも、大事な判断材料になるでしょう。
(???) 2017/07/26(水) 09:29


皆様、引き続きお付き合い頂きありがとうございます。
なかなか返事できなくってホント申し訳ありません。
ちょっと落ち着いてレス返せるの夕方以降になりそうです。

syさん、再検証までして頂いておいてすみません。
多分私がどこか見落しとか早とちりしてるんでしょう。
とりあえず自分の検証コードだけは貼っておかないとさすがにヤバいそうなので、
それだけ貼っておきます。

    Option Explicit

    Public Declare Function timeGetTime Lib "winmm.dll" () As Long
    Public Declare Function timeBeginPeriod Lib "winmm.dll" (ByVal uPeriod As Long) As Long
    Public Declare Function timeEndPeriod Lib "winmm.dll" (ByVal uPeriod As Long) As Long

    Sub 検証6()
        Const RowSize As Long = &H10000
        Const ColSize As Long = 3
        Const OrderIdx As Long = 2
        Const Descend As Boolean = False
        Dim Ra As Range, vOrg() As Variant, vTest() As Variant
        Dim r As Long, c As Long
        Dim t As Long, t2 As Long

        Set Ra = Range(Cells(1, 1), Cells(RowSize, ColSize))

        ReDim vOrg(1 To RowSize, 1 To ColSize)
        For r = 1 To RowSize
            vOrg(r, 1) = r
            For c = 2 To ColSize
                vOrg(r, c) = Int(Rnd() * RowSize)
            Next
        Next
        Debug.Print Format$(RowSize, "#,##0") & " x " & ColSize
        Ra.Resize(, 3).Value = vOrg 'ColSizeの指定を無視して3列だけ

        Call timeBeginPeriod(1)
    Rem クイックソート
        vTest = vOrg
        t = timeGetTime()
        QuickSort2 vTest, OrderIdx, 1, RowSize, Descend, True
        Debug.Print timeGetTime() - t, "クイックソート"
        Ra.Offset(0, 3).Resize(, 3).Value = vTest
    Rem クイックソート(安定版)
        vTest = vOrg
        t = timeGetTime()
        QuickSort2 vTest, OrderIdx, 1, RowSize, Descend, True, True
        Debug.Print timeGetTime() - t, "クイックソート(安定版)"
        Ra.Offset(0, 6).Resize(, 3).Value = vTest
    Rem シート上で並べ替え
        vTest = vOrg
        t = timeGetTime()
        Application.ScreenUpdating = False
    '    t2 = timeGetTime()
        Workbooks.Add
    '    Debug.Print timeGetTime() - t2, " 内)シート追加"
        With Cells(1, 1).Resize(RowSize, ColSize)
    '        t2 = timeGetTime()
            .Value = vTest
    '        Debug.Print timeGetTime() - t2, " 内) 配列貼付"
    '        t2 = timeGetTime()
            .Sort .Columns(OrderIdx), IIf(Descend, xlDescending, xlAscending)
    '        Debug.Print timeGetTime() - t2, " 内)Sort実行"
    '        t2 = timeGetTime()
            vTest = .Value
    '        Debug.Print timeGetTime() - t2, " 内)配列再読込"
        End With
    '    t2 = timeGetTime()
        ActiveWorkbook.Close False
    '    Debug.Print timeGetTime() - t2, " 内)シート破棄"
        Application.ScreenUpdating = True
        Debug.Print timeGetTime() - t, "シート上で並べ替え"
        Ra.Offset(0, 9).Resize(, 3).Value = vTest
    Rem マージソート
        vTest = vOrg
        t = timeGetTime()
        MergeSort2 vTest, OrderIdx, 1, RowSize, Descend, True
        Debug.Print timeGetTime() - t, "マージソート"
        Ra.Offset(0, 12).Resize(, 3).Value = vTest

        Call timeEndPeriod(1)
    End Sub

 ↓Print結果
 65,536 x 3
  154          クイックソート
  173          クイックソート(安定版)
  421          シート上で並べ替え
  389          マージソート

(中途B) 2017/07/26(水) 12:20


 なんかスピード的な流れになっていますが、
 単純なSortならどちらも大差ないでしょう。

 >複雑(数値文字混合等)なデータを複数列に対して的確なソート(昇順・降順)が出来て、

 これをコードでGeneralizeするのは無理でしょう。

 >尚且つ後継者がそれを継承できるなら...

 QuickSort・BubleSort等をを使用するにしても上記を満足させるにはむしろ
 的確なソート結果をもたらすデータの作りこみが大事なのだと思いますよ?

( seiya) 2017/07/26(水) 13:05


すみません。まだ戻って来れてません。

ちょっと思ったので、ひとつだけ。
ひょっとしてSortメソッドよりSortオブジェクト使った方が、
複数フィールドを相手にする場合に、もの凄く優位なのではないか? と。

そんなの当たり前? ですか?

(中途B) 2017/07/26(水) 18:01


 >そんなの当たり前? ですか?
 これ私に対するコメント?

 そんなこと言ってないし、各結果の同一値内の他列の値が微妙に違ってるんですよね。

 この程度の誤差は許容範囲なのであれば私のレスは無視してください。
( seiya) 2017/07/26(水) 19:07

 >多分私がどこか見落しとか早とちりしてるんでしょう。 
 早とちりと言うか、ご自身のコードに関しては思い入れもあるでしょうから、しっかりと作り込まれているし、
 検証コードも最適なコードになっていると思いますが、シートソートの検証コードは正直に申し上げると最適化
 とは程遠い雑なコードになっています。
 おそらくシートソートは遅いから無駄との偏見からくる投げやりなコード、早く処理する為の検討をしていない
 としか思えないコードになっています。

 上でも書きましたが、
 >ソート用のシートなどは一々追加しなくても、それ用のまっさらなシートを一つマクロブックに
 >置いておけば良いので、シートに書出し→ソート→書き戻しの手順だけで良いですよね。
 一時作業ブックの追加も同じ事です。
 ブックの追加などは非常に重たい処理なので、記述すれば遅くなって当然です。
 マクロブックは必ず開いているんですから、そこに作業シートを予め用意すれば、わざわざ別に一時ファイル
 など用意しなくても良いですよね。

 あくまで目的は、
 1、配列内のデータを早くソートしたい。
 2、本来の目的は集計を早く処理したい。
 ですよね?
 なら元が配列内データをソートして配列に戻すと言う事が満足出来れば、良いと言う事ですよね?

 マクロブックにシートを予め用意するのは、それらの邪魔になるんですか?
 私は一切影響しないと思います。
 また見た目余計なシートがあるのが嫌だとかあるなら非表示にすれば良い事ですし、
 しかも非表示の方が僅かに早いですしね。

 それらを反映させたのが私のテストコードです。

 以下の遅くするだけの記述は全く無くても問題ないはずです。
 >Workbooks.Add
 >ActiveWorkbook.Close False
 >Application.ScreenUpdating = False 
 ↑これもソートの速度を落とす原因になります。
 これするなら、初めからシートを非表示にしておけば、画面の更新が行われないので同じ結果になります。
 僅かですけど記述自自体の実行時間が無駄になります。

 配列ソートのコード自体は素晴らしいと思いますが、それでもシートソートには、
 速度(これは微々たる差と思います)・記述の簡単さ・メンテナンス性・引継ぎの容易さ・正確性を担保する為の検証時間、
 どれをとっても敵わないと思います。
 先輩と対決する前に、もう一度じっくり考え直す事をお勧めします。

(sy) 2017/07/26(水) 22:41


 syさん、検証ありがとうございました。
 その前に
 > 、Dictionaryを使えばソートしなくても集計出来るので
 こちらの件ですが、
 実はだいぶ初期の段階で取り入れていた形跡が残ってました。
 早々に切り捨てられていた様ですが、再度検討してみた方が良さそうですね。
 おそらく集計結果をシートに出力した後のレポートとしての体裁を整えるのに
 何かしら不都合があって、方向性を切り替えたのではないかと思います。
 そっちを上手く処理できれば、良い結果が得られそうな気がします。

 >  1次元目の方が扱いやすいと思うのですが?
 >  (シートとの連携と言う意味ですけど)
 引数FirstDim:=Trueで1次元目の方をソート対象に切り替える仕様です。

 >  後各次元の中身の始まりは0からなので、1からしか受け付けないのはどうかと思います。
 すみません。よくわかりません。
 まだアップして頂いた検証コード試せてないもので。
 後ほど見直してみます。

 稲葉さんもありがとうございました。
 >  優先順位は
 >  A列昇順、漢字込
 >  B列降順、数値
 >  C列昇順、英数、半角全角大文字小文字区別しない、
 >  みたいなのもできますか?
 いやいや、全然ダメダメです。
 え? StrComp使って判定? でもその前にデータ型判定は?
 とか考えたらやる気削がれますね。

 はずかしい事に、私、Sortオブジェクトって使った事ないんですよ。
 Sortメソッドばっか使ってました。
 で複数フィールドをソートする場合の記述も、
 優先順位の低い方から1回ずつSortメソッドを実行する
 という記述しかしませんでした。
 なので、コードでやる場合も同様に
 優先順位の低い方から1回ずつソートを実行すればいいじゃん
 って、何の疑問も持たずに今までやってきました。

 まっつわんさん
 > C言語とかでソートする部分を書いて、それをVBAで使うようにする
 ははは^^;
 入門書のインデックスだけ見て本棚で眠ってますね。
 プライベートで頑張ります。

 > クイックソートより速いアルゴリズムがあった気がしましたが、
 鳩ノ巣ソートの事かも知れません
http://fanblogs.jp/booboo7x70/archive/99/0
 違ってたらすみません。

 ???さん
 > 自信を持ってください。
 ありがとうございます。
 先輩しか知らないので、他の人の評価なんて考えた事ありませんでした。

 > コメントにREMを使う
 なんか先輩に叩きこまれました。
 コメント書く時はRem。クォーテーションはコメントアウトだから使い分けなさい。
 だそうです。内心どーでもいいと思いながら従ってます。

 > 長くやっている人ほど、いつまでも自分自身がコード修正するものではない、という事を実感しているので、
 > 先輩は独自ロジックを感覚的に避けるようになっているのかも
 どうやらそうみたいです。
 というのも、
 >>  私はソート1回での処理を思ってたんですが、先輩は4回ソートするつもりだったかもなんです。
 について今日先輩に確認してみたんですが、そういった認識齟齬はありませんでした。
 ではなぜクイックソートは却下なんですか?
 と訊いてみたら、なんか部内限定ではありますがオフィシャルにするみたいです。業務ツールとして。
 なので、皆さんのアドバイス通りの事を考えてのご判断だったようです。

 だったら最初からそう言って欲しかったですよ。もー
 って言ったら、どうやら「試されてた」みたいですね。
 その辺の感覚はやっぱまだまだだね〜 って
 あ た り ま え だ !!

 あ、でも私が組んだソートコードは「絶対残しとけよ」とも言われました。
 はい。またどこかでエッセンスだけでも役に立つ機会があればそれでいいです。

 それと
 >>そんなの当たり前? ですか?
 > これ私に対するコメント?
 は、皆さんへの問いかけでした。
 syさんの検証コードを思い返して「もしや」と思って。
 ダメだな 焦って書き込むもんじゃないですね。
 後で自分で確認します。

 なんかseiyaさんには最初っから謝ってばかりだった気がします。
 いろいろとご意見頂き、ありがとうございました。

 > 各結果の同一値内の他列の値が微妙に違ってる
 QuickSort2のことでしたら引数StableにTrueを指定しなければそうなります。
 という話じゃなかったら、まぁ私のヘマですね。

 ・・・という事でした。
 もうスレの主題としては終わりましたね。
 これ以上脱線したまま伸ばす方が迷惑だと思いますので、締めさせて頂きます。
 みなさん、ありがとうございました。

 syさんの検証コードとの比較検証については自分でやります。
 まぁ100%私のヘマでしょうから。
 判明したら謝りに来ます。

(中途B) 2017/07/26(水) 22:48


 syさん
 ありがとうございます。
 書き込み中に更にお返事頂いたようで。

 > 偏見からくる投げやりなコード、早く処理する為の検討をしていない
 > としか思えないコードになっています。
 いやホントお恥ずかしい限りです。
 ご指摘を踏まえて再度検証して参ります。

 先輩とは対決にすらならなかったです^^;
 でもまあ、その方がじっくり検証できると思うのでよかったか。

 前レスでも申しましたが、頂いたsyさんの検証コードをまだ試せていません。
 証拠を取り揃えて、改めて「敗北宣言」に来ようと思います。

 親身にお付き合い頂き、ありがとうございました。

(中途B) 2017/07/26(水) 23:03


蛇足ながら。

REMと'を使い分けているのですね。 それは先輩のローカルルールですが、一理ありますので、従っておいて損はないです。 そういう細かい配慮は、言われて身につくものではないので、師として従ってよい方に思えます。 今は盲目的に従っておき、後で今回のような機会に、そういう意味だったのか!、と閃くのも、面白いものですよ。

REMについては、おそらくコードの可動性、メンテナンス性を考慮しての考えでしょう。REMと書いてあるのは処理説明であり、大事な情報。 シングルクォートは、デバッグのためや修正のために非実行にした行であり、なんだったら削除しても構わない情報。 この2つが一目で区別できます。 反対意見としては、インタプリンタなんだから、3文字より1文字の方が速い、という点ですが、近年の十分速いCPUならば、数文字くらい計測もできないくらいの差でしょう?、という事を考えての事かと思います。(数文字を気にするなら、変数名やコメントを無くすとか短くするとか、可読性が落ちる方向になっていきますから)
(???) 2017/07/27(木) 09:53


 こんばんは。
 敗北宣言に参りました。

 その前に
 ???さん、追加コメントありがとうございました。
 なるほど「受け継がれていくことを意識しなさい」という事ですね。
 経理の仕事でも、伝票の摘要なんかで上司に「誰が見ても意味が分かるように書け」って言われます。
 結局はこれも、Remの使い方も、皆さんにアドバイスされた事と本質的には同じ意味ですね。

 では、今日再検証した結果を投下して、今夜はこのまま失礼させて頂きます。

 ●syさんに頂いた検証コードをそのままやった場合
  データサイズ262144 x 1 (0以上1024未満の整数)
             シート   クイック  安定クイック  マージ
 10回平均    0.87秒    0.25秒    0.76秒       1.05秒

 データサイズ1048576 x 1 (0以上1048576未満の整数)
             シート   クイック  安定クイック  マージ
 10回平均    2.68秒    2.10秒    2.96秒       4.62秒

 ●配列ソートはFirstDimを使い、シートソートも別シートでやった場合
  データサイズ262144 x 1 (0以上1024未満の整数)
             シート   クイック  安定クイック  マージ
 10回平均    0.60秒    0.25秒    0.76秒       1.05秒

 データサイズ1048576 x 1 (0以上1048576未満の整数)
             シート   クイック  安定クイック  マージ
 10回平均    2.51秒    2.10秒    2.95秒       4.68秒

 ▼2番目の検証に使用した改造コード(あらかじめSheet1のA列にデータを用意して実行)

    Sub 配列ソートtest2次元_FirstDim()
        Dim v() As Variant
        Dim i As Long
        Dim k As Long
        Dim t As Double

        For k = 1 To 10
            v = Sheets("Sheet1").Range("A1").CurrentRegion.Value
    '計測はここから
            t = Timer
    '        Call QuickSort2(v, 1, 1, UBound(v), , True, False)
            Call QuickSort2(v, 1, 1, UBound(v), , True, True)
    '        Call MergeSort2(v, 1, 1, UBound(v), , True)
            Debug.Print " " & k & "回目 : " & WorksheetFunction.Round(Timer - t, 2) & "秒"
    'ここまで
        Next k

    'ソート結果の確認用にシートに書出し
        Sheets("Sheet1").Range("C:C").ClearContents
        Sheets("Sheet1").Range("C1").Resize(UBound(v, 1)).Value = v
    End Sub
    Sub シートソートtest2次元_別シートで()
        Dim v() As Variant
        Dim k As Long
        Dim t As Double

        For k = 1 To 10
            v = Sheets("Sheet1").Range("A1").CurrentRegion.Value
    '計測はここから
            t = Timer
            With Sheet2
                .Range("A:A").ClearContents
                .Range("A1").Resize(UBound(v, 1)).Value = v
            End With
            With Sheet2.Sort
                .SortFields.Clear
                .SortFields.Add Key:=Sheet2.Range("A1"), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
                .SetRange Sheet2.Range("A1").CurrentRegion
                .Header = xlNo
                .Apply
            End With
            v = Sheet2.Range("A1").CurrentRegion.Value
            Debug.Print " " & k & "回目 : " & WorksheetFunction.Round(Timer - t, 2) & "秒"
    'ここまで
        Next k

    'ソート結果の確認用にシートに書出し
        Sheets("Sheet1").Range("C:C").ClearContents
        Sheets("Sheet1").Range("C1").Resize(UBound(v, 1)).Value = v
    End Sub

 ●考察
 syさんによる検証 (データ数20万件)
     QuickSort2         1.08秒
     シートでソート     0.85秒
 私によるsyさんの検証コードの再実施 (データ数262144)
     QuickSort2(安定)   0.76秒
     シートでソート     0.87秒
 改造したコードでの検証 
     QuickSort2(安定)   0.76秒
     シートでソート     0.60秒

 シート上ソートはsyさん結果と私の結果は同じ。
 別シートでソートした検証では更に速くなった。
 syさんのご指摘通り、別シートでのソートが最も速かった。

 QuickSort2はsyさんの結果がシート上ソートより遅いのに対して
 私の再実施結果はシート上ソートより速かった。この件に関しては、
 使用したデータや環境がsyさんとでは異なるはずなので、違いの要因は不明だが、
 裏を返せば「シート上ソートはデータ内容や環境に左右されにくい」という証明だ。

 また1048576行の場合、データが1列とはいえ、不安定ソートのままシートに勝っても意味ないので
 安定クイックと比較すると、やはり負けてしまう。

 結論「負けました」

(中途B) 2017/07/27(木) 22:57


 ●複数列のデータ、及び複数回ソートでの検証
  データは先頭行を見出しとして、2行目以下に262144行の乱数(0以上の整数)を配置
  列数は1列、3列、5列、10列の4パターン
  データの内容は「乱数×4の列番乗」とした。(上位列になるほどデータの重複割合が多いデータ)
  ソート手段は
  ・クイックソート-----------不安定のまま下位列から1回ずつソート
  ・クイックソート(安定版)---引数Stable:=Trueとして下位列から1回ずつソート
  ・Sortオブジェクト---------別シートにデータを転記し、SortFieldを上位列からAddして一括Apply
  ・Sortメソッド-------------別シートにデータを転記し、Key1のみ使用して下位列から1回ずつソート
  
  計測はそれぞれ10回行い、その平均をとった。

 ▼検証に使用したコード(Sheet1とSheet2を使用。Sheet2はあらかじめ非表示にした)

    Public Declare Function timeGetTime Lib "winmm.dll" () As Long
    Public Declare Function timeBeginPeriod Lib "winmm.dll" (ByVal uPeriod As Long) As Long
    Public Declare Function timeEndPeriod Lib "winmm.dll" (ByVal uPeriod As Long) As Long

    Sub 検証8Call()
        Const RowSize As Long = &H40000
        Const ColSize As Long = 10
        Const SortCount As Long = 10 '←1列目から何列目までソートするか
        Dim i As Long

        Application.ScreenUpdating = False
        Sheet1.Cells.NumberFormat = "@"
        Call timeBeginPeriod(1)
        For i = 1 To 10
            Debug.Print i & "回目", RowSize & " x " & ColSize
            Call 検証8(RowSize, ColSize, SortCount)
        Next
        Call timeEndPeriod(1)
        Sheet1.Cells.ClearFormats
        Application.ScreenUpdating = True
    End Sub
    Private Sub 検証8(RowSize As Long, ColSize As Long, OrderLMT As Long)
        Dim vTest() As Variant, vOrg() As Variant
        Dim r As Long, c As Long
        Dim t As Long, i As Long

        ReDim vOrg(0 To RowSize, 1 To ColSize)
        For c = 1 To ColSize
            vOrg(0, c) = "乱数×" & 4 ^ c
        Next
        For r = 1 To RowSize
            For c = 1 To ColSize
                vOrg(r, c) = Int(Rnd() * 4 ^ c)
            Next
        Next
    Rem クイックソート
        vTest = vOrg
        t = timeGetTime() '------------------------------------------▼ココから
        For i = OrderLMT To 1 Step -1
            QuickSort2 vTest, i, 1, RowSize, , True
        Next
        Debug.Print timeGetTime() - t, ",クイックソート" '-----------▲ココまで
        Sheet1.Cells(1, 1 + ColSize * 0).Resize(RowSize + 1, ColSize).Value = vTest
    Rem クイックソート(安定版)
        vTest = vOrg
        t = timeGetTime() '------------------------------------------▼ココから
        For i = OrderLMT To 1 Step -1
            QuickSort2 vTest, i, 1, RowSize, , True, True
        Next
        Debug.Print timeGetTime() - t, ",クイックソート(安定版)" '---▲ココまで
        Sheet1.Cells(1, 1 + ColSize * 1).Resize(RowSize + 1, ColSize).Value = vTest
    Rem Sortオブジェクト
        vTest = vOrg
        With Sheet2
            .Rows.Delete
            t = timeGetTime() '--------------------------------------▼ココから
            .Cells(1, 1).Resize(RowSize + 1, ColSize).Value = vTest
            With .Sort
                .SortFields.Clear
                For i = 1 To OrderLMT
                    .SortFields.Add Key:=Sheet2.Columns(i)
                Next
                .SetRange Sheet2.UsedRange
                .Header = xlYes
                .Apply
            End With
            vTest = .Cells(1, 1).CurrentRegion.Value
        End With
        Debug.Print timeGetTime() - t, ",Sortオブジェクト" '---------▲ココまで
        Sheet1.Cells(1, 1 + ColSize * 2).Resize(RowSize + 1, ColSize).Value = vTest
    Rem Sortメソッド
        vTest = vOrg
        With Sheet2
            .Rows.Delete
            t = timeGetTime() '--------------------------------------▼ココから
            With .Cells(1, 1).Resize(RowSize + 1, ColSize)
                .Value = vTest
                For i = OrderLMT To 1 Step -1
                    .Sort .Columns(i), Header:=xlYes
                Next
            End With
            vTest = .Cells(1, 1).CurrentRegion.Value
        End With
        Debug.Print timeGetTime() - t, ",Sortメソッド" '-------------▲ココまで
        Sheet1.Cells(1, 1 + ColSize * 3).Resize(RowSize + 1, ColSize).Value = vTest
    End Sub

 ●計測結果(秒)
 1列目だけをソート_____|_1列__3列__5列__10列
 クイックソート        | 0.1  0.1  0.2   0.3
 クイックソート(安定版)| 0.7  0.7  0.7   0.8
 Sortオブジェクト      | 0.6  1.3  2.2   4.1
 Sortメソッド          | 0.6  1.3  2.1   4.1

 1〜3列目をソート______|_1列__3列__5列__10列
 クイックソート        |      0.5  0.9   1.3
 クイックソート(安定版)|      2.2  2.4   2.7
 Sortオブジェクト      |      1.7  2.4   4.4
 Sortメソッド          |      2.0  3.1   5.9

 1〜5列目をソート______|_1列__3列__5列__10列
 クイックソート        |           1.9   2.6
 クイックソート(安定版)|           4.1   4.9
 Sortオブジェクト      |           2.5   4.5
 Sortメソッド          |           4.2   7.8

 1〜10列目をソート_____|_1列__3列__5列__10列
 クイックソート        |                 6.8
 クイックソート(安定版)|                10.1
 Sortオブジェクト      |                 4.6
 Sortメソッド          |                12.4

 1列データの結果は先のsyさん検証コードを改造した結果と同じ。
 1列目だけをソートする場合、列数が多いデータほど配列ソートが速くなった。

 複数回ソート場合、クイックソート(不安定のまま)の結果は比較対象外として、
 クイックソート(安定版)とSortメソッドの比較では配列ソートが速くなった。
 但し、Sortメソッドの使い方が「Key1のみ使用で下位列から1回ずつソート」だから
 機能制限を課しており、フェアな比較とは言えない。
 Sortメソッドで1回あたり複数Keyを使用すれば配列ソートは負けるのではないか?(要確認)

 Sortオブジェクトに関しては
 >>複数フィールドを相手にする場合に、もの凄く優位なのではないか?
 との予想の通りの結果だった。
 SortFieldの数が増えても処理時間はほとんど増えてない。
 これはすごい。KO負けだ。

 3列データの3回ソートでも負けてるし、
 結局勝ってるのは5列以上のデータの3回ソート迄か。

 結論「負けました」

 今日は以上です。 では、失礼します。

(中途B) 2017/07/27(木) 22:59


 これで最後です

 ●>>Sortメソッドで1回あたり複数Keyを使用すれば配列ソートは負けるのではないか?(要確認)
  について一応追加確認してみた。

 1〜3列目をソート______|_1列__3列__5列__10列
 クイックソート(安定版)|      2.3  2.4   2.8
 Sortオブジェクト      |      1.6  2.4   4.4
 Sortメソッド          |      1.6  2.4   4.5 ←20%〜23%削減(ソート回数が2回分減った効果)

 1〜5列目をソート______|_1列__3列__5列__10列
 クイックソート(安定版)|           4.3   4.9
 Sortオブジェクト      |           2.5   4.6
 Sortメソッド          |           3.1   5.6 ←26%〜28%削減(ソート回数が3回分減った効果)

 1〜10列目をソート_____|_1列__3列__5列__10列
 クイックソート(安定版)|                10.3
 Sortオブジェクト      |                 4.6
 Sortメソッド          |                 7.5 ←およそ40%削減(ソート回数が6回分減った効果)

 思った通りだった。

 ●SortオブジェクトがSortFieldの数が増えても高速なのは、昇順・降順混在でもそうなのか確認
  仮に10列あるSortFieldをすべて昇順でソートするなら、10列のキーを連結して比較すれば、
  実質的に1回のソートで済む。先の検証はその結果だったのでは?
  
  ・・・などという安易な仮説が正しければ、
  昇順と降順を交互に指示する場合は、その「キーを連結して比較」とやらができず、
  実質ソート回数が増えて多少遅くなるはずだ。
  可能性の排除の意味で確認してみる。

 先の検証と同じく262144行 x 10行の乱数データを使用した1〜10列目をソートするコードを流用し、
 偶数列の並び順のみ降順指定した。(乱数の内容は、全列とも0以上16未満の整数にした)

 1〜10列目をソート_____|10列
 クイックソート(安定版)| 9.7
 Sortオブジェクト      | 4.6
 Sortメソッド          | 7.9

 やはり変わらなかった。

 以上で終わります。
 途中からは検証そのものを楽しんでしまってる節があり、自らスレ汚しだったかもしれません。
 とりあえず手持ちのコードでSortメソッド使ってる部分は
 すべてSortオブジェクトに書き換えなければ、と感じています。
 またプライベートでArrayListやADOも交えて比較してみたいなと思います。

 大変お世話にました。ありがとうございました。

(中途B) 2017/07/28(金) 15:20


いやいや、おそらく殆どの回答者(全員?)が、こういう性能検証情報は大好きですよ。 中途Bさんの技術・知識向上にもなったでしょうし、私達も判断基準の裏付けとなる良い情報でした。

そして、この程度の差なら標準機能のSortだよね、という先輩の意見も適していたでしょうし、インタプリンタはやっぱり不利だよね、という事も確認できました。

いずれDBを試す際は、インデックスキーの有無に気をつけてください。 インデックスを付けると格段に性能向上しますが、DB領域も使用するので、さじ加減は設計者に左右されるのですよ。 遅い、遅いと言っていたシステムが、話を聞くと、インデックス?、なにそれ?、とかもありがちです。 頑張ってください。
(???) 2017/07/28(金) 15:43


 >>  後各次元の中身の始まりは0からなので、1からしか受け付けないのはどうかと思います。
 >すみません。よくわかりません。

 最後これだけ、
 LBOUNDが0の時はエラーになりますね。と言う事です。
 例えばv(0 to 100) とかv(0 to 100 , 0) と言った配列は扱えないですね。と言う事です。

 ファイルシステムオブジェクトの ReadAll などで読み込んだデータを Split関数 で分割して Variant型変数 
 に一括取り込みしたような配列や、
 ADO の GetRowsメソッド などでAccessなどから取り込んだデータも Variant型変数 に一括取り込みした場合は
 1次2次どちらもLBOUNDは 0 になるので、
 これらの配列は使用する時は、別の 1 から始まる配列に書き換えてから使用しないと使えないですね。と言う事です。

 それとも私が気付いてないだけで、引数のどれかを変えたらそのまま使えるのかな?

(sy) 2017/07/29(土) 09:17


 は!! しまった!
 その件、すっかり忘れていました。

 > LBOUNDが0の時はエラーになりますね。
 あれ? 0だろうがマイナススタートだろうが、エラーにはならないですよ・・・?

    Sub マジか()
        Const L1 As Long = 0
        Const U1 As Long = 9
        Dim v(L1 To U1) As Variant, v2(0, L1 To U1) As Variant, v3(L1 To U1, 0) As Variant
        Dim i As Long
        Dim vR(L1 To U1) As Variant

        For i = L1 To U1
            v(i) = Int(Rnd() * 10)
            v2(0, i) = v(i)
            v3(i, 0) = v(i)
        Next
        Debug.Print Join(v, ","), "ソート前"
        Call QuickSort(v, L1, U1)
        Debug.Print Join(v, ","), "ソート後(1次元)"

        Call QuickSort2(v2, 0, L1, U1, , False)
        For i = L1 To U1
            vR(i) = v2(0, i)
        Next
        Debug.Print Join(vR, ","), "ソート後(2次元1)"

        Call QuickSort2(v3, 0, L1, U1, , True)
        For i = L1 To U1
            vR(i) = v3(i, 0)
        Next
        Debug.Print Join(vR, ","), "ソート後(2次元2)"

    End Sub

 と、思ったのですが、そういう意味じゃないですね。
 > Split関数 で分割して Variant型変数に一括取り込みしたような配列
 これですね。

 「配列として宣言したVariant型変数」にSplit関数を代入しようとしても、
 実行時エラー13(型が一致しません)になりますので、
 Split関数の受取りには配列として宣言していない「ただのVariant型変数」を使うハズ。

 でも私のQuickSortに引数として渡す時には「配列として宣言したVariant型変数」しか
 受け付けない様に書いています。

 Public Sub QuickSort(Target() As Variant, ・・・
                            ~~引数の受け付けを明示的配列のみに限定してる

 なので「ただのVariant型変数」として宣言された配列をQuickSortに渡そうとしても、
 「コンパイルエラー:配列またはユーザー定義型を指定してください。」
 となってしまいます。

 これのことでしょうか?
 だとすれば、そこまで考えてませんでしたね〜
 RangeのValueを受け取る時はSplit関数の様なエラーは発生しないので気付かなかったです。

 試しにソートモジュール内のSubステートメント、Functionステートメントの引数部分にある
 「Target() As Variant」をすべて「Target As Variant」に書き変えたら、
 「ただのVariant型変数」として宣言された配列でも扱えるようにはなりました。

 扱えるようにはなったのですが・・・、遅くなりました。処理時間50〜60%増です。
 ダメだなこりゃ。

 syさん、最後まで的確なご指摘をありがとうございます。

(中途B) 2017/07/29(土) 17:48


コメント返信:

[ 一覧(最新更新順) ]


YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki. Modified by kazu.