C#集合之字典

  字典表示一种复杂的数据结构,这种数据结构允许按照某个键来访问元素。字典也称为映射或散列表。
  字典的主要特性是能根据键快速查找值。也可以自由添加和删除元素,这有点像List<T>(http://www.cnblogs.com/afei-24/p/6824791.html),但没有在内存中移动后续元素的性能开销。
  下图是一个简化表示,键会转换位一个散列。利用散列创建一个数字,它将索引和值关联起来。然后索引包含一个到值的链接。一个索引项可以关联多个值,索引可以存储为一个树型结构。
  
  .NET Framework提供了几个字典类。最主要的类是Dictionary<TKey,TValue>。

1.键的类型
  用作字典中的键的类型必须重写Object类的GetHashCode()方法。只要字典类需要确定元素的位置,它就要调用GetHashCode()方法。GetHashCode()方法返回的int有字典用于计算在对应位置放置元素的索引。后面介绍这个算法,现在只需要知道它涉及素数,所以字典的容量是一个素数。
  GetHashCode()方法的实现代码必须满足的要求:
    *相同的对象应总是返回相同的值
    *不同的对象可以返回相同的值
    *它应执行的比较快,计算的开销不大
    *它不能抛出异常
    *它应至少使用一个实例字段
    *散列代码值应平均分布在int可以存储的这个数字范围上
    *散列代码最好在对象的生存期中不发生变化
  字典的性能取决于GetHashCode()方法的实现代码。

  散列代码值应平均分布在int可以存储的这个数字范围上的原因:
  如果两个键返回的散列代码值会得到相同的索引,字典类就必须寻找最近的可用空闲位置来存储第二个数据项,这需要进行一定的搜索,以便以后检索这一项。显然这会降低性能,如果在排序的时候许多键都有相同的索引这中冲突会更可能出现。根据Microsoft的算法工作方式,当计算出来的散列代码值平均分布在int.MinValue和int.MaxValue之间时,这种风险会降到最低。

  除了实现GetHashCode()方法之外,键类型还必须实现IEquatable<T>.Equals()方法,或重写Object.Equals()方法。0因为不同的键对象可能返回相同的散列代码,所以字典使用Equals()方法来比较键。字典检查两个键A和B是否相等,并调用A.Equals(B)方法。这表示必须确保下述条件总是成立:
    如果A.Equals(B)返回true,则A.GetHashCode()和B.GetHashCode()总是返回相同的散列代码。
  这听起来有点奇怪,但它很重要。如果上述条件不成立,这个字典还能工作,但会出现,把一个对象放在字典中后,就再也检索不到它,或者返回了错误的项。
  所以,如果为Equals()方法提供了重写版本,但没提供GetHashCode()方法的重写版本,C#编译器会显示一个警告。

  对于System.Object,这个条件为true,因为Equals()方法只是比较引用,GetHashCode()方法实际上返回一个仅基于对象地址的散列代码。这说明,如果散列表基于一个键,而该键没有重写这些方法,这个散列表可以工作,但只有对象完全相同,键才被认为是相等的。也就是说,把一个对象放在字典中时,必须将它与该键的引用关联起来。也不能以后用相同的值实例化另一个键对象。如果没有重写Equals()方法和GetHashCode()方法,在字典中使用类型时就不太方便。

  System.String实现了IEquatable接口,并重载了GetHashCode()方法。Equals()方法提供了值的比较,GetHashCode()方法根据字符串的值返回一个散列代码。因此,在字典中把字符串用在键很方便。

  数字类型(如Int32)也实现了IEquatable接口,并重载了GetHashCode()方法。但是这些类型返回的散列代码只能映射到值上。如果希望用作键的数字本身没有分布在可能的整数值范围内,把整数用作键就不能满足键值的平均分布规则,于是不能获得最佳的性能。Int32并不适合在字典中使用。

  如果需要使用的键类型没有实现IEquatable接口,并根据存储在字典中的键值重载GetHashCode()方法,就可以创建一个实现IEqualityComparer<T>接口的比较器。IEqualityComparer<T>接口定义了GetHashCode()方法和Equals()方法,并将传递的对象作为参数,这样可以提供与对象类型不同的实现方式。

2.演示字典
  创建一个员工ID(EmployeeId)结构,用作字典的键。存储在字典中的数据是Employee类型的对象。
  该结构的成员是表示员工的一个前缀字符和一个数字。这两个变量都是只读的,只能在构造函数中初始化。字典中的键不应改变,这是必须保证的。
   

      public struct EmployeeId : IEquatable<EmployeeId>
          {
            private readonly char prefix;
            private readonly int number;

            public EmployeeId(string id)
            {
              Contract.Requires<ArgumentNullException>(id != null);

              prefix = (id.ToUpper())[0];
              int numLength = id.Length - 1;
              try
              {
                number = int.Parse(id.Substring(1, numLength > 6 ? 6 : numLength));
              }
              catch (FormatException)
              {
                throw new Exception("Invalid EmployeeId format");
              }
            }

            public override string ToString()
            {
              return prefix.ToString() + string.Format("{0,6:000000}", number);
            }

            //由于没有填满整数取值范围,GetHashCode方法将数字向左移动16位,再与原来的数字进行异或操作,
            //最后将结果乘以16进制数0x15051505。这样,散列代码在整数取值区域上的分布就很均匀。
            public override int GetHashCode()
            {
              return (number ^ number << 16) * 0x15051505;
            }

            public bool Equals(EmployeeId other)
            {
              if (other == null) return false;

              return (prefix == other.prefix && number == other.number);
            }
            //比较两个EmployeeId对象的值
            public override bool Equals(object obj)
            {
              return Equals((EmployeeId)obj);
            }

            public static bool operator ==(EmployeeId left, EmployeeId right)
            {
              return left.Equals(right);
            }

            public static bool operator !=(EmployeeId left, EmployeeId right)
            {
              return !(left == right);
            }
          }

          public class Employee
          {
            private string name;
            private decimal salary;
            private readonly EmployeeId id;

            public Employee(EmployeeId id, string name, decimal salary)
            {
              this.id = id;
              this.name = name;
              this.salary = salary;
            }

            public override string ToString()
            {
              return String.Format("{0}: {1, -20} {2:C}",
                    id.ToString(), name, salary);
            }
          }

  客户端代码:

    static void Main()
        {
            //构造函数指定了31个元素的容量。容量一般是素数。
            //如果指定了一个不是素数的值,Dictionary<TKey,TValue>类会使用指定的整数后面紧接着的一个素数
          var employees = new Dictionary<EmployeeId, Employee>(31);

          var idTony = new EmployeeId("C3755");
          var tony = new Employee(idTony, "Tony Stewart", 379025.00m);
          employees.Add(idTony, tony);
          Console.WriteLine(tony);

          var idCarl = new EmployeeId("F3547");
          var carl = new Employee(idCarl, "Carl Edwards", 403466.00m);
          employees.Add(idCarl, carl);
          Console.WriteLine(carl);

          var idKevin = new EmployeeId("C3386");
          var kevin = new Employee(idKevin, "Kevin Harwick", 415261.00m);
          employees.Add(idKevin, kevin);
          Console.WriteLine(kevin);

          var idMatt = new EmployeeId("F3323");
          var matt = new Employee(idMatt, "Matt Kenseth", 1589390.00m);
          employees[idMatt] = matt;
          Console.WriteLine(matt);

          var idBrad = new EmployeeId("D3234");
          var brad = new Employee(idBrad, "Brad Keselowski", 322295.00m);
          employees[idBrad] = brad;
          Console.WriteLine(brad);
        }

3.Lookup类
  Dictionary<TKey,TValue>类支持每个键关联一个值。Lookup<TKey,TElement>类把键映射到一个值集上。这个类在程序集System.Core中实现,用System.Linq定义。

  Lookup<TKey,TElement>类不能像一般的字典那样创建,必须调用ToLookup()方法,该方法返回一个Lookup<TKey,TElement>对象。ToLookup()方法是一个扩展方法,它可以用于实现了IEnumerable<T>接口的所有类。
     ToLookup()方法需要一个Func<TSource,Tkey>,Func<TSource,Tkey>定义了选择器。

  

    static void Main()
        {
          var racers = new List<Racer>();
          racers.Add(new Racer(26, "Jacques", "Villeneuve", "Canada", 11));
          racers.Add(new Racer(18, "Alan", "Jones", "Australia", 12));
          racers.Add(new Racer(11, "Jackie", "Stewart", "United Kingdom", 27));
          racers.Add(new Racer(15, "James", "Hunt", "United Kingdom", 10));
          racers.Add(new Racer(5, "Jack", "Brabham", "Australia", 14));
          //国家相同的对象关联到一个键
          var lookupRacers = racers.ToLookup(r => r.Country);

          foreach (Racer r in lookupRacers["Australia"])
          {
            Console.WriteLine(r);
          }

        }

输出:
  Alan Jones
  Jack Brabham

4.有序字典
  SortedDictionary<TKey,TValue>类是一个二叉搜索树,其中的元素根据键来排序。该键类型必须实现IComparable<TKey>接口。
  如果键的类型不能排序,还可以创建一个实现了IComparer<TKey>接口的比较器,将比较器用作有序字典的构造函数的一个参数。
  SortedDictionary<TKey,TValue>和有序列表SortedList<TKey,TValue>(http://www.cnblogs.com/afei-24/p/6830376.html)的区别:
    *SortedList<TKey,TValue>类使用的内存比SortedDictionary<TKey,TValue>少。
    *SortedDictionary<TKey,TValue>元素的插入和删除操作比较快。
    *在用已排序好的数据填充集合时,若不需要改变容量,ortedList<TKey,TValue>比较快。

时间: 2024-08-02 06:41:56

C#集合之字典的相关文章

Swift 数组、字符串、集合与字典详解

今天我们来看几个最基本的数据结构:数组,字符串,集合和字典. 数组 数组是最基本的数据结构.Swift编程语言中改变了以前Objective-C时代NSMutableArray和NSArray分开的做法,统一到了Array唯一的数据结构.下面是最基本的一些实现. 1 2 3 4 5 6 7 8 9 10 11 // 声明一个不可修改的数组 let nums = [1, 2, 3] let nums = [Int](count: 5, repeatedValue: 0) // 声明一个可以修改的数

集合-字典 方法练习

- (void)viewDidLoad { [super viewDidLoad]; //用一个或多个键值对初始化一个字典对象,以值,键,值,键,值,键,...,nil的顺序 NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:@1,@"key1",@2,@"key2",@3,@"key3" ,nil]; NSLog(@"dict:%@",dict

what&#39;s the python之基本运算符及字符串、列表、元祖、集合、字典的内置方法

计算机可以进行的运算有很多种,运算按种类可分为算数运算.比较运算.逻辑运算.赋值运算.成员运算.身份运算.字符串和列表的算数运算只能用+和*,字典没有顺序,所以不能进行算数运算和比较运算.比较运算中==比较的是值,is比较的是id.比较运算只能在同种类型下进行比较.字符串的比较是按照顺序依次进行比较.逻辑运算的顺序先后为要用括号来表示. 基本运算符如下: 算术运算 以下假设a=10,b=20 比较运算 以下假设a=10,b=20 赋值运算 逻辑运算  成员运算 身份运算 what's the 内

集合(字典)

# 集合就是字典,数据类型都是set,是通过set来创建# 两个功能:1去重 2关系测试 3无序 s=set([1,3,"hello"])s2={1,2,3} print(type(s))print(type(s2))print(s2) # 去重l=[1,2,2,34,56]print(set(l)) # set是可变数据类型# print({{1,2}:"hello"}) # 运行时报错# print({[1,2]:"hello"}) # 运行

Python列表、元组、集合、字典的区别和相互转换

列表.元组.集合.字典的区别   列表 元组 集合 字典 英文 list tuple set dict 可否读写 读写 只读 读写 读写 可否重复 是 是 否 是 存储方式 值 值 键(不能重复) 键值对(键不能重复) 是否有序 有序 有序 无序 无序,自动正序 初始化 [1,'a'] ('a', 1) set([1,2]) 或 {1,2} {'a':1,'b':2} 添加 append 只读 add d['key'] = 'value' 读元素 l[2:] t[0] 无 d['a'] 列表.元

判断重复数字,不使用集合和字典

随机产生2组各10个数字的列表,要求 取值范围[10,20], 统计20个数字中,多少不同的数字 2组比较,不重复的数字几个,分别是 重复的数字几个,分别是 以下方法为先开辟列表,再对应的数字的位置计数,不使用集合和字典 ###repeated.3 import random l = []#once n = []#repeated m = [0] * 20 for i in range(10): a = random.randrange(1,21) print('{}'.format(a),en

Python的集合与字典练习

集合与字典练习 question1 问题描述:有一个列表,其中包括 10 个元素,例如这个列表是[1,2,3,4,5,6,7,8,9,0],要求将列表中的每个元素一次向前移动一个位置,第一个元素到列表的最后,然后输出这个列表.最终样式是[2,3,4,5,6,7,8,9,0,1] 代码如下: list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] print(list) a = list.pop(0) list.append(a) print(list) 结果如下图: ques

python 数据类型: 数字Nubmer / 字符串String / 列表List / 元组Tuple / 集合Set / 字典Dictionary

#python中标准数据类型 数字Nubmer 字符串String 列表List 元组Tuple 集合Set 字典Dictionary #单个变量赋值countn00 = '10'; #整数countn01 = '100.0' #浮点countn02 = "双权"; #字符串countn03 = '10'; #数字#print("整数 = "+countn00,"浮点 = "+countn01,"字符串 = "+countn0

列表、元组、集合、字典的区别

列表          元组            集合          字典英文             list          tuple              set             dict可否读写      读写           只读            读写          读写可否重复        是            是                否            是存储方式        值           值         

python自学笔记(四)python基本数据类型之元组、集合、字典

一.元组tuple 特性 1.有序集合 2.通过偏移来取数据 3.不可变对象,不能在原地修改内存,没有排序.修改等操作 元组不可变的好处:保证数据的安全,比如我们传给一个不熟悉的方法,确保不会改变我们的数据从而导致程序问题. 二.集合:集合是没有顺序的概念,所以不能用切片和索引操作 1.创建集合:可变的set().不可变的frozenset() 2.添加操作 add(随机插入) update(拆分插入) 3.删除 remove 4.成员关系 in.not in 5.交集(&).并集(|).差集(