Miscellaneous Operators - Concat & SequenceEqual & Zip
原始文章將記錄於此
https://github.com/s0920832252/LinQ-Note
閒話543
不知道怎麼分類的運算丟這裡
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 來比較.
- 內容值是簡單型別時 , 則比較實際資料是否相同
以下是它的多載形式
- public static bool SequenceEqual<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second);
- 使用項目之型別的預設相等比較子來比較項目,以判斷兩個序列是否相等。
- 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 | 無 | 無 | 無 |
多載
- 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
若有謬誤 , 煩請告知 , 新手發帖請多包涵