【Python】Set集合类

Set集合类型.

Python集合类型与数学上的集合定义一致,包含0个或多个独特的、无序的数据项。Python集合中的元素类型只能是不可变的对象,如int,float、string和tuple,而List、Dictionary和Set本身都不能作为Set类型中的元素。这里我们指出,在Python中界定可变与不可变主要考察该类型是否能够进行hash()运算,能够进行hash()运算的都是不可变类型,反之无法进行hash()运算的都是可变类型。可能对于hash()运算不是很熟悉,我们插入哈希(hash)运算的定义:

哈希运算可以将任意长度的二进制数值映射为较短的固定长度的二进制值,这个小的二进制数值称为哈希值,哈希值是对数据的一种有损且紧凑的表示形式。

前面曾经说过Python中各种数据类型的可变与不可变,下面我们给出对他们进行hash()运算的结果:
print(hash(2))
print(hash(2.0))
print(hash(1+1j))
print(hash(True))
print(hash((1,2,3)))
print(hash("PYTHON"))
在这里插入图片描述
上面测试的类型分别是int、float、complex、bool、tuple以及string. 接下来我们对于List、Set和Dictionary这些已知的可变类型进行测试
print(hash([1,2,3]))
print(hash({1,2,3}))
print(hash({1:2,2:3}))
注意进行这部分测试时,一次只能测试一句,因为编译器在发现hash()函数内的参数无法进行hash运算时,就会给出错误信息而停止执行。
在这里插入图片描述
由于集合类型相较于前面介绍的序列类型——String、List、Tuple少了索引这一项性质,所以集合没有所谓的切片、查找位置、逆序等等操作。集合常见的操作有创建、添加、删除、复制以及数学概念的交、并、补、差等。

1.集合的创建、添加、删除、复制等.

对于创建一个集合,我们可以直接用{ }来直接给出包含的元素,也可以使用set()函数来将一个组合数据类型中的内容组成集合(注意集合元素的互异性,set()方法会自动去重)。
set1={1,2,3,1}
string="abcaaad"
set2=set(string)
print(set1)
print(set2)
在这里插入图片描述
上面的代码中,我们故意包含了重复元素,但在创建为集合时,重复元素都被去除了。这也是我们进行数据去重的一种好方法。
通过set()或者{ }创建的集合可以添加、删除元素,之所以这么说,是因为还有一种集合是通过frozenset()方法来创建的,这种方法创建出来的集合,无法添加、删除元素,很"frozen".
set={1,2,3,4}
frozenset=frozenset({1,2,3,44})
print(set)
set.add(9)
print(set)
print(frozenset)
print(type(frozenset))
在这里插入图片描述
上面的代码中演示了如何在集合里添加元素——add()方法;也表明了frozenset和set是存在一定区别的,从它的type()就能够看出。
至于集合的删除方法,可以说有三种:

  1. remove(x),如果x在集合中,就移除它,否则产生KeyError异常;
  2. doscard(x),功能和remove一模一样,区别在于x不属于该集合时也不产生异常;
  3. clear(),清除集合中的所有元素,只留一个空集合;
  4. del 集合名:这自然是最彻底的删除,集合本身都被析构,如果在定义同名集合之前继续使用该集合名,会产生NameError异常。
    set={1,2,3,4}
    set.remove(1)
    print(set)
    set.discard(2)
    print(set)
    set.clear()
    print(set)
    set.remove(1)
    在这里插入图片描述
    从这段代码中,我们可以验证关于remove()和discard()的功能说明,同时也可以看出空集合是用set()来表示的,而不是{ },这是为什么呢?因为{ }在Python中被用于表示空字典,从而空集合以示区分采用set()。
    print(type(set()))
    print(type({}))
    set1={1,2,3}
    del set1
    print(set1)
    在这里插入图片描述
    这段代码中印证了我们所说的del 集合名的功能,以及set()和{ }的各自归属。
    集合的copy()方法能够复制一个自身内容的副本,不知道大家发现没有,只有可变对象才会需要copy()来实现复制的功能,不可变对象可以通过=来直接创建一份引用。C++中褒贬不一的运算符重载功能能够实现=的重载,让它成为复制赋值运算符,从而使得可变对象像不可变对象那样以=进行复制(虽然C++中不是采用可变对象和不可变对象这样的概念,因为C++有指针暴露在外)。之前介绍的List也有着copy方法来复制自己,但tuple和Number数值类型绝对不会有这样的需求,根源在于List等可变对象的赋值导致两个变量指向同一块内存空间,并且对其中一个进行修改就会修改这片内存空间的值,而tuple这样的不可变对象赋值时虽然也是指向同一片内存空间,但对其中一个进行修改时,变量会自己去找那个修改后的值然后指向它,而不是改动原来指向的数据。Python有一个理念叫做“一切皆对象,一切皆对象的引用”,这使得它和C++很大不同,而和Java有某种程度的相似。C/C++中的变量都有着自己的内存空间,它们可以有自己的值,即使是指针变量也是有值的,它们的值是“别人的地址”,所以指针赋值时会使得两个指针拥有同一个“别人的地址”,也就意味着两个指针指向同一个内存,从而对其中一个操作会导致另一个指针的数据被修改,这也是指针被诟病得最多的地方,这是不是类似于Python中的可变对象呢。而C/C++中一般变量,例如int、double进行赋值时,都是拷贝一份数据,放在自己的内存单元中,和别人没有任何联系。Java和Python比较类似,但Java有点“四不像”的味道在里面,它里面的int等类型虽然有对应的封装类Integer,但还是可以像C/C++语法那样int a=9;int b=a;,而它对于其他的类型,这当中包括数组类型,都提倡“一切皆对象”,颇有精神分裂的风范。这个部分我们致力于把它搞清楚,对于我们理解C/C++、Java以及Python都是有帮助的。
    首先我们给出一段C++的代码:
#include <iostream>
using namespace std;

int main()
{
    int a=1;
    int b=a;
    cout<<"addr_a:"<<&a<<endl<<"addr_b:"<<&b<<endl;
    int* p=&a;
    int* q=p; 
    *p=9;
    cout<<"我们修改了p指针指向单元的值,但此时q指针也指向那个单元."<<endl;
    cout<<"q指针单元中的值:"<<*q<<endl;
    cout<<"val_a:"<<a<<endl<<"val_b:"<<b<<endl;
    cout<<"addr_a:"<<&a<<endl<<"addr_b:"<<&b<<endl;
    return 0;
}

在这里插入图片描述
熟悉C++并且知道指针工作方式的人可以很好地理解这段代码,这段代码验证的就是我们上面关于C/C++赋值的论述。&a相当于Python中的id(a),获得这个变量的地址,我们看到a和b的值都是1,但他们的地址显然不同。接下来我们再看一段Python的代码:

# -*- coding: utf-8 -*-
a=1
b=a
print(a)
print(id(a))
print(b)
print(id(b))
b=2
print(a)
print(id(a))
print(b)
print(id(b))
a=[1,2,3]
b=a
print(a)
print(id(a))
print(b)
print(id(b))
b[0]=999
print(a)
print(id(a))
print(b)
print(id(b))

在这里插入图片描述
Python中a和b都是int型时,它们的地址是相同的,都是id(1),但当b被修改为2,它的地址就被修改为了id(2).而对于List类型,它的行为就很像C++的指针,修改其中一个,另一个变量引用的值也被修改了。最后给出一段Java中的代码,由于Java是运行在JVM上的,我们很难看到变量的真正地址,所以这里使用hashCode()作为代替,效果是一样的,因为hash码表示了该对象在JVM中的内存位置,Java虚拟机会根据该hash码最终在真正的的堆空间中给该对象分配一个地址。

	public static void main(String args[])
	{
		String str1="123456";
		String str2=str1;
		System.out.println(str1+"\n"+str2);
		System.out.println("str1_addr: "+str1.hashCode()+"\n"
				+ "str2_addr: "+str2.hashCode());
		str2="123456789";
		System.out.println(str1+"\n"+str2);
		System.out.println("str1_addr: "+str1.hashCode()+"\n"
				+ "str2_addr: "+str2.hashCode());
	}

在这里插入图片描述
这是上述Java代码的运行结果,不难看出Java中的String和Python中的str类型行为一致,是概念相同的不可变对象。
OK,关于这部分的内容属于一时兴起,我们回到Set类型的copy方法,它会返回调用它的集合对象的一个副本,也就是操作这个副本不会影响到该集合本身。
set={1,2,3}
set1=set.copy()
print(set)
print(set1)
print(id(set))
print(id(set1))
set1.remove(1)
print(set)
print(set1)
print(id(set))
print(id(set1))
在这里插入图片描述

2.集合的数学运算.

Python中对于集合的数学运算提供了两种表达方式,运算符形式以及面向对象的成员函数形式。

  • S-T,返回一个新集合,当中的元素在S中而不在T中,即差运算,对应于S.difference(T);
  • S&T,数学定义上的交运算,对应于S.intersection(T);
  • S|T,数学定义上的并运算,对应于S.union(T);
  • S^T,返回一个集合,当中的元素是S和T中的元素,但不包括同时在S和T中的元素,即S|T-S&T,是数学定义上的对称差运算,对应于S.symmetric_difference(T)
  • S<=T,判断S是否为T的子集,对应于S.issubset(T);相应的还有S>=T对应于S.issuperset(T),判断S是否是T的超集。

3.集合推导.

在介绍列表类型时,我们曾经说过列表的推导,详细的语法可以看List列表类型,而集合作为一种可变的数据类型,也有着它的推导操作。Set集合与List列表的推导操作几乎是大同小异,简单地说,只是将[ ]换成了{ }。
set1={1,2,3,4}
set2={x**2+x for x in set1}
set3={x**3 for x in set1 if x<3}
print(set2)
print(type(set2))
print(set3)
print(type(set3))
在这里插入图片描述
从这一代码显示结果我们也可以看出集合的无序性,因为set2中的元素和set1的元素对应的次序之间并不是我们指定的x**2+x的函数关系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值