Miscellaneous Operators - Concat & SequenceEqual & Zip

Miscellaneous Operators - Concat & SequenceEqual & Zip

原始文章將記錄於此
https://github.com/s0920832252/LinQ-Note

閒話543

不知道怎麼分類的運算丟這裡:alien:


Concat

在保持原來自己集合元素的先後顺序的情况下把兩個集合連接起来 , 但不會過濾相同的項目. 因為是回傳值是 IEnumerable 型態 , 所以其具有延後執行的特性

public static IEnumerable<TSource> Concat<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second);

使用時機

  • 需要把兩個序列串接成一個.

Concat 的用法

列出寵物 狗 & 貓 的資訊

static void Main(string[] args)
{
     var dogs = new[] { (Name : "小黃", Age : 1 ),
                        ( Name : "小狗", Age : 2 ),
                      };

     var cats = new[] {  (Name: "小喵", Age: 7) ,
                         (Name: "小貓", Age: 4) ,
                      };
                      
     var combin = dogs.Concat(cats);
     cats[0].Age = 15; // 其具有延後執行的特性 , 所以小喵的 Age 是 15 而非 7
                              // 列出寵物 狗 & 貓 的資訊
     foreach (var (Name, Age) in combin)
     {
          Console.WriteLine($"{Name} {Age}");
     }
     Console.WriteLine();
     // 也可以使用 Select & SelectMany 做出一樣的結果
     foreach (var (Name, Age) in new[] { dogs.Select(d => d), cats.Select(c => c) }.SelectMany(m => m))
     {
          Console.WriteLine($"{Name} {Age}");
     }

    Console.ReadKey();
}
輸出

小黃 1
小狗 2
小喵 15
小貓 4

小黃 1
小狗 2
小喵 15
小貓 4

簡單實作自己的 Concat

public static IEnumerable<TSource> MyConcat<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second)
{
    if (first is null || second is null)
    {
        throw new Exception("null");
    }
    return MyConcatIterator(first, second);
}

private static IEnumerable<TSource> MyConcatIterator<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second)
{
    foreach (var item in first)
    {
        yield return item;
    }
    foreach (var item in second)
    {
        yield return item;
    }
}

SequenceEqual

SequenceEqual 可以比對兩個集合的內容是否完全相同 , 比對順序是循序式的. 如以下 , 先比較第一個是否皆為蘋果 , 在比較第二個是否皆為香蕉 …

集合名稱 第一個 第二個 第三個 …
小明的水果 蘋果 香蕉 鳳梨 …
小王的水果 蘋果 香蕉 鳳梨 …

因此

  • 兩集合的順序不一致會被視為不同 ,
  • 兩集合的數量不同也會被視為不同
  • 內容值為參考型別時 , 若該型別沒有實作 IEquatable , 則預設比較是比較物件的參考位址是否相同
    • 若不希望如此 , 可以使用自訂的 IEqualityComparer 來比較.
  • 內容值是簡單型別時 , 則比較實際資料是否相同

以下是它的多載形式

  1. public static bool SequenceEqual<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second);
    • 使用項目之型別的預設相等比較子來比較項目,以判斷兩個序列是否相等。
  2. public static bool SequenceEqual<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer);
    • 使用指定的 IEqualityComparer<T> 來比較項目,以判斷兩個序列是否相等。

使用時機

  • 需要判斷兩個集合是否完全相同
    • 第一個多載用在簡單型別 , 第二個多載用在參考型別

SequenceEqual 的用法

class Pet
{
     public string Name { get; set; }
     public int Age { get; set; }
}

class EqualityComparer<TSource> : IEqualityComparer<TSource> where TSource : class
{
     private PropertyInfo[] properties = null;
     public bool Equals(TSource x, TSource y)
     {
          if (properties == null)
               properties = x.GetType().GetProperties();
          return properties.Aggregate(true, (result, item) =>
          {
               return result && item.GetValue(x).Equals(item.GetValue(y));
          });
     }

     public int GetHashCode(TSource obj)
     {
          if (properties == null)
               properties = obj.GetType().GetProperties();
          return properties.Aggregate(0, (result, item) =>
          {
               return result ^ item.GetValue(obj).GetHashCode();
          });
     }
}

class Product : IEquatable<Product>
{
     public string Name { get; set; }
     public int Code { get; set; }

     public bool Equals(Product other)
     {
          return Name == other.Name && Code == other.Code;
     }
}

static void Main(string[] args)
{
     Pet pet1 = new Pet { Name = "Turbo", Age = 2 };
     Pet pet2 = new Pet { Name = "Peanut", Age = 8 };

     List<Pet> pets1 = new List<Pet> { pet1, pet2 };
     List<Pet> pets2 = new List<Pet> { pet1, pet2 };
     List<Pet> pets3 = new List<Pet> { pet2, pet1 };
     // 比較參考位址
     Console.Write($"The {nameof(pets1)} & {nameof(pets2)} ");
     Console.WriteLine($"{(pets1.SequenceEqual(pets2) ? "are" : "are not")} equal.");
     Console.Write($"The {nameof(pets1)} & {nameof(pets3)} ");
     Console.WriteLine($"{(pets1.SequenceEqual(pets3) ? "are" : "are not")} equal.");

     Console.WriteLine("********");

     Product[] storeA = { new Product { Name = "apple", Code = 9 },
                          new Product { Name = "orange", Code = 4 }
                        };
     Product[] storeB = { new Product { Name = "apple", Code = 9 },
                          new Product { Name = "orange", Code = 4 }
                        };
     Product[] storeC = { new Product { Name = "bannaaaa", Code = 9 },
                          new Product { Name = "orange", Code = 4 }
                        };
     // 比較 Name & Code 是否都相同
     Console.Write($"The {nameof(storeA)} & {nameof(storeB)} ");
     Console.WriteLine($"{(storeA.SequenceEqual(storeB) ? "are" : "are not")} equal.");
     Console.Write($"The {nameof(storeA)} & {nameof(storeC)} ");
     Console.WriteLine($"{(storeA.SequenceEqual(storeC) ? "are" : "are not")} equal.");

     Console.WriteLine("********");
     // 比較 Name & Age 是否都相同
     Console.Write($"The {nameof(pets1)} & {nameof(pets2)} ");
     Console.WriteLine($"{(pets1.SequenceEqual(pets2,new EqualityComparer<Pet>()) ? "are" : "are not")} equal.");
     Console.Write($"The {nameof(pets1)} & {nameof(pets3)} ");
     Console.WriteLine($"{(pets1.SequenceEqual(pets3, new EqualityComparer<Pet>()) ? "are" : "are not")} equal.");

     Console.ReadKey();
}
輸出結果

The pets1 & pets2 are equal.
The pets1 & pets3 are not equal.
###############
The storeA & storeB are equal.
The storeA & storeC are not equal.
###############
The pets1 & pets2 are equal.
The pets1 & pets3 are not equal.

簡單實作自己的 SequenceEqual

public static bool MySequenceEqual<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second)
{
     return MySequenceEqual(first, second, null);
}

public static bool MySequenceEqual<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
     if (comparer == null)
     {
          comparer = EqualityComparer<TSource>.Default;
     }
     if (first == null || second == null)
     {
          throw new Exception(null);
     }
     
     using (IEnumerator<TSource> e1 = first.GetEnumerator())
     using (IEnumerator<TSource> e2 = second.GetEnumerator())
     {
          while (e1.MoveNext())
          {
               if (!(e2.MoveNext() && comparer.Equals(e1.Current, e2.Current)))
               {
                    return false;
               }
          }
          if (e2.MoveNext())
          {
               return false;
          }
     }
     return true;
}

Zip

Zip 在 C# 4.0 才被加入 . 其會循序的對兩集合的元素做使用者指定的操作 , 例如若是使用者指定的操作為字串合併 , 則在合併小明以及小王的水果時 , 會得到一個 IEnumerable 型態的序列 , 所以其也具有延遲執行的特性. 另外若是兩集合的長度不同 , Zip 會循序對兩集合操作直到某一個集合已被走訪完畢為止.

小王和小明的水果一樣多
集合名稱 第一個 第二個 第三個 …
小明的水果 蘋果1 香蕉1 鳳梨1 …
小王的水果 蘋果2 香蕉2 鳳梨2 …
Zip 的結果 蘋果1蘋果2 香蕉1香蕉2 鳳梨1鳳梨2 …
小王的水果比較多
集合名稱 第一個 第二個 第三個 …
小明的水果 蘋果1 無 無 無
小王的水果 蘋果2 香蕉2 鳳梨2 …
Zip 的結果 蘋果1蘋果2 無 無 無

多載

  1. public static IEnumerable<TResult> Zip<TFirst,TSecond,TResult> (this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst,TSecond,TResult> resultSelector);
    • 將指定的函式套用至兩個序列的對應項目,產生結果的序列。

使用時機

  • 想要對某兩個集合循序操作 , 但卻無法知道兩個集合數量的時候.

Zip 的用法

static void Main(string[] args)
{
     List<(string name, int age)> People = new List<(string name, int cost)>
     {
          ("小王" , 99),
          ("小黃" , 50),
     };
     List<(string id, int cost)> Invoices = new List<(string name, int cost)>
     {
          ("鳳梨" , 100),
          ("電動" , 500),
          ("冰箱" , 95),
     };

     // 冰箱會被忽略. 以較短的那個集合為主.
     var zip = People.Zip(Invoices, (person, invoice) 
         => $"{person.age}歲的{person.name} 買了{invoice.id} , 花了 {invoice.cost}");

     foreach (var item in zip)
     {
          Console.WriteLine(item);
     }

     Console.ReadKey();
}
輸出結果

99歲的小王 買了鳳梨 , 花了 100
50歲的小黃 買了電動 , 花了 500

簡單實作自己的 Zip

public static IEnumerable<TResult> MyZip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector)
{
     if (first is null || second is null || resultSelector is null)
     {
          throw new Exception("source is null");
     }
     return MyZipIterator(first, second, resultSelector);
}

private static IEnumerable<TResult> MyZipIterator<TFirst, TSecond, TResult>(IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector)
{
     var firstEnumerator = first.GetEnumerator();
     var secondEnumerator = second.GetEnumerator();
     while (firstEnumerator.MoveNext() && secondEnumerator.MoveNext())
     {
          yield return resultSelector(firstEnumerator.Current, secondEnumerator.Current);
     }
}

參考資料

EqualityComparer<T> 類別 SequenceEqual source code - donetCorefx , microsoft


Thank you!

You can find me on

若有謬誤 , 煩請告知 , 新手發帖請多包涵

:100: :muscle: :tada: :sheep: