LoginSignup
5
4

More than 1 year has passed since last update.

複数のIEnumerable<T>型をまとめて廻す【Zip】

Last updated at Posted at 2021-12-21

アドベントカレンダーに書くほどの内容でもないですが、空いていたので書いてみます。
実は初アドベントカレンダーです。
お手柔らかに。

複数のIEnumerable型をまとめて廻す

IEnumerable及びその派生型がいくつかあって、それらの数が一緒だとわかっている時同時にまわしたいと思いますよね。


// こういうことをしたい(が、こんな書き方はできない)
IEnumerable<Person> person = xxx;
IEnumerable<Creature> creature = yyy;
foreach((x, y) in (person, creature))
{
	Console.writeLine($"{x} vs {y} の対決!");
}

// ネストさせてもダメ。x^2対決するハメになる
foreach(var x in person)
{
	foreach(var y in creature)
	Console.writeLine($"{x} vs {y} の対決!");
}

Enumerable.Zip()

そこでEnumerable.Zip()を使う
Zipを使うと2つのIEnumerable型をそれぞれイテレートしてタプルで返してくれる。
オーバーロードでイテレートした値をFuncで返すこともできる。

/// とりあえずクラス準備(Personクラス、CreatureクラスのIEnumerable<T>を廻したりする)
// 人間
public class Person
{
    public string Name {get;}
    public string Skill { get; }
    public Person(string x)
    {
        Name = $"{x}";
        Skill = $"{x}の放つ必殺技";
    }
}

// バケモノ
public class Creature
{
        public string Species { get; }
        public string Item { get; }
        public Creature(string x)
        {
            Species = x;
            Item = $"{x}のツメ";
        }
}

// 調合屋さん
public class Shop
{
    public string MixSkillAndItem(string skill, string item, string name)
    {
        return $"{skill}で焼いた${item} ~${name}は見ているだけ~";
    }
}

上のクラスをEnumerable.Zipで結合して廻します。

public class Quest
{
    // ヒーローとヴィランの激アツトーナメント作成!
    // 同じ型のIEnumerabl<T> をそれぞれ対のモノとしてイテレーション
    public void MakeTournament()
    {
        var villains = Enumerable.Range(0, 10).Select(x=>new Person($"{x}番目に強いのヴィラン")).ToList();
        var heroes = Enumerable.Range(10, 20).Select(x=>new Person($"{x}番目に強いのヒーロー")).ToList();

        villains.Zip(heroes, (vi, he)=>(vi, he))
            .Select(x => $"{x.vi.Name} vs {x.he.Name}") // N番目のheroとN番目のヴィランが対決!
            .ToList()
            .ForEach(Console.WriteLine);

    }
	
    // ヒーローがクリーチャーを狩ってショップに売る
    // 違う型のIEumerable<T>もまとめて一度にイテレーション
    public void Battle()
    {
        var heroes = Enumerable.Range(10, 20).Select(x => new Person($"{x}番目に強いのヒーロー")).ToList();
        var goblins = Enumerable.Range(0, 10).Select(x => new Creature($"{x}番目に強いのゴブリン")).ToList();
        var gargoyle = Enumerable.Range(0, 10).Select(x => new Creature($"{x}番目に強いのガーゴイル")).ToList();

        heroes
        	.Zip(goblins, (hero, goblin) => (hero, goblin))
        	.Zip(gargoyle, (x, gargoyle) => (x.hero, x.goblin, gargoyle))
        	.Select(universe =>
        		new Shop().MixSkillAndItem(
        			universe.hero.Skill,
        			universe.gargoyle.Item,
        			universe.goblin.Species)) // N番目ヒーローSkill、N番目ガーゴイルのItem、N番目ゴブリンは見ているだけ。
        	.ToList()
        	.ForEach(Console.WriteLine); 
    }
}

結果は・・・

new Quest().MakeTournament()

image.png
対戦カードが決まったぜ!

new Quest().Battle()

image.png
ユーザビリティの低いアイテムを手に入れたぜ!

ちゃんちゃん

おまけ

ちなみにZip()するコレクションの長さが違った場合はどうなるのか。
インデックスで指定するとIndexOoutOfRangeExceptionを投げるけど、First()やLast()でとると例外にならない。
遅延実行だからなのかな?

これは結合するIEnumerable<T>型の短い方の長さまでしか処理をしないためでした。
今回の場合、要素数がevenは3つ、oddは2つなのでZipで処理される要素数は2つになります。
よってインデックス2を指定してもIndexOutOfRangeException例外が投げられてしまうわけです。

@skitoy4321 様コメントありがとうございました!

var even = new List<int>{2,4,6};
var odd = new List<int>{1,3};

var num = even.Zip(odd).Select(x => (x.First, x.Second)); // Select()で廻しただけではエラーにならない
var first = num.First(); 
var last = num.Last();    // First(), Last()でもエラーにならない
var assign = num.ToArray()[2];  // インデックスを指定するとエラー(IndexOutOfRangeException)になる

5
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4