1

次の形式のデータ構造のフィールドを含む配列があります。

[0] = Record 1 (Name Field)
[1] = Record 1 (ID Field)
[2] = Record 1 (Other Field)
[3] = Record 2 (Name Field)
[4] = Record 2 (ID Field)
[5] = Record 2 (Other Field)

これを次のようにコレクションに処理しています。

for (int i = 0; i < components.Length; i = i + 3)
{
    results.Add(new MyObj
        {
            Name = components[i],
            Id = components[i + 1],
            Other = components[i + 2],
        });
}

これは正常に動作しますが、LINQ で同じ出力を達成する良い方法があるかどうか疑問に思っていましたか? ここに機能要件はありません。それができるかどうかに興味があります。

インデックスによるグループ化をいくつか実験しました(ToList()配列を 'ingした後)。

var groupings = components
    .GroupBy(x => components.IndexOf(x) / 3)
    .Select(g => g.ToArray())
    .Select(a => new
        {
            Name = a[0],
            Id = a[1],
            Other = a[2]
        });

これは機能しますが、私がやろうとしていることには少しやり過ぎだと思います。forループと同じ出力を実現する簡単な方法はありますか?

4

4 に答える 4

2

forループに固執すると思います。ただし、これは Linq で動作するはずです。

List<MyObj> results = components
    .Select((c ,i) => new{ Component = c, Index = i })
    .GroupBy(x => x.Index / 3)
    .Select(g => new MyObj{
        Name = g.First().Component,
        Id = g.ElementAt(1).Component,
        Other = g.Last().Component
    })
    .ToList();
于 2013-02-18T10:55:22.000 に答える
2

Josh Einstein のIEnumerable.Batch拡張機能の完璧な候補のようです。列挙型を特定のサイズのチャンクにスライスし、それらを配列の列挙としてフィードします。

public static IEnumerable<T[]> Batch<T>(this IEnumerable<T> self, int batchSize)

この質問の場合、次のようにします。

var results = 
    from batch in components.Batch(3)
    select new MyObj { Name = batch[0], Id = batch[1], Other = batch[2] };

更新: 2 年が経過し、リンク先の Batch 拡張機能が消えたようです。それは質問に対する答えと考えられていたので、他の誰かがそれを役に立つと思った場合に備えて、私の現在の実装は次のとおりですBatch:

public static partial class EnumExts
{
    /// <summary>Split sequence into blocks of specified size.</summary>
    /// <typeparam name="T">Type of items in sequence</typeparam>
    /// <param name="sequence"><see cref="IEnumerable{T}"/> sequence to split</param>
    /// <param name="batchLength">Number of items per returned array</param>
    /// <returns>Arrays of <paramref name="batchLength"/> items, with last array smaller if sequence count is not a multiple of <paramref name="batchLength"/></returns>
    public static IEnumerable<T[]> Batch<T>(this IEnumerable<T> sequence, int batchLength)
    {
        if (sequence == null)
            throw new ArgumentNullException("sequence");
        if (batchLength < 2)
            throw new ArgumentException("Batch length must be at least 2", "batchLength");

        using (var iter = sequence.GetEnumerator())
        {
            var bfr = new T[batchLength];
            while (true)
            {
                for (int i = 0; i < batchLength; i++)
                {
                    if (!iter.MoveNext())
                    {
                        if (i == 0)
                            yield break;
                        Array.Resize(ref bfr, i);
                        break;
                    }

                    bfr[i] = iter.Current;
                }
                yield return bfr;
                bfr = new T[batchLength];
            }
        }
    }
}

この操作は延期された単一の列挙であり、線形時間で実行されます。Batch結果ごとに新しい配列を割り当てているにもかかわらず、私が見た他のいくつかの実装と比較して比較的高速です。

これは、プロファイリングするまでわからないことを示しています。コードが消えた場合に備えて、常にコードを引用する必要があります.

于 2013-02-18T11:16:51.727 に答える
1

たぶんイテレータが適切かもしれません。

カスタムイテレータを宣言します。

static IEnumerable<Tuple<int, int, int>> ToPartitions(int count)
{
    for (var i = 0; i < count; i += 3)
        yield return new Tuple<int, int, int>(i, i + 1, i + 2);
}

次のLINQを準備します。

var results = from partition in ToPartitions(components.Length)
              select new {Name = components[partition.Item1], Id = components[partition.Item2], Other = components[partition.Item3]};
于 2013-02-18T11:04:20.180 に答える
1

この方法により、コードをより表現力豊かにする方法についてのアイデアが得られる場合があります。

public static IEnumerable<MyObj> AsComponents<T>(this IEnumerable<T> serialized)
    where  T:class
{
    using (var it = serialized.GetEnumerator())
    {
        Func<T> next = () => it.MoveNext() ? it.Current : null;

        var obj = new MyObj
            {
                Name  = next(),
                Id    = next(),
                Other = next()
            };

        if (obj.Name == null)
            yield break;

        yield return obj;
    }
}

現状では、入力の終わりを検出する方法は嫌いですが、これをより適切に行う方法に関するドメイン固有の情報があるかもしれません。

于 2013-02-18T11:13:45.583 に答える