CodeZine(コードジン)

特集ページ一覧

ラムダ式でステップアップ! C#のプログラムから汎用的なアルゴリズムを切り出すことで、LINQについての理解を深めよう

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2014/10/14 14:00

目次

4. 汎用的なアルゴリズムの分離

 さて、前述のプログラムをもう一度よく眺めてみましょう。互いに独立な2つのメソッドをMainメソッドから分離できたように見えます。

 ですが、この2つのメソッドは、次のようなものに依存しているので、汎用的なアルゴリズムを分離したとは言い難いのです。

  1. コレクションの要素の型に依存している
  2. コレクションの構造(リストか配列かなど)に依存している
  3. 抽出条件が何であるかに依存している
  4. それぞれの要素をどうするかに依存している

 これでは、この2つのメソッドは、この文脈でしか使うことができません。すなわち、「抽出(Filter)」は、書籍のリストの中から、「書籍のタイトルまたは出版社の名前に "リ" を含むもの」を抽出するのにしか使えませんし、「表示(Show)」は、書籍リストのそれぞれの書籍を表示するのにしか使うことができません。

 もっと汎用的なアルゴリズムを切り出すことはできないでしょうか? もっと汎用的に抽出のアルゴリズムや繰り返し処理のアルゴリズムは書けないものでしょうか?

 もし仮に、C#でそれができないとすると、C#には汎用的なアルゴリズムの記述能力がないことになりますが、もちろんそんなことはありません。

 これらの依存をメソッドから分離して、汎用的なアルゴリズムにするには、例えば次のようにすれば良いのです。

依存の内容とそれに対する戦略
番号 依存の内容 戦略
1 コレクションの要素の型に依存している ジェネリックプログラミング
2 コレクションの構造(リストか配列かなど)に依存している ジェネリックプログラミングイテレーターパターン(C#ではIEnumerable<T>の利用)
3 抽出条件が何であるかに依存している 抽出条件をメソッドで表し、それをdelegateとして渡す
4 それぞれの要素をどうするかに依存している どうするかをメソッドで表し、それをdelegateとして渡す

 これらを使って、2つのメソッドを次のような汎用的なアルゴリズムにしてみましょう。

2つのメソッドのbefore/after
before(さまざまなものに依存した処理) after(汎用的なアルゴリズム)
書籍のリストの中から、「書籍のタイトルまたは出版社の名前に "リ" を含むもの」を抽出する コレクションから或る条件に合致した要素だけを抽出する
書籍リストのそれぞれの書籍を表示する コレクションのそれぞれの要素に対して特定のことをする

 実際にやってみると、例えば次のようになります。

「コレクションから或る条件に合致した要素だけを抽出する」Filterメソッドと、「コレクションのそれぞれの要素に対して特定のことをする」ForEachメソッド

MyCollection.cs
using System;
using System.Collections.Generic;

namespace MyCollection
{
    public static class Enumerable
    {
        // コレクションから或る条件に合致した要素だけを抽出する
        public static IEnumerable<T> Filter<T>(
            this IEnumerable<T> collection, // コレクション
            Func<T, bool>       isMatch     // 抽出条件
        )
        {
            foreach (var item in collection) {
                if (isMatch(item))
                    yield return item;
            }
        }

        // コレクションのそれぞれの要素に対して特定のことをする
        public static void ForEach<T>(
            this IEnumerable<T> collection, // コレクション
            Action<T>           action      // それぞれの要素に行うこと
        )
        {
            foreach (var item in collection)
                action(item);
        }
    }
}

関心事の分離

 これらのメソッドは、ほぼそのアルゴリズムだけを記述しています。アルゴリズムで必要な部分だけを記述し、アルゴリズムではない部分をあまり記述していません。

 そのアルゴリズムという関心事を高凝集に分離したことになります。

 また、他への依存も少ないです。要素の型やコレクションの種類、抽出条件や各要素に対してやることを、それほど選びません。

 そのため、これらのアルゴリズムをさまざまなプログラムから利用できるはずです。

5. 汎用的なアルゴリズムの試用

 では、これらを元のプログラムで使ってみる前に、これらのC#のメソッドとして書かれたアルゴリズムが本当に汎用的に使えるかどうか試してみましょう。

intのListの中から偶数だけをコンソールに表示する

 まず、intのListの中から偶数を抽出し、コンソールに表示してみます。

要素の型 int
コレクション List
抽出条件 IsEven(偶数であること)
各要素に対してやること Show(コンソールに表示)
Program.cs
using MyCollection;
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var collection = new List<int>  { 1, 4, 9, 16, 25 }; // int の List

        // 汎用的なアルゴリズムを利用
        collection.Filter (IsEven)
                  .ForEach(Show  ); // 抽出されたそれぞれの int を表示
    }

    static bool IsEven(int number)
    { return number % 2 == 0; }

    static void Show<T>(T item)
    { Console.WriteLine(item); }
}

 実行結果は、次のとおりです。うまくいきました。

実行結果
4
16

doubleの配列の中から正数だけを表示する

 コレクションの構造や要素の型、抽出条件を変えてみましょう。

要素の型 double
コレクション 配列
抽出条件 IsPositive(正数であること)
各要素に対してやること Show(コンソールに表示)
Program.cs
using MyCollection;
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // double の配列
        var collection = new List<double> {
            -3.0, 0.1, -0.04, 0.001, -0.0005, 0.00009
        };

        // 汎用的なアルゴリズムを利用
        collection.Filter (IsPositive)
                  .ForEach(Show      ); // 抽出されたそれぞれの double を表示
    }

    static bool IsPositive(double number)
    { return number > 0; }

    static void Show<T>(T item)
    { Console.WriteLine(item); }
}

 今度の実行結果は、次のとおりです。こちらもうまくいくようです。

実行結果
0.1
0.001
9e-005

 ここまでは、汎用的なアルゴリズムとして利用できているように見えます。


  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • 小島 富治雄(フジヲ)

    Microsoft MVP for C# (2005.07~)。 - 注目の MVP - Blog: プログラミング C# - 翔ソフトウェア (Sho's) - Web Site: 翔ソフトウェア (Sho's) - Twitter: Fujiwo...

あなたにオススメ

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5