visual C#(十八)使用集合

参考书:《 visual C# 从入门到精通》
第三部分 用C#定义可扩展类型
第18章 使用集合

18.1 什么是集合

Microsoft .NET Framework提供了几个类,它们在System.Collections.Generic命名空间中。

下面为常用的集合类:

集合说明
List<T>可像数组那样按索引访问列表,提供了一些搜索和排序方法
Queue<T>先入先出,提供方法将数据项添加到队列的一端,从另一端删除项,以及只检查不删除
Stack<T>先入后出,提供方法将数据项压入栈顶,从栈顶出栈,和只检查栈顶不删除
LinkedList<T>双向有序列表,为任何一端插入和删除优化了。既可作为队列又可作为栈,还支持像列表那样的随机访问
HashSet<T>无序值列表,为快速数据获取而优化了。提供了面向集合的方法来判断它容纳的像是不是另一个HashSet对象中的项的子集,以及计算不同HashSet对象的交际和并集
Dictionary<TKey,TValue>字典集合,根据键来获取值
SortedList<TKey,TValue>键值对的有序列表。键必须 实现IComparable接口

18.1.1 List<T>集合类

List<T>集合类相比较数组的优势在于:

  • 创建时无需指定大小,能随元素的增加而自动伸缩。也可以指定初始大小,超过该大小时集合将增大
  • 可用Remove方法删除指定元素,RemoveAt方法删除指定位置的项
  • Add方法在集合尾部添加元素
  • Insert方法在集合中间插入元素
  • Sort方法可对对象进行排序

如下面的程序简单的应用了List<T>类

using System;
using System.Collections.Generic;

namespace C_18_1_1
{
    class Program
    {
        static void dowork()
        {
            List<int> number = new List<int>();
            foreach (int num in new int[12] {10,2,3,4,5,6,7,8,9,0,7,3 })
            {
                number.Add(num);
            }
            number.Insert(number.Count - 1, 99);
            number.Remove(7);
            number.RemoveAt(6);
            Console.WriteLine("Iterating uisng a for statement:");
            for(int i=0;i < number.Count; ++i)
            {
                int num = number[i];
                Console.WriteLine(num);
            }
            Console.WriteLine("\nIterating using a foreach statement:");
            foreach(int num in number)
            {
                Console.WriteLine(num);
            }
        }
        static void Main(string[] args)
        {
            dowork();
        }
    }
}

结果如下

Iterating uisng a for statement:
10
2
3
4
5
6
9
0
7
99
3

Iterating using a foreach statement:
10
2
3
4
5
6
9
0
7
99
3

C:\Users\xhh\Source\Repos\C_18_1_1\C_18_1_1\bin\Debug\netcoreapp3.1\C_18_1_1.exe (进程 25388)已退出,代码为 0。
按任意键关闭此窗口. . .

18.1.2 LinkedList<T>集合类

与List<T>不同的是,它不支持用数组语法插入和检查元素,要用AddFirst方法在列表开头插入元素,下移原来的第一项并将它的previous属性设为对新项的引用。或用AddLast方法在列表尾插入元素,将原来的最后一项的Next属性设为对新项的引用。也可用AddBeforeAddAfter在指定项前后插入元素。

First属性返回对集合的第一项的引用,Last属性返回对最后一项的引用。遍历链表可以从一端开始,查询Nextprevious引用,直到返回null为止。也可用foreach语句正向遍历对象。

删除项可用RemoveRemoveFirstRemoveLast

如下:

using System;
using System.Collections.Generic;
using System.Linq;

namespace C_18_1_2
{
    class Program
    {
        static void dowork()
        {
            LinkedList<int> numbers = new LinkedList<int>();
            foreach (int num in new int[] { 10, 8, 6, 4, 2 })
            {
                numbers.AddFirst(num);
            }
            Console.WriteLine("Iterating using a for statement:");
            for (LinkedListNode<int> node = numbers.First; node != null; node = node.Next)
            {
                int num = node.Value;
                Console.WriteLine(num);
            }
            Console.WriteLine("\nIteratin using a foreach statement:");
            foreach(int num in numbers)
            {
                Console.WriteLine(num);
            }
            Console.WriteLine("\nIterating list in reverse order:");
            for (LinkedListNode<int> node = numbers.Last; node != null; node = node.Previous)
            {
                int num = node.Value;
                Console.WriteLine(num);
            }
        }
        static void Main(string[] args)
        {
            dowork() ;
        }
    }
}

运行结果如下:

Iterating using a for statement:
2
4
6
8
10

Iteratin using a foreach statement:
2
4
6
8
10

Iterating list in reverse order:
10
8
6
4
2

C:\Users\xhh\Source\Repos\C_18_1_2\C_18_1_2\bin\Debug\netcoreapp3.1\C_18_1_2.exe (进程 24064)已退出,代码为 0。
按任意键关闭此窗口. . .

18.1.3 Queue<T>集合类

如下:

using System;
using System.Collections.Generic;
using System.Linq;

namespace C_18_1_2
{
    class Program
    {
        static void dowork()
        {
            Queue<int> number = new Queue<int>();
            Console.WriteLine("Populating the queue:");
            foreach(int num in new int[] { 9, 3, 7, 2 })
            {
                number.Enqueue(num);
                Console.WriteLine($"{num} has joined the queue");
            }
            Console.WriteLine("\n The queue contains the following items:");
            foreach(int num in number)
            {
                Console.WriteLine(num);
            }
            Console.WriteLine("\nDraining the queue:");
            while (number.Count > 0)
            {
                int num = number.Dequeue();
                Console.WriteLine("${num} has left the queue");
            }
        }
        static void Main(string[] args)
        {
            dowork() ;
        }
    }
}

运行结果:

Populating the queue:
9 has joined the queue
3 has joined the queue
7 has joined the queue
2 has joined the queue

 The queue contains the following items:
9
3
7
2

Draining the queue:
${num} has left the queue
${num} has left the queue
${num} has left the queue
${num} has left the queue

C:\Users\xhh\Source\Repos\C_18_1_2\C_18_1_2\bin\Debug\netcoreapp3.1\C_18_1_2.exe (进程 11760)已退出,代码为 0。
按任意键关闭此窗口. . .

18.1.4 Stack<T>集合类

using System;
using System.Collections.Generic;
using System.Linq;

namespace C_18_1_2
{
    class Program
    {
        static void dowork()
        {
            Stack<int> numbers = new Stack<int>();
            Console.WriteLine("Push items onto the stack:");
            foreach(int num in new int[4] { 9, 3, 7, 2 })
            {
                numbers.Push(num);
                Console.WriteLine($"{num} has been pushed on the stack");
            }
            Console.WriteLine("\n The stack new contains");
            foreach(int num in numbers)
            {
                Console.WriteLine(num);
            }
            Console.WriteLine("\nPopping items from the stack:");
            while (numbers.Count > 0)
            {
                int num = numbers.Pop();
                Console.WriteLine($"{num} has been popped off the stack");
            }
        }
        static void Main(string[] args)
        {
            dowork() ;
        }
    }
}

运行结果:

Push items onto the stack:
9 has been pushed on the stack
3 has been pushed on the stack
7 has been pushed on the stack
2 has been pushed on the stack

 The stack new contains
2
7
3
9

Popping items from the stack:
2 has been popped off the stack
7 has been popped off the stack
3 has been popped off the stack
9 has been popped off the stack

C:\Users\xhh\Source\Repos\C_18_1_2\C_18_1_2\bin\Debug\netcoreapp3.1\C_18_1_2.exe (进程 28912)已退出,代码为 0。
按任意键关闭此窗口. . .

18.1.5 Dictionary<TKey,TValue>集合类

注意:

  • 集合中不能包含重复的键。用Add方法添加键数组中已有的键将抛出异常。但如果用方括号记法添加键值对就不会有异常,添加相同的键会覆盖。可用ContainKey方法来确定集合中是否含有特定的键
  • 集合内部采用一中稀疏数据结构,有大量内存时是最高效的,插入元素会快速消耗大量内存
  • foreach语句遍历集合返回的是一个KeyValuePair<TKey,TValue>,其中包含数据项键和值元素的拷贝,可以通过KeyValue属性访问每个元素。元素是只读的,不能修改集合中的元素
using System;
using System.Collections.Generic;
using System.Linq;

namespace C_18_1_2
{
    class Program
    {
        static void dowork()
        {
            Dictionary<string, int> ages = new Dictionary<string, int>();
            ages.Add("John", 51);
            ages.Add("Diana", 50);
            ages["James"] = 23;
            ages["Francesca"]=21;
            Console.WriteLine("The Dictionary contains:");
            foreach(KeyValuePair<string,int>ele in ages)
            {
                string name = ele.Key;
                int age = ele.Value;
                Console.WriteLine($"Name: {name}, Age: {age}");
            }
        }
        static void Main(string[] args)
        {
            dowork() ;
        }
    }
}

运行结果:

The Dictionary contains:
Name: John, Age: 51
Name: Diana, Age: 50
Name: James, Age: 23
Name: Francesca, Age: 21

C:\Users\xhh\Source\Repos\C_18_1_2\C_18_1_2\bin\Debug\netcoreapp3.1\C_18_1_2.exe (进程 30456)已退出,代码为 0。
按任意键关闭此窗口. . .

18.1.6 SortedList<TKey,TValue>集合类

SortedList的键总是排好序的,在对象中插入数据花的时间比Dictionary对象长,但获取数据会快一些,而且它消耗的内存较少。

using System;
using System.Collections.Generic;
using System.Linq;

namespace C_18_1_2
{
    class Program
    {
        static void dowork()
        {
            SortedDictionary<string, int> ages = new SortedDictionary<string, int>();
            ages.Add("John", 51);
            ages.Add("Diana", 50);
            ages["James"] = 23;
            ages["Francesca"]=21;
            Console.WriteLine("The SortedList contains:");
            foreach(KeyValuePair<string,int>ele in ages)
            {
                string name = ele.Key;
                int age = ele.Value;
                Console.WriteLine($"Name: {name}, Age: {age}");
            }
        }
        static void Main(string[] args)
        {
            dowork() ;
        }
    }
}

运行结果:

The SortedList contains:
Name: Diana, Age: 50
Name: Francesca, Age: 21
Name: James, Age: 23
Name: John, Age: 51

C:\Users\xhh\Source\Repos\C_18_1_2\C_18_1_2\bin\Debug\netcoreapp3.1\C_18_1_2.exe (进程 13132)已退出,代码为 0。
按任意键关闭此窗口. . .

18.1.7 HashSet<T>集合类

HashSet<T>类专为集合操作优化。AddRemove分别是插入和删除操作。HashSet有比较强大的方法:IntersectWithUnionWithExceptWith,分别是生成与另一个集合相交、合并或者不包含其数据项的新集合,这些操作会覆盖原始HashSet<T>对象的内容。还有IsSunsetOfIsSupersetOfIsProperSubsetOfIsProperSupersetOf判断集合的数据是否是另一个集合的超集或子集。

集合内部作为哈希表的实现,可以实现数据项的快速查找。但一个大的集合需要消耗大量的内存。

using System;
using System.Collections.Generic;
using System.Linq;

namespace C_18_1_2
{
    class Program
    {
        static void dowork()
        {
            HashSet<string>employees=new HashSet<string>( new string[]{ "Fred", "Bert", "Harry", "John" });
            HashSet<string> customers = new HashSet<string>(new string[] { "John", "Sid", "Harry", "Diana" });
            employees.Add("James");
            customers.Add("Francesca");
            Console.WriteLine("Employees:");
            foreach (string name in employees)
                Console.WriteLine(name);
            Console.WriteLine("\nCustomers:");
            foreach (string name in customers)
                Console.WriteLine(name);
            Console.WriteLine("\nCustomers who are also employees:");
            customers.IntersectWith(employees);
            foreach (string name in customers)
                Console.WriteLine(name);
        }
        static void Main(string[] args)
        {
            dowork() ;
        }
    }
}

运行结果:

Employees:
Fred
Bert
Harry
John
James

Customers:
John
Sid
Harry
Diana
Francesca

Customers who are also employees:
John
Harry

C:\Users\xhh\Source\Repos\C_18_1_2\C_18_1_2\bin\Debug\netcoreapp3.1\C_18_1_2.exe (进程 20336)已退出,代码为 0。
按任意键关闭此窗口. . .

18.2 使用集合初始化器

一些集合允许在声明时用和数组类似的语法初始化:

List<int> numbers=new List<int>(){10,9,8};

编译器内部会将初始化转换成一系列的Add方法调用。所以只有支持Add方法的集合才可以这样写。

对于获取键值对的集合可以用索引器语法:

Dictionary<string,int> ages=new Dictionary<string int>(){
    ["John"]=51,
    ["Diana"]=50,
    ["James"]=23,
    ['Francesca']=21
}

也可以在集合初始化列表中将每个键值对指定为匿名类型:

Dictionary<string,int> ages=new Dictionary<string int>(){
    {"John",51},
    {"Diana",50},
    {"James",23},
    {'Francesca',21}
}

18.3 Find方法、谓词和Lambda表达式

对于支持随机访问的集合,它们无法通过数组语法来查找项,所以提供了Find方法。Find方法的实参是代表搜索条件的谓词谓词是一个方法,它检查每一项,返回布尔值指出该项是否匹配。Find方法返回发现的第一个匹配项。另外FindLast返回最后一个匹配项,FindAll返回所有匹配项的一个List<T>集合。

谓词最好用Lambda表达式指定。Lambda表达式是能返回方法的表达式。

如:

struct Person{
    public int ID{get;set;}
    public string Name{get;set;}
    public int Age{get;set;}
}
...;
List<person> personnel=new List<Person>(){
    new Person(){ID=1,Name="John",Age=51},
    new Person(){ID=2,Name="Sid",Age=28},
    new Person(){ID=3,Name="Fred",Age=34},
    new Person(){ID=4,Name="Paul",Age=22}
}
Person match=personnel.Find((Person p)=>{return p.ID==3});

其中的Lambda表达式(Person p)=>{return p.ID==3},包含以下语法元素:

  • 圆括号中的是参数列表
  • =>操作符,向编译器指出这是一个Lambda表达式
  • 表达式主体,可以包含多个语法

如果Lambda表达式主体只有一个表达式,大括号和分号就可以省略,如果表达式只有一个参数,圆括号也可以省略,很多时候也可以省略参数类型,编译器会根据上下文推断。如:

Person match=personnel.Find(p=>p.ID==3);

Lambda表达式具有以下特点:

  • =>左侧的圆括号内指定要获取的参数,可省略参数类型。如希望全局更改参数值可以用关键字ref
  • 可返回值,返回类型应与对应的委托类型匹配
  • 表达式主体可以是简单表达式,也可以是代码块
  • 表达式中定义的变量会在方法结束时离开作用域
  • 表达式可以访问和修改表达式外部的所有变量,只要这些变量和Lambda表达式处在相同的作用域中

Lambda表达式与匿名方法非常相似,但Lamdba表达式要更灵活点,所以通常都是用Lambda表达式。

18.4 比较数组和集合


我们再反过头来看一下第十章的卡牌游戏,只需要修改其中的两个源文件,这样用上我们这章学到的一些集合:

Pack.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace C_10_1_10
{
    class Pack
    {
        public const int NumSuits = 4;
        public const int CardsPerSuit = 13;
        private Dictionary<Suit,List<PlayingCard>> cardPack;
        private Random randomCardSelector = new Random();

        public Pack()
        {
            this.cardPack = new Dictionary<Suit, List<PlayingCard>>(NumSuits);
            for (Suit suit = Suit.Clus; suit <= Suit.Spades; ++suit)
            {
                List < PlayingCard > cardsInSuit = new List<PlayingCard>(CardsPerSuit);
                for(Value value = Value.Two; value <= Value.Ace; ++value)
                {
                    cardsInSuit.Add(new PlayingCard(suit,value));
                }
                this.cardPack.Add(suit, cardsInSuit);
            }
        }
        public PlayingCard DealCardFrompack()
        {
            Suit suit = (Suit)randomCardSelector.Next(NumSuits);
            while (this.IsSuitEmpty(suit))
            {
                suit = (Suit)randomCardSelector.Next(NumSuits);
            }
            Value value = (Value)randomCardSelector.Next(CardsPerSuit);
            while (this.IsCardAlreadyDealt(suit, value))
            {
                value = (Value)randomCardSelector.Next(CardsPerSuit);
            }
            List<PlayingCard> cardsInSuit = this.cardPack[suit];
            PlayingCard card = cardsInSuit.Find(c => c.CardValue() == value);
            cardsInSuit.Remove(card);
            return card;
        }

        private bool IsCardAlreadyDealt(Suit suit, Value value) {
            List<PlayingCard> cardsInSuit = this.cardPack[suit];
            return !cardsInSuit.Exists(c => c.CardSuit() == suit && c.CardValue() == value);
        }
        private bool IsSuitEmpty(Suit suit)
        {
            bool result = true;
            for(Value value = Value.Two; value <= Value.Ace; value++)
            {
                if (!IsCardAlreadyDealt(suit, value))
                {
                    result = false;
                    break;
                }
            }
            return result;
        }
    }
}

Hand.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace C_10_1_10
{
    class Hand
    {
        public const int HandSize = 13;
        private List<PlayingCard> cards = new List<PlayingCard>(HandSize);

        public override string ToString()
        {
            string result = "";
            foreach(PlayingCard card in this.cards)
            {
                if(card!=null)
                    result += $"{card.ToString()}\n ";// card.ToString()+"/r/n";
            }
            return result;
        }
        public void AddCardToHand(PlayingCard cardDealt)
        {
            if (this.cards.Count >= HandSize)
            {
                throw new ArgumentException("Two many cards");
            }
            this.cards.Add(cardDealt);
        }
    }
}

其它的都保持不变就行,最终运行效果一样。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值