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> secondFunc<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: