LoginSignup
13
7

More than 5 years have passed since last update.

2次元配列のようなデータの縦横の入れ替え

Last updated at Posted at 2017-02-13

目的

行列で言うところの転置をやります。
@IganinTea さんのエントリー
LINQで入れ子になっているListの並びの縦横を入れ替える
中の@yuba さんのコメントを参考に書けたので投稿します。

やること

これを

0 1 2
0 a b c
1 d e f
2 g h i

↓みたいにする

0 1 2
0 a d g
1 b e h
2 c f i

結果

まずは結果、後で過程を示す。
こんな感じでAggregateなしでもいけました。

public IEnumerable<IEnumerable<T>> Do<T>(IEnumerable<IEnumerable<T>> values){
    return Enumerable.Range(0, values.Max(c => c.Count()))
        .Select(i => values.Select(c => i < c.Count() ? c.ElementAt(i) : default(T)));
}

元のソース

@IganinTea さんのエントリー
LINQで入れ子になっているListの並びの縦横を入れ替える
中の@yuba さんのコメント欄のやつ

var resultList = targetList.Aggregate(
    Enumerable.Range(0, targetList.Max(row => row.Count))
        .Select(i => new List<string>())
    ,
    (ll, row) => ll.Select((l, i) => { l.Add(row.Count <= i ? "" : row[i]); return l; })
    )
    .ToList();
    }

1段階目

とりあえず真似る.
見通しが良くなるように3項演算子を外して、中身を改行する。

public IEnumerable<IEnumerable<T>> Do<T>(IEnumerable<IEnumerable<T>> values){ 
    return values.Aggregate(
    Enumerable.Range(0, values.Max(v => v.Count())).Select(i => new List<T>()),
    (ll, row) => ll.Select((l, i) =>
    {
        l.Add(row.ElementAt(i));
        return l;
    }));
}

2段階目

columnを事前に求めてみる

public IEnumerable<IEnumerable<T>> Do<T>(IEnumerable<IEnumerable<T>> values){
 var column = values.Max(v => v.Count());
 return values.Aggregate(
     Enumerable.Range(0, column).Select(i => new List<T>()),
     (ll, row) => ll.Select((l, i) =>
     {
         l.Add(row.ElementAt(i));
         return l;
     }));
}

3段階目

Aggregateの中身がColumnのListを作っていることと理解

public IEnumerable<IEnumerable<T>> Do<T>(IEnumerable<IEnumerable<T>> values){
 var column = values.Max(v => v.Count());
 return Enumerable.Range(0, column).Select((v, i) =>
     {
         var list = new List<T>();
         foreach (var item in values)
         {
             list.Add(item.ElementAt(i));
         }
         return list;
     });
}

4段階目

foreachをselect文で書き換え

public IEnumerable<IEnumerable<T>> Do<T>(IEnumerable<IEnumerable<T>> values){
    var column = values.Max(v => v.Count());
    return Enumerable.Range(0, column).Select((v, i) =>
    {
        return values.Select(v2 => v2.ElementAt(i));
    });
}

5段階目

中のreturnを消す。
大体完成。

public IEnumerable<IEnumerable<T>> Do<T>(IEnumerable<IEnumerable<T>> values){
    var column = values.Max(v => v.Count());
    return Enumerable.Range(0, column).Select(i => values.Select(v => v.ElementAt(i)));
}

6段階目

さっきまでのはジャグ配列だと死ぬから修正。
3項演算子復活

public IEnumerable<IEnumerable<T>> Do<T>(IEnumerable<IEnumerable<T>> values){
    var column = values.Max(v => v.Count());
    return Enumerable.Range(0, column)
    .Select(i => values.Select(v => i < v.Count() ? v.ElementAt(i) : default(T)));
}

7段階目

columnの計算を戻して1つの式内で完結させる
以上の手順で書換。
Aggregateは決まるとすごいけど解読が難しい。

public IEnumerable<IEnumerable<T>> Do<T>(IEnumerable<IEnumerable<T>> values){
    return Enumerable.Range(0, values.Max(c => c.Count()))
     .Select(i => values.Select(c => i < c.Count() ? c.ElementAt(i) : default(T)));
}

ちょっと改良 T[],Listを受けてみる

配列とリスト版を書いた。
理由はなんとなくElementAtがないやつを見たかったから。

public IEnumerable<IEnumerable<T>> Do<T>(IEnumerable<T[]> values)
{
    return Enumerable.Range(0, values.Max(c => c.Length)).Select(i => values.Select(c => i < c.Length ? c[i] : default(T)));
}

public IEnumerable<IEnumerable<T>> Do<T>(IEnumerable<List<T>> values)
{
    return Enumerable.Range(0, values.Max(c => c.Count)).Select(i => values.Select(c => i < c.Count ? c[i] : default(T)));
}

よくよく定義を見てみたらArrayもListもIListを継承しているからまとめられた

public IEnumerable<IEnumerable<T>> Do<T>(IEnumerable<IList<T>> values)
{
    return Enumerable.Range(0, values.Max(c => c.Count))
    .Select(i => values.Select(c => i < c.Count ? c[i] : default(T)));
}

さらに改良 空き場所にdefault(T)以外を入れてみる。

参照型を渡すと毎回参照がコピーされて1つ変更されると全部変わるからそれはできないようにする。
1つが値型に制限する。
もう1つはデリゲートで毎回作るようにして参照のコピーを作らないようにできるようにする。
var tValues = Do(values,()=>new Class());

↓みたいに参照の値を渡したら意味はないけど。
var c = new Class();
var tValues = Do(values,()=>c);

まずはIEnumerableを受けるやつ。

//参照型だと後で変更したときにほかの部分も変わるから値型のみ受け付ける。
public IEnumerable<IEnumerable<T>> Do<T>(IEnumerable<IEnumerable<T>> values, T blankValue)
    where T : struct
{
    return Enumerable.Range(0, values.Max(c => c.Count()))
    .Select(i => values.Select(c => i < c.Count() ? c.ElementAt(i) : blankValue));
}

//毎回newとかさせれば上記の問題が解消できるからFuncデリゲート入れてラムダ式とかでいけるようにする。
public IEnumerable<IEnumerable<T>> Do<T>(IEnumerable<IEnumerable<T>> values, Func<T> blankValue)
{
    return Enumerable.Range(0, values.Max(c => c.Count()))
    .Select(i => values.Select(c => i < c.Count() ? c.ElementAt(i) : blankValue()));
}
//使い方例
var tValues = Do(values, () => new T());

同じ感じでIListで受けるやつ

public IEnumerable<IEnumerable<T>> Do<T>(IEnumerable<IList<T>> values, T blankValue)
    where T : struct
{
    return Enumerable.Range(0, values.Max(c => c.Count))
    .Select(i => values.Select(c => i < c.Count ? c[i] : blankValue));
}

public IEnumerable<IEnumerable<T>> Do<T>(IEnumerable<IList<T>> values, Func<T> blankValue)
{
    return Enumerable.Range(0, values.Max(c => c.Count))
    .Select(i => values.Select(c => i < c.Count ? c[i] : blankValue()));
}

速度的なあれは調べていません。
気が向いたら考えます。

13
7
0

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
13
7