Использование char * в качестве ключа для unordered_map не распознает дубликаты ключей - PullRequest
0 голосов
/ 01 февраля 2019

Я строю пример сборки де Брюйна для сборки генома (или любой строки), получая каждое возможное слово n-длины строки, а затем нахожу правильный путь по чтению, сравнивая конечные части каждогоузел.который принимает в качестве аргументов последовательность и размер для каждого чтения последовательности, он сначала получит собрать все чтения в массив размера [kmer_size] [3], индексы [3] 0 = полное чтение 1 = все, кромекрайний правый символ чтения 2 = все, кроме крайнего левого символа чтения.

часть, которая собирает операции чтения, работает, как и ожидалось, она разделяется на функцию, и эти операции чтения печатаются правильно.

Затем я создаю unordered_map, используя char * в качестве ключей и другую карту в качестве значения, эта карта имеет ключ char * и оценивается как int.

что должно произойти, следует проверить, соответствует ли раздел чтения, исключая крайний левый символ, одному и тому же разделу чтения друг друга, если они совпадают, взять исключающую справа часть совпадающего чтения и создатьновая запись во внутренней карте, которая обозначена левой частью чтения, которое вы тестируете, и увеличивает значение этого элемента на 1.

, если вы посмотрите на вывод, вы увидите, что когда яв отдельном цикле выведите содержимое вложенных карт, есть дубликаты записей как на внешней, так и на внутренней карте.ключи 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;

}

1 Ответ

0 голосов
/ 01 февраля 2019

В вашем коде много проблем (о чем свидетельствуют комментарии к вопросу), я перечислю их в конце ответа, поскольку они не связаны с ядром вопроса.

Проблемная строка, какВы подозревали, что это строка:

unordered_map<char*, unordered_map<char*, int> > nodes

Как вы сказали

это потому, что char * на самом деле является строковым значением, но адресом, и они указывают на разные адреса.

Другими словами, ваши строки (кмеры) сравниваются как указатели.Если двум char * объектам назначаются два разных вызова malloc, то они имеют разные адреса.unordered_map сравнивает только адрес, а не набор символов, которые находятся по адресу.

Решение состоит в том, чтобы начать использовать строки C ++, а не строки с нулем в конце:

std::unordered_map<std::string, std::unordered_map<std::string, int> > nodes

Это исправит другие проблемы, которые возникают в вашем коде:

  1. Ваш код имеет утечку памяти.Он выделяет память с помощью malloc и никогда не освобождает ее.Использование std::string решает проблему.
  2. kmers имеют тенденцию быть относительно короткими строками (большинство из них ниже 12 букв).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 больше не использовался в коде.

unordered_map обычно менее эффективен, чем std::map для контейнеров с менее чем 10 000 элементов.С данными генома есть некоторый шанс, что вы попали в тот случай, когда std::unordered_map того стоит, но я бы протестировал это (особенно для внутреннего контейнера).

Другая проблема заключается в использовании std::endl, который может замедлить выполнение кода в 2-10 раз.Вы должны использовать '\n' вместо endl.endl выполняет очистку вывода в конце строки.Дополнительный системный вызов во многих случаях имеет большое значение с точки зрения производительности.Конечно, если это просто отладочный код, это не имеет значения.

...