Swift学习笔记--Set

set

除了Dictionary之外,Set是Swift标准库中,另一个主要的无序集合类型,包含一组不重复的值。可以把Set理解为一个只包含key而没有value的集合。本质上,Set也是一个哈希表,因此它有着和Dictionary诸多类似的地方。

[TOC]

初始化Set

例如,我们要创建一个包含所有元音的Set:

var vowel: Set<Character> = ["a","o","e","i","u"]

这里,由于初始化SetArray的方式是一样的,因此,我们要定义一个Set对象时,必须明确使用type annotation。Type inference会把这样的定义方式推导为一个Array

Set的常用属性和方法

作为一个集合类型,Set提供了和Array以及Dictionary一样的常用属性:

vowel.count //5
vowel.isEmpty // false

以及常用的编辑方法:

vowel.contains("a") // true
vowel.remove("a") // a
vowel.insert("a") // (true, "a")
vowel.removeAll() // Set([])

在上面的代码里:

  • contains判断它的参数是否在Set中,并返回一个bool值表示判断结果;
  • removeSet中删除参数制定的元素,如果元素存在就成功删除并返回删除的元素,否则就返回nil
  • insertSet中插入参数指定的内容,如果插入的内容已存在,会返回一个值为(false,插入值)的tuple,否则,就返回(true,插入值)
  • removeAll则删除所有的Set中的元素,留下一个空的集合

遍历Set

Dictionary类似,我们有三种方式来遍历Set。首先,最普通的for循环:

for character in vowel {
    print(character)
}
// e,a,i,o,u

其次,是集合自身的forEach方法:

vowel.forEach { print($0) }
// e,a,i,o,u

通过注释中的方法可以看到,当遍历一个Set时,访问元素的顺序,并不是我们定义Set时的顺序。当我们遍历Set时,遍历的顺序,都会根据当前Set包含的值而有所不同。如果你希望按照某种固定的排序方式访问Set中的元素,就要使用它的sorted方法:

for character in vowel.sorted() {
    print(character)
}
// a,e,i,o,u

常用的Set方法

在理解了Set最基本的操作之后,我们来看一些更实际的Set用法,它当然不仅仅是和Dictionary存储值得形式不同那么简单。其中一个要提到的就是,作为表示一组值的无序集合,Set支持各种常用的代数运算方法。

Set的代数运算

为了介绍各种运算方法,先定义两个Set:

var setA: Set = [1,2,3,4,5,6]
var setB: Set = [4,5,6,7,8,9]

然后,我们就可以对setAsetB进行下面的运算:

//{5,6,4}
let interSectAB: Set = setA.intersection(setB)
//{9,7,2,3,1,8}
let symmetricDiffAB: Set = setA.symmetricDifference(setB)
//{2,4,9,5,6,7,3,1,8}
let unionAB = setA.union(setB)
//{2,3,1}
let aSubstractB: Set = setA.subtracting(setB)

除此之外,上面这些API还有一个"可修改Set自身"的版本,而命名方式,就是在这些API的名称前面,加上form,例如:

setA.formIntersetion(setB) // {5,6,4}

这样setA的值,就被修改成了取交集之后的值。

把Set用作内部支持类型

很多时候,除了Set作为一个集合类型返回给用户之外,我们还可以把它作为函数的内部支持类型来使用。例如借助Set不能包含重复元素的特性,为任意一个序列类型去重。我们给Sequence添加下面的拓展:

extension Sequence where Interator.Element: Hashable {
    func unique() -> [Interator.Element] {
        var result: Set<Interator.Element> = []
        
        return filter {
            if result.contains($0) {
                return false
            } else {
                result.insert($0)
                return true
            }
        }
        
    }
}

在这个例子里,我们使用了Set不能包含重复元素的特性,用result保存了所有已经筛选的元素,如果遇到重复的元素,就跳过,否则,就把它添加到result里用于下一次筛选。这样,我们就可以使用unique来去重了:

[1,1,2,2,3,3,4,4].unique() // [1,2,3,4]

IndexSet 和 CharacterSet

在Swift标准库中,Set是唯一一个支持SetAlgebra protocol类型。但是,在Foundation里,却还有两个额外的类型:IndexSetCharacterSet
其中,IndexSetSet<Int>是非常类似的,例如:

let oneToSix: IndexSet = [1,2,3,4,5, 6]

但当我们要表达一连串正整数时,尤其是这个证书范围比较大的时候,使用IndexSet要比使用Set<Int>更加经济一些。因为Set<Int>会保存这个范围内里的每一个整数,而IndexSet则会使用类似1...6这样的形式保存一个范围。因为,要表达的范围越大,是用IndexSet就会越经济。并且,由于IndexSet也完全实现了SetAlgebraCollection这两个protocol,因此,它的用法和Set几乎是相同的。

另一个类Set类型,就是CharacterSet,它主要表示某一类字符的集合,通常,我们用这个类型来判断字符串中是否包含特定类型的字符,例如:

//String
let hw = "Hellow world"

// CharacterSet
let numbers = CharacterSet(CharactersIn: "123456789")
let letters = CharacterSet.letters

//Actions
hw.rangeOfCharacter(from: numbers) //nil
hw.rangeOfCharacter(from: letters) //

定义好集合以后,我们就可以使用rangeOfCharacter(from:) 来判断String对象是否包含特定的字符了。如果包含,rangeOfCharacter会返回一个Range对象,否则,就返回nil

理解Range 和 Collection的关系

在之前Swift操作符的内容里,我们曾经提到了两个和范围有关的操作符:

  • 1..<5表示的半开半闭区间[1,5)
  • 1...5表示闭区间[1,5]

Countable range

实际上,这两个区间操作符在Swift中,是通过两个struct来实现的,叫做CountableRangeCountableClosedRange,他们都遵从ComparableStrideableprotocol。
其中:

  • 只有半开半闭区间可以表达“空区间”的概念,例如:5..<5,而5...5则包含一个元素5
  • 只有闭区间可以包含区间位置的最大值,例如:1 ... Int.max,而1 ..< Int.max则表示1 ... (Int.max - 1)
    之所以这两个range操作符背后的类型都用Countable开头,意思是指他们是可以被迭代的,也就是可以从头到尾计算范围的值。例如:
for i in 1 ... 5 {
    print(i)
}
//12345

Uncountable range

既然有CountableRange,就不难联想到,是否有uncountable的版本呢?实际上,的确是存在的。只是,他们仅能表示一个区间,但我们不能遍历它。例如:

//The following code will FAIL
for i in 1.0 ... 5.0 {
    print(i)
}

这时,Swift编译器就会提示我们ClosedRange<Double>没有遵从Sequence protocol。于是,uncountable的版本就出现了,就是这个ClosedRange。当然还有一个uncountable的半开半闭区间的类型,叫做Range
为了遍历这样的浮点数区间,我们只能用stride(from: to:by:)和stride(from:though:by:)来指定起始、结束范围以及步进值。前者,类似于半开半闭区间,后者类似于闭区间:

for i in stride(from: 1.0, to: 5.0, by: 1.0) {
    print(i)
}
// 1.0 2.0 3.0 4.0

for i in stride(from: 1.0,though: 5.0,by: 1.0) {
    print(i)
}
// 1.0 2.0 3.0 4.0

Conclusion

于是,按照一个区间可以表示的范围,以及它是否可以被遍历,实际上Swift中一共有四种不同的区间类型:

/*
 *               Half-open       | Closed range
 * +------------+----------------+----------------------
 * | Comparable | Range          | ClosedRange         |
 * +------------+----------------+----------------------
 * | Strideable | CountableRange | CountableClosedRange|
 * +------------+----------------+----------------------
 */

相信在后续的Swift版本里,还会对这一系列区间类型进行改进和优化。但至少现在,它还是会给我们带来一些麻烦。对于一个接受Range<T>类型参数的函数来说,我们甚至无法传递一个ClosedRange<T>类型的对象。

为什么会这样呢?其实,和(Closed)Range无法通过for循环遍历一样,我们无从根据一个CloseRange<T>的结束位置,找到闭区间结束位置的上一个位置,因此,这种转换是无法完成的。如果从Swift语言的角度来说,就是,(Closed)Range仅仅实现了Comparable protocol,而没有实现Strideable protocol。

因此,面对这类情况,我们只能自己根据ClosedRange<T>计算需要的范围,再重新创建正确的Range<T>对象。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容