我们将在这个实验室里研究 DNA,所以让我们介绍一些我们将使用的术语。 一个 DNA 分子或链是一个被称为碱基或核苷酸(这些都是同义词)的分子单元序列。有四 种类型,分别表示 A(腺嘌呤)、C(胞嘧啶)、G(鸟嘌呤)和 T(胸腺嘧啶)。我们熟悉 的人类 DNA 双螺旋分子的每条链都包含大约 30 亿个这样的核苷酸。双螺旋中有两条 DNA 链,每一条都是另一条的互补副本:一条链上的 "A "总是与另一条链上的 "T "配对,而 "C "总是与 "G "配对。有时我们称这些为碱基对。因为如果我们有另一条链的序列,重建一条 链的碱基序列是微不足道的,所以我们真的只需要研究一条链就能了解 DNA 的某一特定部 分的作用。 在我们的遗传密码中,DNA 的三个核苷酸(即三个 "字母")构成一个密码子,并指定一个 氨基酸。有 20 种常见的氨基酸类型。一个蛋白质分子只不过是一长串的氨基酸。氨基酸决 定了蛋白质的形状,从而决定了它在制造蛋白质的细胞中的功能。与蛋白质相比,DNA 不 会迅速磨损。这使得 DNA 很适合在细胞中进行数据存储。DNA 本质上是一种生物数据结构。 下表 2.1 中的每个条目都列出了一个密码子--代表 DNA 核苷酸的三个大写字母,以及一个氨 基酸或 "停止 "一词。例如,TCT 指的是氨基酸丝氨酸。停止密码子,即 TAA、TAG 和 TGA, 告诉细胞的翻译机器已经到达了基因的终点。请注意,一个以上的密码子可以指定同一个氨 基酸,因此一个以上的 DNA 序列可以指定同一个蛋白质。例如,蛋氨酸-异亮氨酸-苯丙氨酸 -天冬氨酸-甘氨酸的蛋白质片段由 ATGATCTTTGACGGG 编码,也可以由 ATGATTTTTGATGGT 编 码。 现在假设我们有一部分 DNA,我们希望找出它所编码的蛋白质。由于我们不知道我们是否 有 DNA 序列的开头,而且我们很可能有中间某个地方的一块,我们不知道从哪里开始读。 哪个碱基对是第一个碱基对?我们从哪一端开始读?该片段是在一个密码子的边界正确开 始,还是从一个缺失的密码子的最后一个或两个单位开始?正如你所想象的,选择在哪里开 始翻译和在哪里停止是很重要的(我们已经知道在哪里停止,因为有 "停止代码",TAA,TAG 和 TGA)。 可以用到的函数 #include <string.h> char * strncat(char * s1, const char * s2, size_t n)。 strncat()函数将不超过 n 个字节(一个空字节和它后面的任何字节不被追加)从 s2 指向的字 符串追加到 s1 指向的字符串的结尾。s2 的初始字节覆盖了 s1 末尾的空字节。一个结束的空 字节总是被附加到结果中。strncat()函数返回 s1。 char * strdup(const char * s1); strdup()函数复制 s1 所指向的字符串,并返回一个指向新字符串的指针。返回的指针可以被 传递给 free()。如果不能创建新的字符串,则返回一个空指针。strdup()函数在成功时返回一 个指向新字符串的指针。[注意:Visual Studio 编译器可能会抱怨 strdup()函数的问题。如果 是这样,请尝试用微软的版本_strdup 来代替它。] size_t strlen(const char * s)。 strlen()函数计算并返回 s 所指向的字符串中的字节数,不包括结束的空字节。 int strncmp(const char * s1, const char * s2, size_t n); strncmp()函数对 s1 指向的字符串和 s2 指向的字符串进行不超过 n 个字节的比较(空字节之 后的字节不作比较)。成功完成后,如果 s1 指向的可能为空的字符串大于、等于或小于 s2 指向的可能为空的字符串,strncmp()返回一个大于、等于或小于 0 的整数。 char * strncpy(char * s1, const char * s2, size_t n)。 strncpy()函数将不超过 n 个字节(空字节之后的字节不被复制)从 s2 所指向的字符串复制到 s1 所指向的字符串。如果 s2 所指向的字符串短于 n 个字节,空字节将被追加到 s1 所指向的 字符串中的副本中,直到全部写完 n 个字节。strncpy()函数返回 s1。 char * strstr(const char * s1, const char * s2)。 strstr()函数在 s1 所指向的字节序列中找到 s2 所指向的字节序列的第一次出现(不包括结束 的空字节)。成功完成后,strstr()返回一个指向所定位子串的指针,如果没有找到该字符串, 则返回一个空指针。如果 s2 指向一个零长度的字符串,该函数返回 s1。 在 MacLeod 的二楼实验室里发现了一滴血,一个不可替代的古董示波器丢失。加拿大皇家 骑警到达后发现没有人受伤。他们从这滴血中提取了部分 DNA 样本,并将其带到校园实验 室。 你和你的伙伴在校外喝咖啡,阅读唐纳德 -克努斯的《计算机编程的艺术》,并关注 stackoverflow.com(链接到外部网站。),这时你的电话响了。来电者是你的一个朋友,她 在校园实验室为皇家骑警工作,她很苦恼。一个实习生正在给什么东西编码,发生了一场名 副其实的灾难,她喘着气说。她告诉你,这个实习生去年选修了 CPSC 259,并决定重写实验 室的 DNA 匹配程序,而这个程序恰好是用 C 语言编写的。通常随叫随到处理这种电脑事情 的实验室助理正在休假,无法联系上。你的朋友提到了你的名字,加拿大皇家骑警希望尽快 完成初步分析。 在商店门口迎接你的皇家骑警巡逻车将你带到实验室。一旦你到了那里,你就坐在其中一台 电脑前,评估一下。可执行文件不见了,但你找到了法医技术人员用来编写程序的旧的迭代 框架。你还了解到,DNA 已经被分析了,结果被编码为一串代表核苷酸片段的字符。加拿 大皇家骑警有一些可能的嫌疑人在过去也提供过 DNA,他们的候选样本也在同一个文件中。 你和你的伙伴松了一口气,因为你意识到用于匹配样本的算法非常简单,你把你的任务分解 如下。 在这里下载我们为你创建的框架 在这里下载。. 解压缩该文件夹。这是一个部分实现的 Visual Studio 2019 项目。你需要实现的代码在文件 dna.c 中。你需要完成的唯一代码是在 analyze_segments 和 calculate_scores 函数中。 由于这是该程序的早期迭代,它非常简单,只有一个命令行界面。没有 GUI,即图形用户界 面。 你的程序一开始就为用户提供了一个菜单。菜单的选择是。1. 载入文件,2.进行分析,3. 退出。用户必须输入他们选择的数字。其他的都会被忽略,而菜单会再次提供。你没有责任 知道菜单代码是如何工作的,但是如果你有兴趣,可以看一看 一、 如果用户选择了 1,你的程序会要求用户提供 DNA 数据文件的名称,然后尝试打开该文件 并将其内容复制到一些动态分配的内存中。如果程序不能打开该文件,它将打印一个适当的 信息并返回主菜单。[注意,我们在本项目的资源文件夹中为你提供了 3 个测试文件: long_sample.txt、short_sample.txt 和 perfect_match.txt。为了帮助你确定你的实现的正确性, 还提供了每个文件的正确输出结果] 。 DNA 数据文件将始终与我们在这里描述的完全一样。该文件将永远不会由其他东西组成。 我们提供了打开该文件并将其复制到动态存储器的代码。该文件包含一个样本段和一个或多 个候选段,候选段的长度至少与样本段相同。候选段不会比样本段短。所有的段都以字符串 的形式存储,以换行符结束。你可以不对样本的长度做任何假设。它可能有数百万个字符长。 候选片段的长度总是等于或长于在犯罪现场采集的样本,绝不会短于 二、如果用户选择 3,程序执行结束(我们已经为你写好了这个)。 三、如果用户选择了 2,而一个文件还没有被打开并复制到堆中,则什么也不会发生。 菜 单会再次询问用户要做什么(我们已经为你写好了)。 四、如果用户选择了 2,而一个文件已经被打开并复制到了堆中,程序会立即开始分析(你 需要写出这样的代码)。 1.寻找样本和每个候选序列之间的完全匹配。 测试每个候选序列。 在测试完全匹配时,你 必须测试样本的整个长度,甚至任何尾随的核苷酸。当一个样本和一个候选序列的长度完全 相同并且包含完全相同的字符时,就是完全匹配。每当你的程序找到一个完全匹配,它必须 向标准输出打印一条信息,说明哪个(些)候选序列是一个完全匹配。 其中 XXX,和 YYY 是根据它们在文本文件中提供的候选人名单(当然不包括样本)中的顺序 确定的。如果有一个或多个完美匹配,在你为每个完美匹配打印这个完美匹配信息后,你不 需要再打印其他东西或计算其他分数,你的程序应该返回菜单。 2.如果没有完全匹配,那么必须为每个样本-候选人对找到一个最佳匹配。程序必须给每个 候选人打分。 (1)每个分数从 0 开始。 (2)让 LENGTH 成为样本中密码子(3 个核苷酸组成的组)的数量(记住,样本的长度总是相 等的,或者比候选者短)。忽略样本中任何尾随的核苷酸(可能有 1 或 2 个,但不能多)。 换句话说,如果(样本长度%3)不等于零,我们就忽略样本中(样本长度%3)的最后一个 核苷酸。[%是模数运算符。3 % 3 = 0; 4 % 3 = 1; 7 % 3 = 1; 8 % 3 = 2. (3)对于每个 LENGTH 密码子,如果两个密码子(样本和候选)完全相同,则在分数上加 10。 否则,如果这两个密码子不同,但指定了相同的氨基酸,则在得分上加 5 分。 (4)对于你刚才比较的每一个 LENGTH 密码子,如果它们不完全相同,就是说你刚才没有加 10 分,而且它们没有指定相同的氨基酸,就是说你刚才也没有加 5 分,你必须分别检查密 码子的 3 个核苷酸。对于密码子中的 3 个核苷酸中的每一个,如果样本和候选者中的字符是 相同的,就在分数上加 2。如果这 2 个字符属于一个匹配的碱基对(A 和 T,或 C 和 G), 则得分加 1。否则,在得分上加 0。储存结果。 (5)由于候选基因总是比样本长,在你检查了第一个长度的密码子之后,你必须将样本沿着 候选基因的长度移动 1 个密码子(也就是 3 个核苷酸,也就是 3 个字母),然后检查这个新 的排列的分数(所以现在你要将样本的第一个密码子,或者 3 个字母,与候选基因的第二个 密码子(字母 4、5、6)对齐,或者使用 1 的密码子偏移)。储存分数,然后再做一次,例 如,使用 2 的密码子偏移量。 不断地将样本深入到候选的密码子中,一次一个密码子,检 查分数,直到它不再适合。(记住要忽略尾随的核苷酸)。) 一旦样本超出了候选者的长度, 你的程序就必须停止移位和检查该候选者的分数。这个候选者的分数是所有这些结果中最高 的。下面是你对每个候选者所要做的事情的一个可视化表示。 (6)输出所有候选人的分数,每个都应该如下格式: Candidate number XXX matches with a score of ###.