徳島ゲーム開発ごっこ 技術ブログ

ゲームを作るために役に立ったり立たなかったりする技術を学んでいきます!

【C#,LINQ】Repeat~同じ値の要素がいっぱい入ったシーケンスを作りたいとき~

 C#のLINQの関数であるRepeat()の使い方についてです。
 指定した型の要素を、指定した数だけ入ったシーケンスを作成することが出来ます。
f:id:urahimono:20180617130013p:plain


この記事には.NET Framework 4.6.1を使用しています。

欲しい要素が欲しい数だけ詰まったシーケンスを作るよ

 バイバインというものをご存知でしょうか。
 バイバインは一滴たらすとその物体を5分ごとに倍に分裂させる薬品です。
 かつて、野比のび太氏が所持している栗まんじゅうを増殖させるために使用し、宇宙レベルの問題を引き起こしたことで有名な薬品です。

ドラえもんのひみつ道具 (はあ-はと) - Wikipedia

 さて、コード上でも特定の要素がもっと欲しい! 増殖させたい! という願望が皆さんにもあるのではないでしょうか。
 コード上にバイバインはありませんが、LINQRepeat()を使うことにより、この願望を叶えられるかもしれません。

 Repeat()は、指定した型の要素指定した数だけあるシーケンスを作ることが出来ます。

public static IEnumerable<TResult> Repeat<TResult>( TResult element, int count );
Enumerable.Repeat(TResult) メソッド (TResult, Int32) (System.Linq)

Program.cs

using System.Linq;
using System.Collections;
using System.Collections.Generic;

public static class Program
{
    static void Main( string[] args )
    {
        // 99の値を5個作っちゃう!
        IEnumerable<int> intSequenceA   = Enumerable.Repeat( 99, 5 );
        //  4の値を3個作っちゃう!
        IEnumerable<int> intSequenceB   = Enumerable.Repeat(  4, 3 );
        // 13の値を6個作っちゃう!
        IEnumerable<int> intSequenceC   = Enumerable.Repeat( 13, 6 );

        // 配列とリストの結果も見たいので、それぞれ変換。
        int[]       intArrayB   = intSequenceB.ToArray();
        List<int>   intListC    = intSequenceC.ToList();

        // 結果発表
        System.Console.WriteLine( "intSequenceA:{0}", intSequenceA.Text() );
        System.Console.WriteLine( "intSequenceB:{0}", intArrayB.Text() );
        System.Console.WriteLine( "intSequenceC:{0}", intListC.Text() );

        // 入力待ち用
        System.Console.ReadKey();
    }

    /// <summary>
    /// 簡易的なシーケンスのテキスト取得処理
    /// </summary>
    public static string Text<TSource>( this IEnumerable<TSource> i_source )
    {
        string text = string.Empty;
        foreach( var value in i_source )
        {
            text += string.Format( "[{0}], ", value );
        }
        return text;
    }

} // class Program

intSequenceA:[99], [99], [99], [99], [99],
intSequenceB:[4], [4], [4],
intSequenceC:[13], [13], [13], [13], [13], [13],

 今回はint型を用いましたが、Repeat()に入れる型はどんな型でも大丈夫です。

 ちなみにRepeat()で作成する要素の数を指定する箇所に、0を入れても問題なく動作しますが、負数を入れると例外が発生するので注意が必要です。

using System.Linq;
using System.Collections;
using System.Collections.Generic;

public static class Program
{
    static void Main( string[] args )
    {
        IEnumerable<int> intSequence = null;
        try
        {
            // 10の値を0個作っちゃう!
            intSequence = Enumerable.Repeat( 10, 0 );
        }
        catch( System.Exception i_exception )
        {
            System.Console.WriteLine( "10の値を0個作る:{0}", i_exception );
        }
        finally
        {
            if( intSequence != null )
            {
                System.Console.WriteLine( "10の値を0個作る:{0}", intSequence.Text() );
            }
            
        }

        intSequence = null;

        try
        {
            // 10の値を-1個作っちゃう!
            intSequence = Enumerable.Repeat( 10, -1 );
        }
        catch( System.Exception i_exception )
        {
            System.Console.WriteLine( "10の値を-1個作る:{0}", i_exception );
        }
        finally
        {
            if( intSequence != null )
            {
                System.Console.WriteLine( "10の値を-1個作る:{0}", intSequence.Text() );
            }

        }

        // 入力待ち用
        System.Console.ReadKey();
    }

    /// <summary>
    /// 簡易的なシーケンスのテキスト取得処理
    /// </summary>
    public static string Text<TSource>( this IEnumerable<TSource> i_source )
    {
        string text = string.Empty;
        foreach( var value in i_source )
        {
            text += string.Format( "[{0}], ", value );
        }
        return text;
    }

} // class Program

10の値を0個作る:
10の値を-1個作る:System.ArgumentOutOfRangeException: 指定された引数は、有効な値 の範囲内にありません。

参照型で使うときには気をつけて

 先ほど、「Repeat()に入れる型はどんな型でも大丈夫です」と言いましたが、参照型の値を指定する場合には注意が必要です。

 以下のコードではParameterというクラスをRepeat()を使って複数個入ったシーケンスを作成し、その先頭のParameterのデータのみ値を書き換えるという処理を行っています。

Program.cs

using System.Linq;
using System.Collections;
using System.Collections.Generic;

public static class Program
{
    private class Parameter
    {
        public int      ID      { get; set; }
        public string   Name    { get; set; }

        public override string ToString()
        {
            return string.Format( "ID:{0}, Name:{1}", ID, Name );
        }
    }

    static void Main( string[] args )
    {
        // 征史郎のデータを4個作っちゃう!
        IEnumerable<Parameter> parameterSequence    = Enumerable.Repeat( new Parameter() { Name = "征史郎" }, 4 );

        // 配列に変換し、最初のデータだけ名前を"正一郎"に変えてみたよ。
        Parameter[] parameterArray  = parameterSequence.ToArray();
        parameterArray[ 0 ].Name    = "正一郎";

        // 結果発表
        foreach( Parameter param in parameterArray )
        {
            System.Console.WriteLine( "結果:{0}", param.ToString() );
        }
        // 入力待ち用
        System.Console.ReadKey();
    }

} // class Program

結果:ID:0, Name:正一郎
結果:ID:0, Name:正一郎
結果:ID:0, Name:正一郎
結果:ID:0, Name:正一郎

 結果をみれば分かるとおり、先頭の要素しか書き換えていないのに、全ての要素の値が変更されてしまっています。
 参照型の場合はRepeat()を使って複製しても、参照先は同じところを指しているため、一つの要素の値を書き換えられてしまうのです。

 以下のリンクでも、この点で悪戦苦闘されています。
Enumerable.Repeat()でハマった話

 これはきっと、バイバインの効力により宇宙で増え続ける栗まんじゅうの呪いなのかもしれませんね。
 この点に注意は必要ですがRepeat()はなかなか便利なため、是非とも七兆回ぐらい使って見てください。

LINQのリンク