如何在数组中存储有序数据
想要在一个地方拥有大量数据是非常常见的,无论是一周中的几天、班级的学生名单、过去100年的城市人口,还是无数其他的例子。
在Swift中,我们使用数组进行分组。数组是它们自己的数据类型,就像String
、Int
和Double
一样,但它们不仅包含一个字符串,它们可以包含零字符串、一个字符串、两个字符串、三个、五百万、五百万甚至更多的字符串——它们可以自动适应以包含您需要的字符串,并始终按照您添加的顺序保留数据。
让我们从一些创建数组的简单示例开始:
var beatles = ["John", "Paul", "George", "Ringo"]
let numbers = [4, 8, 15, 16, 23, 42]
var temperatures = [25.3, 28.2, 26.4]
这创造了三个不同的数组:一个包含人名的字符串,一个包含重要数字的整数,一个包含温度的十进制,单位为摄氏度。请注意,我们如何使用方括号开始和结束数组,每个项目之间都带有逗号。
当从数组中读取值时,我们根据它们在数组中出现的位置来询问值。项目在数组中的位置通常称为其索引。
这让初学者有点困惑,但Swift实际上从零开始计算项目的索引,而不是从一开始计算——例如,beatles[0]
是第一个元素,beatles[1]
是第二个元素。
因此,我们可以像这样从我们的数组中读取一些值:
print(beatles[0])
print(numbers[1])
print(temperatures[2])
提示:确保项目存在于您请求的索引中,否则您的代码将崩溃——您的应用程序将停止工作。
如果您的数组是可变的,您可以在创建后对其进行修改。例如,您可以使用append()
来添加新项目:
beatles.append("Adrian")
没有什么能阻止你多次添加项目:
beatles.append("Allen")
beatles.append("Adrian")
beatles.append("Novall")
beatles.append("Vivian")
然而,Swift确实会监视您尝试添加的数据类型,并将确保您的数组一次只包含一种类型的数据。所以,这种代码是不允许的:
temperatures.append("Chris")
This also applies to reading data out of the array – Swift knows that the beatles
array contains strings, so when you read one value out you’ll always get a string. If you try to do the same with numbers
, you’ll always get an integer. Swift won’t let you mix these two different types together, so this kind of code isn’t allowed:
let firstBeatle = beatles[0]
let firstNumber = numbers[0]
let notAllowed = firstBeatle + firstNumber
这是类型安全,就像Swift不允许我们混合整数和小数一样,除了它被提升到更深层次。是的,所有beatles
和numbers
都是数组,但它们是专门的数组类型:一个是字符串数组,一个是整数数组。
当您想从一个空数组开始并逐个添加项目时,您可以更清楚地看到这一点。这是用非常精确的语法完成的:
var scores = Array<Int>()
scores.append(100)
scores.append(80)
scores.append(85)
print(scores[1])
我们已经涵盖了最后四行,但第一行显示了我们如何拥有专门的数组类型——这不仅仅是任何数组,而是一个包含整数的数组。这就是让Swift确定的原因,beatles[0]
必须始终是一个字符串,也是阻止我们将整数添加到字符串数组中的原因。
Array<Int>
之后的开括号和闭括号是存在的,因为如果需要,可以自定义数组的创建方式。例如,在以后添加真实内容之前,您可能想用大量临时数据填充数组。
您可以通过以不同的方式进行专业化来制作其他类型的数组,如下:
var albums = Array<String>()
albums.append("Folklore")
albums.append("Fearless")
albums.append("Red")
再次,我们已经说过,必须始终包含字符串,所以我们不能尝试把整数放在那里。
数组在Swift中非常常见,以至于有一种特殊的方法来创建它们:您可以写[String]
,而不是写Array<String>
。所以,这种代码和以前一模一样:
var albums = [String]()
albums.append("Folklore")
albums.append("Fearless")
albums.append("Red")
Swift的类型安全性意味着它必须始终知道数组存储的数据类型。这可能意味着通过说albums
是Array<String>
来明确,但如果你提供一些初始值,Swift可以自己弄清楚:
var albums = ["Folklore"]
albums.append("Fearless")
albums.append("Red")
在我们完成之前,我想提一下数组附带的一些有用功能。
首先,您可以使用.count
来读取数组中有多少项,就像您对字符串所做的那样:
print(albums.count)
其次,您可以使用remove(at:)
删除特定索引上的一个项目,或使用removeAll()
删除所有内容,从数组中删除项目:
var characters = ["Lana", "Pam", "Ray", "Sterling"]
print(characters.count)
characters.remove(at: 2)
print(characters.count)
characters.removeAll()
print(characters.count)
删除字符后,将打印4,然后打印3,然后打印0。
第三,您可以使用contains()
检查数组是否包含特定项目,如下所列:
let bondMovies = ["Casino Royale", "Spectre", "No Time To Die"]
print(bondMovies.contains("Frozen"))
第四,您可以使用sorted()
对数组进行排序,如下:
let cities = ["London", "Tokyo", "Rome", "Budapest"]
print(cities.sorted())
这返回了一个新数组,其项目按升序排序,这意味着字符串按字母顺序排列,但数字按数字排列——原始数组保持不变。
最后,您可以通过调用reversed()
来反转数组:
let presidents = ["Bush", "Obama", "Trump", "Biden"]
let reversedPresidents = presidents.reversed()
print(reversedPresidents)
提示:当你反转数组时,Swift非常聪明——它实际上并没有重新排列所有项目,而只是记住您希望项目被反转。因此,当你打印出超过reversedPresidents
,不要惊讶地看到它不再只是一个简单的数组了!
数组在Swift中非常常见,随着你的进步,你将有很多机会更多地了解它们。更好的sorted()reversed()
和许多其他数组功能也存在于字符串中——使用sorted()
将字符串的字母按字母顺序排列,使“swift”变成“fistw”。
如何在字典中存储和查找数据
你已经看到了数组是如何存储具有特定顺序的数据的好方法,例如一周中的天数或城市的温度。当项目应该按照您添加它们的顺序存储时,或者当您可能有重复的项目时,数组是一个很好的选择,但通常通过其在数组中的位置访问数据可能会令人讨厌甚至危险。
例如,这是一个包含员工详细信息的数组:
var employee = ["Taylor Swift", "Singer", "Nashville"]
我告诉过你,数据是关于员工的,所以你也许能猜到各个部分做什么:
print("Name: \(employee[0])")
print("Job title: \(employee[1])")
print("Location: \(employee[2])")
但这有几个问题。首先,你无法真正确定employee[2]
是他们的位置——也许那是他们的密码。其次,不能保证项目2甚至在那里,特别是因为我们把数组做成了一个变量。这种代码会造成严重的问题:
print("Name: \(employee[0])")
employee.remove(at: 1)
print("Job title: \(employee[1])")
print("Location: \(employee[2])")
现在将纳什维尔打印为职位,这是错误的,当读取employee[2]
时,会导致我们的代码崩溃,这很糟糕。
Swift对这两个问题都有一个解决方案,称为字典。字典不像数组那样根据项目的位置来存储项目,而是让我们决定项目应该存储在哪里。
例如,我们可以重写我们之前的例子,以便更明确地说明每个项目是什么:
let employee2 = ["name": "Taylor Swift", "job": "Singer", "location": "Nashville"]
如果我们把它分成单独的行,你会更好地了解代码的作用:
let employee2 = [
"name": "Taylor Swift",
"job": "Singer",
"location": "Nashville"
]
正如你所看到的,我们现在非常清楚:名字是泰勒·斯威夫特,工作是辛格,地点是纳什维尔。Swift调用左侧的字符串——名称、工作和位置——字典的键,右侧的字符串是值。
在从字典中读取数据时,您可以使用创建字典时使用的相同键:
print(employee2["name"])
print(employee2["job"])
print(employee2["location"])
如果您在游乐场中尝试,您将看到Xcode会发出各种警告,如“表达式隐式从'字符串中强制?”到“任何”。更糟糕的是,如果你查看游乐场的输出,你会看到它打印了Optional("Taylor Swift")
,而不仅仅是Taylor Swift
——什么给了?
好吧,想想这个:
print(employee2["password"])
print(employee2["status"])
print(employee2["manager"])
所有这些都是有效的Swift代码,但我们正试图读取没有附加值的字典键。当然,Swift可能会在这里崩溃,就像你读取一个不存在的数组索引时一样,它会崩溃,但这会使它非常难以使用——至少如果你有一个包含10项的数组,你知道读取0到9的索引是安全的。(“Indices”只是“index”的复数形式,以防您不确定。)
因此,Swift提供了一个替代方案:当您访问字典中的数据时,它会告诉我们“您可能会获得价值,但您可能什么也得不到。”Swift调用这些可选数据,因为数据的存在是可选的——它可能存在,也可能不存在。
当您编写代码时,Swift甚至会警告您,尽管以一种相当晦涩的方式——它会说“表达式隐式从'字符串'中强制?”到“任何”,但它实际上意味着“这些数据可能实际上不在那里——你确定要打印它吗?”
可选是一个相当复杂的问题,我们稍后会详细介绍,但现在我将向您展示一个更简单的方法:从字典中读取时,如果密钥不存在,您可以提供一个默认值来使用。
看起来是这样的:
print(employee2["name", default: "Unknown"])
print(employee2["job", default: "Unknown"])
print(employee2["location", default: "Unknown"])
所有示例都为键和值使用了字符串,但您可以为其中任何一个使用其他数据类型。例如,我们可以使用字符串来记录哪些学生已经毕业,并使用布尔值来记录他们的毕业状态:
let hasGraduated = [
"Eric": false,
"Maeve": true,
"Otis": false,
]
或者我们可以跟踪奥运会举行的年份及其地点:
let olympics = [
2012: "London",
2016: "Rio de Janeiro",
2021: "Tokyo"
]
print(olympics[2012, default: "Unknown"])
您还可以使用任何要存储的显式类型创建一个空字典,然后逐个设置密钥:
var heights = [String: Int]()
heights["Yao Ming"] = 229
heights["Shaquille O'Neal"] = 216
heights["LeBron James"] = 206
请注意,我们现在需要如何编写[String: Int]
,即一个字典,其键为字符串,其值为整数。
由于每个字典项必须存在于一个特定的密钥中,因此字典不允许存在重复的密钥。相反,如果您为已存在的键设置值,Swift将覆盖之前的任何值。
例如,如果你正在和朋友谈论超级英雄和超级恶棍,你可能会把它们存储在这样的字典里:
var archEnemies = [String: String]()
archEnemies["Batman"] = "The Joker"
archEnemies["Superman"] = "Lex Luthor"
如果你的朋友不同意小丑是蝙蝠侠的宿敌,你可以使用相同的键重写该值:
archEnemies["Batman"] = "Penguin"
如何使用集合进行快速数据查找
到目前为止,您已经了解了在Swift中收集数据的两种方法:数组和字典。还有第三种非常常见的数据分组方法,称为集合——它们类似于数组,只是您不能添加重复的项目,而且它们不会按特定顺序存储项目。
创建集合的工作原理与创建数组非常类似:告诉Swift它将存储什么样的数据,然后继续添加内容。然而,有两个重要的区别,它们最好用一些代码来证明。
首先,以下是你如何制作一组演员的名字:
let people = Set(["Denzel Washington", "Tom Cruise", "Nicolas Cage", "Samuel L Jackson"])
注意到它实际上是如何首先创建一个数组,然后将该数组放入集合中吗?这是有意的,这是从固定数据中创建集合的标准方法。记住,集合将自动删除任何重复的值,并且不会记住数组中使用的确切顺序。
如果您好奇该集是如何对数据进行排序的,请尝试将其打印出来:
print(people)
您可能会在原始订单中看到名字,但您也可能得到一个完全不同的订单——这套只是不在乎其物品以什么顺序进来。
将项目添加到集合时,第二个重要区别是单独添加项目时可见的。这是代码:
var people = Set<String>()
people.insert("Denzel Washington")
people.insert("Tom Cruise")
people.insert("Nicolas Cage")
people.insert("Samuel L Jackson")
注意到我们如何使用insert()
吗?当我们有一个字符串数组时,我们通过调用append()
来添加项目,但这个名字在这里没有意义——我们没有在集合的末尾添加项目,因为集合将按照它想要的任何顺序存储项目。
现在,您可能会认为集合听起来只是简化的数组——毕竟,如果您不能重复,并且丢失了项目的顺序,为什么不直接使用数组呢?嗯,这两个限制实际上都变成了优势。
首先,不存储重复内容有时正是你想要的。我在前面的例子中选择演员是有原因的:美国演员协会要求其所有成员都有一个独特的艺名,以避免混淆,这意味着绝不允许重复。例如,演员迈克尔·基顿(《蜘蛛侠:归来》、《玩具总动员3》、《蝙蝠侠》等)实际上叫迈克尔·道格拉斯,但由于公会中已经有迈克尔·道格拉斯(《复仇者联盟》、《坠落》、《浪漫之石》等),他必须有一个独特的名字。
其次,集合不是按照您指定的确切顺序存储您的项目,而是以高度优化的顺序存储它们,这使得找到项目的速度非常快。区别不小:如果您有一个包含1000个电影名称的数组,并使用类似contains()
的东西来检查它是否包含“黑暗骑士”,Swift需要浏览每个项目,直到找到一个匹配的项目——这可能意味着在返回false之前检查所有1000个电影名称,因为《黑暗骑士》不在数组中。
相比之下,在集合上调用contains()
运行速度非常快,您很难有意义地衡量它。该死,即使你在集合中有一百万个项目,甚至有1000万个项目,它仍然会立即运行,而一个数组可能需要几分钟或更长时间才能完成同样的工作。
如何创建和使用枚举
枚举——枚举的缩写——是我们可以在代码中创建和使用的一组命名值。它们对Swift没有任何特殊意义,但它们更高效、更安全,因此您将在代码中经常使用它们。
为了演示这个问题,假设你想编写一些代码,让用户选择一周中的某一天。你可能会这样开始:
var selected = "Monday"
稍后在你的代码中,你改变它,就像这样:
selected = "Tuesday"
这可能在非常简单的程序中很好用,但看看这个代码:
selected = "January"
哎呦!你不小心输入了一个月而不是一天——你的代码会做什么?好吧,你可能很幸运,同事在审查你的代码时发现了错误,但这个怎么样:
selected = "Friday "
周五末尾有一个空格,在Swift眼中,带空格的“周五”与没有空格的“周五”不同。再说一遍,你的代码会做什么?
将字符串用于这种事情需要一些非常谨慎的编程,但它也相当低效——我们真的需要存储“星期五”的所有字母来跟踪一天吗?
这就是枚数的用处:它们让我们定义一个新的数据类型,其中包含一些特定的值。想想一个布尔值,它只能有真或假——你不能把它设置为“可能”或“可能”,因为它不在它理解的值范围内。枚举是一样的:我们可以提前列出它可以拥有的值范围,Swift将确保您使用它们永远不会出错。
因此,我们可以将工作日改写成像这样的新枚数:
enum Weekday {
case monday
case tuesday
case wednesday
case thursday
case friday
}
这调用了新的枚数Weekday
,并提供了五个案例来处理五个工作日。
现在,我们不使用字符串,而是使用枚枚。在你的游乐场里试试这个:
var day = Weekday.monday
day = Weekday.tuesday
day = Weekday.friday
通过该更改,您不能意外地使用带有额外空格的“星期五”,或者用月份名称代替——您必须始终选择枚举中列出的可能日期之一。当您输入Weekday.
您甚至会看到Swift提供所有可能的选项,因为它知道您将选择其中一个案例。
Swift做了两件事,使枚枚枚枚更容易使用。首先,当你在枚记项中有很多案例时,你可以只写一次case
写,然后用逗号分隔每个案例:
enum Weekday {
case monday, tuesday, wednesday, thursday, friday
}
其次,请记住,一旦将值分配给变量或常量,其数据类型就会变得固定——您不能先将变量设置为字符串,然后再设置为整数。好吧,对于枚举,这意味着您可以在第一个任务后跳过枚举名称,如下:
var day = Weekday.monday
day = .tuesday
day = .friday
[本文翻译自hackingwithswift,点击链接阅读原文]
评论区