Предполагаемый вывод состоит в том, чтобы сначала перевернуть всю строку ДНК, а затем преобразовать A <-> T, C <-> G. Однако в фактическом выводе первый символ печатается как «p», который появляется из ниоткуда, но остальная часть выходной строки в порядке. Вот код:

int main() {
    
    const char dna[] = "GATCACAGGTCTATCACCCTATTAACCACTCACGGGAGCTCTCCATGCAT"
                       "TTGGTATTTTCGTCTGGGGGGTGTGCACGCGATAGCATTGCGAGACGCTG"
                       "GAGCCGGAGCACCCTATGTCGCAGTATCTGTCTTTGATTCCTGCCTCATT"
                       "CTATTATTTATCGCACCTACGTTCAATATTACAGGCGAACATACCTACTA"
                       "AAGTGTGTTAATTAATTAATGCTTGTAGGACATAATAATAACAATTGAAT";

    int dna_len = strlen(dna);

    char rev_comp[dna_len+1];   
    char temp = '\0'; 
    char temp_dna[dna_len+1]; 

    for (int i = 0; i < dna_len + 1; i++) {
        temp_dna[i] = dna[dna_len - i];
        if (temp_dna[i] == 'A') {
            temp = 'T';
            rev_comp[i] = temp;
        }
        else if (temp_dna[i] == 'T') {
            temp = 'A';
            rev_comp[i] = temp;
        }
        else if (temp_dna[i] == 'C') {
            temp = 'G';
            rev_comp[i] = temp;
        }
        else if (temp_dna[i] == 'G') {
            temp = 'C';
            rev_comp[i] = temp;
        }
    }

    rev_comp[dna_len+1] = '\0'; 

    printf("original: %s\n", dna);
    printf("rev_comp: %s\n", rev_comp);

    return 0;
}
c
2
aurora 4 Фев 2022 в 04:00

2 ответа

@TedLyngmo уже указал на ошибки индексации в вашем исходном коде, но еще одно соображение, которое вы можете рассмотреть, — это подумать о возможности повторного использования части кода, который вы пишете, в других программах позже. Вместо написания специализированного кода снова и снова для каждой отдельной программы, которую вы пишете, определение общих частей кода, которые вы, возможно, захотите снова использовать в другой программе, и создание короткой функции для этой части кода делает это возможным.

Скорее всего, вам потребуется перевернуть строку несколько раз, а не только в этой программе, поэтому имеет смысл написать повторно используемую функцию для переворачивания строки, которую вы можете использовать везде, где это необходимо. В зависимости от вашей карьеры вам также может понадобиться преобразовать A <-> T, C <-> G больше, чем в этой программе, поэтому короткая функция для этого также может иметь смысл.

Предупреждение: если требуется максимальная эффективность (работа со строками из миллиардов символов), то имеет смысл объединить операции и воспользоваться преимуществом одной итерации по сравнению со строкой последовательности ДНК. Работая от каждого конца строки к середине, вы можете обрабатывать два символа за итерацию, уменьшая вдвое количество необходимых итераций.

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

#include <stdio.h>
#include <string.h>

/* reverse src in dest copying 2-characters per-iteration. */
void strrev(char *dest, const char *src)
{
  size_t begin = 0, end = strlen(src); /* begin and 1-past-end indexes */

  dest[end] = 0; /* nul-terminate dest */

  for(; begin < end--; ++begin) {
    dest[begin] = src[end]; /* end to begin */
    dest[end] = src[begin]; /* begin to end */
  }
}

/* transform A <-> T, C <-> G */
void xformATCG (char *s)
{
  do {
    if (*s == 'A')
      *s = 'T';
    else if (*s == 'T')
      *s = 'A';
    else if (*s == 'C')
      *s = 'G';
    else if (*s == 'G')
      *s = 'C';
  } while (*s++);
}

Если хотите, вы можете написать простую функцию печати, которая будет разбивать длинные строки вывода на определенное количество символов, подобно тому, как вы показываете при инициализации dna[]. Для чего это стоит, вы могли бы добавить:

/* simple print with break at brk chars function */
void prnwbrk (const char *s, size_t brk)
{
  size_t n = 0;                       /* counter */
  
  while (s[n]) {                      /* loop until end-of-string */
    if (n && n % brk == 0)            /* if brk chars, output \n */
      putchar ('\n');
    putchar (s[n++]);                 /* output char */
  }
  
  putchar ('\n');                     /* final \n */
}

Теперь обращение и преобразование строки просто становится вопросом вызова strrev() и xformATCG() в main(). Вы можете выводить между каждой операцией, чтобы проверить каждый шаг (что немного упрощает отладку). Короткий main() может быть:

int main (void) {
    
  const char dna[] =  "GATCACAGGTCTATCACCCTATTAACCACTCACGGGAGCTCTCCATGCAT"
                      "TTGGTATTTTCGTCTGGGGGGTGTGCACGCGATAGCATTGCGAGACGCTG"
                      "GAGCCGGAGCACCCTATGTCGCAGTATCTGTCTTTGATTCCTGCCTCATT"
                      "CTATTATTTATCGCACCTACGTTCAATATTACAGGCGAACATACCTACTA"
                      "AAGTGTGTTAATTAATTAATGCTTGTAGGACATAATAATAACAATTGAAT";
  char rev_comp[sizeof dna];
  
  prnwbrk (dna, 50);            /* print original dna */
  putchar ('\n');
  
  strrev (rev_comp, dna);       /* reverse and print */
  prnwbrk (rev_comp, 50);
  putchar ('\n');
  
  xformATCG (rev_comp);         /* transform chars and print */
  prnwbrk (rev_comp, 50);
}

Пример использования / вывода

Если я правильно понял ваш вопрос и операции, перевернутые и преобразованные строки будут выглядеть так:

$ ./bin/revdna
GATCACAGGTCTATCACCCTATTAACCACTCACGGGAGCTCTCCATGCAT
TTGGTATTTTCGTCTGGGGGGTGTGCACGCGATAGCATTGCGAGACGCTG
GAGCCGGAGCACCCTATGTCGCAGTATCTGTCTTTGATTCCTGCCTCATT
CTATTATTTATCGCACCTACGTTCAATATTACAGGCGAACATACCTACTA
AAGTGTGTTAATTAATTAATGCTTGTAGGACATAATAATAACAATTGAAT

TAAGTTAACAATAATAATACAGGATGTTCGTAATTAATTAATTGTGTGAA
ATCATCCATACAAGCGGACATTATAACTTGCATCCACGCTATTTATTATC
TTACTCCGTCCTTAGTTTCTGTCTATGACGCTGTATCCCACGAGGCCGAG
GTCGCAGAGCGTTACGATAGCGCACGTGTGGGGGGTCTGCTTTTATGGTT
TACGTACCTCTCGAGGGCACTCACCAATTATCCCACTATCTGGACACTAG

ATTCAATTGTTATTATTATGTCCTACAAGCATTAATTAATTAACACACTT
TAGTAGGTATGTTCGCCTGTAATATTGAACGTAGGTGCGATAAATAATAG
AATGAGGCAGGAATCAAAGACAGATACTGCGACATAGGGTGCTCCGGCTC
CAGCGTCTCGCAATGCTATCGCGTGCACACCCCCCAGACGAAAATACCAA
ATGCATGGAGAGCTCCCGTGAGTGGTTAATAGGGTGATAGACCTGTGATC

Нет ничего плохого в том, чтобы делать все это в main(), но если подумать заранее, это может уберечь вас от необходимости заново изобретать велосипед каждый раз, когда вам нужно сделать то же самое в другой программе. (кроме того, однократное написание и отладка функции предотвращает появление новых ошибок, когда вы позже изобретаете функцию заново)

Просмотрите все и дайте мне знать, если у вас возникнут дополнительные вопросы.

2
Ted Lyngmo 4 Фев 2022 в 11:26
1
Тед. Спасибо за правки. ++*s в xform был преднамерен для проверки нулевого символа, сохраняющего последнюю итерацию (хотя это мало что меняет). Творческое использование теста for и приращения. Мне это нравится. Хотя оба оптимизируют одинаково. (for сохраняет начальное уменьшение). Еще раз спасибо.
 – 
David C. Rankin
4 Фев 2022 в 11:33

Ваш цикл неверен и зацикливается до dna_len (где находится нулевой терминатор). Должен быть:

for (int i = 0; i < dna_len; i++) {     // corrected loop
    temp_dna[i] = dna[dna_len - i - 1]; // corrected calculation

Кроме того, последний нуль-ограничитель в rev_comp должен быть назначен по индексу dna_len, а не dna_len + 1, что выходит за пределы, поэтому ваша программа имеет неопределенное поведение. Печать p является одним из возможных результатов неопределенного поведения.

rev_comp[dna_len] = '\0';

Демо

Вы можете сделать небольшую вспомогательную функцию, чтобы просто выполнить реверсирование, прежде чем вы начнете менять местами символы в строке. Создание небольших специализированных функций, которые делают только одну вещь, хорошо подходит для последующей отладки вашей программы. Тогда легче изолировать и найти проблемы. Пример:

void rev(const char *in, size_t len, char *out) {
    for(size_t i = 0; i < len; ++i) {
        out[len - i - 1] = in[i];
    }
    out[len] = '\0';
}

И позвони с

rev(dna, dna_len, rev_comp);

Перед заменой букв:

for (size_t i = 0; i < dna_len; ++i) {
    switch(rev_comp[i]) {
        case 'A': rev_comp[i] = 'T'; break;
        case 'T': rev_comp[i] = 'A'; break;
        case 'G': rev_comp[i] = 'C'; break;
        case 'C': rev_comp[i] = 'G'; break;
    }
}

Демо

2
Ted Lyngmo 4 Фев 2022 в 13:42