我正在构建一个De Bruijn Assembly的示例,用于通过获取字符串n长度的每个可能单词,然后通过比较每个序列的末端片段来找到正确的阅读路径,从而组装基因组(或任何字符串)节点。它接受序列和每次读取序列的大小作为参数, 它将首先将所有读取收集到一个大小为[kmer_size] [3]的数组中,[3]索引0 =完全读取1 =读取的所有最右字符2 =读取的所有但最左字符。 / p>
组装读段的部分按预期工作,将其分离为一个函数,并且正确读取了这些读段。
然后我使用char *作为键和另一个映射作为值创建一个unordered_map,该映射由char *键控,并由int值。
应该发生的是,应该检查读取部分(不包括最左边的字符)是否与其他读取部分的相同部分匹配(如果匹配),获取匹配读取部分的右排除部分并创建一个新条目在内部地图中,您正在测试的读取的左排除部分将其作为键,并将该元素的值增加1。
如果查看输出,将会看到当i在单独的循环中打印嵌套地图的内容时,外部和内部地图中都有重复的条目。具有相同字符串值的char *键不会将项目放入相同的存储桶中,而是创建具有相同名称的新存储桶。 我认为这是因为char *实际上是一个字符串值,但一个地址,并且它们指向不同的地址。
我将如何修改此代码以使我的地图的每个字符串只有1个存储桶
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<bits/stdc++.h>
#include<unordered_map>
using namespace std;
void extractReads(char* kmers[][3], int num_kmers, int kmer_size, char* seq);
int main(int nargs, char* args[]){
if(nargs!=3){
cout<<"INVALID ARGUMENTS"<<endl;
cout<<"dba <kmer_size> <sequence>"<<endl;
}
char* seq = args[2];
int kmer_size = atoi(args[1]);
int num_kmers = strlen(seq)-(kmer_size -1);
char* kmers[num_kmers][3];
unordered_map<char*, unordered_map<char*, int> > nodes;
extractReads(kmers, num_kmers, kmer_size, seq);
for(int i=0; i< num_kmers; i++)
{
for(int j=0; j<num_kmers; j++)
{
if(strcmp(kmers[i][2], kmers[j][2]) == 0 )
{
// cout<<" match"<<endl;
nodes[kmers[i][2]][kmers[j][1]]++;
}
}
}
for(auto node: nodes)
{
cout<<node.first<<endl;
for (auto n: node.second)
{
cout<<" "<<n.first<<" "<<n.second<<endl;
}
}
return 0;
}
void extractReads(char* kmers[][3], int num_kmers, int kmer_size, char* seq)
{
cout<<"READS"<<endl<<"==========="<<endl;
for (int i=0; i<num_kmers; i++){
kmers[i][0] = (char*) malloc(kmer_size);
kmers[i][1] = (char*) malloc(kmer_size-1);
kmers[i][2] = (char*) malloc(kmer_size-1);
strncpy(kmers[i][0], seq+i, kmer_size);
strncpy(kmers[i][1], kmers[i][0], kmer_size-1);
strncpy(kmers[i][2], kmers[i][0]+1, kmer_size-1);
cout<<kmers[i][0]<<" : "<<kmers[i][1]<<" "<<kmers[i][2]<<endl;
}
cout<<"==========="<<endl;
}
答案 0 :(得分:2)
您的代码有很多问题(正如对该问题的评论所暗示的那样),由于它们与问题的核心无关,因此我将在答案的末尾列出它们。
您怀疑的问题线是:
unordered_map<char*, unordered_map<char*, int> > nodes
如你所说
这是因为char *实际上是一个字符串值,而是一个地址,并且它们指向不同的地址。
换句话说,将您的字符串(kmers)作为指针进行比较。如果为两个char *
对象分配了两个不同的malloc调用,则它们具有不同的地址。 unordered_map
仅比较地址,不比较位于该地址的字符集。
解决方案是开始使用C ++字符串而不是C零终止字符串:
std::unordered_map<std::string, std::unordered_map<std::string, int> > nodes
这将解决代码中的其他问题:
std::string
可解决问题。std::string
专门针对这种情况进行了优化,并且完全避免了这些字符串的堆内存。通过避免不必要的堆分配,使用std::string
可使代码运行更快。
另一个不太理想的选择是提供自己的Hash KeyEqual函数:
class cstr_hash
{
public:
std::size_t operator()(const char *cstr) const
{
std::size_t hash = 5381;
for ( ; *cstr != '\0' ; ++cstr)
hash = (hash * 33) + *cstr;
return hash;
}
};
class cstr_eq
{
public:
using value_type = const char*;
bool operator()(const char* a, const char *b) const
{ return strcmp(a, b) == 0; }
};
然后使用地图:
std::unordered_map<const char *, int, cstr_hash, cstr_eq> nodes;
但是这种方法是不可取的,因为它使避免内存泄漏变得更加困难,并且无法像std::string
那样优化短字符串。
char* kmers[num_kmers][3];
这不是C ++。大多数编译器支持VLA(可变长度数组),但它不是标准的一部分。最好使用std::vector<std::string>
。
内存泄漏。您使用malloc分配字符串,并且永远不会释放它们。最好使用std :: string并传递它,以便在代码中不再使用malloc。
对于少于10,000个元素的容器, unordered_map
的效率通常比std::map
低。利用基因组数据,您很有可能遇到std::unordered_map
值得的情况,但我会对此进行测试(尤其是对于内部容器)。
另一个问题是使用std::endl
,这会使您的代码运行慢2到10倍。您应该使用'\n'
而不是endl
。 endl
的作用是在行尾刷新输出。在许多情况下,附加的系统调用在性能方面有很大的不同。当然,如果这只是调试代码,那就没关系了。