CodeGym /課程 /C# SELF /在 C# 集合中尋找元素

在 C# 集合中尋找元素

C# SELF
等級 29 , 課堂 1
開放

1. 集合搜尋的基本功能

很多時候 「搜尋」「過濾」 這兩個詞會被當成一樣,但寫程式的時候其實不太一樣。

  • 過濾 — 就是我們想拿到集合裡所有符合某個條件的元素(比如所有大於 10 的數字)。
  • 搜尋 — 通常是我們只想找一個元素:第一個符合的、某個特定值的,或只是想知道集合裡有沒有這個東西。

如果拿圖書館來比喻:過濾就像把所有「太空」主題的書都找出來,搜尋則像問:「你們有《尤利西斯》這本書嗎?」「第一本藍色封面的書在哪?」

所有 .NET 的主流集合(陣列、List、HashSet、Dictionary 等等)都支援好幾種搜尋或檢查有沒有元素的方法。

來看看最常用的幾個方法:

集合 檢查有沒有 找索引 找元素 用 key 找
List<T>
Contains
IndexOf
Find
-
T[]
(陣列)
Contains
/
Array.IndexOf
Array.IndexOf
- -
HashSet<T>
Contains
- - -
Dictionary<TKey,V>
ContainsKey
,
ContainsValue
- - 有索引子
[key]

有些方法只有特定型別才有。

2. List 搜尋:List<T>

List<T> 是最萬用的集合之一,可以隨便存元素、隨便取。這裡是幾個主要的搜尋方法:

檢查有沒有這個元素:Contains

這個方法會告訴你 List 裡有沒有這個元素:

List<string> fruits = new List<string> { "蘋果", "香蕉", "奇異果" };
bool hasKiwi = fruits.Contains("奇異果"); // true
bool hasMango = fruits.Contains("芒果"); // false
Console.WriteLine(hasKiwi); // True

Contains 其實就是一個個比,用 Equals

找索引:IndexOf

如果你想知道元素在 List 裡的第幾個(索引):

int index = fruits.IndexOf("香蕉"); // 1(從 0 開始算)
int absentIndex = fruits.IndexOf("西瓜"); // -1(找不到)
Console.WriteLine(index);
Console.WriteLine(absentIndex);

找第一個符合條件的:Find

你可以根據條件找,不一定要指定值!用 Find(或它的兄弟 FindIndex):

// 找第一個名字長度超過 4 的水果
string longFruit = fruits.Find(fruit => fruit.Length > 4); // "蘋果"
Console.WriteLine(longFruit);

注意:如果沒找到,會回傳型別的預設值(參考型別就是 null)。

找多個元素:FindAll

如果你想過濾(找出所有符合的),用 FindAll

// 找所有名字裡有 'и' 的水果
List<string> withI = fruits.FindAll(f => f.Contains('и'));
foreach (var fruit in withI)
    Console.WriteLine(fruit); // "奇異果"

3. 陣列搜尋:Array 類別的方法

陣列本身沒有 Find(不像 List<T>),但有個靜態類別 Array 可以用:

int[] numbers = { 1, 2, 3, 2, 4 };
int pos = Array.IndexOf(numbers, 2); // 1 — 第一個 2 的索引
int lastPos = Array.LastIndexOf(numbers, 2); // 3 — 最後一個 2 的索引
Console.WriteLine(pos + ", " + lastPos);

如果你想根據條件找,可以用簡單的 for 迴圈:

int firstGreaterThanTwo = -1;
for (int i = 0; i < numbers.Length; i++)
{
    if (numbers[i] > 2)
    {
        firstGreaterThanTwo = numbers[i];
        break;
    }
}
Console.WriteLine(firstGreaterThanTwo); // 3

4. 所有集合都能用的搜尋方法

很多集合都有搜尋方法(像 ContainsIndexOfFind 等等)。如果要更複雜的,就自己寫個 for/foreach 迴圈。

範例:檢查有沒有水果名字是 "Б" 開頭

bool hasB = false;
foreach (var f in fruits)
{
    if (f.StartsWith("Б"))
    {
        hasB = true;
        break;
    }
}
Console.WriteLine(hasB); // True

範例:找第一個名字裡有 "и" 的水果

string withI = null;
foreach (var f in fruits)
{
    if (f.Contains('и'))
    {
        withI = f;
        break;
    }
}
Console.WriteLine(withI); // "奇異果"

範例:搜尋自訂物件

class Student
{
    public string Name;
    public int Group;
    public int Id;
}

List<Student> students = new List<Student>
{
    new Student { Name = "伊凡", Group = 101, Id = 1 },
    new Student { Name = "瑪麗亞", Group = 101, Id = 2 },
    new Student { Name = "彼得", Group = 102, Id = 3 },
};

// 找 Id == 2 的學生
Student maria = null;
foreach (var s in students)
{
    if (s.Id == 2)
    {
        maria = s;
        break;
    }
}
if (maria != null)
    Console.WriteLine(maria.Name); // "瑪麗亞"
else
    Console.WriteLine("找不到學生");

5. HashSet<T> 搜尋:只有「有沒有?」

HashSet(HashSet<T>)就是為了超快檢查「有沒有這個東西」而生的。不能用索引找,但檢查有沒有超快:

HashSet<int> set = new HashSet<int> { 1, 3, 5, 7 };
bool hasThree = set.Contains(3); // True
Console.WriteLine(hasThree);

// 如果你想根據條件找(比如有沒有偶數):
bool hasEven = false;
foreach (var x in set)
{
    if (x % 2 == 0)
    {
        hasEven = true;
        break;
    }
}
Console.WriteLine(hasEven); // False

6. Dictionary 搜尋:Dictionary<TKey, TValue>

Dictionary 就是 key-value 配對的集合。用 key 搜尋超強。

檢查有沒有這個 key

Dictionary<int, string> idToName = new Dictionary<int, string>
{
    { 1, "瓦夏" }, { 2, "卡佳" }
};
if (idToName.ContainsKey(2))
    Console.WriteLine(idToName[2]); // "卡佳"

用 key 找 value:安全寫法!

if (idToName.TryGetValue(3, out string result))
    Console.WriteLine(result);
else
    Console.WriteLine("沒有這個 Id 的學生"); // 會印這個

用 value 搜尋(很少用而且慢):

bool containsVasya = idToName.ContainsValue("瓦夏");
Console.WriteLine(containsVasya); // True

根據 key 或 value 條件找紀錄

// 第一個名字是 "К" 開頭的 Id
KeyValuePair<int, string> entry = default;
bool found = false;
foreach (var pair in idToName)
{
    if (pair.Value.StartsWith("К"))
    {
        entry = pair;
        found = true;
        break;
    }
}
if (found)
    Console.WriteLine($"{entry.Key}: {entry.Value}"); // "2: 卡佳"

7. 有用的小細節

搜尋方法表

集合型別 檢查有沒有 Contains 找索引 IndexOf 根據條件找 Find 安全用 key 找 TryGetValue
List<T>
有(
Find
-
T[]
(陣列)
有(
Array.Contains
/for 迴圈)
有(
Array.IndexOf
for 迴圈 -
HashSet<T>
- 沒有(只能自己寫) -
Dictionary<TKey,V>
有(
ContainsKey
- 自己寫 key/value 搜尋

自己寫 for 迴圈搜尋

來練習一下,自己寫個搜尋方法。比如找第一個大於某個值的元素索引:

static int FindFirstGreaterIndex(IEnumerable<int> collection, int minValue)
{
    int index = 0;
    foreach (var item in collection)
    {
        if (item > minValue)
            return index;
        index++;
    }
    return -1; // 沒找到
}

var nums = new List<int> { 1, 4, 7, 2 };
Console.WriteLine(FindFirstGreaterIndex(nums, 3)); // 1(數字 4)

這樣就不管你是 List<T>int[],甚至 HashSet<T> 都能用。

搜尋在實務上的應用

  • 根據登入帳號或 email 在資料庫找使用者。
  • 檢查購物車裡有沒有這個商品。
  • 根據參數名稱快速找設定。
  • 檢查某個步驟是不是已經做過(像 workflow)。
  • 在 log 陣列裡找錯誤的位置。

大部分面試問到 middle 工程師都會問:「怎麼在集合裡找元素?你會怎麼寫一個回傳第一個/全部/索引的搜尋方法?」。所以搜尋真的很重要!

8. 搜尋常見錯誤跟小細節

很重要:搜尋方法會根據物件怎麼比較來決定結果。比如你 List 裡放自訂 class,預設比較是比 reference!如果你想「比內容」,要自己 override EqualsGetHashCode(這個之後會講)。

問題範例:

var s1 = new Student { Name = "葉戈爾", Id = 42 };
students.Add(s1);
// 現在建立一個一模一樣的新物件:
var s2 = new Student { Name = "葉戈爾", Id = 42 };
Console.WriteLine(students.Contains(s2)); // False(!)

編譯器不是比欄位,是比 reference(這是不同的物件)。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION