簡體   English   中英

c# 按順序對嵌套列表進行分組

[英]c# Grouping a nested list sequentially

我在 c# 中生成 yaml 如下:

- type: DEFAULT
  primitives:
  - sequenceNo: 1
    from: Environment
    to: uC
    event: register
- type: DEFAULT
  primitives:
  - sequenceNo: 4
    from: uP
    event: start cells
- type: LOOP
  primitives:
  - sequenceNo: 5
    from: uC
    to: uP
    event: keepAlive Question
- type: LOOP
  primitives:
  - sequenceNo: 6
    from: uP
    to: uC
    event: keepAlive Response
- type: DEFAULT
  primitives:
  - sequenceNo: 10
    from: uC
    to: uP
    event: switch off uP

以下是我的課程:

public class Type
{
    public string type { get; set; }
    public List<Primitive> primitives { get; set; }
}

public class Primitive
{
    [YamlIgnore]
    public int sequenceNo { get; set; }
    [YamlIgnore]
    public int startX { get; set; }
    [YamlIgnore]
    public int startY { get; set; }
    public string from { get; set; }
    public string to { get; set; }
    [YamlMember(Alias = "event")]
    public string events { get; set; }
    public string expectedState { get; set; }
}

我想將列表分組如下:

- type: DEFAULT
  primitives:
    - from: environment
      to: uC
      event: register
    - from: uP
      event: start cells
- type: LOOP
  primitives:
    - from: uC
      to: uP
      event: keepAlive Question
    - from: uP
      to: uC
      event: keepAlive Response
- type: DEFAULT
  primitives:
    from: uC
    to: uP
    event: switch off uP

這意味着,我想根據類型按順序對它們進行分組。 如果我能找到相同的組,例如一個接一個的 DEFAULT,我必須將 2 個順序 DEFAULT 的原語分組到一個 DEFAULT 中,依此類推。 我怎樣才能做到這一點。 到目前為止,我已經嘗試使用如下字典:

var res = types.GroupBy(r => r.type)
                  .ToDictionary(t => t.Key, t => t.Select(r => r.primitives).ToList());

但我沒有按順序獲得列表。

每當您有一系列相似的項目,並且您想要創建具有共同點的項目組時,請考慮使用GroupBy的重載之一。

您想要創建具有相同屬性Type值的Primitives組。 我的建議是使用具有參數 resultSelector 的 GroupBy 的重載。

(為了防止對 class Type 和 property Type 的誤解,我使用 originalType 來標識你原來的 Types 集合中的一個元素)

首先,我將創建具有相同屬性 Type 值的original types組。 然后,從這個 Group 中的元素序列(它們是原始類型)中,獲取它們的基元,並將其展平(SelectMany)。 從這里,select 你想要的屬性。

// make groups of Types that have same value of property Type:
IEnumerable<Type> originalTypes = ...
var result = originalTypes.GroupBy(originalType => originalType.Type,

// parameter resultSelector: from every Type, and all original types that have this
// value of property Type, make one new:
(type, originalTypesThatHaveThisValueForType) => new
{
    Type = type,

    // to get all primitives, use SelectMany to flatten the elements in this group
    Primitives = originalTypesThatHaveThisValueForType
        .SelectMany(originalType => originalType.Primitives,

        (originalType, primitive) => new
        {
            From = primitive.From,
            To = primitive.To,
            ...
        })
        .ToList(),
});

換句話說:從您的原始類型序列中,創建具有相同屬性類型值的類型組。

從您創建的每個組中,創建一個具有兩個屬性的 object: TypePrimitives 屬性 Type 的值很簡單:這是組中所有原始類型的共同值。

為了創建屬性Primitives ,我們將組中的每個元素展平。 請記住:該組是一組“帶有原始類型的類型”(=原始類型)。 我們使用 SelectMany 將其扁平化為 [原始類型,原始類型] 的組合。

因此,如果原始類型 A 具有基元 1、2、3; 並且原始類型 B 具有原語 6、7,我們得到組合:

[A,原始 1] [A,原始 2] [A,原始 3] [B,原始 6] [B,原始 7]

我們將不再使用 A 和 B 中的任何內容,我們已經將它們的共同值用於屬性類型。 但我們對原語感興趣:從組合中的每個原語中,我們制作一個 object:

// from every combination, make one new object:
(originalType, primitive) => new {...}

以下擴展方法可用於按傳染鍵分組:

public static class MyExtensions
{
    public static IEnumerable<IGrouping<TKey, TSource>> ChunkBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
    {
        return source.ChunkBy(keySelector, EqualityComparer<TKey>.Default);
    }

    public static IEnumerable<IGrouping<TKey, TSource>> ChunkBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
    {
        // Flag to signal end of source sequence.
        const bool noMoreSourceElements = true;

        // Auto-generated iterator for the source array.
        var enumerator = source.GetEnumerator();

        // Move to the first element in the source sequence.
        if (!enumerator.MoveNext()) yield break;

        // Iterate through source sequence and create a copy of each Chunk.
        // On each pass, the iterator advances to the first element of the next "Chunk"
        // in the source sequence. This loop corresponds to the outer foreach loop that
        // executes the query.
        Chunk<TKey, TSource> current = null;
        while (true)
        {
            // Get the key for the current Chunk. The source iterator will churn through
            // the source sequence until it finds an element with a key that doesn't match.
            var key = keySelector(enumerator.Current);

            // Make a new Chunk (group) object that initially has one GroupItem, which is a copy of the current source element.
            current = new Chunk<TKey, TSource>(key, enumerator, value => comparer.Equals(key, keySelector(value)));

            // Return the Chunk. A Chunk is an IGrouping<TKey,TSource>, which is the return value of the ChunkBy method.
            // At this point the Chunk only has the first element in its source sequence. The remaining elements will be
            // returned only when the client code foreach's over this chunk. See Chunk.GetEnumerator for more info.
            yield return current;

            // Check to see whether (a) the chunk has made a copy of all its source elements or
            // (b) the iterator has reached the end of the source sequence. If the caller uses an inner
            // foreach loop to iterate the chunk items, and that loop ran to completion,
            // then the Chunk.GetEnumerator method will already have made
            // copies of all chunk items before we get here. If the Chunk.GetEnumerator loop did not
            // enumerate all elements in the chunk, we need to do it here to avoid corrupting the iterator
            // for clients that may be calling us on a separate thread.
            if (current.CopyAllChunkElements() == noMoreSourceElements)
            {
                yield break;
            }
        }
    }

    // A Chunk is a contiguous group of one or more source elements that have the same key. A Chunk
    // has a key and a list of ChunkItem objects, which are copies of the elements in the source sequence.
    class Chunk<TKey, TSource> : IGrouping<TKey, TSource>
    {
        // INVARIANT: DoneCopyingChunk == true ||
        //   (predicate != null && predicate(enumerator.Current) && current.Value == enumerator.Current)

        // A Chunk has a linked list of ChunkItems, which represent the elements in the current chunk. Each ChunkItem
        // has a reference to the next ChunkItem in the list.
        class ChunkItem
        {
            public ChunkItem(TSource value)
            {
                Value = value;
            }
            public readonly TSource Value;
            public ChunkItem Next = null;
        }

        // The value that is used to determine matching elements
        private readonly TKey key;

        // Stores a reference to the enumerator for the source sequence
        private IEnumerator<TSource> enumerator;

        // A reference to the predicate that is used to compare keys.
        private Func<TSource, bool> predicate;

        // Stores the contents of the first source element that
        // belongs with this chunk.
        private readonly ChunkItem head;

        // End of the list. It is repositioned each time a new
        // ChunkItem is added.
        private ChunkItem tail;

        // Flag to indicate the source iterator has reached the end of the source sequence.
        internal bool isLastSourceElement = false;

        // Private object for thread syncronization
        private object m_Lock;

        // REQUIRES: enumerator != null && predicate != null
        public Chunk(TKey key, IEnumerator<TSource> enumerator, Func<TSource, bool> predicate)
        {
            this.key = key;
            this.enumerator = enumerator;
            this.predicate = predicate;

            // A Chunk always contains at least one element.
            head = new ChunkItem(enumerator.Current);

            // The end and beginning are the same until the list contains > 1 elements.
            tail = head;

            m_Lock = new object();
        }

        // Indicates that all chunk elements have been copied to the list of ChunkItems,
        // and the source enumerator is either at the end, or else on an element with a new key.
        // the tail of the linked list is set to null in the CopyNextChunkElement method if the
        // key of the next element does not match the current chunk's key, or there are no more elements in the source.
        private bool DoneCopyingChunk => tail == null;

        // Adds one ChunkItem to the current group
        // REQUIRES: !DoneCopyingChunk && lock(this)
        private void CopyNextChunkElement()
        {
            // Try to advance the iterator on the source sequence.
            // If MoveNext returns false we are at the end, and isLastSourceElement is set to true
            isLastSourceElement = !enumerator.MoveNext();

            // If we are (a) at the end of the source, or (b) at the end of the current chunk
            // then null out the enumerator and predicate for reuse with the next chunk.
            if (isLastSourceElement || !predicate(enumerator.Current))
            {
                enumerator = null;
                predicate = null;
            }
            else
            {
                tail.Next = new ChunkItem(enumerator.Current);
            }

            // tail will be null if we are at the end of the chunk elements
            // This check is made in DoneCopyingChunk.
            tail = tail.Next;
        }

        // Called after the end of the last chunk was reached. It first checks whether
        // there are more elements in the source sequence. If there are, it
        // Returns true if enumerator for this chunk was exhausted.
        internal bool CopyAllChunkElements()
        {
            while (true)
            {
                lock (m_Lock)
                {
                    if (DoneCopyingChunk)
                    {
                        // If isLastSourceElement is false,
                        // it signals to the outer iterator
                        // to continue iterating.
                        return isLastSourceElement;
                    }
                    else
                    {
                        CopyNextChunkElement();
                    }
                }
            }
        }

        public TKey Key => key;

        // Invoked by the inner foreach loop. This method stays just one step ahead
        // of the client requests. It adds the next element of the chunk only after
        // the clients requests the last element in the list so far.
        public IEnumerator<TSource> GetEnumerator()
        {
            //Specify the initial element to enumerate.
            ChunkItem current = head;

            // There should always be at least one ChunkItem in a Chunk.
            while (current != null)
            {
                // Yield the current item in the list.
                yield return current.Value;

                // Copy the next item from the source sequence,
                // if we are at the end of our local list.
                lock (m_Lock)
                {
                    if (current == tail)
                    {
                        CopyNextChunkElement();
                    }
                }

                // Move to the next ChunkItem in the list.
                current = current.Next;
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
    }
}

// A simple named type is used for easier viewing in the debugger. Anonymous types
// work just as well with the ChunkBy operator.
public class KeyValPair
{
    public string Key { get; set; }
    public string Value { get; set; }
}

然后像下面這樣使用它:

class Program
{
    // The source sequence.
    public static IEnumerable<KeyValPair> list;

    // Query variable declared as class member to be available
    // on different threads.
    static IEnumerable<IGrouping<string, KeyValPair>> query;

    static void Main(string[] args)
    {
        // Initialize the source sequence with an array initializer.
        list = new[]
        {
            new KeyValPair{ Key = "A", Value = "We" },
            new KeyValPair{ Key = "A", Value = "think" },
            new KeyValPair{ Key = "A", Value = "that" },
            new KeyValPair{ Key = "B", Value = "Linq" },
            new KeyValPair{ Key = "C", Value = "is" },
            new KeyValPair{ Key = "A", Value = "really" },
            new KeyValPair{ Key = "B", Value = "cool" },
            new KeyValPair{ Key = "B", Value = "!" }
        };

        // Create the query by using our user-defined query operator.
        query = list.ChunkBy(p => p.Key);

        // ChunkBy returns IGrouping objects, therefore a nested
        // foreach loop is required to access the elements in each "chunk".
        foreach (var item in query)
        {
            Console.WriteLine($"Group key = {item.Key}");
            foreach (var inner in item)
            {
                Console.WriteLine($"\t{inner.Value}");
            }
        }

        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }
}

請參閱以下鏈接以獲取更多參考:

按傳染鍵分組

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM