DOBON.NET DOBON.NETプログラミング掲示板過去ログ

イテレータについて

環境/言語:[VisualStudio2005, .Net Framework 2.0]
分類:[.NET]

こんにちは。
雲です。

今までは、C++/ISO を使っていたのですが、最近、C# が気になってきたので、
少し、C# のお勉強をしています。

C# は、ポインタが見えなくなって安全性は高まっているのだと思いますが、
何となく、隔靴掻痒のところがあります。

で、今回は、凄く基本的なことなのですが、配列の要素を n倍する時に
どのように記述するのがC#的なのかな?と思って書いています。

もちろん、単純に、

int[] a = { 1, 2, 3 };
for( int i = 0; i < a.length; i++ ){
a[i] *= 10;
}

としても良いのですが、読み込みのみの時には、

foreach( int b in a ){
WriteLine( b );
}

などのように記述できるので、書き込みの時のみインデックスを
使うというのが、何となく、すっきりしません。

皆さんは、こんな時には、どのようにされますでしょうか?
> 皆さんは、こんな時には、どのようにされますでしょうか?

表面的なことなので慣れました。
> int[] a = { 1, 2, 3 };
> for( int i = 0; i < a.length; i++ ){
> a[i] *= 10;
> }
>
> としても良いのですが、読み込みのみの時には、
>
> foreach( int b in a ){
> WriteLine( b );
> }
>
> などのように記述できるので、書き込みの時のみインデックスを
> 使うというのが、何となく、すっきりしません。

つまりなぜ以下のように書けないか?ということでしょうか?

foreach( int b in a ){
b *= 10;
}

上のソースコードはコンパイルエラーになりますが、ソースの意味を解釈すると
以下のようになりますから、意味を持たないことがわかると思います。

b = a[i]; // 配列aのある要素を列挙しbに代入→foreach
b *= 10; // 変数bの値を10倍してbに代入

a[i]それぞれの値は変化しませんね。
要素そのものを置き換えるような変更はできないからです。

#構文としては、書き換え可能な仕様にすることもできたかもしれませんが、
#ま、そうはなっていないということで、そういうもんです。
雲です。

> 表面的なことなので慣れました。

あれれ。そうでしたか。

自分は数値計算をすることが多いので、例えば、
配列の各要素の自乗を求めたいときがよくあります。
そんなときに、もしかするとサクッと書く書き方が
あるのかと思っていましたが、そうではないのですね。

まぁ、数値計算に foreach を使おうというのがそもそも
無謀だったのかも。。。

とりあえず、インデックスを使って処理をする方向で
行きます。いろいろと、ご回答ありがとうございました。
> 自分は数値計算をすることが多いので、例えば、
> 配列の各要素の自乗を求めたいときがよくあります。
> そんなときに、もしかするとサクッと書く書き方が
> あるのかと思っていましたが、そうではないのですね。

foreach は配列だけを相手にするものではないですからね。

どこにも解説が無いので想像ですが、どんなタイプのコレクションに対しても安定した列挙を行うことを目的としているため、foreach は読み取り専用とされているのではないかと思います。(値を変更すると順序が崩れるコレクションもあることでしょう)

それと、個人的には、「サクッ」とさをもとめるなら演算の度に手でループを書くような方向性の方が違うような気がします。
雲です。

>個人的には、「サクッ」とさをもとめるなら演算の度に手でループを書くような方向性の方が違うような気がします。

う〜ん。そうかも。
ただ、結局、C# のイテレータが読み込み専用ということがネックとなって
しまうため、配列やlistやstackなどのコレクションを統一的に扱って
各要素の自乗を求めることが出来る機能を持った関数(クラス)の設計が
(今の自分の実力では)難しいのが問題だと感じます (^^)。

ま、もうちょっと勉強しないと(←僕)。
2006/08/19(Sat) 12:13:01 編集(投稿者)

■No17176に返信(雲さんの記事)
> 自分は数値計算をすることが多いので、例えば、
> 配列の各要素の自乗を求めたいときがよくあります。
> そんなときに、もしかするとサクッと書く書き方が
> あるのかと思っていましたが、そうではないのですね。

そういうのは関数型言語向きでしょうね.

私が C# で書く場合は,パフォーマンス重視のときは配列+インデックスを使い,
パフォーマンスがそれ程必要でない箇所で凝った書き方をする場合はイテレータ
(IEnumerable<T>) を使います.

後者について,IEnumerable<T> や foreach との相性を考えると,
いわゆるパイプラインパターンを使うのが主流です.

そもそもコンテナを作らないこともしばしばあります.
例えば,{1, 2, 3} という配列を作らなくても,あたかもそのような配列が
あるかのように振る舞わせることは可能です.

C# のイテレータ構文を利用して,次のようなメソッドを作成します.
# レイアウトの都合上,全角スペースが入っているので注意してください.

using System;
using System.Collections.Generic;
using System.Text;

public static class Numeric
{
  public static IEnumerable<int> Sequence(int start, int end)
  {
    for (int i = start; i <= end; i += 1)
    {
      yield return i;
    }
  }
  public static IEnumerable<T> Transform<T>(IEnumerable<int> source,
    Converter<int,T> convert)
  {
    foreach (int i in source)
    {
      yield return convert(i);
    }
  }
}

こうすると,そもそも「配列を書き換える」という発想ではなくて,
データの流れを動的に生成し,それをどのように変換していくか,
という発想でプログラミングを行うことができます.

public static class Program
{
  static void Main(string[] args)
  {
    // 1, 2, 3 と表示される
    foreach (int i in Numeric.Sequence(1, 3))
    {
      Console.WriteLine(i);
    }

    // 10, 20, 30 と表示される
    foreach (int i in
      Numeric.Transform<int>(Numeric.Sequence(1, 3),
        delegate(int n){return n * 10;} ) )
    {
      Console.WriteLine(i);
    }

    // Sqrt(1), Sqrt(2), Sqrt(3) が表示される
    foreach (double d in
      Numeric.Transform<double>(Numeric.Sequence(1, 3),
        delegate(int n) { return Math.Sqrt(n); }))
    {
      Console.WriteLine(d);
    }

    // 明日、明後日、明明後日の日付が表示される
    foreach (DateTime day in
      Numeric.Transform<DateTime>(Numeric.Sequence(1, 3),
        delegate(int n) { return DateTime.Today.AddDays(n); }))
    {
      Console.WriteLine(day);
    }
  }
}
■No17160に返信(雲さんの記事)
> で、今回は、凄く基本的なことなのですが、配列の要素を n倍する時に
> どのように記述するのがC#的なのかな?と思って書いています。

私の場合は,例えばこんな感じに書くことが多いですかね.

# レイアウトの都合上,全角スペースが入っているので注意

using System;
using System.Collections.Generic;
using System.Text;

public static class ArrayUtil
{
  public static void Transform<T>(T[] source,
    Converter<T,T> convert)
  {
    for (int i = 0; i < source.Length; ++i)
    {
      source[i] = convert(source[i]);
    }
  }

  public static void Transform<T>(T[] source,
    Converter<KeyValuePair<int,T>, T> convert)
  {
    for (int i = 0; i < source.Length; ++i)
    {
      source[i] = convert(new KeyValuePair<int,T>(i, source[i]));
    }
  }
}

public static class Program
{
  static void Main(string[] args)
  {
    int[] a = { 1, 2, 3 };

    // 1, 2, 3 と表示される
    Array.ForEach(a, Console.WriteLine);

    // 全ての要素を 10 倍する
    ArrayUtil.Transform(a, delegate(int i) { return i * 10; });

    // 10, 20, 30 と表示される
    Array.ForEach(a, Console.WriteLine);

    // N 番目の要素を N 倍する
    // 0 番目 : 10 * 0 -> 0
    // 1 番目 : 20 * 1 -> 20
    // 2 番目 : 30 * 2 -> 60
    ArrayUtil.Transform(a, delegate(KeyValuePair<int, int> pair)
      { return pair.Key * pair.Value; });

    // 0, 20, 60 と表示される
    Array.ForEach(a, Console.WriteLine);
  }
}
雲です。

>> 自分は数値計算をすることが多いので、例えば、
>> 配列の各要素の自乗を求めたいときがよくあります。
>> そんなときに、もしかするとサクッと書く書き方が
>> あるのかと思っていましたが、そうではないのですね。

>そういうのは関数型言語向きでしょうね.

はい。そう思います。
研究室では、たいてい Fortran ですしね。
(自分は、何故か、C で書いていましたが。。。)

でも、匿名メソッドとArray.ForEach の組み合わせで、
C++ で言うところの関数オブジェクトと同等な処理を
実現できるのにはビックリしました。意外と面白い
というか、楽しいです。
> でも、匿名メソッドとArray.ForEach の組み合わせで、
> C++ で言うところの関数オブジェクトと同等な処理を
> 実現できるのにはビックリしました。意外と面白い
> というか、楽しいです。

ということなら、C#3.0 について調べてみると面白いかもしれません。

DOBON.NET | プログラミング道 | プログラミング掲示板