哈佛-CS50-计算机科学导论笔记-六-
哈佛 CS50 计算机科学导论笔记(六)
哈佛CS50-CS | 计算机科学导论(2020·完整版) - P11:L5- 数据结构 2(数组、链表、树、哈希表、字典树、堆、栈、队列) - ShowMeAI - BV1Hh411W7Up
好的,我们回来了,回想一下我们今天开始时,重新审视数组并指出,如果你做对了,数组搜索是很好的,但一旦你想动态修改数组,它的成本迅速变得非常高。可能需要你大约n步进入一个新的、更大的数组,老实说,随着时间的推移,数据量很大。
即使是大O的n也是昂贵的,你不想不断地复制你的所有数据,避免这种情况的方法是使用指针,反过来将这些结构称为链表拼接在一起,尽管这会增加内存消耗。但有了额外的内存和成本,带来了动态性。
如果我们想的话,插入时甚至可以实现常数时间,但当然我们必须牺牲像可排序性这样的东西。所以我们刚刚看到的这个权衡主题有几个实际的C程序示例,首先是旧式的数组,正如第零周所示。
我们仅使用括号表示法将自己困在了一个角落。所以我们改为使用malloc,这是一个更灵活的工具,可以让我们获取我们想要的内存,并用它来重新创建作为数组实现的列表的概念。但即便如此,我们也看到我必须使用for循环进行复制,或者再次进行复制。
对于这些小程序你甚至不喜欢这样,但对于大型真实世界的软件,所有这些都迅速累积,因此最好尽量避免这一切,达到动态性。所以你可以动态添加到链表的代码实际上是下周问题集五的挑战的一部分,但让我们看看一些构建块,分配。
节点并将它们连接在一起,当我们提前知道我们想要多少个时,这并不是现在的情况,因为我只想要这三样东西,所以我将返回之前的程序,在main内部,我要去声明一个类型,称为结构节点,最初里面有一个数字。
我将称这个整体为节点,这很简单,和我们之前处理的一个人类似,但现在更复杂一些,因为我给结构本身起了个临时名字“节点”,我在结构内部提到这个临时名字,这样我也可以在这里有一个指针,现在是节点。
现在让我们继续,实际上在main中使用这个东西,让我先创建一个空的链表,最简单的方式是用新节点指针创建列表,不幸的是,每当你声明一个没有赋值的变量,它就是垃圾,而垃圾在编程世界中是坏的。
指针再次明确,你不需要显式地初始化它的值。不要像null指向地面那样,而是保持为垃圾值,指向这个方向,这个方向,那种方向,也就是说,你可能在自己的代码中意外地跟随这个箭头指向一个完全虚假的地方。
什么叫做段错误,有些人可能已经在问题集四中体验过。当你触碰不该触碰的内存时,会发生这种情况。因此,垃圾值是坏的,更不用说原因了,所以你很少想这样做,你几乎总是希望将指针初始化为某个已知值,而不是实际地址的缺失。
我们将使用null来表示那里没有东西,但这是我们故意为之的。假设我想插入,就像我之前通过搬运块编号1上台那样。让我继续分配一个节点,我们暂时称之为n。使用malloc,这次请求一个节点的大小,所以故事现在在变化,我不再是个体。
节点内部有足够的空间,节点,这个操作符的大小会算出。
根据这个结构的定义,需要存储一个。
整数和一个指向结构节点的指针,因此我将总是检查n是否等于null。如果是,我将立即退出这个程序,只返回1,因为发生了错误,并且没有足够的内存。但是如果一切顺利,我将继续进入那个节点。
我将进入它的数字字段,并将其赋值为1。接着,我将进入那个节点n,并进入它的下一个字段,暂时将其赋值为null。这就好像我刚刚将带有1的木块分配给null。现在,我将继续更新列表本身,使其指向名为list。
这是我表示整个列表的变量,现在我有一个实际的节点可以指向,我将list设置为n的地址,它指向一个实际的节点,因此在故事的这一点上,我有一个小木块连接到包含1的较大块上。
假设为了讨论,我现在想将数字2添加到这个列表,一个整数。我将继续使用malloc分配n,给自己一个新的节点的大小。我将再次检查,并释放列表,以便不泄漏内存。然后让我继续返回1,以确保我释放了之前已分配的任何内存。
但如果一切顺利,我希望如此,我将继续进入这个节点n。并在它的数字字段中字面地存储数字2,然后因为这个东西也是以有序方式插入的,所以现在下一个是null。如果我确实想把这个数字2的节点放在数字列表后面,我可以去下一个节点,并在内部。
其值我可以说是n,所以这里的这行代码从小块开始,跟随箭头,然后更新第一个节点的下一个指针,指向这个新节点n的地址。最后,让我们再做一次,所以n最后一次调用malloc sizeof node
,让我先做个健全性检查。
如果n等于null,那就发生了坏事,所以现在我将继续,不必担心语法,只需继续调用free list next
,然后继续调用free list
,接着我将继续,处理时间。这只是一个角落案例,在那里发生了一些坏事,但如果没有发生坏事。
如果发生了错误,我将把数字字段更新为三。我将把下一个字段更新为null,现在我要更新列表,n。在这里,之后我可以继续打印这些内容,如果我愿意的话,可以用循环,循环的样子很简单,但事实证明我们可以使用for循环。
在这里也相当强大,但在故事的这个节点上,我的列表指针指向一个节点,这个节点指向第二个节点,第二个节点又指向第三个节点。正如之前有人观察到的,这种双箭头符号在这种情况下并不常见。我敢打赌,我实际上可以在循环中使用它,逐一迭代这些东西。
时候,我们可以在打印时看到这一点,让我继续做这个。四,而不是使用i,因为实际上没有数字在问题中,这不再是一个数组,所以我不能使用方括号符号或指针算术,我需要使用指针,因此这可能感觉有点奇怪。
首先,但没有什么能阻止我用for循环来做到这一点。给我一个临时指针,叫做temp,并将其初始化为列表开头的内容,只要temp不等于null,继续执行以下操作。在每次循环迭代中,不要像i++那样做。
再说一次,现在不相关,但继续更新我的临时指针,让它等于临时指针的下一个字段的值。因此,这看起来可能非常晦涩,尤其是如果你对指针是新手的话。就像上周一样,因为你们大多数人都是,但它和典型的for循环是同一个思路。
你在第一个分号之前初始化某个变量,在第一个分号之后检查某个条件,并在第二个分号之后更新该变量。在这种情况下,它们不是整数,而是我说给自己一个指向列表开头的临时指针,就像我的手指指向的那样。
如果你更喜欢指向列表中某个节点的泡沫手指,就去叫那个临时变量temp,只要它不为null。那是只要它是块的。我想做什么,让我继续,使用printf和%li打印出那个节点的数字字段中的值,就这样,使用这个简单的for循环。
相对简单的for
循环,我可以本质上指向我列表中的第一个节点。并不断更新到下一个字段,更新到下一个字段,直到我的手指在木块列表的末尾走出,从而指向null
。在这一点上,循环停止,没有更多的内容可打印。
对于之前那个问题的回答,我们是否需要使用这个双箭头符号,简短的回答是:不需要。这是这里的秘密成分,这段语法在循环内部。它指向你所指向的对象,跟随一个箭头,然后更新临时变量。现在改为指向那个结构,这在做i
时是等效的。
但这并不像我++
那么简单,你不能仅仅看一个字节。向右或向左,跟随一个箭头,但通过重新分配这个临时变量到你刚刚跟随的地方,这是一种跟随每一个这些橙色箭头的方法,就像我们做的那样。
刚才这一刻,在此之后,为了好好检查,我应该去ah**d
并释放整个列表,让我只是链表,我实际上可以做这样的事情。当列表不等于null
时,所以当整个列表本身不等于null
时,继续ah**d
并像这样获取一个临时指针,指向下一个字段,这样我就能记住。
在当前列表头之后,释放列表节点本身。然后。
更新列表为临时变量,所以这看起来可能很疯狂,很难理解,尤其是在接下来的几天,特别是针对问题集5,你会以更逻辑的方式更好地理解这种逻辑。
从图像上来看,我在这里做什么,首先,只要我的链表不是null
,我就会做以下操作。如果我有三个节点,根据定义它就不是null
,释放我从左到右分配的所有内存。那么我该如何做到呢?如果我有两个。
木块,如果我面前有一个木块,因为那个木块包含指向下一个节点的指针,所以如果我提前释放这个内存,我就把所有后续节点困在了那,因为一旦我告诉计算机,你可以回收这个内存块,对于第一个节点。
这里第52行的代码只是说暂时给我一个名为temp
的变量。第一个节点,指向下一个节点,这就像用我的右手指向下一个节点。这样我就可以在第53行释放列表本身,这不应被字面理解,列表表示链表中的第一个节点,而不是整体。所以当你说释放列表。
这就像释放当前节点,但即便如此,这也是可以的。这个内存已经被归还,我的左手仍然指向每一个后续节点,通过下一个节点来实现,所以现在我可以将列表更新为等于那个临时变量。并继续这个循环,所以这是一个。
像是吃豆人风格那样,从左到右,通过释放第一个节点,第二个节点,第三个节点,然后就完成了,但通过使用一个临时变量提前查看,确保你不会过早地释放内存,从而失去对所有后续节点的访问,好吧。
那是一个大程序,但它是按顺序进行的,从数组开始,过渡到动态分配的数组,最后是一个实现。使用链表,虽然是硬编码的,只支持三个节点,但在这个例子中,你看到了一些可以操作这些节点的样本语法吗?有问题或困惑吗?
帮助解决你那边的任何问题,布赖恩,是的,有人问了类似你之前做的一个例子,为什么我们不能仅仅使用malloc
三次sizeof node
来获取三个节点,以那种方式来做,真是个好问题,难道我不能就这样使用malloc
一次性分配三个节点吗?当然可以,这完全是你的选择,我做得更。
严谨地说,一次一个,但你绝对可以一次性处理所有三个。那样的话,你就需要使用一些指针算术,或者你需要使用方括号。内存,作为节点的一个数组,然后将它们拼接在一起。因此我假设出于演示目的,尽管我们有这些小的语法。
在一个真实世界系统中,你不会一个一个地插入,一般是先插入一个,然后经过一段时间,你想插入两个,所以你分配更多内存,然后再过一段时间,你想插入三个,因此在这些代码块之间有间隔。在现实世界中,还有其他问题或困惑吗?
是的,还有另一个问题,为什么malloc
会失败分配内存?malloc
为何会失败?这很少见,如果你在编写这样一个。内存消耗大的程序时,有那么多数据,你可能会耗尽内存。也许是两千兆,也许是四千兆或更多,但malloc
很可能会返回null。
你应该总是检查,事实上,我敢说,在Mac和PC上。这至今仍然是程序冻结的最常见原因之一。导致你整个计算机重启的,确实是因为有人做了愚蠢的事情,就像我今天和上周已经做过的多次那样。
通过触碰不该触碰的内存,所以在问题集四和现在的五中。每当你遇到那些段错误时,你的程序就崩溃了。这就像是你整台Mac或PC崩溃,因为比你更有经验的人在他们的代码中犯了同样的错误,让我们快速。
最后的例子涉及链表,这实际上是这种一维结构,从左到右,然后我们会添加第二个维度,看看这能给我们带来什么,但我们仍然有我们的列表,它首先指向数字,指向另一个内存块,那里是数字四。
这就是数字五,所以我们有一个大小为三的链表。但我故意将数字分散开来,二、四、五,因为假设我们确实想将更多数字插入这个列表。但按排序顺序进行,结果是我们需要换一种思维。
当我们在中间添加节点时,不是在末尾,也不是在开头。比如说,我们想在中间分配更多的节点,这实际上需要更多的工作,那么我们应该如何去做呢?假设我们想分配数字一,并且我们想添加数字一。
我们可以使用这样的代码,这与我们之前使用的代码相同。我们分配一个节点的大小,检查它是否等于 null,然后用我们关心的值初始化它为 null,图示上可能看起来像这样。它有点漂浮在变量 n 中。
不再显示的节点,我只是指向我分配的数字一。那么这看起来是什么样子呢?就像将数字一放到位,我们运气不错,那里正好有一块内存。那么我现在想要做什么呢?我想去啊**d并连接这个,所以我可以直观地去做。
在此之前,我可以拔掉这个,然后将这个插入这里,这很合理。但如果我在此之前没有做任何其他事情,那么已经存在一个问题。我已经孤立了三个节点,二、四和五。孤立一个节点意味着忘记它在哪里,如果我在代码中没有另一个变量,或者指向。
列表的最初部分,我字面上孤立了剩余的列表,上周的技术含义是,现在我有一个巨大的内存泄漏,内存中的节点,直到你重启计算机或程序,你实际上再也无法找回。
退出时,操作系统会为你清理,因此你不想这样做。运算顺序实际上很重要,所以我应该首先,可能应该意识到这一点,列表,所以我真正应该做的是指向这个,大致上这样做,但让我规定这两个都指向同一个节点。
现在我的新节点,也就是代码中显示的 n,指向这个东西。现在我可以进行某种交换,因为我已经指向了最终目的地,现在我的列表是 n,因此我有插入的变量,这样是正确的,长话短说,运算顺序很重要。
如果我像之前一样从图形上处理这个,简单说列表等于n,如果这是n,坏事就会发生。的确,我们最终会使2、4和5孤立,从而泄漏大量内存,通常泄漏任何内存都是坏事。所以我不想那样做,来看看正确的代码,正确的代码是。
我要从n开始,指向与列表最初指向的相同的东西。然后去更新列表,使得它们两个当前都指向重复的内容。
然后更新列表以指向新的节点,所以这次代码与之前有些不同,因为之前我们一直把它加到末尾,或者我口头提议我们把它加到开头,这里我们在添加它。
的确,在开始时,实际的步骤和代码有点不同。让我们做一个最后的例子,如果我们想分配三个,那我得再malloc一个节点,数字三,假设它最后在计算机的内存中某个地方,这样三就到位了,我现在该怎么做。
像之前一样好好插入这个东西,我不想更新它。指针这样移动,然后把这个插头插到这里,因为现在我把这两个孤立了。所以这又是错误的步骤,当你在中间插入时。如果你关心以有序方式插入,这应该先更新。
我应该有点作弊,现在只有一个物理插头,所以我们就假装这在正常工作,安全地说n和之前的节点已经安全。我可以拔掉这个,然后去更新最后一个箭头,指向新节点的正确位置。让我们再看看代码,如果我去这里。
我在图形上看到节点数字三漂浮在空间中,同时也在四处指向三,目标再次是避免任何内存泄漏或节点孤立。好了,我们即将抛弃链表,因为正如你们中的多位所注意到或可能想到的那样,它们是好的,但可能不是很出色。
它们的优点在于它们是动态的,我可以通过在开头插入来添加它们。如果我真的想这样做,而不关心有序,但如果我想保持有序,在中间或末尾插入就还有很多工作要做,因为那是O(n),如果我一直遍历这些东西的话。
箭头使我们获得了动态性,但我们增加了,我们从根本上为自己打开了一个全新的世界,我们现在可以使用指针作为线索来使用内存。我们可以把内存当作画布,随意绘制任何我们想要的值,而我们的值是二维的。左右的维度,如果我们给自己一个第二维度,假设我们开始思考一些不同的东西。
左右,但同时也上下,所以对计算机来说这没有意义,计算机只是将内存视为字节0123。但我们人类可以更抽象地思考这些数据结构,以一种对我们现实世界熟悉的方式,树木而不是那么多。
从地面生长而来,但如果你熟悉家谱,你可能会有一个家长或祖先,然后有后代在纸上的图形上挂着,比如你在小学时可能制作过的那样。我们可以利用这种树结构的想法,它有一个根。
这种分支和生长,从上到下,实际上更像是一个家谱,而不是土壤中的真正树。因此,树的这个概念,我们可以从链表中吸取一些教训,但我们也可以重新获得数组的一些特性。
我们可以按如下方式进行,考虑一下,我们即将调用一个从第一个新节点开始的二叉搜索树,它的大小为七,回想一下,如果它是有序的,我们可以对这个数组应用二分搜索,并从中间开始查找。然后我们可以左右分半,接着类似地向左移动。
所以二分搜索的复杂度是大 O,反复地把电话簿对折。问题是二分搜索要求你能够以常数时间通过简单的算术(如括号)索引数组。
从零开始到 n 减一,再到 n 减一除以二,得到中间点。你必须能够对数据结构进行算术运算,我们刚才提议摆脱随机访问,而是更多地过渡到动态数据数组。但如果我们这样做,如果你和我开始思考,不再局限于一个维度。
但在两个维度上,如果我们改变思维方式,想象一下。可以将数组视为一个二维结构,不仅有宽度和长度,还有高度,因此我们在视觉上保持它的缝合,所有这些值之间的关系,但你知道我们可以用什么将所有这些值缝合在一起。
指针是将内存中的事物结合在一起的新东西。如果内存中的事物是数字,那很好,它们是整数,但如果我们为它们投入更多内存,使用一个节点,将整数包装在节点中,使得该节点不仅包含数字,还包含指针,我们可能可以画出一幅图。
就像家谱一样,根节点在最上面,然后是孩子,左孩子和右孩子,这一定义重复出现。事实证明,计算机科学家确实使用这种数据结构,链表,你可以通过添加更多的节点来向树中添加更多的元素。
甚至低于1、3、5和7,只需使用更多指针将它们拼接在一起,以便成长这棵树。但一个好的计算机科学家会认识到,你不应该随便把这些数字放在随机位置,你的时间应该使用某种算法并注意。
有人注意到这棵树的模式吗?谁能在聊天中用语言或文本来表达一下这些节点?在这棵树中,它们并不是随机排列的,而是非常有意图地从左到右、从上到下,以某种方式排列的。谁能指出这个东西的定义是什么,除了它只是被绘制出来。
像你在它们中间放置的那样,在它们的上面你放置了中间的数字。在5和7之间你放置了6,所以在它们的上面你放置了中间数字。确切地说,这个模式适用于所有的数字,在1和3之间是2,在5和7之间是6。必须是中间数字,换句话说,我可以概括它,选择这个树中的任何节点。
它的左子节点将小于它的值,而它的右子节点将大于它。比如4,它的左子节点是2,小于4;它的右子节点是6,大于4。我们可以再做一次,去看2。它的左子节点是1,较小;它的右子节点是3,较大。
对于6,它的左子节点是5,较小;它的右子节点是7,较大。因此,这实际上是一个递归数据结构,如果你不介意上周回顾递归的话。递归不仅是通过调用自身的数据结构,它在某种程度上也是递归的。毕竟,这是什么东西?这是树,是的,我会承认,树。
这里的节点4,从技术上讲有两个子节点,而每个子节点本身就是一棵树,它是一棵更小的树,但其定义是完全相同的,再次是数据结构。这实际上将是一个使用递归代码的机会,我们很快会看到,但现在注意我们又取得了什么成就。
使用指针的动态性,这样我们如果想的话,可以通过在底部按正确的顺序串联更多节点来添加更多节点,同时我们也保留了一个重要的顺序。这个数据结构是二叉搜索树,确保左子节点总是较小,右子节点总是较大,因为现在我们可以开始搜索这个东西。
更有效率。那么,如果我想搜索数字3,我该怎么办呢?我从树的开头开始,就像在链表中,你从列表的末尾开始。因此,在树中,你从根节点开始,搜索3。那么我该怎么办呢?3显然小于4,就像在第零周时我拆解它一样。
削减树的一半,因为显然不会在这里,所以我们。数字二,这是另一棵树,只是一个更小的子树。我要如何找到数字三呢?我看右边,因为它更大,砰,我找到了。但相反的,八,我会从这里开始,我会在这里看,我会在这里看,然后得出结论:没有。
它不在那里,但每次我搜索八时,我都在忽略这棵树的一半,这个子树的一半,等等,所以看起来你会实现与我们在零周时看到的相同类型的能力和性能。那么我们如何将这个想法转化为代码呢?我们已经有了所有的构建块。
让我继续提议,使用之前的节点,而不是我们之前使用的。对于一个链表,它看起来像这样,有一个数字和一个称为next
的指针,但我们可以把这些东西称为任何名字。让我们继续并为不仅仅是一个数字留出空间,左边的一个,我称之为右边的两个,仍然是指向一个结构体节点的指针。
所以用之前的术语,但现在我有两个指针,而不是一个。这样一个可以概念上指向树,另一个可以指向右边,并指向一个更大的子树。我们如何实现类似二分搜索的东西呢?好吧,让我们来讨论递归。构建摩罗金字塔时,我们强迫它变得相当酷。
使用递归,没错,你可以这样做,但金字塔确实是我所称的递归。物理结构或虚拟结构,指针,现在递归真正开始闪耀。那么,让我们考虑一下,如果我在C中声明一个函数,它是一个数字。它将根据定义从根节点向下搜索,我们如何实现这一点呢?我会。
我将提出的函数将返回一个布尔值,true或false。这个数字是否在树中,是或否,它将接受两个参数,一个指向节点的指针,也就是树。我可以称它为根节点或其他任何名称,它将接受一个数字,就是我关心的数字,无论是四、六、八还是其他任何数字。
我的第一段代码将是什么呢?让我来做我一直在宣扬的最佳实践。每当你处理指针时,要检查是否为null,这样你的程序就不会冻结、崩溃或发生其他坏事。因为谁知道,也许你会不小心或者故意地把一个null指针传递给这个函数,搞砸了。
这没关系,只要你的代码针对null,如果树是null的话。显然那里没有树,所以数字不在那儿,你就返回false。这是我们的一个基本情况,除了树本身的数字。再次强调,这个箭头符号表示,获取树,这是一个节点指针,所以获取这个指针,它自己。
在数字字段中,如果你要查找的数字来自参数,并且小于树自身的数字字段,那么这意味着你想要向左移动。而在电话簿中我会去电话簿的左边,这里我们要去左子树,但我该如何搜索子树呢,这里重要的是一个。
树就是树,这是一种递归数据结构,树在之前已经存在,所以我已经有了可以用来搜索较小树的代码,子树,如此表达意味着从当前节点开始,走向左子节点,并传入相同的数字,数字没有变化,但树在缩小,我已经在代码中有效地将树一分为二。
忽略右半边,我将返回那个答案。否则,如果我关心的数字大于当前节点中的数字,就进行相反的搜索。因此,就像在电话簿中一样,它不断变小,这里我一直在搜索更小的子树。
因为我在从上到下的过程中不断削减分支,左或右。还有一种最终情况,让我把这个抛出来,抛给大家,第四种情况,口头或文本上还有什么我应该检查和做的。一些人建议,如果树本身就是这个数字,如果树本身包含这个数字。
是的,所以如果树中的数字等于我正在查找的数字,那就为真。这时代码又变得有些令人困惑,只有上面是假的,下面是对的,但在这两个中间分支中没有,毫无讽刺之意,但没关系,因为我的代码设计成这样。
如果我搜索左子树,换句话说,我在树的叶子上,那么它将返回假,因此没关系,如果我搜索数字八,它甚至不在树中,我只会在掉出树的末端,看到,哎呀,空值,我将返回假,但如果我看到。
在这个过程中,调用了这两个递归调用来搜索,而不是自己回答真或假,而是返回较小问题的答案,通过分别搜索左树或右树。所以再一次,这就是递归开始变得不是强制性的或。
甚至并不一定是强制的,但确实是适当的,当你的数据本身是递归的,那么递归作为一种编码技术,确实发光了。所以如果最终我们有哦,还有小的优化,我们当然不需要明确检查数字是否相等,我们可以假设,如果它不为零且不是。
必须正好站在它上面,所以我们刚返回了 true。那么让我重新总结一下,这现在是一个二维数据结构,它比链表要好,因为现在是二维的,我重新获得了二分搜索,这真是太棒了,只要我。
按照这个二叉搜索树的定义保持我的数据有序,但我肯定是付出了代价,对吧,没什么东西是绝对比其他任何东西更好的。在我们迄今为止的故事中,树的缺点是什么?我在这里秘密或不那么秘密地付出了什么代价?
或者时间,或者开发者时间,或者金钱,或其他一些资源,无论是个人的,还是物理的,或真实世界的。有什么想法?嗯,我认为插入不再是常量时间,我想你需要更多的内存。你需要内存来排序两个指针而不是一个,这样时间就会更长,因为如果我需要。
保持排序的顺序,我不能仅仅把它放在顶部,我不能只把其他所有东西往下推。因为那样东西可能会乱,这种情况下似乎不太可行。即使我保持顺序,如果我添加,例如另一个数字,再添加一个数字,我持续把它塞在顶部,它可能会变得非常冗长。
我可能需要保持事物的平衡,如果你愿意,更大的要点是指针,所以现在我的节点变得比这些东西还要大。我现在有空间不仅仅存储一个数字和一个指针,还有另一个指针,这当然会再次占用更多的空间,因此需要权衡。
让我们继续,啊先问问小组,当涉及到插入时,为什么我们不考虑一下插入的运行时间可能是什么。当插入到二叉搜索树中时,如果你想像往常一样打开网址,让我继续,啊提出这个问题,运行时间是什么。
插入到二叉搜索树中,如果你想把数字零插入到那棵树中,或者想插入数字八或介于两者之间的任何数字,或者更大或更小,关于最高柱子的胜利率大约是60%!
你认为对数 n 和良好的直觉,坦率地说,这将是正确的答案,这种直觉在任何时候都是正确的。当你谈论二分搜索时,几乎可以说是某种对数的,但我们也看到了分而治之。在归并排序中有 n log n,因此认为约十个百分点也是合理的。
而 n平方实际上是糟糕的,所以 n平方就像我们迄今为止看到的最糟糕的情况,这表明一棵树比链表还要糟糕,甚至比数组还要糟糕,值得庆幸的是,我们还没有到达那个假设的点。那么为什么呢?如果我们考虑一下刚才的图。
如果我们考虑刚才的树,它看起来大致是这样的,插入到一个树中涉及什么呢?假设我想插入数字8。那么我从这里开始,显然它属于右侧,因为8更大。
我去这里属于右侧,因为8更大,我去这里它属于右侧,因为8更大,因此将会创建一个新节点。在这里的某个地方,即使它不适合屏幕,我绝对可以调用malloc
,我可以更新几个指针,然后就搞定了。
我们给树添加了第八个节点,所以如果我从根开始,走了一些步骤,1、2、3,我如何将其概括为大O符号呢?如果你把一个二叉搜索树整理得漂漂亮亮,像这样,平衡的话,那个二叉搜索树的高度。
结果是,如果现在在这个故事中,n的对数,那么n的底数为2的对数将是树的高度,所以如果你把n个节点、n个数字以这种漂亮的排序方式平衡,整体高度将是log n。那么插入的运行时间是多少呢?这相当于找到新的数字属于哪个位置要多少步。
这个位置是1、2、3。而事实证明,8的底数为2的对数确实是3,所以数学上有时可能会有一点舍入误差,但一般来说,它确实是大O(log n)。但是如果我们稍微马虎一下,如果我们稍微马虎,开始。
插入节点,如果你愿意,可以给我们带来一些坏运气。例如,假设我去d,让我在这里随便做点什么,假设我去d,插入数字1、2和3,使得这就是逻辑上发生的事情。如果这是根,它符合二叉搜索树的定义。
它是没有左子树的,这并不是严格的问题。因为这里没有违反搜索树定义的东西,只是没有东西。两个在正确的位置,三个在正确的位置,因此这也是严格来说是一个二叉搜索树,但它有点边缘案例,或者说是一个扭曲的案例。
你插入东西的方式最终在二叉搜索树中,实际上更像是什么,如果你想传达给聊天和布莱恩的话。人们说它看起来像一个链表,是的,即使我把它画成从上到下的样子,在某种二维的感觉中。
这其实只是艺术家的表现,这棵树是一个二叉搜索树。但它也有点像一个链表,所以即使给定一些糟糕的设计,也可能偶然退化成不同的数据结构。即使在插入1、2、3时,我也可能允许这种扭曲。
这种情况会变得冗长而复杂,此时一切都是O(n),这就只是一个链表,只是刚好是对角线而不是左右延伸。从直觉上讲,不是从代码的角度,没有正式的语言,但这里有一个解决方案。确保这个包含一、二、三的树不会在一开始就变得冗长而复杂。
聊天中有几个人建议,把树的顶部作为新的根节点,所以如果我将两个节点作为新的根节点。让我快速模拟一下,过一会儿我会揭示我认为你刚才表达的内容。如果我确保在插入这些节点时,不会天真地一直向右延伸。
我在行使一些判断时,如果我注意到我的数据结构、我的树变得有些冗长而复杂,也许我应该进行旋转,实际上改变根节点的定义。我们不会讨论实现这一点的代码,但结果是,这正是正确的直觉。
如果你上更高一级的数据结构和算法课程,尤其是在计算机科学领域,你将学习像AVL树或红黑树这样的树,它们是不同类型的树数据结构,它们内部嵌入了算法,以便在需要时进行调整,确保。
如果你在插入时,或者在删除时不断重平衡树,长话短说,这可能会花费你额外的时间,但如果你有大量数据,保持树的平衡、对数高度是不冗长而复杂的,从某种意义上说,可能是根据你的应用而定的。
整体上可以节省你不少时间,所以我们可以说插入操作确实是O(log n),但这依赖于你确保保持它的平衡,而这将涉及比我们今天讨论的更多的代码。不过,这确实是一个可能的设计决策。那么,关于树还有什么问题吗?
特别是二叉搜索树,我们几周前开始讲数组。现在我们有了链表,这很好,但不是很好,树似乎可能很好,但总是有权衡,它们消耗我们更多的空间。但我敢打赌,我们可以继续将这些想法结合起来,构建其他数据结构。
有什么特别的情况吗?是的,有一个问题问,为什么如果像一、二、三都在右侧的一个序列中会成为问题。是的,真是个好问题,为什么这是一个问题?也许并不是,如果你的数据集不大,且值不多。
说实话,谁在乎呢?如果只有三个元素,绝对不在乎。如果是十个元素,如果是一千个元素,天哪,如果你的计算机足够快,那可能有一百万个元素也没关系,但如果是两百万个元素,或者再大一点,那么问题就来了。你正在构建的是什么业务?你的应用是什么?
写下你的数据有多大,你的计算机有多快或多慢,这在最终可能非常重要,确实,当我们对比一些算法时,线性查找的表现。比如,比较冒泡排序、选择排序和归并排序,尽管这些属于不同的运行时间类别,O(n log n)和O(n²),请记住这一点。
显著的差异,O(log n)在搜索的上下文中比O(n)要好得多,所以如果你的数据结构是字符串型,想象一下就像在查找一本有千页的电话簿,但二分查找却让它不那么冗长,给你10步,而不是1000步来搜索同样的页面。
即使在第零周,我们也看到了这些不同运行时间类别之间的显著差异。那么,让我们看看能否取两者的优点。我们见过链表,也见过树,如果我们把它们结合在一起,提取这些结构的最佳特性进行构建。
事实上,我觉得一种更宏大的数据结构就像是数据结构的圣杯,其插入和查找的时间复杂度既不是O(n),也不是O(log n)。但如果有一种数据结构,其运行时间是常量时间O(1),那简直是圣杯,如果你有内存的话。
这样一来,如果你想搜索或插入一个值,啪,你就完成了。啪,你就完成了,而不需要线性或对数的运行时间。所以让我们看看能否追求这个目标,我提议引入一个主题叫做哈希表,哈希表是另一种数据结构。
链表的数组,最终是将数组和链表结合起来。让我们看看这是如何实现的,我将创建一个大小为26的数组,并开始将我的数组垂直绘制,因为这样在视觉上更好,但这些仍然是艺术家的表现。
我们通常从左到右绘制数组,但现在开始从上到下绘制。接下来会更有趣,比如我想存储像字典那样的名字,或者像你的手机中的联系人一样的名字。如果你想跟踪你认识的所有人,那就太好了,不要找人。
更好的是,我提议在英语中故意使用26个字母,从a到z。所以我们假设位置0是a,位置25是z,现在我开始将所有朋友插入我的新手机,进入联系人应用程序。
或者实际上是从a到z,当我插入一个新朋友或联系人的时候,让我把他们放入一个与名字本身有某种关系的位置,让我们不要从头开始,也不一定要按字母顺序放置。
在这个数组中,不仅是从上到下,而是在一个特定的条目中。假设我想添加到我的联系人中的第一个人是阿尔布斯,那么我会提出,因为阿尔布斯以 a 开头,他将放入 a 的位置,所以在这个数组中的第一个条目。假设我下一个想添加的是扎卡里亚斯,他的名字以 z 开头,所以他,又一次,我跳来跳去,我从 0 跳到。
25 但这是一个数组,我可以在常数时间内做到这一点,你,方括号。所以这是常数时间,我不需要把他放在阿尔布斯之后,我可以把他放在任何我想要的地方,假设第三个人是赫敏。那么我会把她放在位置 h,为什么呢?因为我可以做这个数学计算,我。
可以搞清楚 h,好吧,我可以直接跳到字母表的那个字母,并且感谢 ASCII 和进行一点算术,我也可以把它转换成一个数字,所以,五、六、七,因为 h 最终映射到第八个字符或位置,这些其他人,最终也在我的地址簿中,所以他们。
这里并没有那么多数据,但我把每个人都放在这里,但可能会有。这到目前为止,我有点走运,我只认识那些名字独特以某个字母开头的人,但当我在学校认识某人并把他们添加到我的联系人时,嗯,比如哈利,必须放在同一个。
位置。这是个问题,如果我想存储赫敏和以 h 开头的人。但是如果这是一个数组,那绝对是个致命问题,所有事情都崩溃了。因为我可以是的,扩大数组,但如果我扩大数组,它的大小就是 27,那么此时我如何知道哪个数字对应哪个字母。
只是变成一团糟,但如果我借用链表的概念,如果我把我的数组变成一个链表的数组,所以是的,尽管出现了赫敏和哈利的冲突,这没问题。如果发生这种情况,我只是会把他们串联在一起,从左到右放在一个链表中。
所以这并不理想,因为现在我需要两步才能到达哈利,而不是用方括号表示。但至少我仍然可以把他放进我的地址簿,所以这是一个权衡,感觉还算合理。好吧,另一个人海格,好的,现在我需要三步才能到达我的地址簿中的海格,但三步总比没有好。
所以我们再次看到一个问题的表现,哈希表确实就是这样的数据结构,它是一个链表的数组,至少可以这样实现。它的基础是引入哈希函数的概念。这实际上是我们将在函数中看到的,它将允许我们不仅映射所有。
赫敏、哈利和海格,还有罗恩、卢平、斯内普和小天狼星,分别到达他们的目的地。是确定性的,也就是说这里没有随机性,每次我看到这些人的名字时,我都会确定他们所属的位置,而这个位置永远不会改变。那么我该如何做到这一点呢?实际上,这与问题解决本身有关系。
函数就是这样,这就是我们所定义的问题解决,这也是任何语言中的函数,函数在这个黑箱中将是某种秘密成分。那么哈希函数是什么呢?哈希函数实际上是一个函数,无论是数学上的还是编程中的,它将作为输入。
或者说哈利,它返回一些输出,而哈希函数的输出通常是一个数字,在这种情况下,我想要的数字是在0之间,哈希表的概念不仅仅是屏幕上的图像,而在实际代码中,我实际上需要编写一个C语言函数,它以字符串,或者说字符指针作为输入,返回一个在0到25之间的整数。
和25,所以我知道海格对应的数字是7,那么这个哈希函数到底做了什么?它以像阿尔巴斯这样的输入,并输出0。它以像扎卡里亚斯的人作为输入,并输出25。你可能会看到这里的模式。我将为实现这样的功能编写的代码是。
我可能会查看用户输入的字符指针,它会查看第一个字符,对于这两个字符分别是a或z,然后它会进行一些数学运算,减去65或其他的,这样我会得到一个在0到25之间的数字,就像凯撒密码或我们过去对字符串的一些操作一样,因此从这里开始。
不过我们现在可以利用这个构建模块,或许能更有效地解决我们的问题。我并不喜欢,尽管我为哈利、赫敏、海格以及现在的露娜、莉莉、卢修斯和薇薇安腾出了空间,但这些链表有些过长,而且有点儿像。
链接,如果你愿意,因为它们看起来像链环围栏或链中的小链接。这是这些链或链表,它们很长,而我试图实现常数时间O(1)的做法有些愚蠢,但实际上,尽管有些名字确实只需一步,有些却需要两三步。
四个步骤,所以开始变得复杂,那么这里的优化是什么?如果你开始感到不适,因为你太受欢迎,联系人太多,L的处理时间比其他人多,我们可以做些什么来改善这种情况,同时仍然使用哈希表,或许逻辑解决方案是在碰撞太多的时候。
你有太多名字互相碰撞,我们如何能改善我们的性能,并接近位置,还是一步而不是两步、三步、四步,所以真的一步,因为这就是我们的终极目标。有些人建议你应该关注的不仅仅是第一个字母,例如,是的,听起来不错,所以如果看一个字母。
这个人的名字显然不够,因为我们中有很多人的名字以h、a或z等开头,为什么我们不看两个字母,从而降低我们发生这些碰撞的概率呢?所以让我继续,重组并专注于赫敏、哈利和哈格里德的问题。
为什么我们不继续并取我们的数组,想象一下它可能不仅仅是在那个位置为h,而是具体想象那个位置,作为h a,然后h b h c h d h e h f,一直到h z,再到i,列举出所有可能的字母对,从a a到z z,但这似乎对赫敏来说。
赫里在数组中的h e位置,而哈利则在h a,现在是h ag。哦,天哪,像哈格里德仍然在同一个位置,那么可能更好的解决方案是什么?再说,这并不是太糟糕,像两步并不是大问题,尤其是在快速计算机上。但对于足够大的数据集,如果我们不再讨论。
你通讯录中的人,但也许是全世界所有拥有谷歌账户或推特账户的人,你想快速搜索这些信息,名字以h开头,a n z和其他所有名字,最好能更分散一些。那么我们该怎么办呢?好吧,和前两个字母相比。
坦率地说,我认为这个逻辑延伸是使用前三个字母,所以也许这是h-a-a。
这个桶是h-a-b,a e点h-a-c-h-a-d-h-a-e一直到h z z。然后是i a a,但现在当我们对三个朋友进行哈希时,赫敏进入了h e r。可以说这个数组的元素哈利,h a r进入那个桶,哈格里德现在有自己的桶h-a-g,其他人也是,所以似乎。
解决了这个特定问题后,你仍然可以想象,我必须考虑,还有其他以hag或h-a-r或h-e-r开头的哈利·波特名字来寻找其他碰撞。因为你可以想象使用四个字母,但我们付出了什么代价?像我一次又一次地解决这个问题,给我带来了。
一步我可以通过简单的ASCII数学来计算我应该跳到这个越来越大的数组中的哪个索引,但我付出了什么代价,布莱恩,你有什么想法想分享吗?是的,有些人说这会消耗很多内存。
哦,我的天,这占用了大量内存,那么之前占用了多少内存呢?让我稍微查一下,,,可以说是26个桶,数组中的元素,当然这并不算太糟,感觉相当合理,26个插槽。但缺点是链可能会变得有点长,三个名字、四个名字,甚至更多。
但如果我们有一个从a到z的组合,而不是从a到z,那就是26乘以26,总共676个桶。听起来并不是特别大的问题,但这比我们迄今为止在内存中处理的大多数东西都要大。并不算大,但如果我们有3,那就是26种可能性乘以26再乘以26,从aaa到zzzz。现在我的数组中有17576个桶,问题并不是我们在使用它。
内存,因为老实说如果你需要内存,就用吧,这没问题。只是把硬件投入这个问题,买入并升级更多内存。但问题是,我可能不知道多少人,名字以hzz或azz开头,或者字母表的任何这些字母组合。
许多桶将是空的,如果你想要一个数组并希望随机访问,它们必须存在,以便你的算术每周都能顺利进行,你只需使用方括号表示法跳转到你关心的地方,所以找到这种权衡或找到这些权衡的拐点,某种程度上是一种艺术,也是一种科学。
对于你特定的数据、你特定的应用,弄清楚时间、空间哪个更重要,或者两者之间的某个平衡,而在问题集五中,你将看到你实际上必须通过努力最小化自己最终的内存使用和计算机的时间使用来找到这种平衡,但。
让我指出一点,实际上,这个哈希表的概念,到目前为止肯定是我们所看过的最复杂的数据结构。这些可能比你家里的扑克牌还要大,但如果在某个时候你需要为某个游戏排序,有时你需要。
如果你想把它们完全洗牌,为了整洁,你可能会不仅按数字排序,还按花色分类,将红心、黑桃、梅花和方块分到不同的类别,所以说实话,我只是为了隐喻而把这四个桶放在这里,并且已经给它们贴上标签。
提前准备好黑桃,那是一个桶,这里有一个方块的形状。然后我们这里有红心,然后是四叶草,所以如果你曾经没有认真思考过这个,因为它不是特别有趣,你可能会无意识地开始把它们摆放和按花色排序,然后也许按数字,但如果。
如果你已经完成了这一步,如果你看到哦,那是红心10,那是红桃A,你知道,是的,最终你可能关心的是它是红桃,但现在我只是将它放入红桃桶中。这里是红桃2,我将把它放入红桃桶。
我将把这个放在这里,你可以逐渐地对每一张卡进行哈希,哈希实际上只是查看一些输入,并在这种情况下基于输入的一些特征生成一些数字,比如一、二或三。无论是卡片的花色,就像我现在做的,还是其他的。
这是基于字母表的,那么我为什么这样做呢?我不会做完整的,因为52步会花费很长时间,而且很快会变得无聊,但我为什么这么做,因为实际上,你可能已经将这些桶摆在你面前,但你为什么这样做呢?
如果我们知道它是哪个桶,我们可能会更快地找到东西,甚至可以做到一两步,没错,差不多是这样,你开始获得这些优化,至少作为人类,老实说,我处理四个较小问题比解决13个卡片问题要容易得多。
特别是如果我在找特定的卡片,现在我可以在13张卡中找到,而不是52张。所以这就是一种优化,将卡片哈希到特定的桶中,然后继续解决较小的问题,这不是哈希表的本质。
哈希表的核心是存储信息,但以更快的方式获取信息。因此,回到索非亚的问题,如果她只想找像红桃A这样的卡片,解决一个大小为13的链表问题,如果你将输入分组,那么就可以更快速地访问数据。
获取数据的速度可能不一定是一步到位,可能是两步、四步,甚至是13步,但通常来说,比起线性或对数时间,所需的步骤会少一些。理想情况下,你要选择哈希函数,以尽量减少碰撞元素的数量。
使用的不是从a到z,而是从aa到zz等等。那么让我来问一个问题,关于这个数据结构的运行时间,当我的所有联系人都在那里的时候,手机需要多少步骤才能找到赫敏或哈利·波特。
或其他人,所以我再一次看到你们中有80%的人说常数时间O(1)。再次说明,常数时间可能意味着一步、两步、四步,但是一些固定的数字,不依赖于n,约有18%的人说线性时间,我必须承认,约20%的人说线性时间。
从技术上讲,在渐进和数学上,真实世界与学术界之间存在区别。因此,在这里,或者更确切地说,真实世界的程序员会说,正如索菲亚所说的,确实比一个有52张牌的大桶要好,速度就是快,确实快四倍。
找到或填补这13张牌而不是52张,客观上来说是更快的。但学术界会说,是的,但渐进地,渐进是相当大的。我不断描述的那种渐进,走13步在技术上是13张牌,这在技术上是n除以4。是的,确实是13,但如果是n的话。
总的来说,这个桶的大小最终会是n除以4。当我们讨论大O和Ω时,我们会扔掉低阶项,去掉常数,比如除以四,或加上其他东西,所以我们去掉这些,技术上它仍然是O(n)的哈希表搜索。
但在这里我们再次看到现实世界与理论世界之间的对比,确实,如果你想进入学术辩论,链表或数组,在那时你不如从左到右搜索链表,但如果你提前对这些值进行哈希,桶,它最终关系到实际时间,所以当你实际上。
看着墙上的时钟,使用索菲亚的方法比用数组或链表的方法所花的时间更少。因此,提到的大O表示法中,大家认为是O(n)的说法是正确的,但在现实编程中,这可能会是一个净收益,因此多练习,减少对理论的关注。
这些事情,确实这是一个挑战,这个问题五,我一直提到的,将挑战你实现一个表,包含十万多个英语单词,我们将简要给你一个包含每行一个英语单词的大文本文件,你的目标之一将是加载。
所有那140,000多个单词的表,现在如果你简单地使用一个有26个桶的哈希表,从A到Z,你会有140,000多个英语单词,其中有很多单词以A、B、Z或任何其他字母开头,如果你可能选择A到ZZ,可能更好,或者AAA。
经过zzz也许会更好,但在某个时刻,你会开始使用过多的内存,这对你来说并不好。问题集五的一个挑战是愉快地挑战你的同学,如果你选择参与,你可以运行一个命令,这将把你放在大名单上。
课程网站上会显示你使用了多少或多么少的RAM或内存,以及你的代码运行所需的时间。因此,我们只是把那些学术性的说辞暂时搁置一旁。尽管n除以四(n divided by four)的方法在实践中效果要好得多。
这并不是在内存中布局的唯一方式。现在我们有了所有这些构建模块,其中一个数据结构将被称为尝试(try),而try实际上是检索(retrieval)一词的缩写。
尽管它的发音并不明显,树(tree)是一种通常用于存储单词或其他更复杂数据而不仅仅是数字的树形结构。因此,尝试(try)实际上是由节点(node)构成的一种树形结构,你可以在这里看到一种模式。列表(lists)和数组(array)在某个时刻,计算机科学家们开始变得有些创造性。
他们字面上开始将不同的数据结合在一起,因此一个尝试(try)看起来像这样,比这里更好,这就像一个矩形(rectangle)或正方形(square),但在这个节点(node)内部实际上是一个大小为26或z的数组。我们要做的是,每当我们插入一个单词,比如名字“哈利”(harry)或“海格”(hagrid)时。
不论是赫敏(hermione)还是其他人,我们将逐个字母地走过他们的名字,比如h a g r i d,我们将从一个节点到另一个节点遵循一系列指针。例如,如果这是从a到z或从0到25,这里是字母h的位置,所以如果此刻的目标是插入我们的第一个联系人,例如哈利(harry)。
我将从树的根节点(root)开始,查看字母h的位置,并在心中记下哈利(harry)的h是从这里开始的,然后如果我想在哈利(harry)中插入字母a,我将去找字母a的位置。
现在我将有另一个指针,哦,对不起,不是哈利(harry),是海格(hagrid),首先是h a g r i d。那么我刚刚做了什么?尝试(try)仍然是一棵树,每个节点都是一个数组,而每个数组都是指向其他节点的指针数组。因此,我们实际上是在这里将所有东西混合在一起。
和之前的构建模块一样,这棵树中的每个节点(node)从上到下都是一个指针数组(array of pointers)。检查哈格里德(hagrid)是否在我的联系人中,我实际上从第一个节点开始,跟随h指针,然后跟随a指针,再跟随g指针,接着是r指针、i指针,最后检查d。
指针里是否有一个布尔值,可能只是表示是或否,这里有一个叫h-a-g-r-i-d的人。请注意此时没有其他字母标注,也没有其他绿色框,绿色只是为我们的目的表示一个布尔值,这意味着没有任何人名叫h-a-g-r-i-a,h-a-r-h-a-g-r-i-d。
我在联系人中存在这个,但请注意,如果我继续往下走,插入哈利会发生什么。注意哈利和海格共享h、a,然后这个第三个节点,但接下来,指针。用来存储r和y,注意那里的绿色,数据结构中的一种检查标记,一个布尔值,表示我在我的上下文中有一个名叫h-a-r-r-y的人。
然后如果我们添加赫敏,她共享h,然后也共享第二个节点,但赫敏需要一些新的节点。
特性,这种复杂性的原因,因为这可能是我们到目前为止见过的最奇怪的结构,即使我在电话本中有十亿个名字。我找到海格究竟需要多少步骤,随时欢迎在聊天窗口中插入你的意见。如果我在数据结构中有十亿个名字,其他名字的数量并不影响找到赫敏或其他人的步骤,仅仅取决于她们名字的长度。
联系人中,我需要多少步骤来查找并检查海格是否在其中。人们说六个,六个h a g r i d,如果我有二十亿个名字,四个需要,至少在这些人名的长度上是常量时间。数据结构中的书籍,那么这意味着什么,尝试。
这是我们可以稍微学术化的地方,如果你假设任何人类的名字,无论真实还是虚构,字符的数量是有限的,也许是20个。
或者100或者其他什么,总之超过六个,但可能少于几百。然后你可以假设这是常量,所以它可能是大O,比如200,名字。但这就是常量,因此技术上来说,尝试给你提供了查找时间和插入时间的大O为1,因为它不依赖于n。
是数据结构中其他名字的数量,它取决于输入。如果你假设世界上所有的名字都合理长度小于某个有限值,比如6或200或者其他什么,那么你可以称之为技术上是。大O为1,即常量时间,所以这里是整个目标。
今天,像是尝试达到常量时间,似乎是线性时间或对数时间或我们见过的其他任何东西,但,代价,我们刚刚付出了什么。如果你看到,为什么尝试不一定,还是有一些问题,嗯,实际上。比如说,如果假设你在联系人列表中有两个人。
有一个人叫丹尼尔,另一个人叫丹妮尔,你知道丹尼尔在你的列表中,所以l会有一个类似于真的布尔运算符,但那么,如果你的l是一个运算符而不指向丹妮尔的另一个l,你该怎么到达丹妮尔呢?
这是个好问题,算是一个边缘案例,如果有人的名字,比如丹尼尔,d-a-n-i-e-l,而我想你说的是丹妮尔,d-a-n-i-e-l-l-e。那么第二个名字稍微长一点,让我说明我们可以解决这个,我没有展示代表树中每个节点的代码,让我建议我们可以继续拥有。
即使在下面,如果我们在这棵树中有丹尼尔,我们也可以通过在丹尼尔下方再增加几个节点来拥有丹妮尔,只需再添加一个绿色勾选,所以在代码中是可以解决的,尽管从图形表示上并不明显,但绝对是可解决的。
在前缀树中,可能还有另一个缺点,尽管你能以O(1)的时间解决丹尼尔问题,有什么想法吗?对了,还是静音状态,哦,来了,哦,好的,我需要,花费大量的,呃,内存来容纳这一切,这可能需要很多时间,很多,呃。
可能会减慢系统的速度,没错,你可以从我这幅图中看出,虽然我们只向这个数据结构添加了三个名字,但我的天,像这里所示的指针,尽管它们可能都是null,对吧,如果没有箭头就暗示它们是null,0x0,但即使存储null 0x0,也是8个零位,所以这并不。
实际的内存使用却缺乏效率,我们并没有高效地使用它,我们花费了大量的位、字节,或者无论你想如何衡量,因为即使考虑到h,我只使用了26个指针中的一个,25个指针可能初始化为null,这意味着我浪费了25个指针,你可以想象得越来越少。
在这棵树中,名字以h-a-g-r-i-d开头的可能性越小。丹尼尔提供了一个不错的例子,而丹妮尔并不会经常发生,当然在这棵树中你得到的越低,正如e10所说,你浪费了大量的内存,因此是的,你获得了,价格。
你可能在使用兆字节、千兆字节的存储空间,因为最重要的数组属性是它们都是连续的,你必须让每个节点包含一个大小为26的数组,或者其他任何东西,内存会反复使用,所以在这方面也是一种权衡,尽管理论上可能是理想的。
理论并不一定意味着,有些教科书上效率较低的东西在现实世界中可能更有效率,并且,在你的第五个问题集中,呃,拼写检查器,呃,当你建立这个字典时,我们随后用它来对非常大的文本语料库进行拼写检查,你将开始体验到一些。
这些现实世界的权衡你自己,嗯,我们今天想结束的是看一下。你可以用这些种类的数据结构做些什么,只是为了让你尝试一下你还能去哪里,能解决什么其他类型的问题,再次到目前为止我们已经看过,数组,它们实际上是最简单的。
数据结构而且它们甚至不是,结构本身,它只是连续的,内存块。课程中你有这种,一维数据结构,它允许你连接。内存中的节点,让你能够,去分配内存、插入和删除节点,如果你想的话。然后我们有,树,这在某种程度上给了我们最好的,数组的两全其美。
还有链表,但我们必须花费,更多空间并使用更多指针。然后当然哈希表将这两种想法,数组和链表合并在一起,这开始运作,确实这就是你所,检查的。但随后当然还有字典树,乍一看似乎更好。
但是并不是没有巨大代价,正如e10所说,所以结果是,所有这些构建块在你手边,实际上你可以将它们作为低层,实施细节。来解决更高层的问题,这就是所谓的抽象数据。结构或抽象数据类型,抽象数据结构是一种你。
你可以想象实现一些现实世界的,问题,通常这会与一些其他数据一起实现。在这个层面上,但你在思考你所,最终构建的内容。那就是抽象,将低层实现,细节简化。为了讨论或解决更高层的问题,那么这样的数据结构是什么呢。
队列是一个非常常见的,抽象数据结构,什么是队列。好吧,那些在,队列中长大的人,确实是它名字的来源。队列是一个具有,特定属性的数据结构,所以如果你站在。商店或餐厅外,在,健康的时光里等待入内。
你通常在队列中,但队列有一个重要属性,至少。如果你生活在一个公平的社会,你会想,如果你是,第一位排队的,先进先出。如果你是第一位排队,然后他们开始让在你后面的人进入,那会显得有些令人不悦,因此,如果队列实现正确。
它有一个称为,FIFO(先进先出)的属性,而我们人类,具备这一属性。队列通常有两个操作,至少与之相关联。
nq 和 dq 只是约定,你可以称其为添加和删除或插入,删除等。不管怎样,nq 和 dq 是比较,常见的说法,所以入队意味着你走到。不得不等待,出队意味着他们准备好服务你或,那是出队,描述了。一个关键属性就是,它是先进先出,所以你如何实现。
那么,有关该数据结构有趣的是,它比代码中的实际事物更重要,你想实现某种公平的排队系统,因此你将其视为队列,但坦率地说,如果我们要将这个例子翻译成数组,人员数组可以实现一个队列。
你可以使用一个人的链表,底层工作是在较低级别的实现细节,但如果我们将这种现实世界的类比翻译成代码,将排队在商店外面进入,使用数组的话,使用数组表示队列的缺点是什么,即使我们正在做。
从现实世界到代码的转变有点大,但可能会有一些缺点。使用数组的缺点可能是什么,如果你正在考虑在一条线中做这件事,你必须把一个人拿出来。
但你无法真正动态排序。
在那之后更改记忆,是的,是个很好的观点。想象一下有一条线,假设有一条线可以容纳10个人在苹果店外。现在在健康危机期间,让人们进入的限制是一次只能容纳那么多人,所以假设他们有空间容纳10个人,间隔六英尺,这实际上是一个非常贴切的类比,今年比以往任何时候都更明显。
但正如瑞安所说,如果你想让某人,如果你想从队列中删除某人,那么排在第一位的人将进入商店,第二位也将进入商店。使用数组的问题似乎是,现在你基本上在队列的开头有空位,但在队列的末尾仍然没有空间。
对于新来的人来说,现在有一个明显的现实世界解决方案,你只需说:“嘿,大家能否向前走几步。”但这效率不高,不仅仅是商店的问题,而是在代码中,这涉及到值的复制,如果有两个人刚被允许进入商店,你必须移动八个值,两个位置。
所以现在你的双端队列操作是O(n),这感觉并不理想,如果我们是这样的话,但这无疑是队列的一个挑战。使用数组也有限制,因为如果你到达苹果店时,已经有10个人在排队。
线上,他们不让你进入,他们会说对不起,我们已经满了。因为最终队列中会有更多的空间,链表将允许你不断添加更多的人,即使商店外的队伍变得非常长,至少链表可以让你服务所有客户。
随着时间的推移出现的数组,固定大小会使这变得更加困难,你可以分配一个更大的数组,但你得询问所有人。这里不,回去那里,我的意思是你不断在内存中移动人或值,所以这只是说,要实现这一点。
现实世界中有一种队列的概念,在计算机世界中也很常用,以表示某些概念,比如打印队列。当你向公司发送文件时,会有一个队列,理想情况下,第一个打印的人是第一个收到打印件的人,之后队列也在软件中被使用,但。
除了提示,还有一个叫做栈的数据结构,它也可以通过数组、链表或其他方式在底层实现,采用后进先出(LIFO)原则。如果你想象一下自助餐厅的托盘,在大家都在校园里使用托盘的健康时光。
你会记得托盘通常像这样堆叠,最后放上去的托盘是第一个被取出的。如果你去服装店或自己的衣橱,如果你不把东西挂在衣架上或放进抽屉,而是像这样堆放所有的毛衣,这就是我想要的毛衣。
最简单的方式是使用后进先出,因此我不断取出黑色毛衣,如果我把所有的毛衣都存储在这个栈中,你可能永远也得不到下面的红色或蓝色毛衣。这就是数据结构的原因,所以后进先出实际上是使用的属性。
来描述栈,栈的有用与否取决于实际计算。随着时间的推移,我们会看到栈确实发挥作用,那两种操作通常被称为推入(push)和弹出(pop),这与添加、删除或插入是同一回事,但术语有所不同。
通常会从栈中推出,这就是我把一个值推入栈中,但这也是后进先出。然后还有另外一种数据结构,称为字典。字典是一种抽象数据类型,这意味着你可以用数组、链表、哈希表、字典树或其他方法来实现它。
一种抽象数据类型,允许你将键与值关联。这里最好的类比确实是在现实世界中,像一本老式字典那样的字典。
书本形式的纸质印刷品,书里面有什么呢?一堆关键字,一堆加粗的词,比如苹果、香蕉等等,每个词都有定义,也就是所谓的值,它们通常按字母顺序排列,以便于你更快地查找。
我们的一些朋友在另一所机构中区分了栈和队列的概念。字典是一种抽象数据类型,将键与值关联,通过键查找值,就像你通过单词本身查找词义一样。字典实际上随处可见,你可能并不这样认为。如果你曾经去过甜绿,例如,在纽黑文或剑桥。
这里是一个沙拉店,如今尤其可以提前在网上或应用上订购,然后进入店里,从货架上取食物。但在剑桥和其他城市,他们实际上在货架上贴有字母a到z,想法是如果我去取我的。
沙拉可能在d区,如果布莱恩去取的话,它就在b区。如此类推,你可以想象一些反常的边缘情况,这种数据结构,这个字典,其中沙拉的字母并不一定是万无一失的。你可以想到一个反常的边缘情况,甜绿非常出色的系统。
你能想到这里的限制吗?即使你从未去过甜绿或从未吃过沙拉,这个系统如果以你的名字进入商店并取物品,可能会出现什么问题?有些人说如果两个人有同样的名字,可能会有问题,是的,如果两个人有相同的名字。
你开始将东西堆叠起来,甜绿实际上会开始将一个沙拉叠在另一个沙拉上,这实际上是一个有趣的数据类型在另一个数据类型之上的实例。因此,这些都是像我们正在不断重组的自定义拼贴。
不断将它们重新组装成更有趣、更强大的想法。但如果有很多b字母,我肯定会看到这个货架的有限高度。因此,甜绿在使用数组时就像使用栈,因为数组是固定大小的,所以这里垂直空间也只有这么多。
你可以看到一个现实世界的限制,那么甜绿如果出现这种情况怎么办?把b放在c区或者d和e区,谁在乎呢?在现实中,你的眼睛可能会左右扫视。但在算法上,这会减慢速度,在最坏的情况下,如果沙拉。
或者甜绿真的很受欢迎,你的名字可能是阿尔巴斯,但你的沙拉区。如果他们的空间不够,这也是一个有效的算法决策,只是在其他地方腾出空间。但再次强调时间和空间之间的权衡,还有一些。
堆栈和队列,这些抽象数据类型可以以不同方式实现,它们有不同的属性,各自分别是fifo或lifo。这里是一个最终的回顾,在我们今天最后的时刻,关于这些想法如何表现,不同于,[音乐]。
从前有个叫杰克的人,他在交朋友方面很困难。
杰克没有这项本领,所以他去找他认识的最受欢迎的人。他走向蓝色,问我该怎么做,感到沮丧。
好吧,路开始说,看看你是多么不同,确实,杰克,我确实这样做。来我家,我会给你看它们。
所以他们去找杰克,杰克给路展示了盒子,里面放着他所有的衬衫和他的。
裤子和袜子,路说我看到你把所有的衣服都堆在一起,为什么不穿。
有时还有其他的,袜子。
我把它们洗净并放入盒子,然后第二天早上来。
我跳起来,去盒子里取我的衣服,从顶部拿下来,路迅速意识到问题所在。
堆放,当他伸手去拿东西阅读或穿着时,他选择了一本顶书或内衣,然后完成后他。
会把它放回去,回去时它会放在堆的顶部我。
知道解决方案的路说,得意洋洋地说,你需要学会开始使用队列。
一个衣柜,当他把盒子倒空后,你刚好把它扔掉,然后他说,现在杰克。
在一天结束时,把你的衣服放在左边,当你把它们收起来时。
然后明天早上,当你看到阳光时,去右边取你的衣服。
从队伍的末端你难道没看到吗,路说,这样会很好。你会在穿之前穿过每一件。
穿东西两次,所有的东西都排队放在他的衣柜和架子上。杰克开始对自己感到相当自信,都是因为路。
还有他美妙的提示,好吧,这就是cs50的全部,我们会再见。
哈佛CS50-CS | 计算机科学导论(2020·完整版) - P12:L6- Python从语法到应用实战 1 - ShowMeAI - BV1Hh411W7Up
嗨,大家,很高兴见到大家,在这个假期,如果布莱恩或我能解决任何评论或关注,请告诉我。向布兰登、安德鲁以及其他在场的朋友问好。没有,我想念红头发的布兰登,来看看萨默维克,很高兴见到你。布莱恩,你想看看有没有。
好的,这里是cs50,本周的内容。
六号,这又是那些稀有的日子之一,在短短的时间内,你将能够说,你学会了一种新的语言,今天的这种语言将是被称为python的语言。我们认为,可以通过一些更熟悉的方式来介绍python,课程刚开始于零周时我们介绍的。
说你好,世界,然后事情迅速升级,变得更加晦涩,更加难懂,我们引入了c和这样的语法,当然。这确实做了完全相同的事情,只是在屏幕上打印出你好,世界。但要求你理解并包含所有这些,各种语法。
所以今天,所有这些复杂性,c语言的所有语法突然,开始融化,剩下的就是这个,被称为python的新语言。这将以这行代码来实现同样的,目标。也就是说,python往往更容易,但这是因为它建立在这个基础上。
作为人类早年开始的传统,构建这些低级语言如c,意识到缺少什么功能,哪些是一些痛点,然后,在这些旧语言之上进行叠加,所以,真的有数十种,甚至几百种语言。总是有一个子集在任何给定时间都非常,流行。
python就是这些非常流行的语言之一,也是我们将要研究的第三种语言。确实在学期的这个时候,所以,让我们去**介绍一些python的语法,确实通过与我们过去所见的进行比较,因为无论今天的一些主题有多新。
他们应该都是熟悉的,因为我们将再次看到循环、条件、变量、函数返回值,几乎将会是一个功能的翻译,现在的特性,当然在scratch的世界中,这只是一个。拼图块或一个功能,其目的是,屏幕,在第一周我们将其翻译为。
这里有更多晦涩的语法,你必须了解,这个反斜杠,n 来重新表示换行符。
然后当然,这种语句必须以分号结束。今天在这种称为Python的语言中,等效的代码行将非常简单,就是print,而不是printf,我们仍然有这个。
双引号,但是,反斜杠n和分号已经消失了。如果你经常为忘记这些愚蠢的东西,比如分号而自责,Python现在将是你的朋友。好吧,让我们看另一个例子,我们如何获取用户输入。注意我们有一个称为ask的拼图块。
它说询问你的名字是什么,然后等待,下一个难题。说无论人类输入了什么,前面都加上“你好”这个词。从今天开始,我们将看到一些这样的代码,答案等于获取字符串“你的名字是什么”,然后用printf打印出来,给其他的。
在Python中,这些复杂性也将逐渐消失,我们将看到这样的东西,因此不再提到变量的类型。分号也不再存在。
百分号s以及打印的额外参数,所以实际上,让我们去看看这些,转到cs50 ide,稍等片刻。在cs50 ide中,我将继续写我的第一个Python程序。为此,我将继续进行。
创建一个最初称为hello的文件。像在C语言中一样,Python程序有一个标准的文件扩展名,即.dot pi,而不是.dot c。我将执行我所提议的最简单的转换,我将继续说打印“你好,世界”,我将保存我的文件,然后我将去我的终端窗口,使用。
然后我们将执行dot/slash hello或类似的命令,但今天我将简单地运行一个命令,命令本身被称为Python,我将传递我刚创建的文件的名称作为命令行参数,哇,按下Enter键。这是我的第一个命令行参数,因此,这是我在Python中的第一个程序。
所以这非常强大,让我们继续创建这个我刚才提到的第二个程序。这次我也将继续回答。我将继续获取用户输入,并将使用我们之前在C语言中做过的get string,继续询问你的名字是什么。
“你的名字?”我不打算麻烦分号,但在这里我将继续说打印“你好,”然后在百分号s内部加一个空格,实际上我将继续使用加号运算符。
然后,字面上是“答案”这个词,但还没有工作,这还不能工作,因为getstring,结果证明就像它在c中一样。它在python中也不存在,所以我需要做一件事情,那就是不再是#include某个东西,而是字面上说来自cs50,导入getstring,因此在c的世界中。
回想一下我们包含的cs50.h,它有像这样的函数声明。
getstring和getint等,在python的世界中我们将做一些类似的事情,但。
语法略有不同,我们将说来自cs50的,写入。导入的内容是包含一个特定称为getstring的函数。
现在,我可能出现的任何错误都消失了,如果我继续保存这个文件,然后执行python。space hello dot pi并按回车,现在我。
可以继续输入我的实际名字,瞧,我看到了“你好,戴维”。所以让我们仔细看看这段代码有什么不同,并考虑一下我们在这之后还可以做些什么。所以再一次,注意第三行,不再提到字符串。如果我想要一个变量,我只需继续并给自己一个名为answer的变量,函数仍然叫getstring,c,分号。
在我最后一行代码中,打印的是它,确实是打印,而不是printf。然后这是新的语法,但在某种意义上,它会更简单,而不需要提前考虑我想要的百分号s和占位符。这个加号运算符似乎在为我做些什么,让我继续。
这里我问一个问题,这个加号运算符似乎在做什么,因为它不是算术意义上的加法,我们不是在将数字相加。但这个加号显然是在做一些事情,给我们一个可视化的结果。彼得,有什么想法,这个加号在做什么,是在连接字符串,对吧?
连接字符串,这是描述一个字符串与另一个字符串连接的术语。因此,它与我们在c中没有的那个join块的字面翻译非常相似。在c中,我们必须使用printf,我们必须使用百分号s,而python会更友好一些,这样如果你想要空格和那个变量的内容,我们可以。
只需使用这个加号运算符,而我们必须做的最后一件事当然是导入这个库,以便我们可以访问getstring函数本身。好吧,让我们继续参观一下python的其他一些功能,然后主要进行许多实际示例。请记住。
在我们刚看到的例子中,我们有这一行代码,它从用户获取一个字符串并将其存储在一个名为answer的变量中,我们有第二行代码,它是结合在一起,但事实证明,尽管这比在c中方便得多,因为你可以直接使用现有的结合,而不必使用格式字符串或。
诸如此类,结果是有另一种方法,坦率地说,很多方法都能得到相同的结果。我将继续提议,现在将这一行更改为这种奇怪的语法,所以乍一看肯定是丑陋的,这部分是因为这是python的一个相对新特性,但请注意,在python中我们可以使用。
这些大括号,用来插入一个变量的实际值,所以不再是百分号s。python的print函数使用这些大括号,基本上意味着。这里插入一个值,但有一个奇怪的地方,你不能只是开始放大括号和变量名,python,你还必须告诉语言,后面的内容。
是一个格式化字符串,所以这可能是我们见过的最奇怪的事情。但当你有一对双引号像我这里的,前缀加上f将实际告诉计算机格式化该字符串的内容,将值插入那些大括号之间,而不是字面上打印那些大括号。
所以让我继续进行到我的实际代码并尝试这个。与其使用连接运算符,逗号,采用正确的方法。因为如果我重新运行这个程序,python的hello.py,它会问我名字,我将输入david,但它会完全忽略我,因为我。
字面上硬编码了hello,逗号答案,但这也不太对。仅仅开始把它放在大括号中,因为如果我再次运行这个程序python的hello.py并输入我的名字,现在它会说hello,花括号答案。所以这里只是一个微妙的变化,我必须告诉python,这种字符串在。
字符串,输入david。
我现在得到了hello david,所以这比c方便一些,因为你不必在这里有一个占位符,一个占位符,然后是一个以逗号分隔的额外参数列表,所以这只是一个更简洁的方式,如果你愿意,实际上引入更多的值到你想创建的字符串中,这些被称为。
格式字符串或简称f字符串,在我们的工具包中,编程时使用这种新语言。叫做python。那么让我们看看几个其他翻译的拼图块,看看,然后转向python,开始从零构建。这是早期的一个示例,名为counter的变量,初始化为零在c中的第一周。
这个,int计数器等于零分号,这给我们一个初始值为零的int类型变量。在Python中代码将是相似的,但会简单一些,Python提到我想要的变量类型,它会根据上下文推断出是什么,我也不需要有。
所以计数器等于零,在Python中将给你一个叫做计数器的变量。因为你给它赋值,将会是一个int或整数,我们在Scratch中还看到了什么,计数器加一,所以这是一种将变量值增加一的方法。在C中我们有几种不同的方式来实现这个,我们可以说计数器。
等于计数器加一,这种说法有点过于迂腐,有点长且乏味,而是用符号。在C语言中,我们可以做计数器加等于一,这将达到相同的结果。好吧,在Python中我们其实也有几种方法。我们可以像在C中那样明确地说,但只需省略分号。
计数器等于计数器加一,Python中的逻辑与C中完全相同。至于这种简写符号,Python中也存在。再一次,没有分号,唯一不一样的,或我加加,那种语法糖使得一个变量,不幸的是在Python中并不存在。
但是你可以做计数器加等于一或其他任何变量。我们在Scratch中还看到了什么,以及早期的条件。这些条件使用布尔表达式来决定事情或其他完全不同的内容,在C中我们将其转换为看起来有点类似的内容,确实,花括号有点拥抱。
printf行就像黄色条件那样拥抱紫色块,我们有小于y的括号,我们再次在花括号内使用printf,其中有双引号。一个换行的反斜杠n和一个分号,Python非常好地将会在精神上是类似的,但语法上更简单,接下来Python的样子将是x。
小于y,花括号去掉,分号去掉,这里你看到人类编程语言的演变小例子。如果你和我对代码中到处都是愚蠢的分号和花括号感到沮丧,何况要正确,人类决定,知道吗,为什么我们不直接说出我们的意思呢。
不必过于担心所有这些语法复杂性,让我们保持事情简单,确实。这是Python中的一个例子,但有一个关键细节,如果你们中的任何人习惯于马虎,在缩进方面,或许风格50一直在对你大喊,添加空格、添加空格或删除空格或行。
在 Python 中,现在必须正确缩进代码,在 C 中,当然我们 CS50 和世界上许多人都推荐你用四个空格来缩进代码。通常,或一个 Tab,在 Python 的上下文中,如果你不小心遗漏这些空格,就会发生错误。在 print 之前的空格必须存在,否则代码将无法运行。因此,不再有马虎,Python 将会。
不必担心包含大括号,关于两条你可以遵循的路径,如果或 else。在 C 中,我们很直接地翻译成这样。再次是上面的括号,这里的大括号,以及反斜杠 n 和分号。你可以猜到在 Python 中,这将变得更紧凑,因为砰的一声。
现在我们不再需要括号,但我们需要缩进和新行。我们不再需要分号,所以我们正在逐渐摆脱那些现在可以被视为理所当然的特性,但在 Scratch 中的这个例子呢,当我们在路上有三叉路口时,如果用 C,我们会这样翻译,那里并没有太多变化。
这是相当数量的代码行,大约 12 行在 Python 中。注意这里将要消失的是那些括号,那些大括号,那些反斜杠 n,以及分号,这里只有一个奇怪的地方。
只有一个奇怪的地方,对你来说看起来错或奇怪的,或许看起来像是一个错字。我保证我没有搞错。我会说 lift 而不是 elsif,在语法上是不同的。所以在 C 中我们字面上会说。
早些年人们决定在 Python 中使用 else if 时,何不简单地说 elif,节省打字的时间。这里的语法确实是正确的,你可以有更多的 elif,可以有四个、六个,甚至更多,但语法确实有些不同。
更紧凑的右侧,代码的语法干扰更少。你不必忽略那么多分号和大括号。像 Python 通常在语法上更为简洁,确实显得更现代。语言就像它,好的,我们来转向 C,当我们想做的时候。
一次又一次作为循环,或许是永远,我们在 C 中会字面上使用 forever 块。我们可以用几种不同的方式实现这一点,我们提出的相当简单。while true 打印出 hello world 一次又一次,因为布尔表达式永远不会改变,它确实会永远执行。
所以 Python 实际上是非常相似的,但有一些微妙的差异,比如这里。我们有 true 在括号中,大括号,新行,分号,很多特性即将消失,但仍然会有些许差异,注意我们正在缩进。正如我一直强调的,我们不再有新行、分号或大括号。
但true和false现在必须大写,所以在C语言中是小写的false和true,而在Python中,它们将被大写。就像之前一样,if i回到我们最近的条件。
注意,尽管我们去掉了花括号和括号,但现在引入了这些冒号,这在这个表达式后是必要的。紧接着的代码行缩进在下面,确实与if、l、if或else相关,而我们在循环的上下文中再次看到了同样的特征。
在Scratch中,当我们想要做有限次数的事情,比如三次时,我们会重复以下三次。在C中,我们有几种不同的方法来实现这一点,所有这些我敢说都是非常机械的。如果你想做三次,C中的负担在于你需要声明一个变量。
跟踪你已经计算过多少次,像有很多移动部件那样递增。在C语言中,一个叫做i的变量等于零,但我们可以称它为任何我们想要的。在这里我们有一个while块,它在询问一个布尔值,i是否小于三,然后在循环内部我们打印出hello world。
使用C语言的语法糖++表示法,我们不断给i加一,直到隐式地跳出循环,因为当然i不再小于三。因此在Python中,精神上类似,但一些杂乱的部分又消失了,i = 0就是我们需要的。
我们只需要一个变量,while i小于三就足够了,但加上冒号后,在适当缩进的地方我们打印出hello world。虽然我们不能用++,这有点失望,但i += 1会递增i,所以这就是在Python中实现相同功能的一种方式,一个执行三次的循环,当然我们还看到了其他方法。
在C语言中,还有其他方法可以在Python中实现。你可能记得在C中看到这种方法,for循环,而你可能经常会用到for循环。尽管它看起来有点神秘,但你可以将更多特性包装进那一行代码中。
如果你愿意,可以将这些分号看作是相同的逻辑,它只是在使用for循环的情况下打印出hello world三次。相比之下,在Python中事情开始变得有些优雅,虽然乍一看有点奇怪,但确实更简洁。如果你想做三次,结果在Python中就变得简单了。
你可以为 for 循环使用更简洁的语法,for i in,然后在方括号中列出值。因此,就像我们过去在方括号中表示数组和索引一样,在 Python 的世界中,每当你用方括号包裹一堆值时,你实际上是在封装它们。
所以这行代码的意思是,for i in 012,这意味着什么?这是一个 Python 中的 for 循环,它说给我一个变量叫做 i。在这个循环的第一次迭代中,将 i 设为零,在第二次迭代中,将 i 设为二,对我来说,它为你完成了所有这些。
现在归根结底,i 的值其实并不重要。因为我并不打印 i 的值,这完全没问题。你很可能使用过 for 循环,或者重复做某件事,比如打印“你好,世界”。即使你没有在这些方括号中打印出任何三项内容。
就像在 C 语言中列举零、一、二一样,像计算机科学家那样,从零开始计数。但这很容易出错,是否有人觉得在 Python 中使用 for 循环存在问题?如果你必须在方括号之间输入你想要迭代的值列表。
嗯,如果你想做一件事情 50 次,你得写出零、一、二、三、四、五、六,天哪,这样看起来会很可怕,有趣的是你提到 50,因为在为今天的讲座准备这个演示时,我回到了第零周,实际上在第零周的类比。
实际上是打印“你好,世界” 50 次,我心想,天哪,这看起来太糟糕了,因为我必须把它放在方括号内。诺亚说,这看起来实在太糟糕了,肯定有更好的方法,确实有。虽然对于非常短的值来说,这可能很吸引人,但在 Python 中有更简单的方法。
在 Python 中,当你想执行某个操作若干次时,我们可以替换这三个值的列表。它需要一个输入,即你想返回的内容数量,基本上是。
range 会为你做这个,输入像 3 的值,它将为你迭代这三个值,所以在刚才的讨论中,如果我现在想迭代 50 次,我只需将 3 改为 50,而不必手动输入从 0 到 49 的疯狂列表,这当然不会是个很好。
一个设计良好的程序看起来会如此,仅仅因为它的长度和出错的机会。在 Python 中,这或许是现在最“Pythonic”的做法,以某种数字执行某些操作,确实这是个短语,技术人员程序员在某种意义上往往非常讲究。
当谈到正确的做事方式时,实际上在 Python 编程的世界中。许多 Python 程序员确实有这两种观点,同时也有标准化的。建议,规范如何您,这,被认为是 Pythonic。如果您以“正确”的方式做事情,那么您就是在以 Pythonic 的方式在做事情。
这并不意味着绝对正确,其他人,在这个意义上更倾向于同意您。那么,接下来在我们开始构建自己的一些特性之前,让我们看看 Python 的一些最终特性。在 C 中,我们有这整个数据类型列表,当然还有更多。您可以创建自己的类型,但我们看到的原始类型是 float。
在 Python 中,int、long、string 等数据类型,尽管我还没有用到它们。因为我可以给自己一个变量,给它一个名字,比如 counter、i 或 answer,然后。为它赋值,Python 会根据您赋的值推断出,应该是什么数据类型。Python 确实有数据类型,只是,在编程世界中,这被称为一种宽松、强类型的语言。
存在,您必须在 Python 世界中显式使用它们,它是一种,松散类型的。语言,类型存在,但您通常可以隐式推断。负担不在于您,程序员,必须不断指定这些数据类型。让计算机为您解决这个问题,所以这是我们从 C 中得到的列表。
这现在将成为我们的类似物,bool 仍然为真和假,但大写。T 大写 F,我们将拥有 float,它是带小数的实数。我们将有 int,它当然是像负 1、0 和 1 这样的数字等等,str。而在 C 的世界中,技术上没有字符串类型,这是一个提供的特性。
通过 cs50 库,它刚刚变得更加易用,回想一下,C 语言有字符串,它们被称为字符串。但没有数据类型叫做字符串,您给自己的字符串的方式,当然是在 C 中声明某个东西为 char 星,char,星是一个同义词,一个昵称,一个别名,叫做字符串。在 Python 中,实际上存在一种,真正的数据类型用于字符串,简写为 str。
好吧,既然如此,还有什么其他可以使用的呢?好吧,Python 中还有其他数据类型,这实际上将在我们开始开发更复杂的程序时,证明非常有用,并用这个语言做更酷的事情。我们已经看到过 range,它严格在 Python 中,默认会返回一系列值,从零开始。
基于您提供的输入列表,我会不断地口头提及。列表是 Python 中的一种适当数据类型,它在精神上类似于数组,但。在数组中回想一下,过去几周我们花了很多时间强调。数组是固定大小的,您必须,这样做,就像上周如果您决定“哦,我”。
需要更多内存时,你必须动态分配更多空间,复制值过去,然后,很多。可以说是跳过一些障碍,当你想使用数组和C时。如果你想增长它们,甚至缩小它们,Python和其他类似的高级语言。一个数组,自动调整大小,变大和变小,这个功能现在你可以得到。
可以说在语言中是免费的,你不必自己实现。Python有所谓的元组,嗯,或者GPS,你可能有X和Y坐标或。纬度和经度坐标,就像逗号分隔的值。元组是在Python中实现这些的一种方式,字典或字典,因此Python有。
字典允许你存储人类世界,如果你有一个人类字典,例如,对于英语,就像一个物理形式的字典,让你存储单词及其定义。Python中的字典更一般地让你存储任何键和任何值,你可以将一件事与另一件事关联,我们将看到这一点。
这是一个非常有用且多功能的数据结构。最后,为了今天的目的,还有一些叫做集合的东西,如果我们从数学上回忆起来,集合是。三个,没有重复,但是Python为你管理,确保你可以向集合中添加项目。你可以从集合中移除项目,Python会确保没有重复的项。
它也会为你管理所有内存,所以我们在函数方面,有。C中,我们使用cs50
库来获取字符、双精度浮点、浮点数、长整型和字符串。在Python中,感谢上帝,我们不再需要担心双精度浮点或长整型。稍后会多谈谈,但cs50
库在Python中,你几分钟前看到我导入的。
确实给你一个叫做get float
的函数,确实给你一个叫做getint
的函数,确实给你一个叫做getstring
的函数,至少在本周的目的上。只是会让你的生活更轻松,这两个是训练轮,我们将很快去掉,这样你最终只使用原生的Python代码,而不是。
cs50
区库,但为了过渡这一周从C到Python,你会发现这些会让你的生活更轻松。在我们放松并把它们也去掉之前,因此在C中。使用库时,你必须包含cs50.h
,在Python中你将再次去**d。并导入cs50
,或者更明确地导入特定的函数。
你可能想要导入的内容,所以它,导入东西,它们最终实现的目标基本上是相同的。你可以像之前那样,逐个明确导入一个函数,使用get string
,或者你可以一次性导入整个库,只需简单地说,更简洁地。导入cs50
,这将影响我们之后必须使用的语法。
但我们会在接下来的例子中看到多种方式来做到这一点。你也可以稍微简化一下,你可以从像我们这样的库中导入一个以逗号分隔的函数列表,这是我们将经常看到的约定,因为当我们开始使用流行的第三方库时。
互联网上的其他程序员,他们通常会给我们提供很多我们自己可以使用的函数,只需以这种方式在这里指定它们。如果有任何关于Python语法的问题,就这样。
本质上,在我们的Python语法速成课程中,我们现在将开始构建事物,探索Python的特性以及一些细微差别,以及Python的真正力量,但首先,关于语法有什么问题?我们已经看到了循环、条件、变量,奥利维亚,有问题或评论吗?嗯,如果在for循环中你想增量不是1,但又不想明确列出列表,你会怎么做?
很好的问题,如果你想使用for循环并遍历范围,0 2 4 6 8,而不是0 1 2 3,让我回去看一下刚才的幻灯片,我实际上可以动态更改这个,让我去。
我可以做的是。
实际上是指定另一个值,这可能是,如果我更改输入为。
范围不只是一个值,对计算机来说,它应该统计总共三个值,但它应该以每次增量两个而不是默认的一个来递增,并且还有其他能力,你也不需要那样。这就是说,使用Python你会发现更多的功能出现。
使用这种语言,甚至更强大的功能,你可以编写的函数以及你可以在Python中使用的函数,有时可以接受不同数量的参数,有时是零,有时是一个,有时是两个,但这是。
最终这常常取决于你,其他问题问得好,好吧,我们会回到瑞安,哦,索非亚,轮到你了,我们会看到序列主要出现在for循环中吗?还是还有其他应用场景,在什么意义上它们非常有用,序列是指范围或列表,或者其他什么,哦,回到你,我想我们把你静音了。
是的,嗯,是的,关于范围的问题,很好,我们会在其他上下文中使用它们吗?一般来说,这很少见,我现在正在绞尽脑汁想其他我使用范围的用例,我相信我可以想出一些,但我认为毫无疑问,迭代,像在for循环中,我会再想想看看还有其他应用场景,但无论何时。
你想生成一长串遵循某种模式的值。无论是 0 1 2,还是正如 Olivia 指出的那样,带有间隔的值范围,这将使你避免完全硬编码,并且你实际上可以写出自己的生成器函数,可以说是返回任何模式的函数。
你想要生成的值,来自你在聊天中提到的任何内容,或者更多。嗯,看起来所有的问题都已经在这里回答了,好吧,我们现在继续。毕竟,这才是编程真正变得有趣和强大的地方。
当你我不再需要实现那些低级的实现细节时,比如为哈希表实现内存管理,或者为链表实现内存管理,或者在数组中复制值时。过去几周,我们一直专注于一些低级的。
原始数据是理解的有用内容,但编写起来并不有趣,我承认它们在问题集中的形式可能并不好玩。而且在余生中,编写它们肯定不会有趣。每次你想写代码来解决某个问题时。
但再次强调,这就是库的作用,它们提供了一种更简单的语言,用于解决某些类型的问题,其中包括我们在过去的问题集中解决的一些问题。实际上,让我去做这个。我将去做这个,给我一点时间,抓一个文件。
叫做桥的位图,你可能还记得之前的问题集。这是一幅美丽的周末桥,位于剑桥的查尔斯河旁边,麻省理工学院附近。这是由 CS50 团队的一名成员拍摄的一张非常清晰的照片,当然在最近的几周里,你编写了代码来进行各种变换。
其中一张模糊的图片,我敢说模糊并不是最简单的。
要解决的问题是,你必须上下左右查看。平均所有这些像素,你必须理解图像是如何被表示的,一次一个像素,所以有很多低级的细节。我们想做的只是模糊一张图像,因此在过去的几周里,我们需要考虑。
而且在这个较低的层次上写代码,现在用 Python 发现,我们能够以更高的抽象层次思考,为自己编写更少的代码。所以让我去做这个,我将使用我的 Mac,而不是 CS50 IDE,这样我可以更快地打开图片。这就是说,即使如此。
我们将继续使用 CS50 IDE 来编写 Python 代码和其他语言,直到课程结束。你也可以在自己的 Mac、手机上安装所需的软件,但我们在课堂上通常会使用 CS50 IDE,以便于操作。所以我将去做这个,虽然在我的电脑上写一个叫做模糊的程序。
pi,当然是python程序的文件扩展名。我的程序看起来有点不同,现在我有一个黑色和蓝色的窗口,但这只是我个人的mac上的文本编辑器,我要继续进行。我需要一些功能,图像,所以我会继续从中导入。
一个药丸库,一个枕头库,可以说是一种名为图像的特殊功能,以及一种名为过滤器的特殊功能。也就是说,这本质上是两个功能,聪明过我的人写的图像处理代码,他们把代码免费提供在。
互联网是免费的和开源的,这意味着任何人都可以使用代码。我现在被允许将其导入到我的程序中,因为我之前下载并安装了它。现在我会继续进行,给自己一个变量,称为before,并在bridge.bmp上调用image.open。
所以即使我们之前从未见过,也从未使用过,你也在进行。我在左侧有一个变量叫image。open,我传入名称,就像C语言中的f open。现在注意到这个点(dot)在这里担任了一个新的角色,只针对person对象或一个节点对象,我们想进入其中并访问一些变量。
好吧,事实证明,在python中,你有类似于精神上的东西,只包含变量或数据,比如姓名和数字,就像我们几周前为person struct所做的结构体。不仅是变量的数据,你还可以有函数,打开各种功能的可能性。
所以似乎我得到了这个图像对象,这个图像对象是我从其他地方导入的,它里面有一个打开的功能。我们在今天的例子中会越来越多地看到这种语法。让我给自己一个第二个变量,这个变量称为images,传入过滤函数。
image filter dot box blur的一。
我不打算在这个特定的语法上花时间,因为生活中你可能没有太多机会想要模糊一张图像。
代码,但为了今天的目的,请注意我在before变量内,因为我将其赋值为这个新功能的返回值。现在其中一个函数称为filter,这个过滤器函数以另一个函数的返回值为输入,简而言之,将使用一个盒子模糊我的图像。
嗯,使用一个像素半径,就像在C语言中一样,这段代码会告诉我的代码向上、向下、向左和向右查看,计算周围的平均值,就这样。在那之后,我会做点什么,使用点(dot)保存,我会将其保存为out.bmp。我只想创建一个名为out.bmp的新文件,如果我没有犯错误的话。
让我现在继续运行 Python 的 blur。pi,并按下回车,没有错误消息。因此,如果我输入,已经打开的 blur。pi,刚写好的,out。bmp,如果我继续打开 out。bmp,让我们看看,这里是之前的。这里是之后的,嗯,之前,之后,现在在互联网上,可能,mac,就在这里几英寸外。
绝对看起来模糊,但让我们再做得更引人注目一些。与其只向上、下、左、右查看一个像素,为什么不通过观察来查看呢。
添加更多值并取平均,让我继续运行 Python,重新打开。现在你可以看到之前和之后,之前和之后,这说明了什么呢。
好吧,这里是问题集四,用四行代码模糊图像。真不错,很强大,借助他人的肩膀,接下来是解决一个更近期的问题,让我转到一个不同的目录,在那里我有一个先进的,你可以的网站,一个叫 speller。pi,长话短说。
speller。pi 是将 C 转换为 Python 的一个翻译,呃,speller。c 的代码。请回忆一下,那是分发代码的一部分,用于问题集五,在 speller。c 中我们将其翻译为文本,五个,不同大小的字典,以及一堆短文本和长文本。尚未创建的是字典的等价物。c,即现在的字典。
pi 所以让我继续,并在 Python 中实现我的拼写检查器。让我继续创建一个名为 dictionary。pi 的文件,这又是约定。接下来我们要实现,检查、加载大小和卸载,但我可能需要在这里放一个全局变量来存储我的字典,这就是你们所有人的地方。
实现了带指针的哈希表,然后是链表和数组,所有这些都很复杂,你知道我将继续,给自己一个叫 words 的变量。
并将其声明为集合,所以请回忆一下,集合就是一组值,它可以处理重复值。我们可以存储字典中的所有单词,并且直接将它们放入集合中,这样就没有重复的值了。
我可以检查这个集合中是否有一个单词,或者没有,好的,让我们继续,现在加载单词到那个集合中,我将继续定义一个名为 load 的函数,它接受要打开的文件名,至此有一些新的语法。因此到目前为止,我们只是在文件中输入代码,实际上迄今为止最引人注目的区别。
我敢说关于 Python 和 C 的事情是,我从来没有写过一个 main 函数,而这也是 Python 的一个特点。如果你想写一个程序,你不必费心去编写一个名为 main 的默认代码,只需开始编写你的代码,这就是我们如何能够将 C 中的 Hello World 从这么多行代码简化为一行。
在 Python 中,我们甚至不需要 main,但如果我想定义我自己的函数,结果是,在 Python 中,你使用关键字 def 来定义,然后你放入函数的名称,然后在括号中,就像在 C 中一样,你放入你想要的变量或参数名称,而你不需要指定数据类型。
而且我们不使用大括号,而是使用冒号,所以这表示,嘿,Python,给我一个名为 load 的函数,它接受一个名为 dictionary 的参数。那么这个函数应该做什么呢?load 函数在拼写检查器中的目的是从字典中加载每一个单词,并以某种方式将其放入你的哈希表中。
所以我要继续做同样的事情,从字典中读取每一个单词,并赋值给名为 words 的变量。所以我要继续打开文件,我可以用这个函数来做到这一点。在 Python 中,你不使用 f open,而是直接使用一个叫做 open 的函数,我要将 open 的返回值赋值给一个名为 file 的变量,但我可以。
我可以随意称之为任何名称,这就是 Python 变得真正酷的地方。从 C 中读取文件是相当繁琐的,对吧?你必须使用像 f read 或其他某个函数来一次又一次地读取字符。而在 Python 中,你知道吗?如果我打开文件,Python 会自动给我一个 for 循环,初始化并将变量 line 赋值为每一行。
文件中的连续行对我来说,每一行是,我想对每一行做什么呢?我想去做 ah**d,然后添加到我的 word 变量中,每一行代表一个单词,我只想将其添加到我的全局变量 words 中,但这并不完全正确,因为文件中的每一行根据定义都有一个反斜杠 n,这就是原因。
我们给你的大字典中的所有单词都是一行一个。那么,如何去掉行末的换行符呢?比如,使用 malloc 来复制,然后移动所有字符,再稍微缩短一点,通过去掉反斜杠 n。在 Python 中,如果你想去掉字符串末尾的换行符,可以使用 strip。
strip 的意思是默认去掉空格、制表符和反斜杠 n,所以如果你想处理每一行的末尾,你可以简单地说 line.rstrip。这就是 Python 中字符串再次强大的地方,因为它们是自己的数据类型。它们内部不仅包含构成字符串的所有字符。
还有像r strip
这样的函数,它会去掉行尾的任何空白字符,呃,你知道吗,经过这一切,我觉得我完成了,我就要去关闭文件,并返回true
,所以就是这样,这就是Python中的加载函数,打开字典,全局变量。
关闭文件,返回true
,我的代码可能比你的代码实现这个要短好几行,肯定也节省了许多小时。那么,检查一下对吗,也许复杂性就在其他地方,嗯,让我检查一下。它以特定单词作为输入,作为其参数,然后我就要检查这个。
给定单词是否在我的单词集中,实际上在C中,你可能需要使用for
循环或while
循环,并且你需要遍历你加载的整个单词列表,使用二分搜索或线性搜索之类的。我现在已经超越这一点了,这么多周过去了,我只想说。
如果word
在words
中,就返回true
,否则返回false
,这就是我对check
的实现,现在它有点bug,我会修复这个,有人能发现bug吗?即使你从未见过Python,但花了几个小时实现你自己的check
版本,有没有什么步骤我逻辑上遗漏了,这里确实有个bug。
有人发现我没做的事情吗?
你可能在检查某个单词是否实际上在字典中时做过,几个人在评论大小写敏感性,是的,大小写敏感性,所以你在C语言中的实现很可能强制将单词转换为全小写。这是完全可行的,但你可能不得不逐个字符地这样做,使用malloc
。
或者将其逐个字符放入数组中,然后使用two upper
或two lower
来大写化。
哦,像那样会花费很长时间,实际上可能确实如此,所以你知道吗。如果你想将某个单词转换为小写,只需说word.lower()
。Python会处理所有字符,将每一个转换为小写,并返回新的结果,确实我认为这与你所做的一致。
size
函数不接受任何输入,但返回单词的数量。在一组单词中,我这里往前一步,实际上我的缩进有点偏差,让我快速修复一下,如果你想返回字典的大小,或者说你集合中的单词数量。
你可以直接返回那个全局变量words
的长度,字典。让我继续卸载,实际上不需要输入。老实说,因为我没有做过任何等同于malloc
的操作,我没有做任何内存管理,为什么呢?因为在Python中我实际上可以在所有情况下直接返回true
。
因为我的代码无疑是正确的,因为我不需要麻烦于指针、地址和内存管理,所以过去几周你在理解内存管理的低级细节时可能产生的压力,现在都消失了,并不是因为底层的事情,我已经发现。
重新回到 C 语言时,其实我必须调用 file.close。因为现在那个函数关闭与我关联的变量。malloc 和 free 或 realloc 都在背后为你处理,但 Python 语言现在为你管理这一切。
使用所谓的高级语言,你可以获得更多的功能,这样你我就可以专注于构建我们的拼写检查器,而不必在分配内存、复制字符串、转换大小写等问题上浪费时间。老实说,虽然第一次做这些事情可能会很有趣,也很令人满意。
一旦你把这些东西搞定,编程会很快变得乏味。你必须在那种低级别上思考和写代码。好吧,让我去 ahd 试试,真的去运行这段代码,我将去 ahd 运行 speller.pie 的 Python 版本,承认这也是我写的。
提前准备,因为就像我们为你写的拼写检查器中的分发代码,我们提前写好了 speller.c 和 speller.pie,但我们不会查看它的内部结构。我要去 ah**d 测试一下,怎么样,试试莎士比亚的文本。我在这里交叉我的手指,目前为止一切顺利,单词飞速闪过。
我假设它们是正确的,希望我们能得到输出,看起来是的,我想我在这里看到一些熟悉的数字,我有 143,000 和 91 个单词。然后在这里,总时间在一秒之内,所以这相当快。需要明确的是,我的数字可能和其他人的有点不同。
在云端运行时是 0.90 秒,但你知道吗,出于好奇,让我快速打开一个不同的标签页,去 ah**d 制作来自问题集五的 speller。我提前准备了我们自己的解决方案,写在 dictionary.c 和 speller.c 中,并且我刚用 make 编译了它,使用相同的莎士比亚文本。
再次运行 Python 版本后,我现在想用 C 版本运行它。
staffs,所有的实现都很好,哇,真不错,速度飞快,差不多快了一倍。而且请注意,尽管上面的数字是相同的,但时间却不是。我的 C 版本用了 0.52 秒,也就是半秒。
我的 Python 版本耗时 0.9 或大约一秒更快,但我的 Python 版本更慢,为什么会这样?这可能让我有点失望,因为我们刚花了那么多时间宣扬 Python 的优点,而现在我们却在某种意义上写出更差的代码。它是低级的,它明确告诉计算机该做什么。
所以,嗯,这让它快了一点,而在 Python 中,一切都发生在幕后。正如你所说,这可能会使它变得稍微慢一些,是的,在 Python 中,你有一个通用的解决方案来处理内存管理和大写字母,以及其他所有特性。
Python 对所有这些有通用的实现,但使用它会付出代价。
别人的代码为你实现所有这些东西,你还要支付更大的代价。在某种意义上,这在代码之间形成了明显的差异。我会将我的代码从源代码编译成机器代码,并回忆起机器代码是计算机的“大脑”所理解的零和一,所谓的 CPU。
或者说中央处理器,每次我们更改源代码时,我们总是必须编译我们的代码。
然后我们会像这样输入 dot slash hello
来运行程序,但到目前为止在 Python 中的每个演示我都没有使用 make 或 clang,为什么呢?结果发现,Python 通常被实现为我们所描述的,仅仅是一种语言,正如我们一直在写的,它也是一个独立的程序,我不断运行的 Python 程序是一个同名程序。
理解 Python 语言的程序。
你会产生一定的开销,你付出了性能的代价。为什么呢?好吧,计算机从零开始回忆。
到头来,它们只理解零和一,这就是它们的本质。
它们在运作,但我没有输出任何零和一,我这个人只是在写 Python。
因此,我的 Python 代码与这种英语语法之间需要有某种翻译,转化为计算机能够理解的内容。如果你每次更改时不想重新编码,而只是通过解释器运行你的代码,正如在 Python 世界中所常见的,你将要付出代价。
因为有人必须为你实现一个翻译器,实际上在 Python 世界中有正式的术语。例如,我们有一幅看起来更像这样的图。而在 C 的世界中,我们实际上会将源代码作为输入,并将机器代码作为输出,然后运行机器代码。
在 Python 的世界里,到目前为止我在写源代码,然后我立刻运行它,我并没有将其编译成零和一,在这个巧合称为 Python 的程序中,其目的就是将那段代码翻译成计算机能够理解的东西。那么这在实际术语中意味着什么呢?这意味着。
如果我回想起这样一个算法,可能对你们很多人来说都是神秘的,不过有些人可能是一个西班牙语的算法,用于在电话簿中搜索某人,假设我根本不会说西班牙语,这个算法变成我能理解的东西。西班牙语到英语,就像哇,这个英文版本阅读起来好多了,算法。
非常快,因为我对英语相当熟练,但如果你只有。
给我西班牙语版本的源代码,你要求我逐行翻译或解释,老实说,这真的会拖慢我的速度,因为这就像我不得不去查一本西班牙语词典,查每个单词,记录 telefonica,好吧,那我得查一下,什么是 ghia。
电话簿是什么,哦好,拿起电话簿,这一步搞定了,第二步是什么,abread。电话簿所以打开到中间,等等,我不知道那个,剧透那是什么意思,abre 好吧让我查一下,它的意思是中间,呃 de gea 电话簿,电话簿。所以我在这里来回挣扎,显然这是一个比较慢的过程。
过程是这样的,如果我继续去查 de la pahina,查看页面,逐行翻译,毫无疑问会拖慢这个过程。因此,这实际上就是我们运行这些 Python 程序时所发生的情况,像是一个中间人正在查看你的源代码,从上到下,从左到右阅读。
本质上将每一行分别翻译成计算机理解的相应代码,因此好的一面是,感谢地,我们不必运行 make 或声称我们对早期 pset 的修改有多少人这里改过 c 的作业却忘记保存文件然后重新运行那个文件。
而且你重新运行它,程序显然没有变化,因为你实际上并没有。
不仅保存了,还重新编译了,所以那烦人的人类步骤消失了。在 Python 的世界里,你改变了你的,重新解释它,你可以省略那一步,但你要付出的代价是一些额外的开销,实际上我们在这里看到了这一点。
就我的 Python 版本而言,处理莎士比亚的内容。
我的 C 版本只用了半秒钟,因此我在过去几周承诺的这个权衡主题,在计算机科学和编程的世界中非常普遍,坦率地说,在现实世界中,任何时候你进行一些改进或获得一些好处,几乎都要付出代价。
这可能是时间,也可能是空间,可能是金钱,可能是复杂性,或者其他任何东西。
这永恒的权衡,归根结底,是关于找到那些转折点。并且最终了解,交易时要使用哪些工具,好的,咱们先休息五分钟,回来后,我们会看看 Python 的其他功能。今天的课程最后,我们将讨论一些非常强大的内容。
好的,我们回来了,首先让我纠正一下,如果我可以的话,布莱恩友善地指出我对奥利维亚和诺亚后续问题的回答不幸地错过了,错在没有查看文档。所以让我回顾一下这个例子,其中我们有 range 函数返回三个值,0,1 和 2,但我认为。
奥利维亚问如果你想跳过值,例如,每两个数字怎么做。遗憾的是,我搞错了语法,只给了 range 两个输入,而这里需要三个。因此,例如,假设我们想要 0 和 100 包含,但跳过每个其他的,即 0,2,4,6,8。因此所有的偶数。
通过 100。嗯,我们实际上想做的应该是这样的,101。
逗号 2。为什么呢?我们马上就会调出文档,但 0 是你开始计数的地方,停止计数,但根据定义,它是排除在外的。因此,我们必须多走一步,超出我们关心的值,然后你想要以每次 2 的增量,从 0 到 2 到 4 到 6 到 8,一直到 100。
那么,我怎么能提前想出这一点,而不是现在让自己出丑呢?事实证明,Python 有官方文档,我们会始终链接给你。这里最上面有一个搜索框,你可以看到,在休息期间我在搜索 range 的文档。
果然,如果我搜索 range 文档,乍一看,结果中提到了很多类似 range 的内容,这就是我们想要的,如果我点击。
你会看到一些文档,乍一看有点隐晦。但有趣的是,range 有两种不同的形式,它是一个函数。严格来说,它被称为类,但稍后再谈,按照我们的目的,它表现得像一个函数。注意这里有两行,它们相似但又不同。
函数,范围函数可以接受一个输入来停止,三个,显然默认情况下如果是3,您将使用i等于0、1和2。但范围函数还有另一种变体。这并不是我所提出的那个,而是存在的另一个,可以接受这里的三个参数,或者从技术上讲是两个,但它以以下方式工作。
您在Python的文档中看到这样的语法,这意味着范围的替代形式接受一个称为start的参数,后面可以选择一个称为step的第三个参数。我知道作为读者这是可选的,因为它在方*括号中。因此,这与列表或数组无关,这只是人类的文档。
每当您在方*括号中看到东西,人类读者,这意味着这是可选的。那么这意味着什么呢?注意到没有stop和step,我一开始以为在回答Olivia Noah版本时有的。因此,如果我指定我想从零开始,我关心的是100,然后提供一个可选的步长为2,这最终会给我一个程序。
这将打印出所有的偶数,所以让我先这样做。首先让我进入一个程序,我称之为count.dot pi。我要去**d并从零开始。
以2递增,但不经过101,每次步长为2,这次我要打印出i。还有这里另一个Python的方便功能,不再使用百分号s。如果您想叫i,只需说print开放括号i关闭括号。您不需要像C语言那样使用另一个格式字符串,让我现在去**d,运行count的Python。
dot pi输入,它滚动得非常快,但请注意,它在100处停止。如果我滚动到开头,它是从零开始的,因此对于之前搞错的地方,我深感抱歉,但这是一个绝佳的机会,去探索Python。尽管它可能感觉神秘,但它将很快成为您的朋友。
乍一看,好吧,让我们继续前进,然后回顾我们之前开始的另一个程序。那个程序再次是这个相对简单的hello程序,我们在这个状态下停止了,我们使用的是来自cs50库的getstring函数。我们有一个叫做answer的变量,它正在获取返回值。
关于getstring的那个版本,我们打印出hello,逗号。所以我们使用了那个新的神秘功能,但很方便,这只是意味着将花括号中的内容替换为实际值。那么让我们开始去掉我们刚刚放上的辅助轮子。
库,我们如何在Python中实际获取输入。
好吧,让我们继续翻译这个,我将使用来自 cs50 的 get_in 函数。像是,cs50 的 getstring 已不再存在,但可以使用简单的 input 函数。input 函数在 C 和 Python 中与 getstring 非常相似,提示用户输入他们的名字,等待他们输入一个值,并在他们按下回车时返回他们输入的内容。
所以如果我现在去重新运行这个程序 python 的 hello.py。在去掉 cs50 库后,使用 input 代替 getstring。我的名字是大卫,嘿,大卫,所以已经有了,这是原始的。
这是完全与 cs50 无关的原生 Python 代码,但现在我们继续使用 cs50 库,最初是因为我们很快就会看到它的优点,因为我们为你进行了很多错误检查。不过我们最终会完全去掉这些辅助工具。
但是请注意,确实相对简单,可以这样做,让我去打开一个我们提前写好的程序。这个程序在课程网站上总是可以找到,我将打开一个名为 edition0.c 的文件。我们之前其实已经见过这个文件,现在我会在这里,稍等一下。
将我的窗口分割,这样我可以同时查看两个文件,在这里我将创建一个新的。
文件,我将其命名为 addition.py,换句话说,我今天只是临时 rearrange 我的 IDE,以便在左边看到一种语言 C,而在右边看到对应的 Python 语言。如果你想要跟着一起练习,可以在线下载所有这些示例。
在你自己的情况下,如果我翻译这个右边的内容,让我们先回顾一下左边的程序实际上做了什么。这个程序提示用户输入 x,提示用户输入 y,简单地对这两个数进行加法运算,这些都是第一周的内容。
cs50 库会让我现在的生活轻松一些。int。我将继续从用户那里获取一个整数,使用 getint 并提示他们输入 x。接下来,我会继续从用户那里获取一个整数,提示他们输入 y。最后,我将继续输出 x 加 y,让我在这里继续下去。
我将运行 addition.py,现在我甚至为 x 进行了保护,让我们输入一个 y,输入 2,瞧,3 是我的程序,所以非常简单,代码行数更少,因为我没有像 standard io.h 这样的多余包含。我没有任何大括号,公平地说,我也没有任何注释。
所以让我在 Python 中写注释,使用不同的符号,提示用户输入 x。应以哈希符号为前缀,而不是斜杠,但我会前进并提示用户输入 y,不过即使如此,它也相当紧凑,只有 10 行代码和一些注释。好的,那么我可能做点什么不同的呢?
让我们去掉辅助轮,去掉 cs50 库,再次获取输入。如果我前进并获取输入,给 x 和 y 分别赋值,进行加法。pi x 将再次为 1,y 将再次为 2,答案当然是 12。这是错误的,发生了什么?
我怎么会搞错这样一个简单的程序,尽管对我来说是新的语言 Python。我在这里做了什么,是的,Ben,因为它可能将其视为两个字符串,所以它就是。,,*****,确实如此,所以输入这个函数,随 Python 提供。实际上类似于 cs50 的 getstring,将作为键盘输入字符或 ASCII 返回。
几周过去了,即使它们看起来像数字,它们也不会被视为数字,也就是记住在 C 中我们有能力将值从一个转换到另一个,转换意味着将一种数据类型转换为另一种,我们被允许这样做对于字符到整数或整数到字符,但不能这样做字符串到整数或从整数到字符串。为此,我们需要特殊函数,而你们中的一些人。
可能使用了 a to i,实际上查看 ASCII 字符串中的所有字符,整数。在 Python 中坦白说稍微简单些,所以我会前进并将输入的返回值强制转换为使用这个 int,并且我也会对 y 做同样的事,将输入的返回值传递给它以转换看起来像的内容。
类似于将字符串转换为看起来像整数的内容,现在让我继续进行加法,再次进行 Python 的加法。pi,注意这次,希望能回应 Ben 的观点,默认的加法行为是当你有两个字符串时,希望现在它会对 x 进行加法计算,x 等于 1。
y 等于 2,瞧,现在我们回到了正轨,合作用户,我输入 cat,发生了什么。所以请注意,我们在运行程序时触发了第一个错误。我的程序甚至无法运行,稍微隐晦的,最后的文件 edition.py 第二行,好的,这至少是熟悉的,我搞砸了。
在第二行某处,它给我显示了这行代码,并且说值错误,基于 10 的无效字面量。
cat 这是一个非常隐晦的方式,意味着我只是尝试将不是整数的东西强制转换为整数,这就是为什么我们使用类似于 cs50 库的东西。写所有检查并确保用户输入数字且仅输入数字的代码其实有点烦人,而不是 cat 或 dog 或其他一些隐晦字符串。
我们现在自己必须实现那种错误检查,如果我们不进行权衡。也许你更喜欢自己编写所有代码,不想使用互联网上某个随机人的库,无论是 cs50 还是其他人的,即使是免费的开源,你想自己编写。
现在我必须添加更多代码行来检查,用户是否输入了小数数字或其他 ASCII 字符,因此再次需要在使用库与否之间进行权衡。通常答案是使用一个通用库来解决这些问题。那么,让我们继续稍微修改程序,让我继续。
打开一个名为 division 的新文件。
点 pi 这里做一点除法,让我继续往右侧复制粘贴之前的内容,但只需在这里更改为除法。让我继续,将 x 除以 y,并在一会儿将 x 输入 1,y 输入 2,然后我将运行 division.py。输入 x 为 1,y 为 2,但在我按下回车之前,答案。
如果你想,如果这是一个 C 语言的程序,我正在将 x 除以 y。那么在第一周和此后每周,我得到的结果是什么,布莱恩,大家的共识看起来是零,对吧?因为截断,如果将 1 除以 2,当然是 1/2 或 0.5。0.5 是浮点数,但如果我处理的是整数,尽管到目前为止它隐式为整数。
现在,既然我已经强制转换了它们,我似乎会丢弃 0.5,然后运行 division.py,设置 x 等于 1,y 等于 2,瞧,哇,这是最简单的之一。
令人烦恼的是,似乎在 Python 中已经解决了这个问题。通过除法可以实现你想要的,如果你在 Python 中将一个整数除以另一个整数,结果会是语言的另一个特性,它按照你程序员的意图来处理。浮点数和整数的细微差别就不再是问题。
哈佛CS50-CS | 计算机科学导论(2020·完整版) - P13:L6- Python从语法到应用实战 2 - ShowMeAI - BV1Hh411W7Up
说得对,让我继续打开另一个程序。这也是来自第一周,这个程序叫做 conditions.c,给我一会儿时间打开它。在左边,这个程序的目的是从用户那里获取一个名为 x 的输入,再获取一个名为 y 的输入,然后就这样做了。
x 小于 y 时打印出尽量多的内容,如果 x 大于 y,打印出尽量多的内容,以此类推。让我们继续将这个程序翻译成相应的 python 代码。使用我们已经看到的一些语法,我会把它保存为 conditions.pi。我想我还会继续使用 cs50 库。
所以我不需要担心那些错误,当将坏输入转换到另一个时,从 cs50 导入 getint,让我继续获取用户输入。用户输入,称之为 y,这次我不打注释了,节省时间。现在让我问这个问题,在 c 中我会这样做,如果 x 小于 y。
python 如果 x 小于 y 的话就可以更简洁,但是要加上冒号。在那之后,我会说,打印 x 小于 y,y,x 等于 y。我想这差不多就是了,我会继续运行这个条件的 python。时间用 2,再用 1,已经工作了,让我再指出一件事。
之前提到过你只需说,导入 cs50 库,如果你不使用函数名称,这完全没问题,但注意到 ide 在第三和第四行对我大喊,getint 不再被识别,这因为 python 支持这个特性,当使用其他人的库时,就是说,你不能再引用 getint。
直接你必须更明确地说,熟悉的点,库就像 c。结构并调用其中的函数,getint,所以我现在可以继续运行这个 python 的条件。pi,现在又可以工作了,所以哪个更好,这要看情况。我是说,如果它更易读,这将为你节省很多时间。
按键操作,因为你不需要不断地输入 cs50.dot.cs50.dot。如果你在写一个比较大的程序,可能会使用两个不同的库,它们都实现了一个名为 getint 的函数。你想能够区分它们,所以你可能想要这样做。
按名称导入库,我在这里做了,这被称为命名空间。命名空间意味着你可以在两个不同的命名空间中有两个同名的变量或函数,它们不会冲突,库。或者其他库的名称,接下来,让我继续打开另一个文件。
呃,这个是同意
。C语言和这个程序提示用户输入他们是否同意,我们有点好奇地检查了第一周。使用==
符号来检查y
或小写的y
、n
,那么我们该如何转换这个呢?让我给自己一个新的文件,我将它命名为agree.py
。
结果我们可以用几种不同的方法来解决这个问题,让我先从导入cs50开始,获得整型只是因为它更方便,而不是获得字符串
。让我继续通过getstring
获取用户的输入,并问他们同样的问题:你同意吗?
那么让我检查一下,如果s
等于y
或s
等于小写的y
,然后我会继续打印出同意
,否则如果s
等于大写的N
或s
等于小写的n
,让我在这里打印出不同意
。我想这样应该可以,但有些地方有些奇怪,是什么让你觉得不同?
那么,和C语言不同,你现在可能需要打破哪些肌肉记忆呢?在使用带有多个布尔表达式的条件时,结合这种方式还有另一个细微之处,C语言和Python之间至少有两个显著的区别,聊天室中有没有想法?我本来想说,对于这个,我们可以直接使用逻辑符号。
是的,我们可以直接输入英语单词,或者如果我们想表达逻辑或,在C语言中,左边我们会使用竖线的方式。虽然这样很好,你会习惯它,但从英语的角度来看,Python采用了使用更频繁的实际英语或类似英语的单词,这些单词确实是从左到右阅读的。
确实,在你阅读Python代码时,这里出现了一个主题,它比C语言更接近英语。因为你不会像在C语言中那样频繁被标点符号绊倒,每一行的Python代码更像是一个英语短语或句子,这里还有一个细微之处,回到第一周的左边,y的部分。
本周结束时,我使用双引号,但说实话,其实并没有关系,我可以在任何地方使用单引号,只要保持一致。但在Python中,双引号和单引号之间没有根本性的区别。原因是我们在查看不同数据类型时发现的。
在C语言中,Python数据类型列表中缺少的是字符,在Python中并没有单独的字符字符串,即使只有一个字符,一切都是字符串。缺点是我们没有那么细致的控制,优点是我们可以获得更多的功能。正如我们在第一周看到的那样。
嗯,让我继续,我想我可以简化这一点,比如说我想容忍类似于,不仅是y或y的大写,或者容忍。是的的大写或小写,你可以想象开始逐步。添加代码或s等于等于yes或s等于等于yes,但等一下,这是什么。
如果用户有点马虎,假设我想说,嗯。假如他们在喊,s等于等于yes,全大写,还有其他一些排列,比如说,这很快就变成了一团糟,但如果最终你真的只想检测,为什么或单词yes,无论。
大小写,我敢打赌我们可以在Python中非常聪明,假如我,继续说如果s在。所谓的y或yes,实际上我可以借用之前的一个想法,我可以使用方括号符号来给我一个列表,它会自动根据需要增长或缩小,你不必提前决定它的大小,这个介词在是Python中的一个新关键字。
这将字面上回答我的问题,我们之前用过。当我实现拼写检查器时,我说如果单词在我的单词集合中。返回true,所以如果s在这个列表中,我将返回是的,嗯,基于这个问题的答案的真或假,但再说一次,这并不容忍大小写,但没什么大不了的。
dot lower现在我可以说是s的小写版本,无论人类输入了什么。在这个两个值的列表中,这意味着现在用户可以输入。全大写、交替大小写或其他任意排列。
好吧,那就是我们的条件,让我暂停一下,看看是否有。任何问题,任何问题或混淆,我们可以用条件的语法、布尔变量或布尔值来澄清。所以出现了一个问题。在Python中,我们可以使用等于的语法来比较两个字符串。
是的,所以在Python中还有一个非常好的捕捉,底层没有指针。它们仍然是地址,就像你的内存没有消失一样,但。
在底层,所有这些现在都由语言本身为你管理,因此如果你想。概念上比较,一个字符串与另一个字符串,就像我在这里做的,现在在第七行。Python会为你做“正确的事情”,你不需要回退。使用stir comp,而是现在只需,这个如果s.dot lower在所谓的n。
或者逗号,不,我们可以通过使用同样的技术实现同样的结果,其他问题或。都好吗?好吧,让我继续,打开另一个例子。你可能还记得我们做过,嗯,更好,然后最好,这个例子。涉及到一只猫在某种形式下叫,所以让我继续打开。
从第一周开始,有一个叫做“meow zero”的例子,相对简单,它只是这样做。它简单地叫了三次,所以足够了。
也就是说现在在Python中,做三次这样的事情相当简单,我要去继续。
然后称这个文件为meow.py
,当然我可以做类似print(meow)
的操作,我可以复制粘贴,但当然这个例子在第一周的重点并不是退化为简单复制粘贴,肯定有更好的方法。我们这次看到了更好的方法,如果在C中是int i=0; i<3; i++
。
然后在一些花括号中,我们可以做printf
输出“meow”,新行分号,所以这是我们的meow
代码在C中的下一个版本,但在Python中,当然更简洁,我可以只做for i in range(3): print("meow")
,这在精神上与我们之前的“hello world”非常相似。但再次强调,我们不需要包含任何主函数,或者不需要那些花括号。
花括号或分号,如果你喜欢,我们可以直接深入并专注于上次。将meow
程序演变为拥有自己的辅助函数,实际上允许我们在“meowing”之上创建一个抽象,这就是我们的第三个版本,即meow2
。让我去继续并在标签中打开这个版本,注意这个版本。
开始变得有点复杂,因为我们需要在顶部有一个原型。因为我现在有一个meow
函数,打印“meow”,但为了将其抽象为一个新的辅助函数,然后我在这里有一段包含for
循环的代码。好吧,在Python中,如果我想这样做,它会变得简单一点。
现在要做三次meow
,当然meow
还不存在。我可以解决这个问题,之前在拼写检查器中我们已经快速看到过,我可以定义自己的函数像meow
,没有更多的void
,因为如果你不想在函数中有参数,就不要放在那里。
在Python中没有返回值指定,它们是隐式的,因此这样做就足够了。现在,我有一个程序,迭代三次,每次调用meow
,而meow
在下面定义,让我去继续并运行这个Python,追踪最近的调用发生错误,问题出现在meow.py
的第二行,因为名称错误,名称meow
未定义。
现在使用的语言在Python中与C的有些不同,坦率来说更人性化,但究竟发生了什么?出现了什么问题,我直到现在还没有遇到过,甚至如果你从未编程过,那可能是什么问题呢,如果找不到这个函数的话。
正在尝试调用它,因为它在下面描述,当我们调用它时。是的,所以这是没有原型的,实际上在Python中并没有原型的概念,所以不幸的是,我们在第一周看到的解决方案不是简单地复制粘贴上面的第一行并以分号结束。
这不是我可以做的事情,我可以把我的 meow 函数移动到文件顶部,从而首先定义函数,然后最后使用它,这实际上会解决问题。长期来看这会对我们很有帮助,因为你可能能想象一个情况,这个函数想调用那个函数,但那个函数又调用这个函数,而你根本无法整齐地排序。
而且这并不会像,维护起来那么方便。记住,把 main 放在 rc 程序顶部的一个价值在于,任何想理解你代码的合理人,可能他们不想去找实际的 main 代码。因此,事实证明,在 Python 中,即使你不需要一个 main。
实际上,定义一个函数是很常见的,尽管如此,它会用类似这样的方式实现,我只需在下面缩进我的代码。因此,现在我已定义了 main,但我还没有执行任何代码。在第 6 行,我现在定义了 meow,但我还没有执行任何代码,字面意思是如果我现在运行 Python 的 meow 并按下回车。
我希望能看到“喵喵喵”,奇怪,但 Python 正在字面上执行我告诉它做的事情。我告诉它定义一个名为 main 的函数,我还告诉它定义一个名为 meow 的函数,但我从未告诉它去调用这两个函数。因此,这里最简单的解决办法是,最后调用 main,这有点不同于 C,稍微奇怪。
在文件中,所以把 main 定义在顶部,调用它在底部。让我去继续现在运行我的程序,现在看,喵喵喵又回来了,因为我已经定义了 main,我已经定义了 meow,现在我正在调用 main。顺便提一下,你会在各种文档和在线教程中,看到有很多情况需要你输入,这样就能实现。
同样的目标,但这并不是严格必要的。如果你在任何在线参考、示例、书籍或章节中看到这行代码,它仅在你使用自己的库时是必要的,比如你自己的 cs50 库或你自己的图像模糊库,而在我们仅仅编写自己的单独程序时并不是必要的。
所以我打算去继续保持简单,字面上只调用 main,并让我解释一下为什么在这种情况下你需要这个语法,但让我去继续修改最后一次。因为回想一下,在 C 中,我程序的最后版本让我运行 meow 并传递一个输入。
因为我定义的 meow 是接受一个输入,比如 n,然后做一些类似于 for int i。gets 0,i 小于 n,i 加 1,然后在我的大括号内,我打印 meow。因此,现在我有了一个我发明的助手函数,它接受一个整数类型的输入 n,并循环那么多次,打印出非常好的抽象,这样我的程序就是。
精简了,就是喵三次,实施方式无所谓。我可以在python中做同样的事情,一个名为n的参数,我不必烦恼指定它的类型,我现在可以说对于i,范围在n中,我可以打印出喵那么多次,现在我可以去掉main中的循环,只说喵三次,依然如此。
功能性如果我最终运行这个,喵喵喵,以更复杂的方式,实际上给自己一些实际的助手函数,好的,有任何问题吗?关于这个进程,现在我们并没有看到新的python语法,我们只是看到一些过去的c程序翻译成python,以便真正展示。
你那边有什么,布莱恩?没有,好的,那我们继续打开另一个版本。来自第一周的一个程序,positive。c,那时这是一个机会,不是叫getpositiveint的函数,但它也让我们熟悉了do while。
循环,不幸的是,我们将从你那里拿走这个。现在python没有do while循环。
但当然,能够在条件为真时做某事是非常有用的。毕竟,我们几乎每次在课堂上获取用户输入时,都使用了do while,这样我们至少提示他们一次,然后选择性地再次提示,直到他们合作。所以让我前进,现在在python中实现这个。
在一个名为positive。pi的文件中,我要前进,翻译如下:让我前进,从cs50 import getint
开始,定义一个名为main的函数,现在我只是要开始养成这个习惯。我要前进,给自己一个变量i并调用。
获取正整数,然后我只是要前进并打印出i,保持简单。现在我必须实现get positive int,它不需要任何输入,所以我不会给它任何参数,现在我必须做do while的事情。因此,pythonic的做法几乎总是故意诱发。
一个无限循环,想法是如果你想一直做某事,就开始永远这样做,然后在准备好时退出循环。那么在这个函数中我想永远做什么呢?我想前进,获取正整数,然后在下一行继续,直到大于0,从而使其为正,退出。
这里最后一行代码将返回n,所以请注意,左侧的c我做了这个do while的事情,我必须在do while循环外声明n,因为它必须在大括号外才能在作用域内,这里实际上有一点不同。哦,是的,我搞砸了。如果询问实际问题,如果n大于。
小于零,所以我实际上在右侧做了什么不同的呢?注意我故意在第10行引入了这个无限循环,这意味着永远执行以下操作:获取整数,如果满足条件,则跳出循环。我怎么跳出这个循环呢?注意,一致性使我回到了原来的缩进位置。
返回,与while循环对齐,这意味着它是该循环外的第一行代码。在过去,我们会有非常明确的花括号,现在我们只依赖缩进,这让我可以返回n。那么这里有什么不同呢?首先,do while循环完全消失了;其次,在Python中,当你声明一个变量时。
它存在于那个函数结束之前,你不需要担心像我们在C语言中那样先声明一个变量,然后在下面返回。当我们执行这一行代码11时,n突然在那个函数的剩余部分中存在。
因此,尽管我们在循环内部声明了变量,正如缩进所示。
在程序的最后,让我暂停一下,看看是否有任何问题或困惑,关于获取用户输入,做出与do while逻辑等价的操作,但现在以更Pythonic的方式。彼得,在Python中,变量是否可以跨函数访问?好问题,不可以。如果你声明了一个局部变量。
也就是说,传递给那个函数并不是返回,而是将其作为输出传递给输入,否则你必须将其定义为全局变量。好吧,那么还有什么我们可以翻译的呢?回想一下,嗯,回想一下我们在第一周的早期工作中玩过这些。
马里奥的例子,比如我们想从这些金字塔、硬币或小砖块中打印出一些东西。在这里,让我打开一个名为mario的文件,我将不再总是显示之前和之后,而是现在开始。
更专注于Python代码,你总是可以回头看看相应的C版本。我怎么去打印出三个这样垂直的砖块呢?在Python中,我可能会说像for i in range of three一样简单。就像我们已经做过几次的那样,直接打印出一个哈希。
我不需要担心换行,因为可以说这是免费获得的。但我现在要去运行这个马里奥的Python版本。那么如果我。
想要做硬币呢?如果我想做这个水平的。
出现在这四个砖块中的硬币,并打印出一个,呃,版本的那个我该怎么做呢。那好吧,让我去啊d并把这个改成,在我的代码中对for i in range of
四次循环,这样我可以打印出四个这样的东西,让我去啊d打印出一个问号,然后运行这个,所以让我运行mario。派对,瞧,天哪,这不是我想要的,所以。
这是一个权衡,你可能,已经有点兴奋。到目前为止,可能对代码感到兴奋,呃,像是哦,我的天,你。再也不需要做那些愚蠢的新行,字符了,但如果你现在不想要它,我们。算是发现了获取那些新行的一个缺点,事实证明如果我们读取。
python,python 2强大之处在于它,不仅支持位置参数。你只需用逗号分隔多个参数进行调用。命名参数,如果一个函数,比如print,接受多个输入,比如这个,那个和这个其他东西,每个输入都可以有名字。
你是那个函数的用户,print在python中支持一个参数。叫做end,你可以明确地说,想给这个。参数什么值,通过提到它的名字,在这里我将字面上这样做。我要告诉print函数,给它一个参数,内容是引号中的内容,原因在于。
如果我阅读文档,默认其实是这个,如果你阅读。文档会告诉你,print的结束参数的默认值。是反斜杠n,这两个是c没有的特性,c没有可选参数。它们要么存在,要么不存在,呃,确切地说要么必须存在,要么不能存在。
python支持可选参数,甚至有默认值。所以在这种情况下,文档中的默认值是,这个结束,为什么。每一行都以那个值结束,如果你,想把它改成什么都没有,也就是所谓的空字符串。你把它改成引号里的内容,所以让我去啊d现在运行这个,瞧。
更近了一点,有点傻,因为现在我的光标结束了我的。提示符也在同一行,所以,也许在这行之后让我就去。打印没有,即是换行,现在如果我运行mario。派对,瞧。现在我得到了我想要的效果,如果你想看看这里到底发生了什么。
我可以做一些傻事,比如hello,现在我可以在每次打印中结束。用hello hello hello hello,这并不意味着,那表达式,但正确的版本当然是。只需将其清空。
以这种方式,但这里有一些很酷的事情,如果你,像我一样是个极客,生活开始变得非常有趣,我实际上可以改变我的python。
代码以在天空中打印出这四个问号,简单来说就是打印引号中的内容。
这个程序,完成了,这里再次展示你获得了很多语言特性,你不需要思考很多语法。如果你想把问号做四次,你可以真的使用星号操作符,它被重载了。数字,但也自动连接,如果你愿意的话,用字符串的方式,所以让我去。
前进,做一个最终版本,记得上次我们做了这样的事情。让我去更改我的马里奥代码,现在是为i
在三的范围内。因为这是一个三乘三的网格。
砖块,假设我们现在进入这个循环,做一个嵌套循环,在这里我想要一次打印一个哈希。可是我不想换行,只想在这里换行,结果是,基本上因为Python会自动给你反斜杠结束符。
本质上,你过去写的任何逻辑现在需要被反转。如果你曾经打印过换行,现在你不想打印换行;如果你以前没有打印过换行,现在在某种意义上你要打印,所以让我去,不要在马里奥.py中写错Python代码,瞧,我的三乘三网格,这意味着在Python中。
我们可以嵌套循环,就像我们在C中做的那样,我可以使用多个变量名,比如i
和j
是常规的,没有。
再次逻辑,想法还是一样,只是例如一些新的语法。关于马里奥或者循环或重建这些程序有什么问题吗?现在有没有问题或者困惑?让我去回忆一下,在C中我们很早就遇到了一个整数问题,让我创建一个。
这里的程序叫做int.py,让我初始化一个名为i
的变量为1。让我继续在一个while true
块中做这个,让我在每次迭代中打印出i
的值,从1到i
。让我继续运行这个程序,让我扩大我的窗口大小,然后运行这个东西。
哎呀,那是马里奥,让我在Python中运行这个程序,计数到无限,老实说这会花费一段时间。你知道什么比按一计数更快?也许是乘以二,所以让我改为乘以二。像在C中那样结束程序,我使用了控制C,所以我看到了键盘中断。
它尊重我想取消程序的意图,让我现在重新运行这个,只是计数一个非常大的数字。尽管互联网有点慢,这就是为什么它有点不稳定。这个数字已经非常大,如果我继续加倍的话,如果在这一点上我使用C来实现这个程序,会发生什么。如果在C中我声明一个变量。
我称之为i,它是一个int类型,我不断地重复,如果有什么想法,嗯,我想它可能会崩溃,会占用很多内存。好想法,所以它不会真正崩溃。只是会出现问题,但不会崩溃,因为它仍然是一个int,在C语言中,至少在典型的计算机上仍会占用32位或四个字节。
到现在为止可能已经开始打印零或负数,C语言中的整数是有限大小的,只有32位或四个字节,这意味着如果你不断从1、2、4、8、16、百万、二百万、四百万、八百万等等,一直到十亿,当你越过20亿的阈值,或者如果使用有符号或无符号数字,可能是40亿的阈值,情况就会变得复杂。
当数字变得过大时,你会遇到整数溢出。在Python的世界里,溢出已经不再是问题,你的数字可以变得和你需要的一样大。然而,不幸的是,浮点不精确仍然存在,所以我早些时候只将一除以二,但如果我继续除以其他值,我会发现足够多的。
不幸的是,在小数点方面,我们仍然会遭遇浮点和精度的问题。然而在Python的世界中,
在Java和其他语言中,有科学库允许你根据需要使用尽可能多的精度,或者至少使用你计算机所拥有的尽可能多的内存。因此,这些问题在现代语言中得到了更好的解决,而不是像C语言那样需要一遍又一遍地乘以那个数字。
比我们之前几周看到的数字更大。好吧,让我继续进行另一个程序,这个程序叫做scores。pi,这将是一个真正跟踪分数的例子,这在课堂上,而在Python中,我将继续给自己一个这样的分数列表,72,73和33,再次是对我们的一种戏谑式的参考。
ASCII数字,但在这个上下文中,它们是测验分数,所以两个测验分数和一个相对较低的测验分数,假设这些分数是从100分中得出的。但注意我使用的语法,方括号在Python中给我一个列表。我不需要提前决定它的大小,它不是数组,但。
在精神上是相似的,但它会自动增长或缩小。而且语法甚至更简单。假设我想在Python中计算这些分数的平均值,我可以这样做,我可以打印出这些分数的平均值,比如说,然后我可以这样做,我可以把分数的和除以新列表的长度。
事实证明在Python中,有一个sum函数,可以接收一个列表作为输入,并返回这些项的和。而且我们已经看到有一个长度函数l-e-n,它告诉你列表的长度,所以如果我把我所有的分数加起来,然后除以总分数,这应该根据定义给我结果。
我的平均值,所以 Python 的分数点 pi 哇,哎,我在这里做了什么,我搞错了,所以显然不是故意的,但让我试试,这个错误信息有点隐晦,说明类型错误只能将字符串与浮点数连接。因此,长话短说,在这种情况下 Python 不喜欢我试图将一个字符串与右侧的浮点数相结合,所以有几个。
我可以用多种方法解决这个问题,我们之前看到了基本解决方案,如果这个我已经突出显示的表达式,按定义在数学上是浮点数。但是我希望它变成字符串,我可以直接告诉 Python 将这个浮点数转换为字符串。因此,就像你们发现的 i2a 函数一样,它是 a2i 的相反。
在 Python 中,我可以接受一个浮点数并将其转换为字符串等价物,所以现在如果我运行分数的 Python,我的平均值是 59.3333,你已经看到了一些不精确,最后有一些舍入误差,实际上这不是一个完美的三分之一,但我还有另一种方法可以做到这一点,虽然有点丑陋。
但是我可以使用其中一个 f 字符串,我可以说继续并在这里插入一个值。然后直接打印用户的平均值,因此,事实证明在这些大括号内部,你不必打印整个编码表达式,我会鼓励你不要粘贴疯狂长的代码行,因为那样会很快变得难以阅读。
在那时你可能应该使用一个变量,但在这里我可以继续运行分数的 Python。pi,哇,我又搞错了,这也不是故意的,但我可以修复这个。是的,我遗漏了字符串答案中的 f,所以我又有了多种方法。这里还有第三种我可以在那种情况下做的方式。
在格式字符串内部,Python 会假设我想要这样,这很好。或者我可以把这个提取出来,我可以说类似于这个,给我一个变量叫 average,并将其赋值为 average。因此,就像在 C 语言中,解决这个问题有很多不同的方法,而哪一个是最好的,实际上取决于什么可能是最。
可读性最强、最易维护或者最简单的方法,让我继续动态添加一些分数。现在不再是硬编码我的三分数,让我问自己在整个学期内的分数。从 CS50 导入 get_int,以便我可以轻松获得一些数字。让我给自己一个空的分数列表,其语法就是打开。
括号关闭,所以最开始里面什么也没有。现在让我继续这样做,让我获取这个术语,现在,范围在三之内,我可以继续并附加,get_int 的值是这样的,现在我也可以用很多方式做到这一点。让我把这里的这个去掉,嗯,哎,不,我们还是留着这里,我正在做的事情。
我得到一个整数,并将整数的返回值传递给一个名为append
的新函数。事实证明,列表使用方括号,一旦你在一个变量中定义了它们,例如scores
,它们也有append
函数,以便将数字添加到列表中。现在让我去运行python of scores.dot pi
,手动输入我的72和73。
我的33,瞧!.
说到得到确切的答案,但想想如果在C中你必须提前决定数组的大小,或者不提前决定并使用malloc
和realloc
来不断增长,这将是多么麻烦。这个列表变量中的函数会自动处理这一切,为我们提供功能。
不过有任何问题我可以回答吗?嗯,是的,我有一个问题。即使是写作的笔,它的底层是否只是做像malloc
和realloc
这样的事情?这一切在Python内部发生吗?是的,语言确实这样,所有的malloc
和realloc
的内容。
也许它的底层实现是一个数组,就像在链表中一样。正如我们上周看到的,但所有这些都是为你发生的,这也是代码的一部分原因。
最终运行会慢一些,因为你有别人的代码在中间。
这其中有些工作,在我们打印的方式之间是否存在效率差异?比如说使用f
格式化或不使用。如果我理解正确,确实有一些它的花哨功能。例如,有语法可以指定你想要多少小数位。
在浮点值后打印时,我使用%s %f
等等,幸运的是。由于你不必担心,确实减少了。好吧,让我再做一个可能熟悉的例子,来自几周前,让我快速准备一个。
大写的例子只是为了将我们早期的一个例子联系在一起。
在这种情况下,命名一个文件为uppercase.pi
,让我从cs50
库导入getstring
,然后一旦我有了这个,让我去获取用户的字符串,并要求他们做以下事情,目标是我想将整个字符串转为大写。
我将把所有内容保持在上面,这将在打印之前。询问用户一些输入,然后在之后显示整个字符串的大写版本。那么我该如何做到呢?我们已经看到了一种方法,我可以字面意思地做到。例如s.dot upper
,让我继续保存这个,然后运行python of uppercase
。
让我输入高低写的版本,但如果你愿意,你实际上也可以操作单个字符,让我去 ah**d 细致一些。对于 cns 打印 c,现在这还不是我想要的,但它是一个跳板。注意现在如果我输入高低写,我看到 h i 感叹号,所有的仍然是小写,所以我还没有完成。
让我去掉换行,这样所有内容保持在同一行,因为那样有点丑。让我再试一次,好点了,实际上让我在程序的最后添加一个换行,让我的更多内容,好吧,我没有大写任何东西,但如果我把 c 改为 c.upper,那我再次得到 hi,boom,现在我有另一个可工作。
程序,但现在的新特性是注意第五行的酷炫。如果你想遍历字符串,将 i 初始化为零,然后像在 c 中一样使用方括号表示法。你只需说 for c in s 或者 for x in y,无论是什么,四也可以用来遍历个体,这在进行像密码学这样的事情时很有用。
一次性字符串,我们仍然可以访问我们的各个值。在 python 中还有其他你可以做的事情,类似于我们在 c 中能做的。让我去 ah**d 创建一个名为 argv 的程序,它是输入到 main 的名称,允许你访问命令行参数。今天我们已经看到,这不需要。
但这很传统,并不是必需的,所以我们还没有看到 argc 或 rv,但这是因为它们在 python 的其他地方。如果你想在 python 中访问命令行参数,结果是你可以导入一个名为 argv 的模块。这有点新,但它遵循 cs50s 库的相同模式。
我要从系统库导入一个称为的特性,使用 python。但要使用它,你必须显式导入。现在我会这样做,如果 arg v 的长度等于 2,那么我将去 ah**d 打印出,就像我们几周前做的一样。hello 然后 argv 括号 1。这有点神秘,但我稍后会回来讲这个。
嗯,接下来我要 ah**d 打印出默认的 hello world,所以我们几周前在第二周做过这个,我们运行了一个程序,如果用户在提示符下输入他们的名字,它会说 hello david 或 hello brian。如果他们没有输入,它只会说 hello,world。为了明确,如果我运行这个东西。
我运行它时没有任何命令行参数,只看到 hello world。如果我再次运行并输入我的名字,然后按回车键,我现在看到 hello。david,那这是怎么工作的呢,这第一行代码让我访问了 argv 库。如果你愿意,可以这么说,sys 包,但它的工作方式是一样的。
没有arg c,但没问题,如果v,是一个命令行参数的列表,长度len。将告诉我这个列表的长度,等同于argc,所以我可以,重建我在C中的相同想法,接下来我有一个格式字符串,打印出你好。逗号,然后是大括号中的内容,rgb是一个列表,和C中的数组一样。
列表只是一个数组,可以,动态增长和缩小。你仍然可以使用方括号表示法,人类输入,所以让我为了清晰起见,改变这个为零,如果我现在重新运行它,并,输入大卫,它奇怪地说你好rgb。pi,所以你。没有看到的是单词python,python是,解释器,但这不是部分。
你的程序的执行本身,argv 0将是你正在运行的python程序的,名称,而arg v1将是接下来的,第一个单词,依此类推。所以我们仍然可以访问这个特性,但现在我们可以,将其转换为python,事实上,如果。我要打印出所有的命令行,参数,我可以更简单地这样做。
对于arg in argv去啊d并打印arg,瞥一眼,现在让我去啊d并输入。像大卫·马林这样的两个单词,回车后你现在看到所有打印,出来了。所以。这里也注意到我们如何干净地在python中,遍历一个列表,没有。i,没有必要的方括号,你可以直接说for arg和argv。
就像我刚才说的,对于C,几乎就是python的for循环。它足够聪明,可以判断你想要迭代的对象,无论是。字符串还是列表,天哪,编程变得如此有趣或愉快。现在你不必,像,递增和加加,以及所有这些。
这里,例子很快,但它们实际上只是翻译,再次为即将到来的问题。更多地,系统地比较前后,任何在你这边的事情,布赖恩,这里没有。好吧,我们来看看一些我们的,最终的过去例子,然后我们会,看看一些。甚至更强大的事情,因为现在有像python这样的语言。
让我去啊**d并创建一个程序,这次叫exit。pi,exit。我,这个程序的目的和。生命只是为了演示退出,状态。回想一下,最终在C中,我们引入了返回0的概念,主函数,我们现在在python中也有这个能力。你将会在更多,较大的程序中看到,此外,我要去,导入cis。
整个过程这次只是为了展示一个,不同的做法,我要说如果sys。org v的长度,不等于2,让我去啊d并,喊用户。缺少命令行参数,然后,在这之后我要去啊d并,做sys。exit。1否则,我将去啊**d并,打印出,格式化字符串,显示你好,逗号,r v。
在前面加上cisnow的括号1,理由我稍后会解释。然后在最后,我将默认打印sys.exit(0)
。好的,那么这里发生了什么?不同的事情,我决定不特别导入rv,而是导入整个库,但因为这样做,我不能直接。
在任何地方写下单词argv
,我现在必须用它所在的包或库的名称进行前缀,这就是为什么我开始做sys.argv
,sys
库,这让我可以访问一个退出函数,这等同于从主函数返回。因此在C语言中,这有点二分法,你必须返回零、一个或其他。
从主函数调用其他整数,在Python中,你相应地调用sys.exit
,用相同类型的数字,语法上稍有不同,但基本思想是一样的。这个程序的目的是什么?如果我运行这个程序,它的目的仅仅是返回我的程序名称,所以请注意,如果我只运行python exit.py
。
如果我运行它却缺少命令行参数,它在大吼。现在它说“你好,David”,这个愚蠢的程序只是为了演示如何返回不同的值,因为你不再在主函数中。你不能直接返回,但你可以在Python中根据需要退出,所以这是可以比较的地方。
好的,那么关于退出状态有没有问题?我们只是不断浏览我们看到的特性,即使它们不会自然出现在你面前,Python世界里也有类似的东西。没有问题。那么请记住,在那之后,我们开始真正专注于类和算法,这就是那时候。
我们数据集的大小和代码的效率开始变得非常重要,所以让我去写一个名为numbers.py
的程序,里面包含一个导入,在一会儿,然后给我一个数字数组,比如4682750,你可能还记得这些。
第三周门后面的数字,假设我想要搜索数字零。在C语言中,实施线性搜索时,你会使用一个for
循环和一个类似于i的变量,检查所有位置。Python的方法简单得多,如果在数字中找到零,就直接去打印出“找到”,然后我会去,否则打印出“未找到”。
就这样,所以让我去执行python numbers.py
。希望我能看到确实找到了,因为它实际上在那里,所以这就是你想要的真假。
线性搜索,如果我想对名字做同样的事情,那我就去给自己一个第二个类似的文件。
其实,如果我真的想与我们的 C 版本完全相同,让我去 ahd,并以零退出。这里让我以一退出,但严格来说,这就是我做的。当我们在 C 中做这个时,反而在 names 中让我去 ahd 并做一些类似的事情。让我给自己一个包含很多名字的 names 列表,比尔和查理。
还有 Fred、George、Ginny、Percy,最后是 Ron,在最后的位置,然后让我线性搜索,如果 Ron 在 names 中,去 ahd 并打印出找到,否则去 ahd 并打印出未找到。这次我不会麻烦地打印出或以 0 或 1 退出,但让我去 ah**d 并运行 Python 的 names,哎呀,Python 的 names,瞧。
我们找到了 Ron,注意我没有作弊,我认为我没有搞错。如果我去 ah**d 说 Ron old,那确实是他的正式名字。现在我搜索 Ron,未找到,它确实在寻找一个确切的匹配。所以这真的很酷,我们可以轻松做到,但记住,我刚才说过我。
提出了 Python 还有其他数据类型,其中包括被称为字典(dictionaries 或 dicks,d-i-c-t)的东西,它们代表一组。
键值对在精神上类似于,西班牙语的键和英语的值。将一个转换为另一个,这个英语字典包含英语单词和英语定义,但同样的思路是一组键和值,通过一个你可以找到另一个。那么,让我们去 ah**d,把它翻译成。
Python 在一个名为 phonebook 的程序中。pi 并实现某个回调。在 C 中,我们最初使用了几个数组,然后我们放弃了这个,转而使用一个结构体数组,这更符合。让我去 ahd,并从 cs50 导入 get_string,然后让我去 ahd,给自己一个不同的人的字典。
不过,我将去 ahd 预先使用花括号,它们又回来了,目的是为了字典。然后这是如何定义键值对,将会是,。那是他的号码,然后我将是其他键之一。暂时我们会保持一个非常小的电话簿或字典,**,哦,就这样。
所以花括号在技术上可以位于不同的行,我可以将其移到上面,我可以去掉这个,但在 Python 中有某些风格约定。这里的要点是,字典是用开始和结束的花括号定义的,键和值由冒号分隔。
键值对用逗号分隔,这就是为什么按我所做的方式书写是惯例,显得更加明显。
这是一个包含两个键的字典,每个键都有一个值,可以说它将左侧与右侧关联起来。那么这意味着什么呢?假设我想搜索某人的名字,嗯,让我去 ah**d,并给自己一个名为 getstring 的变量,询问用户姓名。然后让我实现一个自己的虚拟电话簿,类似于。
你的手机让我先去,等我有了名字,如果名字在人员中。这太好了,如果我在人员中找到了名字,让我继续打印出人员。括号名称,这就是字典将要真正解释的地方,Python的电话簿。哎呀,Python的电话簿。让我搜索布莱恩的号码。
突然有了布莱恩的号码,让我继续用大卫的名字运行一下,手机号码还没有,他是未列出的。
所以我输入的其他任何东西,那么在这里发生了什么?在顶部是“人员”,这是一个字典,一组左右的键值对。
然后我像以前一样从用户那里获取字符串。这也很强大!.
实质上在第9行搜索整个字典,我在这里找到与那个名字相关的号码。抱歉,与那个人的名字相关的号码,让我更清楚一点,通过将其提取出来,给自己一个变量叫做“号码”,然后更明确地打印出该变量的名称,今天有什么不同的是,如果名字。
而“人员”在这里是写好的,这样做是让Python搜索所有与那个名字相关的键,而不是值。当你说如果名字在某个字典如“人员”中,它只搜索键。如果你找到键,我可以确定是大卫或布莱恩。
字典中的内容注意到这一点,这就像C语言的数组语法。你现在可以使用方括号符号,通过一个单词如大卫或布莱恩来索引字典,并获得一个值,比如我们的手机号码,而在C语言中,迄今为止在Python中每当我们看到方括号符号,它通常只适用于数字。
因为数组或列表有索引,这些数字指向第一个位置、中间和最后一个等,字典的不同之处在于它们也被称为关联数组,字典是键值对的集合,如果你想查找一个键,你只需使用方括号符号,就像我们过去用方括号处理数字一样。
并且因为Python是一种相当高级的语言,它为你处理搜索,并提供常数时间的搜索,使用我们上周所说的哈希表。字典通常是通过哈希表实现的,回想一下,即使这确实是实现常数时间的目标,如果你选择一个非常好的哈希函数。
你可以接近常数时间,因此,再次强调其特性,给你非常高的性能,这不是线性搜索,实际上,回想一下当我们开始玩拼写器时,使用大约10或20行代码,而不是你可能为第5个编程作业写的那么多,拼写器使用了一个集合,而集合只是一个值的集合。
长话短说,从精神上讲,这与字典相似,因为它在底层使用哈希表来快速获得答案。所以如果你回想一下那个拼写检查器的代码实例,实际上实现了你整个拼写检查器的所有指针和链表,你就会明白。
关于语言本身没问题,任何问题都会回到字典和它们的结构上,因为这种将某物与其他事物关联、组织数据的能力会不断出现。任何问题,嗨,索非亚,字典是否只有一种哈希函数?还是我们可以以任何方式改变这个哈希函数?好问题,嗯。
它带有一个哈希函数,所以你,应该把这种细节留给库,因为其他人花了很多时间考虑如何动态调整数据结构,根据需要移动事物。因此你不再需要为自己实现拼写检查器而感到压力,结果是其他。
事情也会变得简单,这并不是一项必需的常见功能,但这是我们可以做到的。让我写一个叫做交换的快速程序。我们之前给出了 2,然后我打印出像 x 是 x,y 是 y,但这一周我使用格式字符串来打印出来,然后我做了类似交换 x y 的操作。
我只是希望能有好的结果,然后我再次打印出那些值。结果是,在 Python 中,由于你没有指针,也没有可以访问的地址,你不能像上周那样通过引用传递这些变量。
按照它们的地址进行操作,这显然是不可能的。为什么会这样呢?看起来这是一项功能被剥夺了,但老实说,如果这一周有什么启示,包括之前的一周,指针是很难的,像分段这样的东西是困难的,最糟糕的情况下,你的程序可能会被妥协,因为有人可以。
访问不该访问的内存,因此 Python 除去了这一特性,Java 也将这项特性从程序员那里去掉,以保护你自己,避免像上周那样出错。但事实是,如果你想交换 x 和 y,那也没问题,交换 x 和 y,现在如果我在这个程序中运行 Python 的交换功能。
瞧,嘭,它浓缩成另一行,所以尽管他们从我们这里拿走了一些东西,但你仍然可以做很多事情,我们可以把一个更强大的特性交还给你。注意,这个单行代码用于交换,左边是 x,y,而右边是 y,x,这样就实现了布赖恩所做的效果。
这些液体的杯子,进行交换,即使没有临时变量的出现。
在底层,好的,让我们回顾一下这一周。
四,并且在第六周引入我们自己的几个。让我继续实现另一个持久的。
让我继续,创建一个名为的文件。
将此命名为,名称、逗号、数字,所以csv
文件的回忆是。
像一个非常简单的电子表格,我将继续创建这个文件,以便我可以随时使用,然后我将创建一个名为的文件。
电话簿。pi
,我要做的是,从cs50
导入getstring
函数,就像以前一样。但我还将导入一个名为csv
的库,事实证明,Python自带了很多与csv
文件相关的功能,可以让你的生活更轻松,使处理csv
文件变得更简单。我可能想要做的事情之一是。
让我继续打开那个文件phonebook.csv
,以追加模式打开,类似于两周前的f open
,然后让我继续将其赋值给一个名为file
的变量。然后让我继续从用户那里获取一个名字,所以让我使用getstring
来获取某人的名字。然后让我继续获取号码,所以使用number
,最后这个。
这是新的代码,让我将名称和数字保存到一个文件中,并回忆起第4个编程作业。保存文件和将字节写入文件的过程相当复杂,就像恢复或模糊处理任何涉及创建新文件的过滤器一样,结果证明csv
库让这变得相当简单,让我继续给自己一个所谓的writer
。
我将给自己返回一个调用csv.writer
的文件值。那么这个文件再次代表我试图打开的文件。csv.writer
是一个函数,它接受一个你已经打开的文件,并且以某种方式包装该文件,提供一些更高级的功能,这是程序员需要的。
要写入该文件,我要做的是使用writer
变量写入一行,专门包含一个名称和一个号码,并且我使用列表,因为如果你考虑行,列表是一个合适的概念,左到右的每个单元格就像一个列表,一行就像一个列表,所以我将故意在这里使用列表。
最后,我将关闭文件,就像我过去所做的一样。所以这里有点晦涩,但再说一次,getstring
现在已经过时了,唯一的新内容是导入csv
,我以追加模式打开这个文件,类似于我在C
中所做的,然后。
这些行涉及使用csv
功能包装文件。写入一行到这个文件中并关闭它。所以让我现在试试这个,打开phonebook.csv
,目前只包含这些内容。稍早之前的内容,然后让我继续运行这个Python程序,来自phonebook.pi
。
让我加上布莱恩,布莱恩将是加一,*****,千,回车。现在让我去我的 CSV 文件这里,啊,该死,我搞砸了,呃,假装我没有按回车,现在它有效,让我现在再次输入,创建文件时我应该按回车,但我搞砸了。
创建时我就这样做,所以让我挥挥手,证明我在代码中正确做到了,通过添加我自己,大卫,呃,2750 回车,让我回到我的 CSV 文件,瞧,现在格式正确,给我。
还注意到,如果我下载这个文件,让我下载 phonebook.csv,像我在上周那样,让我下载到我的 Mac,打开这个 CSV 文件,无论你是否安装了 Apple Numbers 或 Microsoft Excel,你会打开看起来像这样的东西,瞧,我现在已经动态创建了,使用 Python 代码。
我自己的 CSV 文件,结果是。
有一种方法可以更紧凑地处理这个,我做过,但实际上你也可以以稍微不同的方式打开和关闭文件,你可以用 with open as file
来做到这一点。这样我可以在这里缩进所有这些,并且可以去掉我的关闭行,所以以我之前的方式打开和关闭并没有什么大不了的。
但我在这里的做法有点更 Pythonic,with
关键字并不是我们在 C 中见过的任何东西,当你打开文件时,它会自动为你关闭,最终你可能会在一些在线参考资料或其他材料中看到这个,但它会自动为你完成。
好吧,让我们继续,我喜欢。
我们现在可以处理 CSV 文件,事实证明如果你曾经使用过 Google 表单,那是一个非常流行的。
像这样收集用户数据的方式,实际上让我去一个 URL,cs50.ly/hogwarts。如果布莱恩不介意,打这个进聊天,去那个 URL cs50.ly/hogwarts,如果大家不介意,告诉我你希望被分配到哪个房屋,分院帽会把你放在哪。
如果你以前使用过 Google 表单,结果当然会在 Google 表单本身中,已经有 122 人参与进来,我们可以看到图表中的分布。然而我想要的,不是分布的图示。我要去打开一个电子表格,如果你从未使用过。
之前使用过 Google 表单的,你可以点击一个按钮,然后你可以获得所有当前实时响应的列表。默认情况下,Google 会跟踪表单提交时的时间戳和实际使用的房屋,所以我现在要去做这个。
让我继续在另一个标签页下载它,给我一点时间在这个屏幕上,我要继续下载这个 CSV 文件到我的 Mac 本地,通过选择文件下载 CSV,这会将其放入我的下载文件夹。
将其上传到我的 IDE,只需拖动和浏览器,我将通过拖动和放置文件来完成,好的,现在我有了那个文件,让我继续检查文件是否在那里,我有这个名为“Sorting Hat Responses”的文件,表单回应 1 等等,好的,让我继续写一个程序。
现在如果你像运行一个学生小组一样操控这些数据,这就像是在收集谷歌表单中的数据,或者你只是在一般情况下收集信息并以 CSV 格式保存,那么你现在如何汇总所有的数据呢?
特别是如果谷歌没有结果,结果就是,嗯,让我去写一个程序。
名为霍格沃茨,这不是我们在 C 中见过的东西,让我继续导入这个 CSV 库,最初给自己一个字典,称为 houses,包含一堆键,比如格兰芬多,初始计数为零,赫奇帕奇,初始计数为零,拉文克劳,初始计数为零。
还要带一个初始计数为零,所以注意在 Python 中的字典,或称为 dict。
键和值不需要都是字符串,它当然可以是字符串和数字,因为我要统计某个房子的所有投票或另一个房子的投票,所以让我继续这样做,让我继续打开“Sorting Hat”文件,回应 1 dot csv,文件名很长,但这是谷歌的默认设置。
文件,所以我将使用我的单行代码,而不是打开和关闭。我将给自己一个之前没有见过的读取器,CSV 库有一个读取器函数,可以让我自动读取 CSV 文件。我将继续跳过第一行,时间戳。
而我确实想要忽略的房子,我想要你们提供的真实数据。这就是 CSV 和 Python 的酷炫之处,我可以选择遍历那个电子表格中的所有行,我可以这样做:for row in reader
,现在让我继续获取,比如说,问题中的房子,所以给定行中的房子将是行的第一个元素。
条目是零索引的,那么这里发生了什么呢?让我回到刚才的谷歌电子表格,在谷歌电子表格中,有两列,而 CSV 读取器的工作方式是,它一次返回一行,这在概念上是相当简单的,它完美地映射到电子表格的概念,但每一行都会以一个。
列表,实际上是大小为 2 的列表,所以行括号 0 会给我一个给定的时间戳。
行括号 1 会给我一个给定的房子名称。
这就是为什么在 IDE 中我继续声明一个叫做括号一的变量。因为我不在乎时间戳,我们大致在同一时间做了这个,像在 C 语言中一样索引字典,我可以使用字符串,所以我将继续进入我在上面定义的 houses 字典。
将其增加一,至此我已打开 csv 文件,并使用这个库读取它。在这个循环中,我在每一行中迭代你们通过填写那个表单所创建的电子表格。我再次使用一个变量来获取第二列中的内容,也就是行,括号一,因为行记录零将是时间戳。
然后我进入字典,叫做 houses,我们在这里定义过。我像访问数组一样索引它,但在这种情况下它是一个列表。使用它的房子名称来查找相应的键,增加其值,所以这很好。
进入字典并增加,进入并增加。所以现在我们就到这里的最后,打印出结果。对于 houses 来说,是迭代字典中所有键的华丽方式。继续打印出一个格式化的字符串,如下所示。
让我打印出房子名称,后面跟着冒号,然后是房子的字典。我用房子索引它,所以再次,霍格沃茨的第二个,交叉我的手指,希望我没有搞砸,这确实如此,IDE 在我之前就知道了,好的,现在,真该死。好吧,文件的名称与我之前的稍微不同。
练习一下,让我复制这个,关闭排序帽的响应。啊,它有括号,我忘记了,所有程序,真该死。好吧,没有这样的文件或目录,哦,我忘了 csv,点 csv 现在交叉手指,哦,感谢上帝,好吧,狮子座,呃,哦。
击败赫夫帕夫非常有趣,出于某种社会学原因。但现在我们有一个程序,使用傻乎乎的哈利·波特数据,但想象一下。从用户那里收集任何你想要的数据,将其下载为 csv 到你的 Mac 或 PC,或你的 IDE。然后编写代码以你想要的方式分析这些数据,我做了一个非常简单的求和。
但你当然可以想象做一些更复杂的事情,比如进行求和或平均值。标准差等所有这些功能,我们也能获取,有关字典的问题。在编程中,我们尚未看到的强大功能,布莱恩,你那边有什么问题吗?没有人举手,好吧,那我就去吧,现在我要转换到。
我的 Mac 上预先安装了 Python,这样我就可以在本地进行操作。这会让事情稍微快一点,我不必担心互联网速度等问题,而这确实是一个 PC 解释器,可以在自己的 Mac 和 PC 上运行,但我建议你继续使用这个 IDE,特别是为了问题集的原因。
直到学期结束,也许过渡到你的 Mac 或 PC,仅仅用于最终项目,因为我这个周末花了大量时间让愚蠢的库在我的 Mac 上工作,这通常说起来容易做起来难。
编写的代码应该能够在世界上每一个可能的 Mac 和 PC 上运行。你我以及其他每个人的版本号略有不同,安装的软件也不同,存在不同的不兼容性,所以在本地操作时很快就会出现这些麻烦。因此,让我鼓励你等到学期末的最终项目,或许可以考虑脱离这个 IDE,做我现在要做的事情,因为你能够在这里更清楚地看到这些演示。
我现在在自己的 Mac 上调用一个支持语音合成的库,如果我想访问该功能,只需导入 pyttsx3
,这就是它的名称。
这是我下载并安装的一个开源免费的库,我在过去一周才开始使用它。我发现我可以声明一个变量叫 engine,例如,我可以调用 pyttsx3.init
来初始化这个库。这是因为程序员的设计方式,你必须先初始化它。
然后我可以使用这个引擎说话,比如说,应该运行引擎并等待它完成,然后我的程序再退出。好的,让我继续,现在关闭它并在我的 Mac 上运行 speech.py
,这里是 hello world,确实,我可以让这个更有趣,让我继续说一些像这样的内容,让我再次打开 speech.py
。
添加一些功能,我不会使用 CS50 库,但我可能会使用输入函数。让我继续说,name = input("你叫什么名字?"),然后让我继续说,不是 hello world,而是使用 f 字符串,这可以在任何接受字符串的函数中使用。让我继续说 hello。
好的,继续,再次运行 python speech.py
。哎呀,让我继续并运行 python speech.py
,我的名字是什么,David?你好,David,我们在选择语调上,但确实它合成了。让我们试试 Brian,你好,Brian。好的,我们可以调整设置,让声音听起来更自然一些。
但这真的很酷,让我进入一些我提前写好的代码,这次使用一个不同的库,这个库与面部和面部检测相关,当然在很多网站自动标记你时,联邦政府和执法机构都非常常用,让我打开一个文件。
这里例如更温和一点。
办公室,所以这是一些人在办公室的照片,那里有很多脸。那里有很多盒子,但让我先快速看看一个叫做tech dot pi的程序,这个文件大部分是注释,以便于你在家可以跟随并了解它的功能。
但让我突出一些显著的行,这里又是那个pillow库。我正在访问从预安装的python函数中获取与图像相关的功能。这一项相当惊人,只需导入面部识别,你就可以访问这样的强大功能,现在我只知道通过阅读一些文档来弄明白这一点。
你调用了一个名为face recognition dot load image file的库,这是一个功能,做它所说的事情,我正在打开office.jpg,然后向下滚动到蓝色的注释部分,回想一下这行代码,是使用面部识别库查找给定图像中所有面部位置所需的全部内容。
将它们存储在名为face locations的列表中,这行代码只是一个python循环,遍历每个面孔。
在检测到的面孔中,然后这些几行代码,长话短说,就是裁剪出独立的面孔,并创建一个包含找到的面孔的新图像。
所以在不深入图书馆细节的情况下,这些内容并不是特别有趣的。
目前我们对这些功能很感兴趣,让我运行python来检测。pi让我。
嗯,看看这里,如果我放大,我们看到其他所有照片。被裁剪成了独立的面孔。
如果你曾经注意到在自己和facebook上传照片时的小方块。这正是facebook和其他人执行的代码。好吧,你知道在同一张办公室照片中,有一个人总是显得与众不同,没人真的喜欢他,那就是toby。
如果我们有toby的单独照片,比如这样。
我们能在这些办公室的人群中找到Toby吗?当然可以,让我现在运行一个叫recognize.py的程序,你可以在线查看。它的代码行类似,没多少。
这将进行一些思考,它正在打开办公室的jpeg和这个文件,注意刚刚发生了什么,如果我放大,可以看到他脸部周围确实有一个大大的绿色框被识别出来了。所以,回顾一下代码,这次如果我打开recognize.py,它多了几行代码,但仍然是其他内容。
我正在加载toby.jpg和office.jpg,然后这里还有一些代码在寻找Toby,寻找Toby,然后在他找到的脸周围画一个大大的绿色框。所以说,归根结底,它只是循环,函数和变量,但现在这些函数是。
相当华丽且强大,因为它们充分利用了我们自己在像C语言这样的语言中实现的所有其他特性,或者现在在Python世界中窥见的一些特性。好吧,让我们再做一个,我来快速打开其中一个2D条形码,也就是所谓的二维码,让我创建一个名为qr.py的文件。
在这个文件中,让我导入操作系统库,原因很快就会明了,然后让我导入二维码库,这将为我完成所有的艰苦工作。接下来,让我创建一个名为making的图像,并粘贴课程讲座视频的URL,比如说。
然后让我将这个图像保存为qr.png,可移植网络图形,确实是一个png文件。其他东西,然后让我实际上打开这个,打开系统。其实不,这样也好,让我保持简单,我们不需要操作系统库。不,我们确实需要,让我打开它,使用open qr.png,所以三行代码就可以生成二维码。
带着这个URL。
保存为qr.ping并打开文件,三行代码让我运行python qr.py。
瞧,速度相当快,如果你想拿出自己的iPhone或Android手机,打开相机,如果你的手机支持这一功能,就可以通过尴尬地指向这个3D条形码来扫描它。
在讲座进行时,它应该轻松地为你打开YouTube。我为此向你们道歉,是的,感谢你们告诉我你们现在看到的东西。我再次为此道歉,这真是再正常不过了,但我们所做的只是以二维格式嵌入了一些细节,这些细节我们将在课堂上不深入讨论,其中包含一个URL,暗示着你可以存储。
这些2D条形码中的任何内容,以及你的相机,这些天手机上运行的软件都可以为你解码这些东西。让我做点别的,这次涉及到另一个感官,这个是听觉。让我进入一个名为listen.py
的文件,接下来让我去做,使用,然后让我把所有内容转换为小写,以保持简单。
现在让我做这件事,一旦我获取到用户的词语,接下来让我说,如果"hello"在他们的词中,就继续打印"hello to you too"。所以如果他们说"hello",我想说回去,如果在词中是"how are you",类似于"我很好,谢谢"。如果在词中是"goodbye",那么让我继续说点什么。
合理的像"再见你也好",然后最后,如果没有匹配的,就让我继续打印出像"嗯,无法识别"之类的东西。
一个AI,对吧,一个将以某种方式与我这个人互动的程序。我输入短语给这个东西,所以如果我做对了,接下来让我去运行listen.py
。
我没有正确地做某件事,哦,不在,好的抱歉,让我继续运行listen.py
。说点什么,我会说你好,哦,你也好,真是个友好的程序。让我问它怎么样,"你好吗?" 它似乎能检测到。让我继续说,"好的,再见",它也能检测到。
因为"goodbye"在用户输入的短语中,但如果我说点像"hey there",就无法识别。所以很酷,我们可以使用非常简单的字符串比较,使用in
介词来检测内容,但我敢打赌,如果我们使用正确的库,我们可以让这个变得更强大。让我继续,就像我导入的那样。
面部识别让我在Python中导入语音识别,这又是一个我预先安装的库。让我继续,现在做这个,recognizer
等于speech.recognition.recognizer
,这只是创建一个变量。使用这个库的文档,然后让我继续。
还要从文档中导入speech.recognition.microphone
作为源,这样实际上是在某种意义上打开我的麦克风,再次遵循文档。让我继续说,给用户说点什么,然后在那之后,让我继续,等于recognizer.listen
函数,将我的麦克风作为源传入,然后在这里。
让我继续说,打印出你说了什么,然后我将在下面打印出recognizer.recognize
,这是今天为止最难的部分,出于某种原因,谷歌音频,好的,那么这些代码行在做什么?这里是打开与我在Mac上的麦克风的连接,然后使用语音识别。
用于监听我的麦克风,并将音频存储在一个名为的变量中。
这里的代码行字面上在打印你所说的内容,然后它传递给google。thegoogle。com。
我刚刚在麦克风上录制的音频文件,它打印出从google返回的任何内容,所以让我们看看又会出现什么,交叉我的手指希望我没有搞砸。这是相当不错的语音识别,交给google,但现在让我们把事情做得更花哨,实际上回应,添加一些之前的逻辑并说。
如果hello在words中,那么就继续打印,像之前那样。呃,你好,我很好,谢谢。如果我在words中说再见,那么就继续,再见你也是,识别到这一点了吗。现在让我们继续进行python的listen。pie,hello there,哦,该死,好的,稍等。
抱歉,让我找一找并替换一下,我把变量命名为words而不是audio,我刚刚执行了一个复杂的命令到处替换,所以这次我想说的是audio,现在让我们去运行这个python,listen。pi。hello world,该死,音频数据不是间隔,这是一个bug,记录在案,这是我第一次。
但我以错误的方式来做,让我把变量改回words,好的,我忘了调用一行代码,这实际上就在我面前,我需要转换识别器的返回值,识别google音频,我需要存储将音频传递给google的返回值,并在这里存储结果文本。
所以我在这里恢复了使用words变量,好吧,现在让我继续运行python。非常好,你今天怎么样。
好吧,所以我们有了一个更引人注目的人工智能。诚然,它并不是那么智能,它只是在寻找预先设定的字符串。但我敢打赌我们能做得更好,实际上让我继续进行,无法自拔。在这个剧院的大型高级PC上做一些实时的事情。
我们正在运行其他的python程序。
在一个足够快的cpu上能够实时处理,我们已将我们的一个摄像头连接到那台pc,所以你即将看到的是我们的一台摄像头的结果,它被连接到这台运行着python软件的pc,并且我们已经使用这个python软件训练了这台pc,识别过去的某些图像。
让我们看看能不能做到这一点,继续,我想我们是在线的。所以你再次看到我嘴巴的动作与,呃,爱因斯坦在同步,他的嘴唇与我的相匹配,他的头部动作也在匹配,我们甚至可以表现得好奇,如果我的眉毛抬起,我的嘴巴就会这样那样移动,你可以看到这个python程序实时运行。
这是将我的面部动作映射到深度伪造技术上,错误的环节,我们能试一下吗?类似于配合一个大微笑的人,某些时候会有些失真。但是,如果我们预先渲染所有这些,而不是现场进行,计算机可能会做得更好,能否邀请哈佛校长Larry Bakau加入呢?
这是哈佛大学的CS50课程,介绍计算机科学的知识体系和编程艺术。这是耶鲁大学的CS50课程,介绍计算机科学的知识体系和编程艺术。此时,现实世界的影响应该越来越明显。
Instagram、TikTok等平台,实际上在做同样的事情。你可以看到图像并没有完全跟上我,如果我现在开始稍微快速移动,这在政治、政府、商业和现实世界中都有非常真实的影响。
更一般来说,因为我本质上是在用别人的嘴说我的话,尽管迄今为止这些例子并不那么引人注目,但如果我开始移动太多,你会看到事情开始失去同步,想象一下如果我们再等一年。
我们的计算机将会变得双倍强大。
软件的速度和内存等都在不断提升,库和训练也在改进,接下来几周课程的主题不仅是如何使用技术和编写代码,而是直面更大、更重要的问题。
我们是否应该用技术做某些事情,是否真的应该写这样的代码?我们提前征得了他们的同意,以这种方式调侃他们,但我们想更玩味地结束,给出几个你可能在Instagram、TikTok等上看到的例子。可以邀请Pam加入今天的讨论吗?
哈佛CS50-CS | 计算机科学导论(2020·完整版) - P14:L7- 数据库与SQL知识体系 1 - ShowMeAI - BV1Hh411W7Up
好吧。
这是CS50,这是第七周,今天的重点将完全放在。
数据的收集过程,存储过程,搜索过程,以及更多,你可能还记得上周我们开始时,玩弄一个相对较小的数据集,我们问了每个人,他们在霍格沃茨的首选房子是什么,然后我们继续使用了一些Python。
统计有多少人想要,好的,最终我们是通过使用一个。谷歌表单来收集这些信息,并将所有数据存储在谷歌电子表格中。
然后我们当然将其导出为CSV文件,所以这周我们想收集更多数据,看看开始使用,仅仅一个电子表格或转而使用一个CSV文件,来存储我们关心的数据,所以,您看到的这个网址,应该能看到另一个。谷歌表单,这个表单向您提出一些不同的问题,我们所有人可能都有一些,也许。
我们想要做的是请每个人在那个表单中输入,类型或。
类型,属于,所以请花一点时间来这样做。
如果你在家无法跟上,大家正在查看的是一个表单。就像这个表单一样,我们在询问他们,首选电视剧的标题和该特定电视剧的类型或类型,所以请填写一下,如果可以的话。我们会关注回应。
在进入的时候,我们会给每个人几分钟时间思考,自己首选的电视剧。我自己最近在重看《办公室》,我看了很多旧剧的重播,我想我,看电视的时间有点多。在这一切中,不过为了辩解,它一直在背景中播放,而我在。
我在我的笔记本电脑上工作,所以希望这没问题,让我看看已收集的回应。好吧,我们收到了,数百条回应,再给你一会儿。当前的问题是最喜欢的,电视剧的标题,以及该电视剧所属的类型或类型,布赖恩,你觉得我开始查看数据可以吗?如果我们继续这样也没问题。
收集一些更多的信息,但我会继续展示前几行,如果这样听起来不错。好吧,所以让我们去看一下一些已收集的数据。这是谷歌表单为我们创建的谷歌电子表格,和表单。这个特定的工具至少有三列,用于这个表单,一列是给我们的。
根据大家在标题和类型方面反馈的日期和时间,我手动加粗了它。
提前突出显示,但你会注意到这里的标题和类型完美匹配我们在谷歌表单中提出的问题。回复中,我们的问题可以看到,惩罚者是输入的第一个最喜欢的电视节目,接着是《阿奇》,《办公室》等等,在第三列。
在类型下,你会看到这里有一些有趣的内容,尽管某些单元格,即文本的小框框只有像喜剧或剧情这样的单词。你会注意到其中一些有一个用逗号分隔的列表,而这个逗号分隔的列表是因为你们中的一些人勾选了多个复选框以表示剧情。
还有惊悚片,因此谷歌表单处理这个问题的方式有点懒惰,因为它们只是将所有这些值作为用逗号分隔的列表放入电子表格中,如果我们最终将其下载为CSV,这可能是一个问题。现在在逗号之间又有逗号,幸运的是有解决方案。
我们最终会看到的,所以我们这里有相当多的数据。实际上,如果我继续向下滚动,我们会看到几百条回复,现在以某种方式分析这些数据并找出最受欢迎的电视节目会很好。也许通过它们的类型搜索我可能喜欢的新节目,所以你可以想象一些。
查询可以通过考虑像这样的电子表格来回答,我们都可能偶尔使用谷歌电子表格、苹果数字、微软Excel或其他工具,所以让我们考虑一下电子表格擅长什么,以及它们不擅长什么。有没有人愿意自愿回答第一个问题,是什么?
电子表格擅长什么,或不太确定如何回答。你用电子表格做什么,它们解决了哪些有用的问题?呃,安德鲁,你怎么看,接下来让安德鲁·帕克说说。哦,嘿,它们非常适合快速排序,好的,非常适合快速排序。
像我可以点击标题列的顶部,立刻按字母顺序对所有这些标题进行排序,我喜欢这样。使用电子表格的其他原因是什么,它们解决了哪些问题,它们擅长什么?关于电子表格的其他想法是,存储大量数据,以便你可以稍后分析。
好的,存储大量数据,以便你可以稍后分析,这是一种很好的存储大量数据的模型,可以这么说。我会说实际上是有一个限制,事实上,限制是。
长话短说,在研究生院我研究数据,Excel,支持的行具体来说我有 65,536 行,这在当时对 Excel 来说已经太多了,因为长话短说,如果你回忆起电子表格,每一行都从一开始编号。不幸的是,那时微软。
这些数字中的 2 的 16 次方大约是 65,000。所以在那时我达到了总行数的上限,现在对彼得的观点,他们在最近几年增加了这一点,你实际上可以存储更多数据,所以电子表格确实在所有方面都很好,因为在某个时刻你的电子表格。
比你的 Mac 或 PC 能处理的更多,事实上,如果你实际上是在尝试构建一个应用,无论是 Twitter、Instagram 还是 Facebook,或者任何那种规模的东西,这些公司肯定不会将它们的数据仅仅存储在电子表格中,因为那样会有太多数据可供使用。
而且没有人能真正地在他们的电脑上打开它,因此我们需要一个解决方案来解决这个规模的问题,但我认为我们不需要抛弃电子表格中有效的部分,所以以行的形式,但似乎你也可以以列的形式存储大量数据,尽管我只展示了列 a 和 b。
当然,你可能用过 d e f 模型来思考电子表格中的行与列,我觉得我们可能在某种程度上以稍微不同的方式使用它们,从概念上讲,我们可能会稍微不同地看待它们,行和添加更多条目(如添加更多数据)之间的区别是,那些是在行内,但像是。
数据的实际属性或特征应该在列中。确切地说,当你向其中添加更多数据时,!
电子表格你应该真的在底部添加数据,添加更多和更多的行,所以这些东西在垂直方向上生长,尽管当然这只是人类的感知。它们从上到下生长,通过添加更多和更多的行,但为了索非亚的观点,你的列代表我们可能称之为属性或字段或任何其他这样的特征。
这类数据的类型是,表单,时间戳是第一列的标题,第二列是流派,第三列是那些字段或数据的属性,实际上在你第一次创建表单时,提前决定这些,在我们的案例中,或者在你其他的案例中,使用电子表格时不应该养成这样的习惯。
养成从左侧添加数据的习惯,除非你决定收集更多类型的数据。所以仅仅因为有人在你的数据集中添加了一部新的最爱电视剧,你不应该从左到右在新列中添加,而是向下添加,但假设我们实际上决定从每个人那里收集更多信息,也许那个表单曾问过你。
对于你的姓名或电子邮件地址或其他问题,这些属性或字段将作为新列,因此我们通常提前决定数据,然后再添加更多行,而不是列,除非我们改变主意,需要更改架构。
我们特定数据的情况表明,电子表格确实非常有用,正如彼得所说,对于你知道的大型或合理规模的数据集,我们可以当然根据上周导出那些数据集为.csv文件,因此我们可以从电子表格转到一个简单的文本文件。
存储在ASCII或Unicode中,通常在你自己的硬盘上或云端的某个地方。你实际上可以把那个文件,那个.csv数据库想象成一个数据库。一般来说,数据库是一个存储数据的文件,或者是一个为你存储数据的程序,大家可能都考虑过或者在某种程度上使用过数据库。
你可能熟悉这些大型网站,比如谷歌、推特和脸书等,它们使用数据库存储我们的数据,而这些数据库要么是包含大量数据的巨型文件,要么是为我们存储数据的特殊程序。平面文件只是指在简单文本文件中存储数据时的设计选择。
如果你想存储不同类型的数据,比如索非亚提到的不同属性或者特征,简单起见,我们只需分隔那些列,也就是.csv,你可以使用其他方式,使用制表符,甚至可以使用任何你想要的东西,但如果你的实际数据中包含逗号,那就会有问题。
如果你最喜欢的电视节目标题中有逗号,或者谷歌假设将流派存储为逗号分隔的列表,使用.csv文件时可能会出现不好的情况。
对于你的平面文件数据库,这里有解决方案,在你有的时候。
在你的.csv文件中,如果有逗号,你只需确保整个字符串在最左边和最右边都用双引号括起来,双引号内的任何内容。
以免被误解为划分列,就像文件中的其他逗号一样。这就是平面文件数据库的含义,而.csv可能是其中之一,仅仅因为所有这些程序,数字,都允许你将文件保存为.csv文件。长话短说,那些使用过电子表格高级功能的人。
像内置函数和公式这些功能,都是谷歌电子表格和Excel专有的,.csv文件或.tsv文件,或更一般的平面文件数据库是不可更改的值,因此当你导出数据时,所见即所得,这就是为什么人们使用更高级的程序,比如Excel和谷歌电子表格。
因为你获得了更多的功能,但如果你想导出数据,你只能获取原始的文本数据,但我敢说,这将是可以的,实际上布莱恩,你介意我继续下载这个电子表格作为CSV文件吗?好的,我将继续去。
打开Google表格,点击文件,下载,你会看到一大堆选项,PDF,网页,逗号分隔值,这就是我想要的,所以我确实要选择。csv,从这个下拉菜单中,在表格中,这当然会为我下载该文件,现在我将要继续去,正如上周,我能够将一个文件上传到IDE。
我这周也要继续这样做。我将继续抓取我的文件,它最终在我的下载文件夹中。
在我这台特定的计算机上,我将继续拖放这个文件。
进入这个IDE,最终到了我的。
所以可以这么说,现在我有这个文件《最爱的电视节目》在IDE中。
逗号,标题,逗号,类型是我们的标题行,包含了这个文件中的属性或特征的名称,然后我们有时间戳,逗号,最爱标题,逗号,然后是一个逗号分隔的类型列表,这里确实注意到任何值本身有逗号时的引号,所以这是一种相对简单的文件格式。
我当然可以浏览一下,看看谁喜欢这些节目。但根据上周的情况,我们现在有了一种相当有用的编程语言供我们使用,Python。这可以让我们更容易地开始操作和分析这些数据。回到我的观点,最后的工作,你绝对可以完成我们要做的一切。
在之前的CS50周中,我们可以使用C来做我们即将要做的事情,但你可能会发现C通常是很麻烦的。
某些事情,比如任何涉及字符串操作的,改变字符串、分析字符串的,都是一种真正的麻烦,天哪,如果你不得不将CSV文件加载到内存中,不同于你的拼写检查器,你将不得不在各处使用malloc,或者realloc等等,涉及的工作量非常大。
分析文本文件时,Python为我们处理了所有这些,通过提供更多的功能,让我们可以打开数据,所以让我继续关闭这个文件。让我继续创建一个新的文件,叫做favorites.py,在这个文件中,我将开始回答一些关于它的问题,坦率地说,即使到今天,20多年后。
我自己非常习惯于,当简单,并且没有解决我最终。想要的问题,但一些,简单的作为一种管道的证明,去写一个快速的。程序,仅仅打开这个文件,底部,并逐一打印出每个标题。作为一个快速的理智检查,我知道,数据,他们在这里,所以让我去啊**d 并导入。
csv 然后我可以以几种不同的方式做到这一点,但到现在为止你可能已经看到或记得使用,像打开命令这样的东西。和 with 关键字来打开,文件给我,dash,表单响应 1.dot csv。我将严格地以读取模式打开这个 r。
这不是必需的,你可能会在网上看到例子,不包括它,那是因为,c。和 f 打开我将明确并,实际上做引号中的 r。我将去啊**d 并给这个。
一个文件的变量名,因此这个,csv,文件以只读模式打开并创建一个,引用。现在我将去啊**d 并使用一些,csv 功能,我将要,读取器。这个我可以叫 xyz 其他任何东西,但读取器很好地描述了。这个变量将要做什么,并且,它将是调用 csv 的返回值。
对于那个文件的读取器,基本上是 csv 库的每个。
上周有很多炫酷的功能,内置的,所需输入的只是一个已经打开的文本文件,然后可以说,它会将那个文件包装起来,功能。像现在我可以逐列、逐行读取,好的,现在我,我将跳过第一行,因为。
第一行有我的标题 timest*mp、标题和类型,我知道我的,那。行现在我将这样做,对于行中的,读取器让我去啊**d 并简单地打印。行,我只想要标题,所以我想如果,它有三列。从左到右是 0 1 2,所以我想,打印出列,括号 1,这将是。
第二列 0,索引好的,让我去啊**d 并,保存它,然后去我的终端窗口。运行 python favorites.dot pi 并,交叉我的手指,好的,瞧,它看起来飞快地过去了。它看起来确实这些都是,大家输入的电视节目。向上滚动,所以我的程序似乎在工作,但让我们稍微改进一下。
有一点,结果是使用 csv,读取器不一定是最好的。方法在 python 中,许多人已经发现了,一个字典读取器,这很好,因为你不必知道,或者不断检查。你的数据在哪一列,你可以通过的方式来引用它。
标头本身,因此通过标题引号中的,或者通过类型,这也是好的,因为如果你或。也许一个同事在搞定,电子表格并且他们。通过拖动列左右重新排列它们,任何在代码中使用的数字 0。1 2 等等,可能会突然不正确,如果你的,同事重新排列了那些列。
因此,使用字典阅读器通常是标题,而不是单纯的数字。如果有人,比如你自己或者其他人,改变了那第一行的值并重命名标题或类型,那么事情会崩溃,但在那时我们不得不责怪你没有跟踪你的代码与数据。
仍然存在风险,所以我将这里改为字典阅读器或字典读取器。我的代码几乎可以保持不变,除了我不需要在第五行这里的黑客技巧。我不需要直接跳到下一行,因为我现在想让字典阅读器为我处理读取第一行的过程,但。
其他方面都保持不变,除了最后一行,现在我认为我可以将行作为字典使用,而不是作为列表,标题,来自每一行。因此让我去运行 Python 的 favorites.py,再次运行,哇,看起来我得到了相同的结果,几百个,但让我说明它做的事情是一样的。
好的,在我继续之前,实际上想要在此基础上增强新的功能。对于我们刚刚写的这个 Python 脚本,打开一个文件,用阅读器或字典阅读器包装它,然后逐行迭代打印标题,有什么问题或困惑吗?没关系,我们只知道。
如果你看过 Python 一周,没关系,如果它仍然很新,布莱恩,我们应该解决任何问题吗?是的,为什么你不需要在使用你所用的语法时关闭文件呢?这里是个很好的问题,上周我更严格地单独使用了 open,然后我使用了与我刚刚打开的文件相关联的关闭函数。
实际上是使用这个关键字,它在 C 语言中并不存在。它在 Python 中通常是一个有用的特性,如果你说 with open ... 它会为你打开文件。
然后,只要你的代码缩进在那个关键字块内,它就会保持打开状态。一旦你到达程序的结尾,它会自动为你关闭。因此,这是 Python 在某种意义上试图保护我们的一项特性,这对于人类来说可能是相当普遍的。
这可能会导致保存问题,造成内存泄漏,正如我们在 C 语言中所知。因此,宽度关键字假设我不会傻到忘记关闭文件。Python 会为我自动处理这个问题,布莱恩,还有其他问题或困惑吗?字典阅读器如何知道标题是字典中的键名,这真是太好了。
这是由 python 语言的作者设计的。查看文件中的第一行,第一行,短语,在第一个逗号之前是。第一列的名称,第二个词或短语在,第一个逗号之后是。第二列的名称,以此类推,因此 dict 读取器仅假定。
按照 csv 的惯例,你的,第一行将包含。你想用来,引用这些列的标题。如果你的 csv 恰好没有这样的标题,那么它将直接跳到。第一行的实际数据,那么你没有,正确的,配置,好吧。
让我们继续,现在我觉得,这里有一堆麻烦,你知道。某些节目相当受欢迎,当我浏览这个时,我,很多人喜欢。办公室,很多人喜欢,绝命毒师,权力的游戏以及许多其他节目,所以我认为如果我们这样做会更好。
通过仅查看唯一值来缩小我们对这些,数据的视角。你在查看唯一值,因此,不要只是从文件中遍历。自上而下打印出一个,标题接一个,为什么我们不继续积累这些,数据到某种数据结构中。
这样我们就可以丢弃重复,值,然后只打印出。我们积累的唯一标题,我打赌我们可以用几种方式做到这一点。但如果我们回想上周的,字典演示。你会记得我使用了所谓的集合,我将继续执行这个。
创建一个名为标题的变量,并,将其设置为等于某个名为。
集合只是一个,值的集合,它有点像。一个列表,但它为我消除了重复项,这似乎正是我想要的。每个标题,如果我想要。首先过滤掉重复项,我将继续执行这个。
将继续添加到标题,行,我反而,添加到标题集合,特定的。处理,python 中的集合数据结构将为我丢弃重复项,并且,唯一。现在在我的文件底部,我需要,做更多的工作,诚然,现在我必须遍历。集合,只打印出那些唯一,标题,所以让我为标题执行这个。
标题继续前进,并打印出标题和,用户友好的,少于。n 或者你可以说标题,在标题中,如果标题变量是数据结构的类型。你可以,列表,或者如果它是一个集合,甚至如果它是一个。字典,我们上周在 python 中看到的另一种数据结构,python 中的 for 循环将会知道。
这将循环遍历,所有标题在标题,集合中,所以让我继续保存这个。文件,然后继续运行 python 的,favorites。pi,结果看起来是的,列表有。某种不同,绝对比以前少,因为我的。滚动条没有跳那么远,但老实说,这有点乱。
现在在C中进行排序会有些麻烦,我们不得不拿出伪代码,可能是冒泡排序、选择排序,或者天哪,归并排序,然后自己实现。但在Python中,有很多函数可以用。
所以,如果你想要对这个集合进行排序,你知道的,比如说你想要它排序。Python中有一个叫做sorted的函数,可以使用一些更好的算法,也许是归并排序,也许是快速排序,可能是别的。
这是完全不同的,它不会使用O(n平方)的排序,Python中的某个人可能花时间为我们实现了更好的排序。现在让我再做一次,让我增加我的终端窗口大小,并重新运行Python的favorites.py,好吧,现在我们有一个有趣的。
这些节目组合让我更容易理解,因为我现在在这里进行了整理。实际上,如果我向上滚动,我们应该能看到所有以数字或句点开头的节目,呃,这可能只是有人在玩。接下来是以字母a、b开头的节目,等等,现在稍微容易一点。
理解这些内容,但有些不对劲,我感觉很多人喜欢《最后的气宗》(Avatar: The Last Airbender),然而,我确实看到它出现了四次,但我以为我们是通过使用集合结构来筛选出唯一的。那么,究竟发生了什么?实际上,如果我继续滚动,我可以肯定我在这里看到了更多重复的《波杰克马戏团》(Bojack Horseman)和《绝命毒师》(Breaking Bad)。
布鲁克林九号(Brooklyn Nine-Nine)、CS50和几种不同的风格。
嗯,是的,朋友们,我看到很多重复值,所以发生了什么呢?
是的,你当前的排序是大小写不敏感的,某些地方会导致每次结果不同。是的,正是如此,有些人在大写方面不够认真,事实上,现实是Kudana指出了大小写之间的差异,现在我们已经解决了这个问题。
事实上,当你实现自己的拼写检查器时,早就处理过这个问题了。
你已经处理过这个问题了,当文本中,有些词可能是大写的,有些可能是全小写或全大写,而你想要容忍不同的大小写。所以我们可能通过强制所有内容为大写或小写来解决这个问题,因此实现大小写不敏感的处理。请给我一点时间,快速。
更改我的表单,我要去调整,并且我要改变这一点,以便我们可以,缩回我的代码,继续改变这一点,使我们实际上强制所有内容为大写或小写,实际上无所谓,但我们需要规范化。
可以这么说,以某种方式标准化事物,意味着以某种标准格式化所有数据,所以就像刀锋所说的,让我们将所有内容标准化为大写或小写,我们只需做一个判断,所以我将进行一些调整,我仍然会使用集合,我仍然会像以前一样读取csv。
但我不是仅仅用行括号标题添加标题,而是将强制它为大写,纯粹是为了统一。
也要去,然后让我们前往检查一下,究竟发生了什么,我不会改变其他任何东西,但让我去增加我的终端窗口的大小,重新运行。python 的最爱。pi,瞧,它有点难以阅读,因为我不习惯阅读全大写,感觉像是在对自己喊话,但。
我不明白,等等,我仍然在这里看到了办公室两次,如果我继续滚动,奇怪。更奇怪的是,看来这只是一个错字,我看到两个福尔摩斯,不过,你。看来我还没有完全解决问题。
这个就稍微微妙一点,我可能还应该对我的数据做些什么,以确保我们去除重复项,可能是修整一下边缘,我们会……但。你是什么意思,那有什么用哦,像是修剪掉额外的空格,以防单词。是的,确实很常见,按下空格键的地方不该按,其实我。
有点推测,我敢打赌,你们中的一个或多个意外地输入了福尔摩斯 空格,然后决定不,这样就不再输入其他任何东西,但那个空格。尽管我们看不到,它显然是存在的,当我们做结构时,它实际上会被注意到,因为它们不会相同,所以我可以在几种方式中做到这一点。
python,你可以将函数链接在一起,这也是一种比较花哨的特性。注意我在这里做什么,我仍在访问标题集合,我在添加以下值。我要添加的值是行括号标题,但不完全是,我将继续并进行修剪,这意味着如果我们查看这个函数的文档,正如奥利维亚所说的。
我将去掉或修剪所有左侧和右侧的空白,不论是空格键、回车键、制表符或其他几个字符。这将去掉前导和尾随空白,然后,无论剩下什么,我将强制所有内容为大写。
kadana 建议 2。所以我们现在有点结合了两个好主意,真的在处理数据,变成更清晰的格式,这是一个现实世界的现实。像你我这样的普通人,有时无法被信任,输入数据方式也许并不正确。因为我们有点懒惰,或者有点社交,从亚马逊。
尝试输入有效的邮政编码,因为我可以想到我生活中的几个人,现在还没想好。所以所有东西可能都是大写的,这对需要精确度的计算机系统来说不好。我们在第零周强调过,因此处理数据意味着清理它,进行一些变更,这些变更并不真正改变数据的含义,而是规范化。
它将其标准化,以便可以说你在比较苹果与苹果,而不是苹果与橙子。
好吧,让我继续在更大的终端窗口中运行这个。python favorites stop high voila,向上滚动,看到,向上和向上,相关于空格,我认为我们有一个更干净的独特标题列表。当然,如果我们向上滚动,我会需要更聪明。
如果我想检测像“你”这样的事情,你是非常认真地输入句点,但最后无聊而漏掉了最后一个句点,但这在接收用户输入时是会发生的,当然我们有所有这些变种,来清理它们。
添加一大堆条件和其他处理,以便清理所有内容,如果我们确实想要规范化,去掉引号。
cs50,这真的是一条滑坡之路,你我可以开始写一个,但这就是处理真实数据时的现实。好吧,让我们现在继续改进这个程序,做一些更花哨的事情,因为我现在可以信任我的数据已经被规范化,除了实际内容。让我们继续找出什么是。
在这里观众最喜欢的电视节目是什么,所以我会从我以前的代码开始,因为我认为我已经有了大部分构建模块,我会继续清理我的代码,给自己一个新的变量,叫做标题。
这样我可以以更有条理的方式思考事情。
但我不打算再向这个集合添加东西,事实上,我认为集合并不足以跟踪电视节目的受欢迎程度,因为根据定义,集合会丢弃重复项,但现在的目标恰恰相反,我想知道哪些是重复的,以便我可以告诉你有多少人喜欢这个。
办公室里很多人喜欢《绝命毒师》等等,那又怎么样。
在Python的工具箱中,我们有哪些工具。
提取出,任何关于什么,数据结构的信息,显示流行度,显示流行度,嗯,我想,一个选项可能是使用,字典,这样你可以像,办公室。我不知道20票,然后《权力的游戏》另一个,所以。字典真的可以帮助你,直观地理解,是的,完美的直觉记住一个。
字典,归根结底,无论它在底层实现得多么复杂,就像,你的拼写检查器,它只是一个键值对的集合,实际上它,可能是任何语言中最有用的数据结构之一。因为这种将一个数据片段与另一个数据片段关联的能力。
只是一个非常通用的解决方案,关键是,如果手头的问题是弄清楚。节目的人气,那就让我们把,键设为我们节目的标题,来说。那些键的值我们将映射标题,投票标题,投票标题,投票,因此。让我去啊**d并向上滚动,我可以,设置,我可以改为说字典并给自己。
只是一个空字典,实际上有更常用的简写表示法。用两个空的大括号,这只意味着完全相同的事情。给我一个最初为空的字典,没有复杂的快捷方式用于。集合,你必须字面上输入s e t,开括号闭括号,但字典。
是如此常见,如此流行,如此强大,它们有这个小的语法快捷方式,只有两个。大括号打开和关闭,所以现在,我有了这个,让我去啊**d并在我的。for循环内部做这个,而不是打印标题,我,真的不想这样做,而是将其添加。到集合中,我现在想将其添加到字典中,那我该怎么做呢?如果我的。
字典叫做标题,我认为我基本上可以做类似这样的事情,标题括号。标题等于,或者也许加,等于1,也许我可以有点使用这个。
字典,数字,从零开始,然后加一,加二,加三,所以每次我看到。等于1,加等于1。我们不能做加加。
因为这在Python中不是一件事,它只在C中存在,但这似乎会进入。名为标题的字典,查找与这个特定标题匹配的键。
然后将那里任何值增加,1,但我将去啊d并运行。这个有点天真,让我去啊d并运行python的,favorites.dot pi。哇,或者它已经在第九行崩溃了,所以这在一开始就是一个适当的选择。我们遇到了一个关于惩罚者的键错误,所以,惩罚者是坏的,刚刚发生了一些坏事。
发生了,但这意味着什么呢?一个关键错误是指我尝试访问字典中的一个无效键。这里的代码是,即使标题是一个字典,即使单数的标题的值是“惩罚者”,我仍然遇到了一个关键错误。因为那个标题尚不存在,所以。
即使你不确定 Python,这里也有直观的解决方案。我无法增加“惩罚者”的频率,因为“惩罚者”不在字典中。我觉得你首先需要创建一个 for 循环,并可能给字典中的每个元素分配一个值,例如零,然后再添加。
一,没错,所以直觉很好,在这里我可以用另一个隐喻,我担心我们可能会有一个“鸡生蛋”的问题,因为我觉得我不能在代码的顶部循环中,将字典中的所有值初始化为零,因为。
我需要知道此时所有节目的名称,这很好。
我想我可以更字面地带你走,打开 CSV 文件并从上到下遍历它。每当我看到一个标题时,就初始化为零。然后再有一个 for 循环,也许重新打开文件,做同样的事情,这样可以工作,但这可以说不是非常有效,从大 O 的角度来看是渐进的。
但这似乎是在对文件进行两次遍历。一次只是为了初始化所有内容,再一次只是为了增加计数。我认为我们可以更有效地做事,我认为我们可以实现,不仅是对如何解决这个问题的思考,而是不用遍历整个文件两次,来检查是否。
该键在字典中,如果它还未被添加,那么继续增加值,之后再处理。我们完全可以这样做,所以让我们就应用这种直觉。如果问题是我试图访问一个尚不存在的键,那我们就想办法处理。如果存在,那么增加它,但如果不存在,那么只有遵循 Grid 的建议。
将其初始化为零,所以让我这样做,让我去继续,用一种优雅的方式提出问题,这比在 C 中干净得多。让我再说一次,如果被调用的标题,那也没关系,我可以继续,说 titles[title] = 0。这里的区别是我可以确实索引到一个字典。
使用一个不存在的键,如果我打算在那一刻给它一个值,那是可以的,而且自上周以来一直都可以,但是如果我想去继续增加那个值,我将会在这一行进行操作,但我已经。
引入一个bug,我在这里确实引入了一个bug,我想我需要进一步一步。逻辑上我觉得我不想把这个初始化为零。
作为一回事,有人看到我逻辑中的细微bug吗,如果标题已经在字典中,我就把它加一,有什么细微的捕捉吗,哦,奥利维亚,你觉得我应该把它初始化为一,因为这是第一次实例。确切的,我应该将其初始化为一,否则我就不小心忽略了。
这个特定的标题,我会继续进行计数,以便我可以通过这样做来修复它,或者老实说,如果你愿意,我不需要使用if else,我可以只用if,通过做,如果标题不在标题中,那么我可以继续说标题。括号标题得到零,然后之后,我可以盲目地这么做,那么这到底是哪个。
我觉得第二个可能是更好的一行代码。
但通过那个if条件确保了某人的建议,字典。直到我确定标题在里面,现在,python of favorites dot pi,输入。好的,它没有崩溃,这很好,信息,但我现在可以访问更多,让我来程序。现在我有这个循环,让我继续打印出不仅仅是标题。
还包括字典中该键的值,仅仅通过在这里索引进去,你可以,但用print。
你实际上可以传入多个参数,默认情况下,print会用空格分隔它们,你可以用任何东西来分隔,但这只是为了快速演示一下,所以,让我再运行一次,python of favorites dot pi,瞧,这有点乱,但在办公室非常受欢迎,有26票,真是多。
单个投票在这里,呃,很多《大爆炸理论》有九票,我觉得这会让我花很长时间来理清哪些是最受欢迎的节目,所以我们当然该如何做到这一点。
回到之前提到的要点,使用电子表格,天哪,在微软的Excel或谷歌电子表格或苹果中,标题一出,哗啦一下排序,我们似乎失去了这个能力,除非现在用代码来做,所以让我为我们做一下,哦继续回到我的代码,结果看起来像是排序过的,尽管它确实有效。
字典实际上是按键而不是按值排序的,在这里我们的Python编程技巧需要变得更加复杂,我们想在这里引入Python的另一个特性,这将以一种一般的方式解决这个问题。所以如果我们阅读sorted
的文档,它们的排序是。
它按值对列表进行排序,按键对字典进行排序,因为字典对于每个元素都有两部分信息,它有一个键和一个值,而不仅仅是一个值。因此默认情况下,sorted按键排序,所以我们必须以某种方式重写该行为。那么我们该如何做到呢?实际上,sorted函数。
key参数的值是函数的名称,这里事情变得非常有趣,如果不说令人困惑的话。实际上,在python中,参数可以通过其名称传递。技术上你可以在c中做到这一点,但语法上复杂得多。但在python中非常常见。
javascript在许多语言中都很常见,通常将函数视为一等对象,这是一种花哨的说法,意思是你可以像传递其他东西一样传递它们。
调用它们,但你可以按名称传递它们。那么我所说的是什么呢?好吧,我现在需要一个函数来对我的字典进行排序,这,也许让我去啊**d给它一个叫f的名字,因为我们要暂时把它去掉。定义一个名为f的函数,输入一个标题,然后返回给我该值。
对应于该键,所以我要去啊**d并返回。
这个函数的目的是非常简单,你给它一个标题。它给你对应的计数,查找起来简单,但这就是它唯一的目的。但是,根据文档,sorted现在要做的是因为我传入了一个名为key的第二个参数。
sorted函数并不是仅按键的字母顺序排序,而是会在字典中的每个元素上调用该函数f,并根据你的返回值来确定实际的。
默认情况下,排序仅查看键,我实际上是在做这个值,每个键对应的,因此,即使语法有点。
新的字典是按键排序的,因为默认情况下它按键排序。但如果我定义自己的键函数并重写该行为以返回对应的值。它是值、数字和计数。
好的,让我们去啊d看看这在实践中是否真实,让我去啊d重新运行python favorites dot pi,我应该能看到所有标题,瞧,最受欢迎的节目似乎是《权力的游戏》,27。其次是《办公室》,26,等等,但当然这个列表有点。
倒着说,我的意思是这很方便,我。
我可以在屏幕底部看到它,但如果我们在创建列表时,它应该真的在顶部,那我们如何覆盖这种行为?事实证明,sorted 函数,如果你阅读它的文档,还接受另一个可选参数,称为 reverse。在 Python 中,这将去 ah d 并现在给我们。
该排序的反向顺序,所以窗口,再次运行它,瞧,如果我向上滚动到顶部,它不是按字母顺序排序的,但如果我继续向下滚动,数字越来越大,瞧。
现在《权力的游戏》以 33 分的成绩高居榜首,太酷了。而且至少是我们可以传递函数的两个函数,并让后者调用前者,这样说复杂了,不过现在有关于我们如何使用字典和以这种反向值排序的方式有什么问题或困惑吗?
有什么问题或困惑吗?聊天或口头上,布莱恩。嗯,看起来所有问题都已得到解答,那在这种情况下让我指出一个常见的错误,注意即使 f 是一个函数,这样写也是不正确的,原因是我们故意想将函数 f 传递给 sorted 函数,以便。
sorted 函数可以自作主张地反复调用 f。我们不想只用括号调用它一次,而是想通过名称传递它,这样 Python 的 sorted 函数就可以为我们完成这项工作。圣地亚哥做了,我们把 f 作为 title 呀,那么为什么不呢?
我本来要问那个问题,特别是哦,因为那样会调用函数一次而且仅一次,我们希望 sorted 能够再次和再三调用它。现在这实际上是一个示例,正如我们在过去看到的那样,这是一个正确的解决方案,这表现得正如我所期望的,从上到下的排序标题列表。
按受欢迎程度排序,但设计得有点糟糕,因为我定义的这个函数 f 首先名字有点无聊。我只为了在一个地方使用而定义一个函数,天哪,这样的按键浪费。
所以事实证明,在 Python 中,如果你有一个非常短的函数,其生命意义就是这样,而它短得足以让你确定可以放在一行代码上而不会换行并且开始变得风格丑陋,实际上你可以这样做。你可以像这样复制你脑海中的代码,函数名称。
你实际上可以使用一个在 Python 中称为 lambda 的特殊关键字。你可以像以前一样为函数指定一个参数的名称,然后简单地指定返回值,随后删除函数本身。所以为了明确,key 仍然是传递给 sorted 函数的一个参数,它通常期望的值是。
函数的名称,但如果你认为这似乎是浪费额外的精力去定义一个函数然后传递这个函数,尤其是当它如此简短时,你可以在一行内做到。lambda函数是Python中的一个,因此你不必为它选择名称,但它仍然在意。
关于它的参数和返回值,所以仍然由你提供零个或多个参数和一个返回值。注意我已经指定了关键字lambda
,后面跟着我希望这个匿名的无名函数接受的参数名称,然后我指定返回值,而对于lambda函数,你不需要指定返回值。
不需要指定返回值,你在冒号后写的内容就是字面上将被返回的内容。
自动化,所以这再次是一个非常Pythonic的做法,这是一种非常巧妙的单行代码,尽管它需要一些时间,但它允许你将思想浓缩成一个简洁的陈述,完成工作,因此你不必开始定义更多的内容,也无需跟踪,好的,有什么问题吗?
复杂或精致的程度与我们今天的Python代码相当,我在想为什么使用lambda
,而不是其他一些关键字?是的,这背后有一段漫长的历史,如果你在哈佛上了一门功能性编程的课程,叫做CS51,这个问题我留到下次再说,但实际上不仅在Python中。
其他语言中也存在这些东西,称为lambda函数,所以它们实际上在其他语言中相当常见,因此Python也采用了这个术语。从数学上讲,lambda通常用作函数的符号,因此它们借用了这个想法。
编程,好吧,所以没有其他问题,我们继续解决一个相关问题,仍然使用Python,但这将接近效率的极限,涉及到将我们的数据存储在CSV文件中。让我开始在这个文件favorites.py
中重新开始。
到目前为止我写的所有代码都已经提前放在了课程网站上,所以你可以看到逐步改进。我将继续在顶部再次导入CSV,然后这次让我们写一个程序,这个程序不仅仅是自动打开CSV并分析它,寻找节目的总流行度。
然后,哦,继续输出它的流行度,我可以用很多不同的方式来做这个,但我首先会让用户输入一个标题。我可以使用CS50的getstring
函数,但请记住,它与Python的input
函数几乎相同,所以今天我将使用Python的input
函数。
然后我将去做,像之前那样打开同一个csv,叫做最喜欢的电视剧。form responses 1.csv文件,我将给自己一个阅读器。我会再次使用字典读取器,这样我就不必担心,知道每列是什么内容。传入文件,然后让我们看看,如果我只关心一个标题,我可以保持这个程序。
更简单,我不需要搞清楚每个节目的受欢迎程度,我只需搞清楚一个节目的受欢迎程度,也就是人输入的标题。所以我要去做一个非常简单的整数,叫做counter,并将其初始化为零,我不需要整个字典,只需要一个。
变量现在足够了,我要去做一个遍历,呃行,接着说。如果当前行的标题呃,等于人输入的标题,就加一,它已经被初始化,因为这样很好,然后在这个程序的最后,让我们简单地打印出这个值。
然后我只需在文件中报告其受欢迎程度。所以让我去做这个,用python来处理最爱的节目。让我输入《办公室》,回车。
19。现在我不记得确切的数字,但我记得。
《办公室》比那个更受欢迎,我很确定它不是19。直觉认为这个程序是。buggy,或者说似乎我做了什么,聊天中有任何想法吗?
聊天中有几个人在说你需要记得处理。大写和空格问题,没错,所以我们需要练习之前学到的那些相同教训,所以我真的应该标准化人刚刚输入的内容,还有来自的输入,这在上面。
首先去掉前后空格,以防我有点马虎,按下不该按的空格键,然后让我们去,因为。大写或小写都无所谓,这样,当我这样做时,看看当前行的标题,我觉得如果我要标准化一个,我真的需要做同样的事情。
需要将其他内容标准化,现在比较两个字符串的全大写去空格版本。让我重新运行,现在我要输入《办公室》。回车,瞧,现在我在26,这我想是我们之前的地方,实际上现在我作为用户可以有点马虎,我可以说《办公室》。
我可以再运行一次,输入《办公室》。
空格键按了很多次,回车,这仍然会有效,确实我们似乎在这里过于挑剔,比如去掉空格等等。想想在相对较小的听众中,有多少人不小心按下空格键或以不同方式大写这个。
当规模巨大的时候,这变得非常重要。想象一下,当你在某些社交媒体帐户中标记朋友时,这会很重要。你不想要求用户输入大写的B、小写的r、i、a、n等等,这是一个非常常见的问题。
在我们今天使用的应用程序中,好吧,关于这个有任何问题吗?
那我想问你一个问题,在什么意义上这个程序设计得不好?在什么意义上这个程序设计得不好?这更微妙,但想想这个程序的运行时间,以大O的形式,这个程序的运行时间是什么,如果csv文件中有n个不同的节目或者n个不同的提交。
所以n是我们讨论的变量,是的,安德鲁,运行时间是什么?是大O的n,因为你在使用线性搜索,是的,它是n的O。因为我确实在通过for循环使用线性搜索,这就是c的方式。从开始一直潜在到结束,所以我。
隐式使用线性搜索,因为我没有使用任何复杂的数据。想象一下,如果我们不仅对班上的所有学生进行调查,而是对校园里的每个人,甚至对全世界的人进行调查,或许我们是互联网电影数据库imdb。可能会有大量的投票和节目。
所以,无论是在我的终端窗口还是在其他地方,写一个程序会更加优雅。
一个移动设备或者可能是在网页上,适用于你的笔记本或台式电脑。这可能不是最好的设计,不断遍历你数据库中的所有节目,只为回答一个问题。
从上到下,仅仅是为了回答一个问题。
以记录结束时间或以常量时间的方式处理事情,会更好。感谢过去几周,我们在cnn和python中看到了实践。我在这里讲的内容,实际上在某个时刻,平面文件数据库的概念开始对我们来说过于原始。平面文件数据库如csv文件。
当你只想快速做点什么或从标准中下载数据时,它们非常有用。
便携的方式,便携意味着它可以在系统之间使用,csv文件是最简单的格式,无论是word、苹果数字或其他特定产品,它只是一个文本文件,因此你可以使用任何文本编辑程序或任何编程语言来访问它,但平面文件数据库并不一定是最佳的结构,最终用于较大的数据集。
因为它们并不真正适合更高效的查询,所以要搜索更好的数据库,通常被称为关系数据库,这些数据库存储数据,公平地说,这些程序使用了大量内存,它们确实会持久化你的数据,通过将数据存储在文件中来长期保存。
在你和你的数据之间,有一个正在运行的程序,如果你听说过Oracle、MySQL、Postgres、SQL Server或Microsoft Access等其他许多流行的产品,无论是商业的还是开源的,关系数据库在精神上与电子表格非常相似,但它们是通过软件实现的。
它们给我们提供越来越多的功能,使用越来越多的数据结构,以便我们能比仅使用CSV文件更高效地搜索、插入、删除和更新数据,所以我们来这里休息五分钟,回来后我们将查看关系数据库。
好的,我们回来了,现在的目标是从这些相对简单的。
从平面文件数据库过渡到更正式的关系数据库,而关系数据库确实是支持今天许多移动应用、网络应用等的基础。现在我们开始过渡到现实世界的软件和真实世界的语言,所以现在,让我介绍我们将称之为SQL Lite的东西。
因此,关系数据库是一种以行和列存储所有数据的数据库,但它不是通过电子表格,而是使用我们称之为表格的方式,因此基本上是同样的想法,但在表格中,我们获得了一些额外的功能。
使用这些表格,我们将能够搜索数据、更新数据、删除数据、插入新数据等,这些都是我们在电子表格中绝对可以做到的,但在电子表格的世界里,通常是你这个人手动点击和滚动。如果你想插入数据,通常是你手动输入的。
如果你想删除某个东西,可以删除整行或更新其中的单元格,使用结构化查询语言SQL,我们有一种新的编程语言,它通常与其他编程语言结合使用,所以今天我们将看到最初独立使用的SQL。
但我们也会看到在Python的上下文中,它本身可以独立使用SQL。SQL Lite就像SQL的轻量版,更友好,更便携,可以在Mac、PC、手机、笔记本、台式机和服务器上使用,但在手机上极其普遍。
您今天在自己设备上运行的许多应用程序都是在后台使用sqlite,所以它并不是一种玩具语言。它实际上是对通常称为sql的语言的相对简单实现,但长话短说,还有其他实现。
我已经列举了几种关系数据库,包括oracle、mysql和postgres等,它们都有略微不同的sql语言风格或方言,用于与数据库进行交互。
社区在某种程度上添加或删除了他们自己的首选特性。因此,您使用的语法通常在所有平台上是恒定的。但为了我们的目的,我们将以sql lite为标准,实际上这就是您如今在关系数据库世界中会使用的。
移动应用程序,所以这与此密切相关。使用sqlite,我们最终将能够查询数据、更新数据、删除数据等。但为了做到这一点,我们实际上需要一个程序,因此您的数据仍然存储在文件中,但现在它是一个包含零和一的二进制文件。
那些零和一可能代表文本,也可能代表数字,但这是一种比单纯的csv文件使用ascii或unicode更紧凑高效的表示方式。因此,sqlite的第一个区别是,它使用一个单一的二进制文件来存储所有数据,并通过所有这些零和一在该文件中进行表示。
这是我之前提到的表格,它们是数据库世界中类似于电子表格的概念。因此,为了与存储所有数据的二进制文件进行交互,我们需要某种用户界面程序,而sqlite被称为sql lite3,实质上是版本工具,精神上类似于任何命令。
迄今为止,您在终端窗口中运行的程序允许您打开该二进制文件并与所有表格进行交互。现在,我们再次面临一个“鸡与蛋”的问题:如果我想使用数据库,但我还没有数据库,同时又想从数据库中选择数据,我该如何加载内容呢?
您可以通过至少两种方式将数据加载到sqlite数据库中,直接导入csv。您所要做的就是将csv保存在您的mac或pc和cs50 ide上,它将自动完成。
查找所有的逗号,它将内部构建。
该二进制文件的相应行和列使用适当的零和一来存储所有信息,因此它会自动为您导入。第二种方法是实际编写其他代码,手动将所有数据插入数据库中,我们将提前进行此操作,运行sqlite3,这是。
在cs50 ide上预安装,并且在mac和pc上也不难启动。我将在我的终端窗口中继续运行sql light 3,输出。它告诉我如果我想查看一些使用提示,可以输入句点帮助,但我知道大多数命令。
你可能需要的,事实上我们可以导入的命令之一。
所以一般来说,你不会频繁使用这些,通常只有在第一次创建数据库时,才会使用它们,当你从现有的csv文件创建数据库时,事实上这是我目前的目标,让我将包含你最喜欢的电视节目的csv文件加载到sql lite中,形成一个合适的关系数据库。
这样我们在搜索数据和进行其他操作时可以表现得比例如大O(n)更好,因此为了做到这一点,我需要执行两个命令,第一个是将sql light置于csv模式,这只是为了将其与其他平面文件格式区分开,比如tsv或其他格式,现在我要继续执行。
导入时,我必须指定要导入的文件名,即csv。我将继续将我的表称为shows。
所以点导入需要两个参数,您要导入的文件名。
从该文件创建表格,再次强调,表格有行和列,明确这些列的开始和结束,我将按下回车,看起来速度非常快,似乎没有发生什么。但我想这没关系,因为现在。
实际上操作数据的能力,但我们如何操作数据。我们需要一种新的语言,sql结构化查询语言,是sql lights、oracle、mysql和postgres及其他一些产品所使用的语言,你不需要在短时间内知道或记住它们的名字,但sql将是我们用来。
查询数据库以获取信息并进行操作,一般来说。关系数据库以及相应的sql,作为与关系数据库交互的语言,支持四种基本操作,这些操作可以用一个简单的首字母缩略词记住。
操作与关系数据库相关的,crud代表创建、读取、更新和删除,确实这个缩写是crud,这样有助于你记住任何关系数据库支持的四个基本操作是创建、读取、更新和删除。创建意味着创建或添加新数据,读取意味着访问并加载新数据文件到内存中。
更新和删除的意思是,如果你想操纵数据集中的数据。现在这些是任何关系数据库的通用术语,通常由任何关系数据库支持的四个属性,具体的实现是这四个功能,它们是创建和插入。
通常来说,创建更多的关键字是用来从数据库中读取数据的,更新和删除也是如此。
一种令人恼火的不一致性,术语或行业术语是 CRUD,创建、读取、更新、删除,但在 SQL 语言中你会看到这五个关键字,以便于实际对数据库进行操作,那么这意味着什么呢?
假设你想手动操作时间,那你该怎么办?在电子表格的世界里,这很简单,对吧?你打开谷歌电子表格,选择文件新建或其他,然后就可以得到一个新的电子表格,就像微软 Excel 和苹果数字一样。
文件菜单中新建电子表格或其他,然后你现在有一个新的电子表格,在 SQL 的世界里。
然而,与代码互动有图形用户界面,通过这些界面你也可以与它们互动,但今天我们将使用代码来实现这一点。在命令行程序中,结果是你可以通过运行类似的命令程序化地创建表格,所以如果你按字面意思输入语法,就像创建。
表格的名称在这里用小写字母表示,然后是括号,接着是你想创建的列名,以及该列的类型。
你将在这个被称为 SQL 的语言中使用,表格用小写字母表示,列用小写字母表示,是你想给自己列的名称,也许是标题,也许是类型。而点点点则意味着你当然可以有更多的列。
如果我在终端窗口输入这种命令。
sqlite3 程序,我可以为自己开始创建一个或多个表格。实际上,这已经为我发生了,点导入命令,这不是 SQL 的一部分。在 Excel 或谷歌电子表格中,点导入只是为我自动化了某个过程,实际上它为我做的是,如果我现在输入点架构,这就是。
另一个特定于 sqlite 的命令,任何以点开头的命令仅特定于 sqlite 3 的这个终端窗口程序。
请注意,通过运行点导入,输出的表格在我称为timest*mp的数据库中,标题和类型这些列名来自哪里,实际上它们来自csv的第一行,并且它们看起来都是文本,因此这些值的类型显然是清晰的。我本可以手动输入这些,创建这三个新列。
表格称为显示给我,但再次提到的点导入命令只是自动化了,从csv中导入。
哈佛CS50-CS | 计算机科学导论(2020·完整版) - P15:L7- 数据库与SQL知识体系 2 - ShowMeAI - BV1Hh411W7Up
表格。
shows 等等,也就是说现在,在这个数据库中有一个文件,或者说有一个表。名为 shows,其中包含了来自那个 CSV 的所有数据。我实际上该如何获取这些数据呢,结果表明还有其他命令,不仅仅是 create。还有 select,结果表明 select 相当于 read。
从数据库获取数据,这个功能相当强大,许多数据科学家和统计学家,使用并喜欢使用像 SQL 这样的语言,来处理和。过滤数据并分析数据,使用今天对我们来说新的语法。但相对于我们见过的其他东西,这个语法相对简单,SQL 中的选择命令让你。
从你的表中按照给定名称选择一个或多个列,我们稍后会看到这一点。那么我该如何进行呢,让我继续。
在我清除完这个之后,让我继续选择。
假设从 shows 中选择标题,分号,那我为什么要这样做呢。
选择命令,更多列,然后是字面上的介词 from,接着是你想要选择的数据的表名,列名叫。标题,这就意味着 select,title from shows 应该会给我返回我想要的数据。请注意一些风格选择,这并不是严格要求的,但却是好的风格。
按照惯例,我会将任何 SQL 关键字,包括 select 和 from 首字母大写。在这种情况下,然后将任何列名或表名小写,假设你创建了。那些列和表,实际上是小写的,还有其他不同的惯例,有些人会使用大写,有些人使用类似的方式。
但一般来说,我会鼓励,对于 SQL 语法使用全大写,而列名和表名使用小写,输入,值,从数据库输出,如果你。回想一下,你可能会意识到,这实际上是与之前相同的顺序。因为 CSV 文件是,从上到下加载到这个同一个数据库表中。
相同的数据,重复和错误的大写,以及奇怪的空格。但假设我想查看 CSV 中的所有数据,结果表明你可以选择。标题,但也许时间戳是感兴趣的,这个显然是大写的,电子表格。这不是我手动选择的,因此如果我只使用一个用逗号分隔的列表。
列名,注意我现在能做的事情,对于我们人类来说有点难以看清。因为现在有很多事情在进行,但请注意,在左边的双引号中,有所有的时间戳,表示你们提交你们最喜欢的节目的时间,而在逗号右侧还有另一个,被引号的字符串。
那是你喜欢的节目的标题,尽管sqlite在仅是单词(如friends)时会省略引号,这只是一个约定。事实上,如果我想获取所有列,结果发现有一些简写语法可以做到这一点。
这将让我获取我的表格中从左到右的所有列,瞧,现在我可以看到流派。所以现在我实际上有三列在输出,非常有用。到目前为止,我所做的只是处理csv,但sql很强大,因为它自带其他功能,有点类似于函数和excel。
但现在我们可以最终在计数、唯一值、转小写、最大值、最小值和转大写等函数中使用它们,还有很多其他功能。这些都是内置于sql中的函数,可以作为查询的一部分使用,以某种方式在从数据库返回数据时,改变数据的格式。
所以例如我关心的就是我的唯一标题,我们不得不写所有那些烦人的代码,使用集合并将内容添加到集合中,然后再次循环,这确实不算大量代码,但我们花了五到十分钟才能完成工作。在sql中,您可以将所有内容放在一行中。
选择不仅仅是从节目中提取标题,让我去选择唯一值。
从节目中提取标题,因此再次,唯一值在sql中是一个可用的函数,它的功能正如其名称所示。
过滤掉所有标题,只给我返回唯一值,因此如果我现在按回车,你会看到一个类似的混乱列表,但不包括那些不看电视的人的标题。包含这些标题的未排序列表,所以我认为我们可以开始像之前一样整理这个东西。让我去选择,不仅仅是唯一值,让我去选择。
还要将所有内容转为大写,我可以使用转大写作为另一个函数。注意,我只是将一个函数的输出嵌套到另一个函数中。让我现在按回车,现在看起来更标准化,因为我对所有内容都进行了大写处理,但似乎事情仍然不太。
排序仅是您输入它们的相同顺序,但没有,sql有其他语法可以使我们的查询更加精确和强大。因此,除了可以用来改变返回数据的这些函数外,您还可以使用这些类型的子句。
在sql查询中,您可以说“where”,这相当于一个条件。
你可以说选择所有这些数据,条件是某些东西是真或假,你可以说像“where”,也可以说给我一些不完全是这样的数据,而是像这样的。你可以按某一列排序数据,可以限制返回的行数,并可以以某种方式将相同的值分组。
让我们看看几个例子,如何选择标题。来自shows表,标题等于,我添加了这个,标题等于引号中的“the office”,所以SQL在精神上与Python类似,或许比C更用户友好。尽管它有点。
精确且更简洁,让我去**并按下回车。瞧,这就是你们输入“the office”的数量,但注意并不是所有人都是这样,我们仍然缺少一些。似乎我只得到了那些字面上输入了“the office”大写T和大写O的人的结果。那么如果我想更加宽容呢?
好吧,让我返回任何你们输入的“office”,也许你省略了“the”这个冠词。所以让我去并说标题不等于office,但让我去并说标题是“office”,我想允许末尾有一些内容。尽管这在上下文中似乎有些不一致。
使用像另一个通配符一样的字符,即百分号(%),在左侧。这个百分号表示零个或多个字符在右侧。所以这是一种通用的方式,可以找到所有包含o-f-f-i-c-e的标题。结果表明,like是大小写不敏感的,所以我甚至不需要。
现在让我们不必担心大小写的问题,按下回车,瞧,我得到了更多的答案,你可以真正看到这里的杂乱。注意上面,有人使用了小写字母,快速输入时这往往是常见的,有人确实在这里使用了小写字母,并且还给我们。
在结尾有一个额外的空格,有人只输入了“office”,结尾有一个空格。所以这里有很多变化,这就是为什么当我们强制所有内容时,我们能够消除许多冗余。那么实际上,让我们去**并排序,所以让我返回选择,distinct,upper of title。
从shows中,现在让我按新的子句进行排序,标题的大写版本。所以现在注意这里有一些事情在进行,但我只是在构建更复杂的查询,类似于Scratch的学习,我们只是开始不断地向一个问题抛出更多的拼图块。我选择所有不同的大写标题。
从shows表中,但这次我要按标题的大写版本来排序结果,所以所有内容都将是大写的。然后将按字母顺序从A到Z排序,现在按下回车,事情会变得更容易理解,注意,引号只有在。
标题中有多个单词,但注意这些是所有节目,以及m's,l's等等,确实是按字母顺序排列的,感谢使用按顺序排列。好吧,让我暂停一下,因为我知道一下子有很多内容,到目前为止对选择、去重、上层表名、where子句、order by子句有任何问题吗?
这很快,但一般来说,它表达了我们已经看到的各种问题。布赖恩,你那边有什么吗?没有人举手,好吧,让我们开始解决更多类似的问题。现在在SQL中,之前在Python中,如果我想实际计算。将所有相同的节目合并,找出所有相应的计数,好吧。
让我继续尝试这个,让我继续选择一下嗯。标题的大写版本,但我这次不会做不同的选择。我将选择标题的大写版本,计数那些标题的出现次数,所以把它视为一个新的关键词,现在,什么。
计数,嗯,如果你想象这个表有很多标题,标题,标题,标题,标题,能够将相同的标题分组在一起,然后实际计算有多少个这样的标题,我们可以说。按大写标题分组,这告诉SQL将所有的大写标题分组。
一起将多个行合并为一行,但保持标题的计数。按回车键,你会看到非常类似于左侧写的所有标题,后面跟着一个逗号,后面是计数,因此你们中有一个人真的喜欢《猫和老鼠》,而有一个人真的喜欢《顶级车》。不过,如果我向上滚动,有两个人真的喜欢《线人》。
在场的你们中有23人喜欢《办公室》,尽管我们还没有处理这个问题。因此,如果我们想的话,可以通过修剪空格进一步合并。但现在我们得到这些计数,嗯,我该如何继续排序呢?就像我们之前做的那样,让我继续这里,按标题的计数排序,然后点分号,现在。
现在注意,就像在Python中,所有内容都是从最小到最大。
在底部,我该如何修复这个呢?结果发现,如果你能将事物按降序排列。d-e-s-c是缩写,而asc是默认的升序。所以如果我以降序排列,现在,我必须一直向上滚动到最顶部,看看行从哪里开始,哎呀,如果我一直向上滚动到开头。
在这里,现在如果我想,哎呀,我这样做对吗?抱歉,我不想这样,哦,来吧。按计数降序排列,现在让我继续,这看起来有点太笨重了,让我限制自己到前10个,只看前10个值,瞧,现在我有《权力的游戏》33。
在 26 的办公室和 23 的朋友们,不过我觉得我仍然遗漏了一些,布莱恩,你记得修剪前导和后导空格的序列函数吗?我想就是 trim,trim,好吧,我自己不记得了,所以如果有疑问,就修正它。让我继续前进,选择大写的内容。
现在输入,瞧,谢谢布莱恩,所以现在我们达到了我们的。
这里有 26 个办公室,总之,我们花了一些时间才到达 SQL 的这个故事节点,但请注意,我们做了什么,我们把一个程序浓缩成了几分钟,并且确实是十几行代码,浓缩成了一个新的语言。
但这只是一句简单的说法,一旦你对一种语言,比如特别是 SQL,感到舒适。如果你不是计算机科学家,而是数据科学家或某种分析师,整天查看财务信息、医疗信息或任何可以加载成行和列的数据集。
一旦你开始说。
人类可以阅读 SQL,你能开始表达一些非常强大的查询,你的答案。通过使用像这样的命令行程序。
结果在这里,尽管只是非常简单的文本,但如前所述,还有一些商业软件也支持 SQL,你仍然可以输入这些命令,然后它会以更用户友好的方式显示给你,就像 Windows 或 Mac OS 默认那样。
所以现在关于选择语句的语法或功能有什么问题吗?关于选择的任何问题。布莱恩,你那边看到什么了,一个问题来了,数据实际存储在哪个文件里?这是个好问题,文件实际存储在哪里?在退出之前,我可以实际保存这个。
文件可以用我想要的任何名字保存,文件扩展名通常是 db,实际上布莱恩,你介意检查一下手动写文件的语法吗,.something。我想是 .save,后面跟着文件名 .save,所以我会称之为 shows。
嗯,输入数据库,现在如果我再打开一个终端窗口只是为了演示,哎呀。
抱歉,关闭整个东西,如果我现在要继续打开另一个 ls。你会看到我现在有一个 csv 文件,我有之前的 python 文件,我创建的那个是包含表格的二进制文件,我动态加载的表格来自那个 csv 文件。关于选择查询或其他任何问题,布莱恩,有几个。
人们在询问这个的运行时间,是的,非常好的问题,运行时间是什么。我会回到这个,好的,现在诚然是大O符号为n。我实际上还没有做得比我们之前的csv文件或python代码更好,现在默认仍然是大O符号为n,但还有。
这会是一个更好的答案,这将使其更具对数性质,所以让我回到这个特性,当时机成熟时再启用它。实际上,让我们开始朝着那个方向迈出一些步骤,因为事实证明。在加载数据时,我们并不总是有奢侈的机会,只用一个大csv文件。
导入我们使用的格式,我们需要提前决定如何存储数据,以及我们想存储什么数据,关系是什么,跨越的不仅是一个表,而是多个表。所以让我继续,运行另一个命令。
实际上引入了第一个问题,让我去选择,从节目中选择标题,其中流派等于例如喜剧,那是其中一个流派。注意我们返回了很多结果,但我敢打赌我漏掉了一些,我快速浏览这些。可是我打赌我遗漏了一些,因为如果我检查流派等于喜剧,我遗漏了什么。
好吧,那些勾选了多个选项的人可能会说某个节目是喜剧和剧情,或者是喜剧和浪漫,或者其他几种流派的组合。如果我在这里搜索相等,等于喜剧,我只会得到你喜欢的那些节目,你只说我最喜欢的电视节目是喜剧。如果我们想做些别的事情。
比如喜剧,我们可以说,只要包含“喜剧”这个词,就应该返回更多结果。让我明确一下,确实我现在有一个更长的结果列表,现在我们有所有的节目,你至少勾选了喜剧选项。但不幸的是,这开始变得有些杂乱,因为回想一下选择。
来自节目的流派;注意这个表格,流派列表,这就是谷歌的方式。对于sql来说,这样是可以的,但这有点混乱。一般来说,在sql数据库中存储用逗号分隔的值列表并不是你应该做的,使用sql数据库的整个意义就在于此。
是从逗号和csv中转移,实际上更干净地存储信息。因为让我提出一个问题,关于喜剧,但也许还有音乐。这样就能让我找到任何节目,其中包含“音乐”这个词在逗号分隔的列表中,这里有一个微妙的bug,你可能。
我得回想一下,你提到的地方,我不能在这里展示全部内容,但我们有传记。省略号音乐视频与音乐剧截然不同,这两种类型是不同的流派,但请注意我目前的提问。这里存在的问题是什么,以及,音乐剧,这就是事情的所在。
现在变得混乱了,是的,你知道,我们可以稍微清理一下。也许我们可以在这里放一个逗号,这样它不能只是,音乐某些东西,它必须是音乐逗号。但是如果音乐是你选中的最后一个框,那么就是音乐。那里没有逗号,所以现在我需要,把东西结合起来。
所以也许我得做一些,比如,我们的音乐像这样,或者,类型像所谓的音乐。
像这样,但老实说,这只是,变得凌乱,这样设计得很糟糕,如果你只是将你的,内容存储在一列中,而不得不 resort 这种。黑客方法来弄清楚,或许在这里或那里,或这里,考虑所有语法的排列组合,你做错了,你没有使用一个。
完全利用sql数据库的潜力,那么我们该如何设计这个。东西更好,实际上加载这个csv,简短地说,我们如何摆脱那些愚蠢的逗号,在类型。列中,而是将一个词放入那些单元格,换句话说,不是两个,不是三个,只有一个,类型。那么让我介绍几个建筑,块帮助我们达到目的。
事实证明,在sql中,当你想要,创建自己的表时,我们可以,抱歉。事实是当你自己创建时,我们需要的不仅仅是,阅读。但是如果我们想更好地做到这一点,而不仅仅是使用sqlite 3的。内置的点导入命令,而是,我们要写一些代码,表。
一个用于标题,一个用于类型,我们需要更多的表现力。谈到sql,所以为此我们需要一个。创建我们自己表的能力,我们之前见过这一点,但。我们还需要看看另一个,语法的部分,因此插入也是另一个。
你可以,实际,向数据库添加数据的命令,这很好,因为如果我想。最终遍历那个相同的csv,但这次手动添加所有的,行到数据库。自己,那么我将需要一些,是,表,您想要插入值的列或列,然后字面上是单词值,然后,字面上在括号中。
实际的值列表,因此这是一个,通用形式。
只是,这里稍等一下,当涉及到将某些东西插入数据库时。让我们继续尝试一下,比如木偶秀,就像我在70年代成长的那样。我认为它不在列表中,但我可以确认这一点,所以,选择所有内容从节目中,其中,标题像我们只是搜索一下,muppets,带一个通配符,我猜没有人把它放在那儿。
好的,所以这是一个错失的机会,我,忘了填写表单。我可以回去填写表单,并重新导入csv,但让我们继续,插入到节目中。哪些列,标题和类型,为了好玩,然后我将插入什么值,无论现在是什么时候,所以我要作弊,标题将是像木偶秀这样的。
类型将是它有点像,嗯,类型。
就这样留下分号,所以这遵循标准语法,指定你想插入的表,插入的列,以及你想放入的值。
那些列,我现在要去啊**d,按下回车,现在似乎没有发生什么,但如果我。现在选择,哦,好的还是没什么,因为我犯了一个小错误。嗯,我不是在搜索 muppets(复数),我在搜索 muppet,看看我的。行在这个数据库中,因此插入将使我们现在能够插入新的数据。
将行插入数据库,假设你想更新某些内容,也许你知道。某些木偶节目实际上非常戏剧化,那么我们该如何做呢?嗯,我可以说更新 shows 设置,看看类型等于喜剧、戏剧、音乐剧,条件是。标题等于木偶秀,所以我再次提取经典版本。
语法稍后会讲,但现在只是一个小提示,你可以非常简单地更新内容。尽管需要一点时间来适应语法,但它确实如所说的那样工作,更新。shows 设置类型等于这个,条件是标题等于那个,现在我可以去啊**d,输入。如果我继续选择同样的东西,就像在终端窗口中一样,你可以快速向上滚动。
我只是在上下滚动以前的命令,瞧,现在我看到木偶秀是一个喜剧,一个戏剧,戏剧和音乐剧,不过,我对列表中更受欢迎的节目有些意见,你们中有很多人喜欢,嗯,假设是朋友,我从来没有真正喜欢过,让我去啊**d,选择。一个标题从 shows 中。
标题等于朋友,也许我应该更严格一点,我应该说标题。就像朋友,以防有不同的大小写出现。很多人真的很喜欢朋友,实际上你们中有多少人还记得我。可以做到这一点,我可以说计数,并且我可以让续集为我计数。
你们中有 26 个人我强烈不同意,还有几个人甚至在后面添加了内容。那么假设我对此有异议,嗯从 shows 中删除,标题等于引用的朋友,实际上标题类似朋友,来吧把它们都找出来。
输入,现在如果我们再次选择,抱歉,朋友已经被取消了,所以你可以再次。更新你可以执行这些基本的 CRUD 命令,创建、读取、更新和删除,选择。通过字面意义上的更新和删除来实现,而就这些而言,即使这很快,确实只是这四个基本操作。
在 SQL 中,一些附加功能,比如这些额外的函数,比如计数,你可以使用,还有类似的功能,嗯,让我提议我们现在做数据。创建表并插入数据,让我们开始吧,啊**d,自己写点代码。
python 脚本使用 sql,以循环的方式读取我的 csv 文件,并逐行手动插入,因为老实说,这样手动输入数百条 sql 查询将花费我太多时间。为了将你的所有行导入到一个新的表中,我将这样做,并建议我们设计。
以以下方式进行,我这次将有两个表。
renditions,其中一个将被称为 shows,另一个将被称为 genres。这是设计关系数据库的基本原则,以弄清楚数据之间的关系,并规范化你的数据。规范化你的数据意味着消除冗余,规范化你的数据意味着消除对相同词汇的多次提及,*****。
*****,我的意思是,我将建议我们创建一个更简单的表,称为 shows,只有两列,一列将被称为 id,这是新的,另一列将像以前一样被称为 title。老实说,我不在乎时间戳,所以我们将把那个值丢弃,这也是自己编写的一个好处。
对于 id,我引入了这个,它将是一个唯一标识符,字面上是一个简单的整数,从一、二、三一直到十亿。我将让它随着我们进行而自增,为什么?我建议我们转到另一个表。
拥有一到三个或五个类型的列表,这在某种意义上是愚蠢的,因为这只是杂乱无章,这意味着我必须运行愚蠢的命令,检查这里的逗号,那里也有逗号,这非常 hackish。我将创建另一个表,它也有两列,一列将被称为 show id。
另一列将被称为 genre,而这里的 genre 只会是一个单词。那一列将包含单词,例如 comedy、music 或 musical,但 genres 将与它们所属的原始节目相关联,根据你的 Google 表单提交。通过在这里使用这个 show id,这具体意味着什么。
通过向我们的第一个表 shows 添加这五、六,我现在可以以非常高效的方式引用同一个节目,使用一个非常简单的数字,而不是冗余地写出 the office、the office、the office,只需一个规范的数字,这个数字只会是四个字节或 32 位,效率很高,但我仍然可以。
将这个节目与一个、两个、三个或更多类型关联,甚至没有。这个表将变成我们新的一对表中的一行或多行,我们正在将类型提取出来,以便我们可以为每个节目添加多行,但仍然将这些类型重新映射回原始节目本身。那么,这些流行词是什么呢?
这里或者说一下我们有哪些语言,哪些种类,类型可以使用。为此我想提到,在SQLite中有五种主要的数据类型,这些数据类型中有一些看起来很熟悉,还有一些稍显奇怪。
整数是一个东西,而实数和浮点数是相同的。因此,一个整数可能是一个32位或四字节的值,负数,一个实数将会有小数点,一个浮点值,通常默认是32位,但这些类型的大小因系统而异,就像它们支持。
从技术上讲,在C中也是如此,因此它们在SQL世界中因系统而异,但一般来说,这些都是很好的经验法则,文本就是这样的,它是某种长度的字符串的等价物,但在SQLite中,似乎还有两种我们之前没有见过的数据类型,数字型和blob。
但是稍后会详细介绍这些。可以在数据库中存储零和一,数字型将是某种类似数字但不完全是数字的东西,比如年份或时间。某些有数字的东西但不是简单的整数,然后我们提出两个,SQLite将允许我们手动指定,通过自己执行SQL代码。
可以指定某一列,不能为null。到目前为止我们一直忽略这个,但你们中的一些人可能已经选择不提供,节目或类型的标题。你的答案可能是空白,你们中的一些人,在注册网站时,不想提供信息,比如你住在哪里或你的电话号码。
空值,但你可能想说,它不能为null。一个网站可能需要你的电子邮件地址,需要你的密码和其他几个字段,但不是所有字段。而且在SQL中还有另一个关键词,叫做unique,你可以在想确保同一个。
电子邮件地址不能多次注册你的网站,你只需指定电子邮件列。多个用户,长话短说,这只是我们SQL工具包中的更多工具,因为我们现在会间接看到其中一些,而在设计我们自己的表之前,我们需要的最后一个术语是SQL的主键概念。
外键,我们在电子表格中没有见过,除非你在真实世界中工作了几年。
在前面有相当复杂的电子表格, odds are 你可能没有以相同的方式看到键或唯一标识符。
但它们实际上相对简单,让我回到之前的图示,提出当你有两个这样的表时,你想用一个简单的整数来唯一标识。这被称为,技术上是一个ID,按惯例我会称之为。你可以称它为任何你想要的东西,但ID只意味着这是,一个唯一标识符,但在语义上,这个ID就是。
称为主键,主键是一个表中的列,唯一标识每一行。这意味着你可以在标题字段中有多个版本的《办公室》,但每一行将有自己的唯一编号。因此主键唯一,就像我建议我们马上创建的类型一样,参考。
通过那个唯一标识符,称为外键,虽然我在这里称它为显示ID,这在很多SQL数据库中是一个约定,意味着这是技术上在一个表中称为ID的列。叫做显示或复数形式的显示,因此如果这里是数字一,假设。
办公室有一个唯一的ID为一,在这个表中我们会有一行,ID为一。在喜剧类别,剧情类别,输出三行,分别是第一、第一、第一,喜剧。所以再次强调,这里的目标是设计得更好。不要在单一列中有愚蠢的逗号分隔值列表,我们想要。
一种将其拆分为单独行的方式,你可能会想到,列。但根据我们的原则,来自电子表格,你不应该习惯性地增加更多和更多的列,当数据都是相同的,比如类型类型类型,没错,这在电子表格中是一种愚蠢的做法,类型一个,再添加一个叫做类型二的列。
一列叫做类型三,类型四,你可以想象这有多愚蠢和低效。很多这样的列将会为空,对于那些类型非常少的节目来说,这显得有点杂乱,因此在关系数据库的世界里,更好的是。拥有一个第二个表,那里有多行,某种方式链接回。
通过我们所称的概念上,一个外键来实现主键,所以让我们现在继续,尝试写这个代码,让我回到我的IDE,让我退出SQL Lite。让我稍微移动一下我的文件,暂时移开,让我们只保留原始数据,开始实现我的Python文件的最终版本。
被称为,显示一个叫做类型的表,然后在一个循环中遍历那个CSV,并插入一些数据到显示中,其他数据到类型中,我们如何以编程方式做到这一点。好吧,我们还需要一个拼图的最后一部分,我们需要某种方法来连接Python和SQL的世界,在这里我们确实需要一个库,因为它。
如果没有库,这将会非常痛苦,cs50s如我们所见,使得这变得非常简单。还有其他第三方商业和开源库,你也可以在现实世界中使用,它们做的事情相同,但语法就不那么友好了。所以我们将首先使用cs50库,记得在python中有函数。
像getstring、getint和getfloat,out。
也为sql能力,所以我将回到我最喜欢的文件。我不仅要导入csv,还要从cs50库中导入一个功能。你将在cs50库中找到这个,或者说是sql,如果我调用它,它将允许我。
将一个sql lite数据库加载到内存中,那我该怎么做呢?让我继续添加几行新的代码,让我继续打开一个名为shows.db的文件。但这次以写模式,然后,为了好玩,暂时我会。以一种pythonic的方式创建一个空文件,这看起来有点傻。
但通过以写模式打开一个名为shows.db的文件,然后立即关闭文件。我现在有一个空文件可以进行交互。我也可以通过执行这个touch shows.db,这个命令有点奇怪。
但在终端窗口中,它意味着创建一个文件,如果它不存在。所以我们也可以这样做,但那将是独立于python的。所以一旦我创建了这个sqlite数据库,我将声明一个名为db的变量,用于数据库,我将使用cs50s库中的sql函数。
我将通过一些有点神秘的字符串打开这个sqlite数据库,sqlite://shows.db,现在看起来像一个url,http://,但它是sql lite,而有三个斜杠而不是通常的两个,但是。
结果是,目前还没有内容,作为一个sqlite数据库,使用cs50s库,为什么我这样做?好吧,我这样做是因为我现在想要创建我的第一个表,让我继续执行db。execute。所以在cs50 sql库中有一个名为execute的函数。我将继续运行这个,创建一个名为shows的表,其类型为。
它的列有一个id,它将是一个整数,一个标题,它将是文本,主键将是id列,所以这有点神秘,但让我们看看发生了什么。我似乎现在在第8行将python与sql结合在一起,这就是编程变得真正强大、花哨、酷和困难的地方,随你怎么理解。
我实际上可以在一种语言中使用另一种语言,SQL只是一堆文本命令。直到现在,我一直在手动输入它们,轻松一点。不过没有什么阻止我使用字符串,然后将它们传递给数据库,使用代码,我使用的代码是一个名为execute
的函数,它在CS50中的作用是传递参数。
从你的Python代码中执行到数据库,这就像是以编程的方式手动在SQL Lite提示符下输入几分钟前的命令。因此,这将去创建一个名为shows的表格,我将存储所有唯一的id和标题,然后让我再次执行db.execute
。
创建表格genres,它将有一列叫做show id,类型为整数,还有genre,类型为文本,最后它将有一个外键,引用show id,指向shows表的id。好吧,这信息量很大,让我们从左到右回顾一下。
db.execute
是我执行任何SQL的Python函数,创建表genres,创建一个名为genres的表格,该表格的列将有一个叫做show id的整数和一个文本字段genre,但它将是一次一个genre,而不是多个,然后在这里我指定一个外键,即show id列。
它正好指向shows表,Brian,交给你了,没关系。哦,好的,我刚刚修复了这个bug,是的,Brian在秘密解决这个问题方面很不错。好吧,我知道有什么地方不对,好的,所以这有点晦涩。
在那个闪烁的提示符下输入这两个SQL命令,但我想写一个程序,现在用Python为我创建表格,更有趣的是将数据加载到数据库中。所以让我们继续,我不打算从用户那里选择一个标题,因为我想导入所有内容,我不会使用任何计数或。
任何这样的东西。所以让我们继续,像以前一样进入我的循环。这次让我们继续,从reader中获取当前标题,像往常一样,但也让我们继续去掉空格并大写,以便规范化,执行db.execute
,插入。
shows表中的标题列的值为“标题”,所以我想在这里放入标题。事实证明,像我们的SQL库支持一个占位符。在C语言中我们用%s
,在Python中我们用“”,在SQL中我们有第三种方法,概念上是相同的,你放置一个问号在你想放置占位符的地方。
然后在这个字符串外面,我实际上会输入我想要插入那个问号的值,这与第一周的printf
非常相似。现在,接着是你想为那些占位符插入的参数的逗号分隔列表。现在这行代码16已经将所有这些值插入到我的数据库中。
让我继续,啊**d,做这个,我现在要运行python。一个喜欢的停止点,心中祈祷,一如既往,正在加载,正在加载。这是因为那里有一个相当大的文件,或者我搞砸了。等这个太久了,我做错了什么,好的,这显然花费了太长时间。
它变得越来越大,哦,好吧,我应该更有耐心。好了,看来我的连接有点慢,所以,嗯,正如我预期的一切都是100。正确,并且运行良好,所以现在,如果我输入ls,请注意我有一个名为shows.db的文件。这是全新的,因为我的python程序创建了它。现在,让我们继续运行sqlite3。
在其中,我注意到我可以做。schema只是查看,表是否存在。但请注意,如果我执行选择,星号从shows,让我们看看所有数据。瞧,已经程序性创建了一个表,这次注意没有时间。st*mps,没有类型,标题,id。
从1开始单调递增到513,这种情况为什么会这样?那么你在sql数据库中获得的一个特性是,如果你将一列定义为主键,在sql lite中,它将为你自动递增。请记住,在我的代码中,根本没有一行是整数。
输入一个然后是两个然后是三个,我完全可以做到,我可以这样做。计数器,实际上我可以这样做,计数器等于一。然后在这里我可以说id,逗号标题,给自己两个占位符。然后每次传入计数器,我可以自己实现这个。
然后在每次迭代中,计数器加一,但正如我们所见,在sql数据库中,你会得到更多内置的功能,我不必做任何事情,因为如果我已经将该id声明为主键。
sqlite将为我插入它,所以,如果我回到sql lite,请注意我确实有id和标题,但如果我选择星号,还没有。
那么我现在如何获取每个节目的所有类型,我需要完成我的脚本。当前行中只有标题,但我也有当前行中的类型。但是类型是用逗号分隔的,回想一下在csv中,每个标题旁边都有一个逗号分隔的标题列表。那么我该如何访问每个类型呢?假设对,类型在行括号中。
但是这行不通,因为不会基于这些进行分割。实际上,正如上周所看到的,我们需要对字符串中的所有字符进行迭代。但事实证明,Python中的字符串有一个华丽的分割函数。我可以在逗号后面跟一个空格进行分割,而这个函数将为我在类型中执行。
并且可以说会将其分割,在每个逗号和空格上,将其转换为包含类型的Python列表。现在我可以对这个单独类型的列表进行迭代,在这里我可以执行db执行插入到类型中,节目ID类型的值,问号问号。
嗯,有个问题,我肯定可以将当前的某些东西插入这里,仍然需要第一个问号的值,我怎么知道当前电视节目的ID是什么呢?事实证明,库可以在你向有主键的表插入新行时提供帮助。
大多数库会以某种方式返回你这个值,如果我回到第15行,实际上在使用插入后存储db执行的返回值,库会告诉我刚刚为这个给定的节目使用了哪个整数,可能是123,我不需要知道或者关心。
程序员,但我可以将返回值存储在一个变量中,然后在这里我可以直接放置那个相同的ID,这样如果我输入的办公室ID是1,并且其类型是喜剧、戏剧、爱情,我可以在这个嵌套的for循环中,一次插入喜剧、爱情的三行数据。
让我们回到这里,呃,在我的终端窗口,让我删除旧的shows.db,使用rm命令以重新开始。让我继续重新运行,呃,python favorites.py。这次我会更加耐心,因为云端有点慢,所以它正在思考,实际上现在正在进行更多的工作。
在故事的这个时刻,我的程序正在CSV中插入行,并且正在将一个或多个类型插入到类型表中,让它在那里处理一下。它有点慢,如果我们在更快的系统上,或者在我自己的Mac或PC上,它可能会更快。
更快地下降,但你可以在这里看到我为什么最初使用点导入命令的一个例子,它自动化了这个过程的一部分,但改变了我的数据格式。但关键要点是,尽管一次插入几百行花了一些时间。
我只需要做一次,刚才有问过关于性能的事情,事实证明现在我们完全控制了SQL数据库,因此我们有能力提升其性能,而我真的不想继续。
这里我只是想让这个事情完成,我应该使用一个需要的时间。几乎在其他系统上所需的时间一样,但让我再拖延几秒钟。拖延拖延拖延拖延,好吧,让我暂停我们的悬念,利用这个时间,让我切换到,哦,好的,正如预期的那样,它正好完成了。
在shows.db上亮灯3。好的,现在我回到了我的原始SQL环境,之前的操作。如果我从shows中选择星星,其中标题等于所谓的《办公室》,我将看到所有这些的实际唯一ID,我们没有消除重复项,只是保持所有内容原样,但我们给了每个项目一个唯一ID。
但如果我现在执行选择星星,从类型中,我们将看到所有的值,并注意关键细节。这里每行只有一种类型,因此我们可以最终将其排列,有,嗯,出了点问题。让我想一会儿,我想得到。
好的,让我们继续,进行第二次也是最后一次五分钟的休息。然后我们会回来,我会解释。
好吧,我们回来了,就在我们分开之前,我的自我怀疑开始滋生。但我很高兴地说,没有任何华丽的魔法在后台,一切实际上都运作得很好,我,返回,得到两列,一列是唯一ID,所谓的主键,接着是,类似地,从类型中搜索星星,我得到了单一的。
在某个时间点的类型,但左侧并不是主要的主键。现在这些相同的数字在这个上下文中被称为外键,它们将一个映射到另一个。例如,无论如何,显示512有五种不同的类型与之相关,实际上如果我稍微回顾一下,似乎《权力的游戏》是由你们中的一个决定的,属于冒险类。
动作和战争也是,那五个。所以现在这就是关系数据库的意思,你在多个表之间有这种关系,将某些数据连接起来。问题是,现在回答问题似乎会更难,因为我现在得查询两个表,或者执行两个单独的查询,然后。
假设我想回答在你最喜欢的电视节目中有哪些音乐剧,我不能仅仅选择节目,因为那里不再有类型,但有一个值,将一者与另一者连接起来的外键与主键关系。
所以你知道我能在脑海中迅速做的,我很确定我可以从类型表中选择所有的节目ID,其中某种特定类型等于所谓的音乐剧。我不需要担心逗号和版本,因为我用代码编写的音乐剧和其他每个类型都是一个单词,如果我按下回车键,所有这些节目ID。
你们决定这些都属于音乐剧,但现在这并不有趣。我当然不想手动执行 10 个查询,查找每一个 id,但请注意我们在 SQL 中也能做到什么。我可以嵌套查询,让我先把这个整个查询放在括号里。
然后将以下内容添加到它之前,选择标题,从显示中选择主键。id 在这个子查询中,这样你就可以拥有嵌套查询,类似于在 Python 中看到的,当你有嵌套的 for 循环时。在这种情况下,就像在小学数学中,括号内的内容将会优先执行。
利用那个内查询的结果,如果我在那个 id 列表中,瞧,似乎有点好玩的是,许多人认为《绝命毒师》、《超自然》、《欢乐合唱团》、《夏洛克》、《我遇到的律师》和《我和我弟弟》都是音乐剧。我对其中一些有异议,但这些节目,所以即使我们已经算是做了一些工作。
我们在设计上做得更好,因而我们通过提取共性来规范化我们的数据库,或者说我们清理了数据。诚然,仍然有一些冗余,但至少现在我的数据是干净的,每一列仅有一个值,而不是一些。
设想一个人为的逗号分隔列表,假设我想找出所有的类型。让我们问一个相反的问题,首先,我需要从显示中选择 id,条件是标题等于“办公室”,因为有很多你们输入了《办公室》,我们给每一个答案分配了一个唯一的标识符。
我们可以跟踪这些数据,现在有所有这些数字。这就像数十个响应,我当然不想执行那么多查询。但我认为子查询将会帮助我们。让我再把这个整个内容放在括号里。现在让我说选择独特的类型,从类型表中选择,条件是显示。
在类型表中的 id 在那个查询中,仅仅是为了好玩,让我去排序,呃,按类型排序,所以让我执行这个,好的,有点好玩的是,输入《办公室》的你们勾选了动画、喜剧、纪录片、剧情、家庭、恐怖等选项。
现实电视、浪漫和科幻我也对其中的一些有例外,用户,SQL 语言能够相对简洁地表达,尽管今天有很多新特性一下子出现,否则我需要在 Python 代码中实现这一点需要十几行或更多,天知道那需要多少行代码和多少小时。
在 C 中,诚然我们可以比这个设计做得更好,这张表或这个图表示的是我们现在的情况,但你会注意到类型表中有很多冗余。每当你勾选喜剧框时,我现在就有一行显示喜剧、喜剧、喜剧。尽管节目 id 不同,但我又有了“喜剧”这个词,重复多次。
这在喜剧或音乐类等类型中往往是不被赞同的,你理想中应该将这些内容集中在一个地方,所以如果我们真的想,要特别注意,并且真正规范化这个数据库,这个学术术语指的是,去除所有此类冗余。
我们实际上可以这样做,我们,仍然可以有一个节目表。
ID和标题没有区别,但我们可以有一个类型表,包含两列ID和名称,现在这是它自己的ID,和节目ID没有关联,它只是它自己的唯一标识符,这是一个主键,名称对应类型。
“喜剧”、“戏剧”、“音乐”、“音乐剧”以及其他所有类型,然后你将使用一个第三个表,这个表在口语上称为连接表,我会在这里绘制,但我们已经画过。称它为shows_genres,以明确这个表实现了这两个表之间的关系,并注意在这个表中是。
里面没有什么重要的数据,只有外键、节目ID、类型ID,通过这个第三个表我们现在可以确保“喜剧”这个词,只有在某一行中出现,“音乐”这个词也只出现在一行中,但我们使用这些更高效的,称为节目ID和类型ID的整数,分别指向那两个主键。
这是一个在数据库世界中称为多对多关系的示例,一个节目可以有多种类型,一个类型可以属于多个节目,因此通过拥有这个第三个表,你可以实现这种多对多关系。这个第三个表现在允许我们真正规范化我们的数据集。
通过消除所有重复的“喜剧”,为什么这很重要,对类型来说可能不是一个大问题,但想象一下,在我当前的设计中,如果我错误命名了“喜剧”,我现在需要在每一行中再次更改“喜剧”这个词,或者如果你更改了节目的类型,你必须在多个地方进行更改。
你可以争辩说,现在你只需要在一个地方更改类型的名称,而不是到处更改,这在一般情况下,在C语言、Python和SQL中通常是一件好事,不要复制粘贴相同的值。
在现实世界中使用SQL,除了这五种大对象、整数、二进制数据以外,通常不用于其他更专业的应用,假设整数是一个通常为32位的int,数字是像日期、年份或时间之类的,实数是浮点值和文本。
还有什么其他工具可以使用呢?事实证明,还有其他数据类型。
最终从手机和Mac、PC上的SQLite,过渡到运行Oracle、MySQL等实际服务器的数据库,这些数据库还带有其他子类型。除了整数之外,你可以指定小整数(small int)用于小数字,可能只使用几个比特而不是32位整数,或者大整数(bigint),它使用64位而不是32位。
社交媒体如Facebook和Twitter需要频繁使用大整数(bigint),因为它们有大量数据。你和我可以使用简单的整数,因为我们不会有超过40亿个最喜欢的电视节目,当然像real这样的类,你可以有32位实数,或一个命名有些奇怪的双精度数(double precision),它就像一个双精度数。
精度,数值型(numeric)是一种通用类型,你不仅可以有日期和时间,还有布尔值。你可以使用这种数值刻度和精度指定要存储的数字总位数,因此它涉及到的不仅仅是整数,还有其他类型的数字。然后你还有文本类别,char后面跟着一个数字。
这指定了每个值在字符中是有帮助的,适用于你事先知道长度的情况,比如在美国,所有50个州都有两位数字代码或两个字符代码,比如马萨诸塞州的MA和加利福尼亚州的CA。在这种情况下,字符长度是合适的,因为你知道每个值的长度。
当你不知道时,你可以使用varchar
,而varchar
指定了最大字符数,因此你可以指定varchar
为32,没人在名字中输入超过32个字符,或者varchar
为200,如果你想允许更大的输入。
但这与我们在网络上的现实世界体验是相关的。如果你曾经访问过一个网站,开始填写表格,突然发现你无法再输入任何字符,提示你输入的内容太长。这是为什么呢?可能程序员不希望你继续详细表达自己。
特别是在客户服务网站上的投诉表单,但从实际角度看,可能是因为他们的数据库设计用于存储有限数量的阈值,你当然不想像在C语言中那样发生缓冲区溢出。因此,数据库不会强制一个最大值,而文本则用于更大的输入。
如果你让人们处理文档,或更大的文本集合,你可能会使用text类型,这样事情变得非常有趣,所有这些非常学术的想法和建议实际上是相对的,但当我们有成千上万的数据时就不一样了。因此我接下来要做的就是下载一个文件。
请给我一点时间从课程网站上获取它,我将下载一个今天的文件,这是IMDB(互联网电影数据库)的SQLite版本,你们中的一些人可能在网站上使用过,以查找电影及其评分等。我们事先写了一个脚本。
我写了一个脚本,提前下载了所有信息。作为 tsv 文件,结果表明他们提供所有数据作为 tsv 文件(制表符分隔值),脚本名为 shows.db,如下所示,所以我将去,哦继续片刻,打开 shows.db,这不是我之前创建的版本。
根据你的喜好,这现在是我们工作人员提前创建的版本,通过下载成千上万的。
电影和电视节目、演员和导演来自 imdb.com,在他们的许可下,然后导入到 sqlite 数据库中。那么我该如何查看这里的内容呢?让我去看看,哦继续输入点模式回忆。
其中,实际上在图示形式中,它实际上看起来像这样,这是一个土地。将会有一个人员表,其中每个人都有一个 ID、名字和出生年份,将会有一个节目表,就像我们一直在讨论的那样,包含节目 ID 和标题,以及节目的首播年份和剧集数量。
该节目的剧集数量,然后将会有一个与之前设计相似的类型,所以我们没有完全展开,将其拆分为第三个表。我们在类型上确实有一些重复,但随后有一个评级表,在这里你可以看到关系数据库变得有趣的地方。
可以有一个评级表来存储评分,例如从一到五,但也可以将这些评分与某个节目的显示 ID 关联,这样你就可以跟踪该节目获得的投票数。编剧通知是一个单独的表格,注意这有点酷。这个表格通过箭头与节目表和人员表相关联。
因为这是一个连接表,外键的人 ID 分别引用节目表和人员表,这样一个人可以是多个节目的编剧,而一个节目也可以有多个编剧,这又是一个多对多关系。最后,明星是节目的演员,注意这也是一个连接表,显示 ID 和一个人 ID 分别引用那些表格。
这才是真正有意义的地方,关系数据库如果你将所有导演、编剧和明星的名字放在不同的表中重复列出,那将是相当愚蠢和糟糕的设计。
史蒂夫·卡瑞尔,史蒂夫·卡瑞尔,史蒂夫·卡瑞尔,所有这些演员、导演、编剧和业务中的其他角色,归根结底都是人。所以在关系数据库中,建议是将所有这些人放在一个人员表中,然后使用主键和外键来将它们关联起来。
这些其他类型的表格的关键是,当我们这样做时,事实证明,当我们有大量数据时,事情可能会变得缓慢。例如,让我进入这个。让我继续从shows中选择星级,分号,这有很多数据,在Mac上还挺快。为了节省时间,因为这是在云中,让我继续计算一下。
IMDB数据库中节目的数量,使用计数153。
来自人员表的人员计数,457,886个可能是明星、编剧或其他角色的人,所以这是一个相当大的数据集。让我继续做一些事情,从标题等于办公室的节目中选择星级。这次我不必担心奇怪的大小写或空格。
这是IMDB,这是来自权威来源的干净数据。
请注意,实际上有不同版本的办公室,你可能知道英国和美国版本,还有其他与该特定类型的节目无关的节目,但它们每个都按年份区分。好吧,这有点多,让我们再做一次,我继续。
让我暂时开启一个功能,在这个程序中,再次运行它,似乎花了0.012秒的实际时间来完成这个搜索,这真的很快,我几乎没注意到。显然因为它如此之快,但让我继续去做,创建一个,叫做。好的,最终回答之前关于默认性能的问题。
我们一直在做的确实是大O的N,它只是在从上到下线性搜索,这似乎质疑了SQL的整个目的,如果我们做得不比CSV好。但索引是数据库的一个线索,以一种高效的方式,让你获得对数级别的效率。
索引是一种高级数据结构,SQLite数据库或Oracle数据库或MySQL数据库,无论你使用什么产品,都会为你在内存中构建,然后它使用类似这样的语法进行操作,称为B树,我们曾经谈论过树,树的外观就像家谱树,宽而不高。
这是一种数据结构,精神上类似于我们在C中看到的,但它试图将所有的叶子节点,或者说所有的子节点,尽可能靠近根节点,而它使用的算法,往往是专有的或基于你使用的系统进行文档化。但它不以列表形式存储数据。
在底层,这些看起来很高的表格,实际上在底层是树,如果我们通过创建它们被正确称为的索引来创建这些树,这可能需要我们一个索引。但现在注意之前发生的事情,办公室,使用线性搜索时花了0.012。
如果我在创建索引并告诉 SQL Lite 在内存中构建这个花哨的树之后,再次执行相同的查询,只需几秒钟。
瞧,0.001 秒,所以现在速度快了几个数量级。对我们人类来说,这两者都很快,但想象一下数据集更大、查询更大,呃,它们的查询可能会比这更长,因此需要更长的时间。但不幸的是,如果我的数据分散得一团糟。
就像在图表中,就像在这样的图示中,我的天啊,我如何才能实际完成有用的工作?我如何才能获取到人们的信息?
电影、编剧、明星和评分,如果一切都是混乱的,我似乎制造了一团糟,我现在需要执行所有这些,以至于变得复杂。事实证明,在 SQL 中还有另一个关键字,实际上我们这里要查看的最后一个,称为连接(join)。连接关键字可以隐式或显式使用。
允许你将表连接在一起,并重新构建一个更大、更用户友好的表。例如,假设我想获取《办公室》,好吧,记得我可以从人员表中选择史蒂夫的 ID,条件是名称等于史蒂夫·卡瑞尔。所以在这个表中他有不同的 ID,因为这是来自 IMDb,但这是他的 ID,让我去做,嗯,关闭计时器。
现在好吧,这里是他的 ID,136-797,我可以把它复制粘贴到我的代码中,但这并不是必要的。得益于这些嵌套查询,我可以做这样的事情。让我去做,嗯,接下来从星级表中选择所有节目 ID,条件是那张表的人 ID 在这个结果中。
连接人们并展示,错误的关键,重新输入它,从星级表中选择显示 ID,条件是人 ID 相等。不论史蒂夫·卡瑞尔的想法是否正确,这都是史蒂夫的所有节目 ID。卡瑞尔的电视节目很多,且其内容并不明显。所以让我再做一个嵌套查询,把所有内容放在括号里,现在选择标题。
呃,从节目中选择,节目的 ID 在这份很长的节目 ID 列表中。那是他参与的所有节目,包括当年的达娜·卡维秀和顶部的《办公室》,还有最近的像苹果电视上的《早间秀》。好吧,这些数据很不错,但事实证明有不同的方式。
在接下来的几周以及问题集、实验室等中会有更多类似的内容,但事实证明我们也可以做其他事情,让我就展示这个语法,尽管只是瞥一眼。你也可以像这样使用连接关键字,我可以选择标题,来自与星级表中人 ID 列相等的人员表。
换句话说,我可以从连接结果中选择一个标题。像这样在ID列上连接“人物”和“明星”,在另一边的“人物ID”列上,我可以在“节目”表中连接,“明星.show ID”等于“节目的ID”,所以,现在我在连接主键和外键。
这两张表,其中名字等于“引用引号”,史蒂夫·卡瑞尔。所以这是我们见过的最隐晦的东西,但它只意味着取这张表并与这一张连接,然后再与这一张连接。
过滤所有的连接结果行。
我们也得到了所有的答案,还有其他方式来做到这一点。
我现在会保留一些语法不说,但这感觉有点慢,事实上让我继续并重新启动我的计时器,让我重新执行这个最后的查询:选择“人物”中的标题,连接“明星”连接“节目”,条件是名字等于史蒂夫·卡瑞尔,这花费了超过半秒的时间。
所以那实际上承认有点慢,但又一次。
索引来拯救我们,如果我们不允许线性搜索占主导地位,但让我继续创建几个索引,创建一个名为“人物索引”的索引,为什么?因为我刚才的查询使用了“人物ID”列,进行了过滤,所以这可能是一个瓶颈。我将继续创建另一个索引。
在“明星”表上显示索引,基于显示ID,稍早前我的查询使用了显示ID列。因此这也可能成为一个瓶颈,从上到下线性地。让我创建那个索引,然后,最后让我创建一个名为“姓名索引”的索引,这也许是最重要的,在“人物”表的姓名列上。
而且这也花了一点时间,现在总的来说,这几乎花了一整秒。但这些索引只是随着时间的推移而创建,但你并不会在每个查询中都承担这个。现在让我再做一次选择,让我从“人物”中选择标题,连接“明星”表,再连接。
在“节目”表中,条件是名字等于史蒂夫·卡瑞尔,哗!
0.001秒,这比刚才花费的超过半秒快了一个数量级。所以在这里你也看到了关系数据库的力量。尽管我们随着时间的推移给自己制造了一些问题,我们最终解决了它们,当然,凭借一些更复杂的特性和额外的语法。
但是关系数据库确实是你在现实世界中使用它们的原因,像谷歌这样。因为它们能够如此高效地存储数据,没有冗余,因为你可以对它们进行规范化并分解一切,但它们仍然可以保持电子表格。使用接近对数的结构,得益于那些树状结构,但也存在问题。
我们今天想要结束的内容是关于SQL,因为它们确实不幸地非常常见。请注意这里有一个攻击,你在任何接受用户输入的应用中都是脆弱的,这对我最喜欢的pi.file
来说并不是一个问题,因为我只从CSV中获取输入,但如果你们中的一个人是恶意的,那会怎样。
你们中的一个恶意地在你的展示中输入了“删除”这个词。当我在执行查询时,不小心将其插入了我自己的Python代码,你可能会注入SQL,如果通过耶鲁大学登录,你通常会看到一个这样的表单,或者通过哈佛大学登录时,你会看到一个这样的表单,这里有一个我非常确定的例子。
假设我在这个登录表单中输入我的电子邮件地址为mailand.harvard.edu
,SQL两个短横线是用于注释的符号,如果你想注释掉某些内容,结果发现单引号是某种像史蒂夫·卡瑞尔,或者在这种情况下是mailing@
。
在harvard.edu
,可以使用双引号,也可以使用单引号,在这种情况下,我使用单引号。但是让我们考虑一些样本代码,如果你愿意,这是我提出的代码行,可能存在于哈佛的身份验证后端,或耶鲁大学的,或者任何其他人的。也许有人写了这样的Python代码,使用select * from users
。
其中username
等于问号,password
等于问号。他们将用户名和密码插入到用户刚才在网页表单中输入的内容中,这很好,这是好的代码,因为你使用了SQL问号占位符,所以如果你真的只按我们今天所说的去做。
并且使用这些问号占位符,你就可以安全地防止SQL注入攻击。不幸的是,世界上有太多开发者并不这样做,或者没有意识到这一点,或者确实忘记了,如果你转而使用这样的Python方法,使用f字符串,这可能是你上周的直觉。
因为它们非常方便。
带有大括号,假设你真的插入了。
用户名和密码,不是带问号占位符,而是字面意思。在这些大括号之间,注意如果我恶意地输入了mailin.harbor.edu
,会发生什么。
在harvard.edu
单引号两个短横线,这将使这段Python代码基本上执行这个,让我来进行查找和替换,欺骗Python执行username = 'mailin.harvard.edu' --
和其他内容。
不幸的是,两个短横线又意味着注释,这意味着被忽略。
SQL查询的整个密码部分是检查这个用户名和密码是否有效,以便你可以决定登录用户,基本上,注释掉所有相关内容。
密码通知我做了什么,我刚刚理论上登录了自己。
作为大陆harvard.edu,甚至在不知道或输入密码的情况下。
而且连字符连字符,正在被忽略。
密码等于检查,所以结果显示db。
执行插入时,它返回给你,正如所说,你使用db执行从数据库表中选择行,它返回给你一系列每个都是字典的行。所以这是现在的伪代码,以及我的评论。
但如果你得到一行,似乎暗示有一个用户名为malin@harvard.edu的人。我不知道他的密码是什么,因为这个人恶意地欺骗了服务器忽略那个语法,所以SQL注入攻击不幸地是对SQL数据库最常见的攻击之一,它们是完全可以防止的。
如果你简单地使用占位符并使用库,无论是cs50s还是其他的。网上有一个常见的迷因,这里有张图片,如果我们放大这个人的执照,这就是一个理论上试图欺骗高速公路上的某个相机的例子,掉掉整个数据库,删除是另一个表。
这个人可能是故意的,或者只是幽默地,试图用这样的语法让它执行SQL。所以,分号都是潜在危险字符。如果它们不加改变地传递到数据库,非常流行的。xkcd漫画,让我给你一点时间,去阅读它,现在,在计算机科学中,如果你想的话。
阅读这个,在嗯教育学习者的家庭中,知道得很少。
Bobby Tables 不幸的是这里死寂,我无法判断,如果,但是无论如何这是一个众所周知的迷因,所以如果你是一名了解SQL的计算机科学家,你知道这个,还有一个最后的问题我们想介绍,如果你不介意,最后几分钟,这就是一个根本性的问题。
计算机中称为竞争条件的情况,现在第一次在我们对SQL的讨论中显现出来,结果显示SQL和SQL数据库在现实世界中非常常用,用于非常高性能的应用程序,换句话说,再次是谷歌、脸书、推特等。
在这个世界上,许多数据是同时进入服务器的。例如,你们中的一些人可能在不久前点击了这个蛋的赞,这是有史以来点赞最多的Instagram帖子。截止到昨晚,它的点赞数已经超过了五千多万,嗯,确实超过了金·卡戴珊。
这个问题是很难解决的,嗯,这种点赞以如此惊人的速度涌入的概念,因为假设长话短说,Instagram实际上有一台使用SQL、C++或任何语言与那个数据库进行交互的服务器。假设它们有代码试图增加总的。
那么逻辑上这可能如何运作呢?首先,为了增加这个蛋的点赞数,你可能需要先从数据库中选择当前的点赞数,针对那个蛋的照片的id
,然后你可能给它加一。接着你可能会更新数据库,我之前没用过,但就像。
插入和删除都是有的,还有更新,因此你可能会用新的计数来更新数据库,加一,所以相关的代码可能看起来像这样,这里。你执行从posts
中选择likes
,其中id
等于问号,id
是那个蛋的唯一标识符,然后我将结果存储在一个列表中。
我将进入第一行,所以是rows[0]
。然后我将进入likes
列以获取实际数字,这个数字我将存储在一个变量中,五千万,我想让它变成五百万一,那么我该如何做到呢?posts
,设置likes
等于问号。
然后我只需将点赞数加一,问题在于Instagram、Google和Twitter这样的世界,实际上它们并不只有一台服务器。它们有成千上万的服务器,而所有这些服务器可能在网络上,而这些点击则转化为这段代码被执行,执行,执行。
问题是当你有三行代码时,假设布莱恩和我大约在同一时间点击那个蛋,我的三行代码可能在他的三行代码之前没有被执行。相反地,它们可能会混合在一起,按时间顺序,我的第一行可能会被执行,然后布莱恩的第一行可能会。
被执行,我的第二行可能会被执行,布莱恩的第二行也可能会,所以它们可能会在不同的服务器上交错,或者只是暂时存在问题,因为假设布莱恩和我大约在同一时间点击那个蛋,我们查询五千万是当前计数,然后我们的下一行代码在我们所处的服务器上执行,增加一个点赞。
更新那个蛋的行到五千万,因为根本问题是。执行时,我们基本上是在同一时间检查一个变量的值,哦。当前的点赞数是五千万,我们然后做出决定,给五千万加一,我们接着更新值为五百万一,问题是,如果布莱恩的代码或这个。
Instagram碰巧首先选择了点赞数,他应该被允许完成正在执行的代码,这样当我选择时,我看到5000万,我再加一,所以新的总数是5000万两。这就是在多服务器环境或被称为多线程环境中编写代码时,所称的竞争条件。
按时间顺序排列的代码行可以得到,根本问题源于,如果布莱恩的服务器正处于检查变量状态的过程中,我应该被锁定,不能同时点击那个按钮,或者我的逻辑代码,可能不应该被允许执行。
从逻辑上讲,当你需要像这样编写代码时,这是一个解决方案,这在推特、Instagram、Facebook等中很常见,使用被称为交易的东西。交易增加了一些新的语法,我们今天不会深入探讨,你在接下来的日子里也不需要使用,但它们确实解决了一个根本性的难题。
交易本质上允许你锁定一个表,或者实际上是表中的一行,以便如果布莱恩点击那个鸡蛋,导致一些代码在检查总点赞数的过程中执行,我对鸡蛋的点击将不会被服务器处理,直到他的代码执行完成,所以在这里我提出了你。
你应该这样做,你不应该只是执行中间的三行,作为Facebook的用户,在这种情况下,Instagram应该首先执行开始交易,然后在最后提交交易。
交易的设计是,所有的操作要么全部成功,要么全部失败,数据库不会进入这种奇怪的状态,我们开始失去对“喜欢”的跟踪。尽管近年来这不是问题,但在推特刚开始的时候,推特非常受欢迎。
并且经常处于离线状态,曾经有个叫做“失败鲸”的东西,那是他们网站上展示的图片。
处理过多流量的问题,正是因为当人们喜欢、推文和转发时,数据量巨大。事实证明,解决这些问题非常困难,但通过这些交易锁定数据库表或行是一种方式,最后,今天我们想。
在我之前提到的同一个例子中,假设当前的场景是。
你和室友有一个不错的宿舍冰箱,大家都习惯喝。
大量的牛奶,而你想喝点牛奶,但你去冰箱的时候,像我即将要做的那样,你意识到,哦,我们没有牛奶了,所以现在我在检查这个的状态。
冰箱,这个变量是空的,告诉我我应该去CVS再买些牛奶。
那我接下来该怎么做,我想我会关上冰箱,然后去离开,前往CVS,不幸的是,同样的问题出现了,我们将在最后60秒左右的时间里重演这个场景,假设现在我的室友布莱恩在这个故事中也想要一些牛奶。他来找我时,我已经走向冰箱,意识到哦,我们没有牛奶了。
很好,会去补货,所以让我们看看这个会如何发展。
我们来看看是否有类似的,呃,类比解决方案。
我检查了变量的状态,返回。
做吧。
好的,我现在回到商店,买了一些牛奶,要去把它放进冰箱,哦,这怎么发生的,现在有好几瓶牛奶,当然,你知道牛奶不会保存太久,而布莱恩和我喝的牛奶也不多,所以这真是一个严重的问题,我们已经尝试更新这个非常。
这个变量的值,同时,怎么去解决这个问题呢,实际的解决方案是什么呢,嗯,我敢说,我们可以得到一些灵感。
从事务的世界和数据库的世界中,或许可以在这里创建一个视觉化,让你希望永远记住,如果今天你什么都没学到,就去把这个重演一次。这次我会更极端一些,我去打开冰箱。
冰箱,我意识到啊,我们没有牛奶了,我要去商店,我不想让这种情况发生,也就是说,嗯,让我先去把这个挂起来。有点极端,但我觉得只要他进不了冰箱,这应该就没问题。让我现在去把锁装上,快好了,加油,好的。
现在冰箱锁上了,我要去。
我可以上台并告诉我,哦,好吧,CS50就到此为止,抱歉让你们久等,我们下次再见。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!