LINQは本当に強力だ (4) アルゴリズムヘルパーメソッド

列挙子を列挙するときに、現在の位置が知りたい場合がある。頭の中にまだLINQの武器が少ないときは、諦めてfor文に逃げたりする。

int[] data = new int[] { 123, 456, 789 };
List<int> results = new List<int>();
for (int index = 0; index < data.Length; index++)
{
    results.Add(data[index] * (index + 1));
}

LINQを使うなら、こういったループ制御構文を出来るだけ使わないようにする事だ。これが最終的に、ロジック重視の設計からデータドリブンの設計に移行するカギとなる。
インデックスが必要なら、インデックスを列挙させれば良い。

int[] data = new int[] { 123, 456, 789 };
List<int> results =
    (from index in Enumerable.Range(0, data.Length)
     select data[index] * (index + 1)).
     ToList();

for文やforeach文で(ロジック的に)記述してしまうと、その後の応用性が失われてしまう。Enumerable.Rangeでインデックスの列挙子を生成すれば、結果は列挙子となる。上の例では早々にToListしてしまったが、もちろん、ここから続けて別のクエリを書く事が出来るし、そうすることでパイプライン実行が行われる。AsParallelすれば並列実行も出来る。応用性が全く違うというわけだ。

ところで、上の例は元ネタとなるdataが配列であるので、要素数が分かる。だからEnumerable.Rangeでインデックスを生成できるのだが、配列である事を想定しているので詰めが甘い。ここは一つ、任意の列挙子で実現してみよう。
ただ置き換えるのであれば、以下のようになる。

IEnumerable<int> data = new int[] { 123, 456, 789 };
List<int> results =
    (from index in Enumerable.Range(0, data.Count())
     select data.ElementAt(index) * (index + 1)).
     ToList();

配列はIEnumerableを実装しているので、Count拡張メソッドを使って要素数を取得するのは間違っていない。また、Count拡張メソッドの実装は、対象がICollectionを実装していることを検知して、実際に列挙することなく要素数を得る事が出来るので、効率も良い。

しかし、列挙子が配列ではない、任意の列挙子であった場合、しかもICollectionを実装していない場合は、一旦列挙を行って要素数を数え上げる。要素数を取得するだけならこれでも良いが、その直後、LINQクエリ内のElementAtで、また列挙を開始するため、二重に列挙を行っていることになる。

コストについてピンと来ないかもしれないが、仮に列挙子がデータベースクエリを抽象化していると、データベースに対して2回のクエリ実行を行うことになる。ほとんどの場合において、これは許容出来ない(LINQ to SQLやLINQ to Entitiesでこのような事をしないように)。

では、どうやって改善するか。

public static IEnumerable<Tuple<int, T>> Indexing<T>(this IEnumerable<T> enumerable)
{
    int index = 0;
    return from value in enumerable select Tuple.Create(index++, value);
}

またしても、ヘルパー拡張メソッドだ。これを使えば、タプルクラスで作った「インデックス詰め合わせ」に変換できる。

IEnumerable<int> data = new int[] { 123, 456, 789 };
List<int> results =
    (from entry in data.Indexing()
     select entry.Item2 * (entry.Item1 + 1)).
     ToList();

危ういロジック記述に頼ることなく、インデックスを保持する外部変数に頼ることなく、2回列挙することなく、クエリを簡便に記述可能になった。しかも、Indexingメソッドの実装もまたLINQクエリなので、返された列挙子は遅延実行される。つまり、パイプライン実行が可能ということだ。


Windows Formsで、コントロールの階層を親方向に辿りたいと思ったことがあるかも知れない。

List<Control> list = new List<Control>();
Control current = innerMostControl;
while (current != null)
{
    list.Add(current);  // 実際にはリストに溜めるのではなく、ロジックを書いちゃったりするよね
    current = current.Parent;
}

この「while」を止めたいのだ。LINQに慣れてくると、「ループ終了条件」が正しいかどうかを、演算式だけで担保したくなる。whileやforやifによる制御構文があると、ロジックによる分岐が一々大丈夫かどうか確認するのが煩わしいし、ミスも生じやすくなる。「インデックスって0からだっけ、1だっけ?最後は”<"か"<="か?」とか、ifでこっちに行って、elseであっちに行ってとか、非常にうっとおしい。思考の邪魔だ。

このようなループ制御を行っている場合は、うまく考えればクエリ化出来る。もうかなりのLINQクエリを書いているが、どうしてもループ制御構文を使わなければ実現できなかったコードは、ほんの僅かだった。
この親コントロールをたどるwhileループは、要するに親までの一方向リンクをリスト化するということだ。やってみよう。

public static IEnumerable<T> TraverseLink<T>(this T begin, Func<T, T> predict)
{
    T current = begin;
    while (current != null)
    {
        yield return current;
        current = predict(current);
    }
}

このyield構文は、前回のyield構文と書き方は同じだが、メソッドが直接IEnumerableを返してしまう点で、更に強力だ。と、同時に、使い慣れていないと、メソッドが返却しているモノが一体何であるのか、混乱するかもしれない。

さて、この武器を使えば、リンクリストの追跡など、安全でたやすく、シンプルだ。

List<Control> list = innerMostControl.TraverseLink(delegate(control) { return control.Parent; }).ToList();

ええ、と? あ、一行か :-)

もちろん、型がControlである必要はない。リンク先がParentである必要もない。これらを担保するのは、ジェネリック引数とFuncデリゲートだ。つまり、任意のリンクリストをこのTraverseLink拡張メソッドでたどる事が出来る。

Outlook 2010 から Outlook.com への移行 (3)

あれから、個人・会社のメールをすべてOutlook.comに移行した。と入っても、ドメイン3個分だが。
自分のメールアドレスをそのまま使えるのは、大変便利だ。

ところで、移行後に新規にメールアドレスを増やしたくなったらどうすれば良いのだろうか?
前の記事でも書いたが、現在のOutlook.comの新規アカウント作成画面では、自分のメールアドレスを指定出来ない(ほにゃらら@outlook.comにしか出来ない)。
以前は自由に指定できた気がしたのだが… と思っていたら、Outlook.comから新規アカウント作成を行うとこうなるようだ。以下のリンクから登録すれば、自分のメールアドレスで登録出来る。

新規登録 – マイクロソフトアカウント

しかし、だ。この登録画面から登録を行った場合、メールアドレスの有効性を確認するために、メールアドレスに対して確認メールが送信される。通常の使い方であれば、これは当然の処理であり、送られてきたメールのリンクをクリックして認証が完了する。

だが、Outlook.comで独自ドメインをハンドリングしていると、これで「鶏と卵」が出来上がる。これからメールボックスを作ろうとしているのに、そのメールアドレスに対して確認のメールが送られてしまう(当然、読めない)。だから、事前にすべてのメールアカウントを移行しておく必要がある、と書いた。

実は、前回のWindows Liveアドミンセンターの管理画面に、新しくメールアドレスを追加する画面がある(これは、独自ドメイン委譲の手続きが完了するまで表示されない)。

つまり、独自ドメインをOutlook.comに移行した後は、この画面からアカウントを追加すればいい。簡単だ!
管理アカウントに紐づけられたユーザーだけが、アカウントの追加・削除を行えるので、管理者にも優しい。

判っていれば、「何をいまさら」なネタではあるし、移行開始前までは、どこかにこのような管理画面があることを期待していたのだが、現在のOutlook.comとWindows Liveのシステム統合が(特にユーザーインターフェイスが)、中途半端で非常にわかりにくいために、まったく気が付かなかった。時間の経過とともに、ここも改善されていくだろう。


ところで、元のネタはOutlook 2010からの移行なので、その件をメモっておく。

Outlook ExpressなどからOutlook.comへの移行には、専用の移行ツールがあるが、Outlook 2010からの移行ツールは無い。では、どのようにすれば良いかだが、Outlook 2010にはHotmail connectorというアドインが存在する。

Microsoft Office Outlook Hotmail Connector の概要

これをインストールしてサインインすると、Outlook.comのメールボックスを直接Outlook 2010内にマウント出来る。

Hotmail専用ではないのか?と思うが、実はOutlook.comのバックエンドはHotmailそのままか、あるいはインターフェイスが同じであるようだ。実際、HotmailアカウントをそのままOutlook.comに移行できるし(最近は、「Outlook.comを試す」という表示が出る)、Outlook.comのアカウントをHotmailにダウングレード(?)も出来る。

さらに、iPhoneからHotmailコネクタで直接Outlook.comのアカウントに接続できることも確認した。どうもプッシュ通信にも対応しているようで、メール着信が瞬時に通知される。これは良い。

Outlook 2010に話を戻すと、これでOutlook.comのメールボックスと、従来のメールが行き来できるようになったので、後は従来のメールのうち、必要なものをOutlook.comのメールボックスにドラッグ&ドロップで移すだけ。
(忘れず、溜りに溜まった迷惑メールも移行する。SPAM学習ネタになるだろうか?)

ただし、操作は簡単だが、移動には死ぬほど時間がかかる(結局一日がかりだった orz)。あまりに多いメールを一度に移動しようとすると刺さる、など、やや不安定な部分もあるので注意。この際、必要なメールだけ移動して、後はPSTファイルでバックアップするか削除するなど、整理しても良いのではないだろうか。

さて、これでとうとうメールサーバのNetBSDを止める時が来たようだ。Windows 8への準備はまだ一つ大きな壁(移動ユーザープロファイルの廃止)があるのだが、一つ重荷が減った思いだ。