Python中的字典和集合

導語:本文章記錄了本人在學習Python基礎之數據結構篇的重點知識及我的心得,打算入門Python的朋友們能夠來一塊兒學習並交流。

本文重點:html

一、掌握常見的字典建立,查詢,判別方法;
二、瞭解字典中的defaultdict、子類化Userdict和常見映射類型;
三、瞭解支撐字典和集合背後的散列表的工做原理。

1、常見的字典方法

一、建立方法

分爲字面量句法和構造方法兩種,下面以{"one":1,"two":2,"three":3}爲例java

d1={"one":1,"two":2,"three":3}#字面量句法
d2=dict(one=1,two=2,three=3)
d3=dict([("one",1),("two",2),("three",3)])
d4=dict({"one":1,"two":2,"three":3})
d5=dict(zip(["one","two","three"],[1,2,3]))#zip並行解包
print(d1==d2==d3==d4==d5)#True

以上五種方法建立的字典是相等的。python

二、isintance

映射類型(Mapping Types)是一種關聯式的容器類型,它存儲了對象與對象之間的映射關係。
字典是Python中惟一的映射類型,它是存儲了若干鍵值對(由鍵映射到值)的關聯容器。算法

collections.abc模塊中有兩個抽象基類,分別是Mapping和MutableMapping,它們爲dict和其餘相似的類型定義形式接口。安全

isinstance:斷定object的類型
語法:isinstance(object, classinfo)數據結構

  • 其中,object 是變量,classinfo 是類型即 (tuple,dict,int,float,list,bool等) 和 class類
  • 若參數object是classinfo類的實例,或者object是classinfo類的子類的一個實例, 返回 True;若 object 不是一個給定類型的的對象,則返回結果老是False。
  • 若classinfo不是一種數據類型或者由數據類型構成的元組,將引起一個TypeError 異常。

eg:app

from _collections_abc import Mapping
my_dict={}
print(isinstance(my_dict,Mapping))#判斷數據是否爲廣義映射類型。輸出True.

isinstance和type的區別:
若對象是classinfo中一個類的子類,isinstance能夠判斷出來返回True,而type是不能的。函數

三、字典推導

字典推導:在{}中使用命令語句加for甚至if實現迭代推導出新列表的操做。post

Country_Codes=[(86,"China"),(91,"India"),(1,"United States"),(62,"Indonesia"),(55,"Brazil"),(92,"Pakistan"),(81,"Japan")]

dict1={country:code for code,country in Country_Codes}#推導過程
print(dict1)

dict2={code:country.upper() for code,country in Country_Codes if code>80}#由限制要求建立字典
print(dict2)
#輸出:
{'China': 86, 'India': 91, 'United States': 1, 'Indonesia': 62, 'Brazil': 55, 'Pakistan': 92, 'Japan': 81}
{86: 'CHINA', 91: 'INDIA', 92: 'PAKISTAN', 81: 'JAPAN'}

四、setdefault:處理找不到的鍵

d.setdefault VS d.get
d.setdefault(k,[default])和d.get(k,[default])兩種方法均可以處理找不到的鍵的狀況,區別在於setdefault在返回默認值的同時可以在原字典建立新的k-default鍵值對。
因此更新某個鍵值對但鍵不必定存在時,用d.setdefault更好一些.學習

eg1:處理找不到的鍵

names=["Ailee","Bob","Cindy"]
ages=["19","17","15"]
dict3={x:y for x,y in zip(names,ages)}#用zip能夠並行拆包.
print(dict3)
print(dict3.get("David","20"))
print(dict3)#get處理查不到的鍵時返回默認值,但不會在原字典建立這個鍵.
dict3.setdefault("David","20")
print(dict3)#setdefault處理查不到的鍵時返回默認值,而且會在原字典建立這個鍵.

2、多樣化的字典

一、defaultdict:處理找不到的鍵的另外一選擇

格式:class collections.defaultdict([default_factory[, ...]])
defaultdict是內建dict的子類,它可以在查詢找不到的鍵時爲其創造默認值,由此避免拋出keyerror。其餘功能與dict相同。

eg:defaultdict推導

from _collections import defaultdict
dict3=defaultdict(list,[(x,y) for x,y in zip([1,2,3,4,5],list("apple"))])
print(dict3)
#輸出:
defaultdict(<class 'list'>, {1: 'a', 2: 'p', 3: 'p', 4: 'l', 5: 'e'})

eg:查詢點名冊同窗的出席次數

from _collections import defaultdict

namelist=['Ailee', 'Bob', 'Cindy', 'Ailee', 'Bob', 'Cindy', 'Cindy', 'Cindy', 'Bob', 'Cindy', 'Ailee', 'Bob', 'Bob']
count=defaultdict(int)#使用記錄值數據結構整型做爲默認的工廠函數

for x in namelist:
    count[x]+=1
    
print(count)#defaultdict(<class 'int'>, {'Ailee': 3, 'Bob': 5, 'Cindy': 5})

原理解釋:defaultdict在查詢找不到的鍵時會經過__getitem__調用__missing__,而後__missing__根據default_factory選擇返回默認值。當不輸入default_factory時,會拋出keyerror。
咱們能夠經過print (defaultdict.__missing__.__doc__)來看__missing__的內部實現:

__missing__(key) # Called by __getitem__ for missing key; pseudo-code:
  if self.default_factory is None: raise KeyError((key,))
  self[key] = value = self.default_factory()#爲找不到的鍵建立默認值
  return value

注意:__missing__只能被__getitem__調用,調用__getitem__可用d[k],d.get(k)無效。

default_factory的選擇

  • 類型名稱做爲初始化函數參數
    此類設置根據建立字典的值的需求而定;
    若值以整型記錄可用int;若用列表記錄多個數據可用list。
  • 可調用函數做爲初始化函數參數
    使用任何不帶參數的可調用函數,並以該函數返回值做爲默認值。
    仍以點名code爲例,有兩種方法:
    1)自定義函數:

    def zero():
        return 0
    count=defaultdict(zero)

    2)使用lambda建立匿名函數

    count=defaultdict(lambda :0)

二、子類化UserDict

UserDict繼承自抽象基類(abstract based class)中的MutableMapping。
圖片描述
UserDict是讓用戶繼承寫子類的。之因此傾向於從UserDict而不是dict繼承的緣由是,這是由於在覆蓋重寫dict類的 get(k, default)、__setitem__( )、__contain__( )、__missing__( ) 等方法時,經常又會使用到 mapObj[k]、 k in mapObj、mapObj[k] 等語法形式,這樣一不當心就會形成這些內部方法的無窮遞歸調用。可是UserDict就不會有此類問題。

UserDict有一個data的屬性,是dict的實例。用戶定義UserDict的子類時若是重寫方法,並不會遞歸調用UserDict的其餘方法,而是對UserDict.data進行操做,這樣就減小了用戶自定義dict時防範死循環遞歸的難度。

eg:

import collections
class Modified_Dict(collections.UserDict):#繼承自UserDict
    def __missing__(self,key):
        if isinstance(key, str):#防止遞歸循環,及時拋出keyerror
            raise KeyError(key)
        return self[str(key)]
    def __contains__(self,key):
        return str(key) in self.data
    def __setitem__(self, key, item):
        self.data[str(key)]=item
dict4=Modified_Dict({'Ailee': 3, 'Bob': 5, 'Cindy': 5})#使用新dict類構造字典
print(dict4["Ailee"])#輸出:3
dict4.update({"one":1,"two":2})
print(dict4)#輸出:{'Ailee': 3, 'Bob': 5, 'Cindy': 5, 'one': 1, 'two': 2}

錯誤示範:這裏應該加圓括號創建自定義dict的空字典,不然以後的數據沒法被更新

dict5=Modified_Dict
dict5.update({"one":1,"two":2})
print(dict5)#<class '__main__.Modified_Dict'>發現update失敗 -_-!

UserDict繼承自Mapping基類,諸如MutableMapping.update和Mapping.get也很實用。(截止2017.12.15 未掌握Mapping.get)

三、不可變映射類型

從Python3.3開始,type模塊引入了一個封裝類名叫作MappingProxyType。MappingProxyType提供一個可讀的動態映射視圖,即用戶沒法從這個視圖對原映射進行改動,可是原映射有改動時能夠經過這個視圖觀察到。
此類型特色在於防止用戶錯誤的修改映射。
圖片描述

from types import MappingProxyType
Prize_number={'Ailee': 3, 'Bob': 5, 'Cindy': 5}
dict6=MappingProxyType(Prize_number)
dict6["Ailee"]=6#不支持改動。TypeError: 'mappingproxy' object does not support item assignment
print(dict6)

Prize_number["Ailee"]=6
print(dict6)#{'Ailee': 6, 'Bob': 5, 'Cindy': 5}原映射改動可視。

四、其它映射類型

collections.OrderedDict
OrderedDict可以記住key的插入前後順序。
eg:

from _collections import OrderedDict
d = {'banana': 3, 'apple': 4, 'pear': 1, 'orange': 2}
print(OrderedDict(sorted(d.items())))
print(OrderedDict(sorted(d.items(),key=lambda t :t[1])))

輸出:

OrderedDict([('apple', 4), ('banana', 3), ('orange', 2), ('pear', 1)])
OrderedDict([('pear', 1), ('orange', 2), ('banana', 3), ('apple', 4)])

在以前第二章namedtuple中也提到過。namedtuple的實例方法_asdict()把具名元組以collections.OrderedDict的形式返回。

collections.ChainMap
ChainMap能夠容納數個不一樣的映射對象,而後在進行鍵查找操做的時候,這些對象會被當成一個總體被逐個查找,直到鍵被找到爲止。
查詢規則片斷:

import builtins
pylookup = ChainMap(locals(), globals(), vars(builtins))

想了解更多:
https://docs.python.org/3/lib...
collections.Counter
counter用來統計目標集合中不一樣的元素及其頻數,利用most_common([n])返回前n個頻數最高的值以及相應的計數。

eg:

from collections import Counter
ct=Counter('wasdddsasd')
print(ct)#Counter({'d': 4, 's': 3, 'a': 2, 'w': 1})
ct.update("dassddd")
print(ct.most_common(2))#[('d', 8), ('s', 5)]

3、集合

一、集合的定義與字面量

定義:Python標準文庫給出的定義:A set object is an unordered collection of distinct hashable objects.
翻譯過來就是:set是一個包含不一樣可散列對象的無序集合
種類:集合這種數據結構包含set和frozenset,二者的區別在於後者不可變而前者可變,相似於元組之於列表。所以frozenset相比set不具有修改一類的方法。
本質:集合是許多惟一對象的彙集,因此能夠用來去重。
新建set:

  • 在大括號中直接填寫元素,相似字典
    set1={"apple","banana","pear"}
  • 利用構造方法set(),相似list()
    set4=set("apple")
  • 空集的構造
    注意空集的構造只能用set()而不能用{},{}是空字典而非空集
    set3=set()

新建frozenset:

  • 只能使用構造方法frozenset()
    frozenset1=frozenset(range(5))
    print(frozenset1)#frozenset({0, 1, 2, 3, 4})
    只能使用此方法的緣由是Python中沒有針對frozenset的特殊字面量句法(對於列表的字面量句法就是[]這樣子 )。

集合推導:
集合推導在大括號中進行,思路與列表推導,字典推導相似。
eg:

set3={chr(i)for i in range(100,110)}
print(set3)#{'k', 'f', 'i', 'e', 'd', 'm', 'l', 'g', 'j', 'h'}

二、集合操做

set的操做方法包含frozenset的操做方法,區別在於frozenset不支持就地改變集合的方法,這一點與元組很相似。
下面展現set的操做方法,其中涉及修改自己的不適用於frozenset

  • 集合的數學操做

圖片描述

  • 集合的比較操做

圖片描述

  • 集合的實用操做

圖片描述

4、深刻理解dict和set

若想深刻理解dict和set,首先須要瞭解它們背後的散列表。

一、散列

散列(hashing)是電腦科學中一種對資料的處理方法,經過某種特定的函數/算法(稱爲散列函數/算法)將要檢索的項與用來檢索的索引(稱爲散列,或者散列值)關聯起來,生成一種便於搜索的數據結構(稱爲散列表)。也譯爲散列。舊譯哈希(誤覺得是人名而採用了音譯)。它也經常使用做一種資訊安全的實做方法,由一串資料中通過散列算法(Hashing algorithms)計算出來的資料指紋(data fingerprint),常常用來識別檔案與資料是否有被竄改,以保證檔案與資料確實是由原創者所提供。

二、散列表

  • 若關鍵字爲k,則其值存放在f(k)的存儲位置上。由此,不需比較即可直接取得所查記錄。稱這個對應關係f爲散列函數,按這個思想創建的表爲散列表。
  • 對不一樣的關鍵字可能獲得同一散列地址,即k1≠k2,而f(k1)=f(k2),這種現象稱爲衝突。具備相同函數值的關鍵字對該散列函數來講稱作同義詞。綜上所述,根據散列函數f(k)和處理衝突的方法將一組關鍵字映射到一個有限的連續的地址集(區間)上,並以關鍵字在地址集中的「像」做爲記錄在表中的存儲位置,這種表便稱爲散列表,這一映射過程稱爲散列造表或散列,所得的存儲位置稱散列地址。
  • 若對於關鍵字集合中的任一個關鍵字,經散列函數映象到地址集合中任何一個地址的機率是相等的,則稱此類散列函數爲均勻散列函數(Uniform Hash function),這就是使關鍵字通過散列函數獲得一個「隨機的地址」,從而減小衝突。

減小衝突的方法:

  • 開放定址法
    開放定址法就是產生衝突以後去尋找下一個空閒的空間。函數定義爲:
    圖片描述
    其中,hash(key)是哈希函數,di是增量序列,i爲已衝突的次數。
  • 鏈表法
    散列到同一位置的元素,不是繼續往下探測,而是在這個位置是一個鏈表,這些元素則都放到這一個鏈表上。java的HashMap就採用的是這個。
  • 再散列
    若是一次不夠,就再來一次,直到衝突再也不發生。
  • 創建公共溢出區
    將哈希表分爲基本表和溢出表兩部分,凡是和基本表發生衝突的元素,一概填入溢出表(注意:在這個方法裏面是把元素分開兩個表來存儲)。

散列表的存儲特色
衡量散列表的利用率有一個概念叫作載荷因子:

`α= 已有的元素個數/表的長度`

載荷因子越大,插入到散列表中的元素越多,產生衝突的機率隨之增大。所以一般載荷因子被設計成0.75,保證必定的表元是空的。
散列表的存儲特色決定了它耗費存儲空間的特色。

三、散列表的意義

散列表本質要解決的是查找時間的問題。若是順序查找的話,時間複雜度爲O(n);而散列表,時間複雜度則爲O(1)!直接甩了一個次元,這也就是爲何在大量數據存儲查找的時候,散列表獲得大量應用的緣由。

注:散列表知識引自
做者:SakuraWood
連接:https://juejin.im/post/5a1bd0...
來源:掘金

三、散列表取值算法

圖片描述
給定一個鍵,要麼返回查詢值,要麼拋出keyerror。

四、依託散列表實現的dict的特色

  • 鍵必須是可散列的
    可散列對象知足的要求
    (1)支持hash()函數,而且經過hash()獲得的散列值是不變的;
    (2)支持經過__eq__()方法來檢測相等性;
    (3)若a==b爲真,則hash(a)=hash(b)也爲真。
    原子不可變數據類型都是可散列類型。例如:字符串,字節,數值類型
  • 字典很消耗內存
    緣由在於減小衝突的發生
  • 鍵查詢很快
    時間複雜度爲o(1),列表的遍歷查找對應的時間複雜度爲o(n)。當數據規模較大時能夠明顯發現散列表查詢快人一大步。
  • 鍵的次序取決於添加順序
  • 向字典裏添加新鍵可能會改變已有鍵的順序
    當載荷因子增大到必定程度時(0.75),Python解釋器會爲字典擴容,把原字典的元素存儲到新的散列表中。新的存儲過程當中有可能發生散列衝突,致使新散列表中鍵的次序發生變化。

Tips:不要對字典同時進行修改和迭代。由於你的修改有可能致使鍵的次序發生變化,從而在迭代中遺漏某些數據

五、依託散列表實現的set的特色

  • 集合裏的元素必須是可散列的
  • 集合很消耗內存
  • 能夠很高效地判斷元素是否存在於某個集合
  • 元素的次序取決於被添加到集合裏的次序
  • 向集合裏添加新元素可能會改變已有元素的順序
相關文章
相關標籤/搜索