как сопоставить образец последовательности ДНК

У меня возникают проблемы с поиском подхода к решению этой проблемы.

Последовательности ввода-вывода следующие:

 **input1 :** aaagctgctagag 
 **output1 :** a3gct2ag2

 **input2 :** aaaaaaagctaagctaag 
 **output2 :** a6agcta2ag

Входная последовательность может состоять из 10 ^ 6 символов, и будут учитываться самые большие непрерывные шаблоны.

Например, для input2 «agctaagcta» вывод будет не «agcta2gcta», а «agcta2».

Любая помощь приветствуется.


person user2442890    schedule 01.06.2013    source источник
comment
Какой вывод должен быть предоставлен для ввода aabbaabb? Два возможных варианта: a2b2a2b2 и aabb2.   -  person Egor Skriptunoff    schedule 01.06.2013
comment
вывод должен быть aabb2   -  person user2442890    schedule 01.06.2013
comment
А как насчет aaaaaaaaabbbbbbbbbaaaaaaaaabbbbbbbbb: a9b9a9b9 или aaaaaaaaabbbbbbbbb2? Первый короче ;-)   -  person Egor Skriptunoff    schedule 01.06.2013
comment
количество символов и их количество должны быть минимальными.. например, a9b9a9b9 принимает 8 буквенно-цифровых значений, а aaaaaaaaabbbbbbbbb2 принимает 19 буквенно-цифровых значений   -  person user2442890    schedule 01.06.2013
comment
Итак, вам нужно написать своего рода архиватор, работающий в режиме наилучшего сжатия?   -  person Egor Skriptunoff    schedule 01.06.2013
comment
да... в лучшем режиме сжатия   -  person user2442890    schedule 01.06.2013
comment
Есть ли в каждой входной последовательности какое-либо ограничение на размер повторяющегося шаблона, например, может ли у вас быть повторяющаяся подпоследовательность длиной 10 ^ 3?   -  person גלעד ברקן    schedule 01.06.2013
comment
в основном мы работаем над последовательностями ДНК ... нет ограничений на длину подпоследовательности ... она может быть любой длины ... но длина подпоследовательности не будет достигать длины 10 ^ 3 ... максимальная длина до 100 символов, как мне кажется... так как я ссылался на разные последовательности ДНК   -  person user2442890    schedule 01.06.2013
comment
как бы вы закодировали это: aaagctgctxyzagag ?   -  person גלעד ברקן    schedule 01.06.2013
comment
Вы пытаетесь создать вывод? Также что произойдет, если в середине нет повтора, например. aabbaabbcxyzxyz может быть aabb2cxyz2, но реконструированный ввод может быть aabbaabbcxyzcxyz?   -  person glh    schedule 02.06.2013
comment
@ user2442890: Если бы это было хотя бы упражнение из учебника с жадным подходом и плохими алгоритмами, но у вас, похоже, здесь проблема из реального мира ... И если вам требуется оптимальное, не жадное решение, это шепчет мне: «динамический программирование» и «алгоритм Уконнена». Высокий вопрос, который вы задаете здесь.   -  person Boris Stitnicky    schedule 02.06.2013
comment
Как быстро вам нужно обработать один вход длиной 10 ^ 6?   -  person גלעד ברקן    schedule 02.06.2013
comment
@groovy: Поверьте мне, экспоненциальной сложности будет недостаточно. Я предполагаю, что речь идет о сжатии целых хромосом.   -  person Boris Stitnicky    schedule 02.06.2013


Ответы (3)


Пояснение алгоритма:

  • Имея последовательность S с символами s(1), s(2),…, s(N).
  • Пусть B(i) — наилучшая сжатая последовательность с элементами s(1), s(2),…,s(i).
  • Так, например, B(3) будет лучшей сжатой последовательностью для s(1), s(2), s(3).
  • Нам нужно знать B(N).

Чтобы найти его, будем действовать по индукции. Мы хотим вычислить B(i+1), зная B(i), B(i-1), B(i-2), …, B(1), B(0), где B(0) пусто последовательность, и и B(1) = s(1). В то же время это является доказательством оптимальности решения. ;)

Чтобы вычислить B(i+1), мы выберем лучшую последовательность среди кандидатов:

  1. Последовательности-кандидаты, в которых последний блок имеет один элемент:

    B(i)s(i+1)1 B(i-1)s(i+1)2 ; только если s(i) = s(i+1) B(i-2)s(i+1)3 ; только если s(i-1) = s(i) и s(i) = s(i+1) … B(1)s(i+1)[i-1] ; только если s(2)=s(3) и s(3)=s(4) и … и s(i) = s(i+1) B(0)s(i+1)i = s(i +1)я; только если s(1)=s(2) и s(2)=s(3) и … и s(i) = s(i+1)

  2. Последовательности-кандидаты, в которых последний блок состоит из 2 элементов:

    B(i-1)s(i)s(i+1)1 B(i-3)s(i)s(i+1)2 ; только если s(i-2)s(i-1)=s(i)s(i+1) B(i-5)s(i)s(i+1)3 ; только если s(i-4)s(i-3)=s(i-2)s(i-1) и s(i-2)s(i-1)=s(i)s(i+1) ) …

  3. Последовательности-кандидаты, в которых последний блок состоит из 3 элементов:

  4. Последовательности-кандидаты, в которых последний блок состоит из 4 элементов:

  5. Последовательности-кандидаты, в которых последний блок содержит n+1 элементов:

    s(1)s(2)s(3)………s(i+1)

Для каждой возможности алгоритм останавливается, когда блок последовательности больше не повторяется. И это все.

Алгоритм будет примерно таким в коде psude-c:

B(0) = “”
for (i=1; i<=N; i++) {
    // Calculate all the candidates for B(i)
    BestCandidate=null
    for (j=1; j<=i; j++) {
        Calculate all the candidates of length (i)

        r=1;
        do {
             Candidadte = B([i-j]*r-1) s(i-j+1)…s(i-1)s(i) r
             If (   (BestCandidate==null) 
                      || (Candidate is shorter that BestCandidate)) 
                 {
            BestCandidate=Candidate.
                 }
             r++;
        } while (  ([i-j]*r <= i) 
             &&(s(i-j*r+1) s(i-j*r+2)…s(i-j*r+j) == s(i-j+1) s(i-j+2)…s(i-j+j))

    }
    B(i)=BestCandidate
}

Надеюсь, что это может помочь немного больше.

Полная программа на C, выполняющая требуемую задачу, приведена ниже. Он работает за O (n ^ 2). Центральная часть состоит всего из 30 строк кода.

РЕДАКТИРОВАНИЕ Я немного реструктурировал код, изменил имена переменных и добавил комментарий, чтобы сделать его более читабельным.

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


// This struct represents a compressed segment like atg4, g3,  agc1
    struct Segment {
        char *elements;
        int nElements;
        int count;
    };
         // As an example, for the segment agagt3  elements would be:
         // {
         //      elements: "agagt",
         //      nElements: 5,
         //      count: 3
         // }

    struct Sequence {
        struct Segment lastSegment;
        struct Sequence *prev;      // Points to a sequence without the last segment or NULL if it is the first segment
        int totalLen;  // Total length of the compressed sequence.
    };
       // as an example, for the sequence agt32ta5, the representation will be:
       // {
       //     lastSegment:{"ta" , 2 , 5},
       //     prev: @A,
       //     totalLen: 8
       // }
       // and A will be
       // {
       //     lastSegment{ "agt", 3, 32},
       //     prev: NULL,
       //     totalLen: 5
       // }


// This function converts a sequence to a string.
// You have to free the string after using it.
// The strategy is to construct the string from right to left.

char *sequence2string(struct Sequence *S) {
    char *Res=malloc(S->totalLen + 1);
    char *digits="0123456789";

    int p= S->totalLen;
    Res[p]=0;

    while (S!=NULL) {
            // first we insert the count of the last element.
            // We do digit by digit starting with the units.
            int C = S->lastSegment.count;
            while (C) {
                p--;
                Res[p] = digits[ C % 10 ];
                C /= 10;
            }

            p -= S->lastSegment.nElements;
            strncpy(Res + p , S->lastSegment.elements, S->lastSegment.nElements);

            S = S ->prev;
    }


    return Res;
}


// Compresses a dna sequence.
// Returns a string with the in sequence compressed.
// The returned string must be freed after using it.
char *dnaCompress(char *in) {
    int i,j;

    int N = strlen(in);;            // Number of elements of a in sequence.



    // B is an array of N+1 sequences where B(i) is the best compressed sequence sequence of the first i characters.
    // What we want to return is B[N];
    struct Sequence *B;
    B = malloc((N+1) * sizeof (struct Sequence));

    // We first do an initialization for i=0

    B[0].lastSegment.elements="";
    B[0].lastSegment.nElements=0;
    B[0].lastSegment.count=0;
    B[0].prev = NULL;
    B[0].totalLen=0;

    // and set totalLen of all the sequences to a very HIGH VALUE in this case N*2 will be enougth,  We will try different sequences and keep the minimum one.
    for (i=1; i<=N; i++) B[i].totalLen = INT_MAX;   // A very high value

    for (i=1; i<=N; i++) {

        // at this point we want to calculate B[i] and we know B[i-1], B[i-2], .... ,B[0]
        for (j=1; j<=i; j++) {

            // Here we will check all the candidates where the last segment has j elements

            int r=1;                  // number of times the last segment is repeated
            int rNDigits=1;           // Number of digits of r
            int rNDigitsBound=10;     // We will increment r, so this value is when r will have an extra digit.
                                      // when r = 0,1,...,9 => rNDigitsBound = 10
                                      // when r = 10,11,...,99 => rNDigitsBound = 100
                                      // when r = 100,101,.,999 => rNDigitsBound = 1000 and so on.

            do {

                // Here we analitze a candidate B(i).
                // where the las segment has j elements repeated r times.

                int CandidateLen = B[i-j*r].totalLen + j + rNDigits;
                if (CandidateLen < B[i].totalLen) {
                    B[i].lastSegment.elements = in + i - j*r;
                    B[i].lastSegment.nElements = j;
                    B[i].lastSegment.count = r;
                    B[i].prev = &(B[i-j*r]);
                    B[i].totalLen = CandidateLen;
                }

                r++;
                if (r == rNDigitsBound ) {
                    rNDigits++;
                    rNDigitsBound *= 10;
                }
            } while (   (i - j*r >= 0)
                     && (strncmp(in + i -j, in + i - j*r, j)==0));

        }
    }

    char *Res=sequence2string(&(B[N]));
    free(B);

    return Res;
}

int main(int argc, char** argv) {
    char *compressedDNA=dnaCompress(argv[1]);
    puts(compressedDNA);
    free(compressedDNA);
    return 0;
}
person jbaylina    schedule 03.06.2013
comment
+1, кажется, работает достаточно хорошо, также C - хороший язык для реализации этого, хотя его немного сложно читать. Например. что это за переменная ss. Является ли это решение оптимальным? Для ОП слишком легко просто передать ему такое решение. Он должен отдать вам должное в своей статье :-))) - person Boris Stitnicky; 03.06.2013
comment
Видя разницу между вашей текущей и заслуженной репутацией, я должен дать вам награду за это. - person Boris Stitnicky; 03.06.2013
comment
Я обещаю попытаться объяснить немного больше алгоритма. В любом случае, s — это количество цифр r, а ss — это следующее число, которое увеличит количество шифров r на единицу. - person jbaylina; 04.06.2013
comment
Алгоритм должен давать оптимум. И если вы хотите ограничить максимальную длину последовательности, которая может быть повторена, как вы сказали в одном из своих комментариев, вы можете просто ограничить второй цикл for для запуска максимум N (где N - максимальная последовательность). поэтому строка будет выглядеть так: for (j=i-i, (j›0)&&(i-j ‹ N), j++) { . Это должно улучшить производительность до O(n*N) - person jbaylina; 04.06.2013
comment
@jbaylina: В конце концов, это динамическое программирование. До сих пор я очень мало писал на C в своей жизни и не мог понять ваш код. Именно такие вопросы и ответы делают НАСТОЛЬКО интересным. После того, как вы объяснили фокус, это звучит почти как наивный алгоритм :-). Но, интересно, можно ли это сделать короче О? Например, n log n или линейный, с использованием деревьев суффиксов или что-то в этом роде? - person Boris Stitnicky; 05.06.2013
comment
@BorisStitnicky Да, C-код не очень ясен. Я должен прокомментировать это и немного почистить. - person jbaylina; 06.06.2013
comment
@BorisStitnicky Что касается вопроса, можно ли его улучшить, вот что я бы сделал, чтобы улучшить его: во втором цикле я считаю, что не всегда необходимо снижаться до 0. Можно найти границу, которая гарантирует, что это нет необходимости продолжать цикл. Я считаю, что это улучшение может работать в последовательностях, которые можно сжать более чем в 2 раза. (Я понятия не имею, удовлетворяет ли ДНК этому условию…). В любом случае, эту рабочую строку нужно немного изучить, но я считаю, что O(n^2) можно улучшить в последовательности с высокой степенью сжатия. - person jbaylina; 06.06.2013
comment
@jbaylina, не могли бы вы рассказать, как работает алгоритм ... его трудно понять ... и объяснить использование переменных в коде - person user2442890; 06.06.2013
comment
@user2442890 user2442890: Переполнение стека потрясающе, не так ли? Имхо, его объяснение алгоритма уже довольно хорошо, может быть, какое-то форматирование... - person Boris Stitnicky; 07.06.2013
comment
@user2442890 user2442890 Я изменил код C, чтобы сделать его более читаемым. Я немного протестировал, но, пожалуйста, проверьте его сами, чтобы убедиться, что он работает. И заполните бесплатно, чтобы улучшить формат. - person jbaylina; 07.06.2013
comment
@BorisStitnicky @jbaylina @user2442890 ...в качестве примечания -- я продолжал работать с моими ограниченными навыками -- посмотрите этот пример двух разных сжатий реальных данных: pastebin.com/JFirziJb -- Что мне кажется интересным, так это то, что более короткое сжатие на самом деле намного ближе к исходной строке, оставляя множество повторений одиночных нот одинаковыми, а не выбирая для, вероятно, многих 1, которые будут добавлены из-за слишком большого сжатия. jbaylina, не могли бы вы куда-нибудь вставить результат своего алгоритма на примере str? - person גלעד ברקן; 10.06.2013
comment
@groovy: Не просите нас начать делать за вас черную работу, просто скомпилируйте его с помощью gcc и получите сжатие самостоятельно. Если вы хотите продолжать интересоваться этой проблемой, вы можете попробовать перейти от O (n ^ 2) к O (n.log n) или линейному времени через постфиксные деревья или что-то в этом роде. Но faiac, O (n ^ 2) достаточно хорош. - person Boris Stitnicky; 10.06.2013
comment
Я немного подумал об оптимизации решения и совершенно уверен, что его можно улучшить. Как указал @BorisStitnicky, дерево суффиксов можно использовать для поиска повторяющихся последовательностей со стоимостью O (n * log (n)). Имея эту информацию, вы можете улучшить алгоритм, который я предложил. - person jbaylina; 10.06.2013
comment
@groovy Скопируйте код C в файл с именем dna.c и из командной строки машины *nix просто скомпилируйте его: gcc dna.c -o dna Измените разрешения исполняемого файла: chmod 755 dna и выполните с первым параметром строка, которую вы хотите сжать: ./dna дададагг - person jbaylina; 10.06.2013
comment
@jbaylina, алгоритм работает хорошо ... я понял, как работает алгоритм ... можете ли вы объяснить работу этого алгоритма, взяв несколько примеров последовательностей - person user2442890; 15.06.2013

Забудь об Уконнене. Динамическое программирование это. С 3-мерной таблицей:

  1. положение последовательности
  2. размер подпоследовательности
  3. количество сегментов

ТЕРМИНОЛОГИЯ: Например, при наличии a = "aaagctgctagag" координата позиции последовательности будет иметь значение от 1 до 13. В позиции последовательности 3 (буква 'g') при размере подпоследовательности 4 подпоследовательность будет "gctg". Понял? А что касается количества сегментов, то выражение a как "aaagctgctagag1" состоит из 1 сегмента (сама последовательность). Выражая его как «a3gct2ag2», он состоит из 3 сегментов. "aaagctgct1ag2" состоит из 2 сегментов. "a2a1ctg2ag2" будет состоять из 4 сегментов. Понял? Теперь, с этим, вы начинаете заполнять трехмерный массив 13 x 13 x 13, поэтому ваше время и сложность памяти, кажется, составляют около n ** 3 для этого. Вы уверены, что справитесь с последовательностями в миллион п.н.? Я думаю, что жадный подход был бы лучше, потому что большие последовательности ДНК вряд ли будут точно повторяться. И я бы посоветовал вам расширить свое задание до приблизительных совпадений, и вы можете опубликовать его прямо в журнале.

В любом случае, вы начнете заполнять таблицу сжатия подпоследовательности, начиная с некоторой позиции (размерность 1) с длиной, равной координате размерности 2, имеющей не более 3 сегментов размерности. Таким образом, вы сначала заполняете первую строку, представляющую сжатие подпоследовательностей длины 1, состоящих не более чем из 1 сегмента:

a        a        a        g        c        t        g        c        t        a        g        a        g
1(a1)    1(a1)    1(a1)    1(g1)    1(c1)    1(t1)    1(g1)    1(c1)    1(t1)    1(a1)    1(g1)    1(a1)    1(g1)

Число — это стоимость символа (всегда 1 для этих тривиальных 1-символьных последовательностей; число 1 не учитывается в стоимости символа), а в скобках у вас есть сжатие (также тривиальное для этого простого случая). Второй ряд будет по-прежнему прост:

2(a2)    2(a2)    2(ag1)   2(gc1)   2(ct1)   2(tg1)   2(gc1)   2(ct1)   2(ta1)   2(ag1)   2(ga1)    2(ag1)

Существует только 1 способ разложить 2-символьную последовательность на 2 подпоследовательности — 1 символ + 1 символ. Если они идентичны, результат похож на a + a = a2. Если они разные, например a + g, то, поскольку допустимы только последовательности из 1 сегмента, результат не может быть a1g1, а должен быть ag1. Третий ряд будет, наконец, более интересным:

2(a3)    2(aag1)  3(agc1)  3(gct1)  3(ctg1)  3(tgc1)  3(gct1)  3(cta1)  3(tag1)  3(aga1)  3(gag1)

Здесь вы всегда можете выбрать один из двух способов составления сжатой строки. Например, aag можно составить как aa + g, так и a + ag. Но опять же, у нас не может быть 2 сегментов, как в aa1g1 или a1ag1, поэтому мы должны довольствоваться aag1, если только оба компонента не состоят из одного и того же символа, как в aa + a => a3, со стоимостью символа 2. Мы можем продолжить на 4-м линия:

4(aaag1) 4(aagc1) 4(agct1) 4(gctg1) 4(ctgc1) 4(tgct1) 4(gcta1) 4(ctag1) 4(taga1) 3(ag2)

Здесь на первой позиции мы не можем использовать a3g1, потому что на этом слое разрешен только 1 сегмент. Но в последней позиции сжатие до стоимости символа 3 достигается за ag1 + ag1 = ag2. Таким образом, можно заполнить всю таблицу первого уровня вплоть до одной подпоследовательности из 13 символов, и каждая подпоследовательность будет иметь свою оптимальную стоимость символа и ее сжатие при ограничении первого уровня не более 1 связанного с ней сегмента. .

Затем вы переходите на 2-й уровень, где разрешено 2 сегмента... И снова снизу вверх вы определяете оптимальную стоимость и сжатие каждой координаты таблицы при ограничении количества сегментов данного уровня, сравнивая все возможные способы составьте подпоследовательность, используя уже вычисленные позиции, пока вы полностью не заполните таблицу и, таким образом, не вычислите глобальный оптимум. Есть некоторые детали, которые нужно решить, но извините, я не буду кодировать это для вас.

person Boris Stitnicky    schedule 02.06.2013
comment
Это будет около O (n ^ 4), поскольку вычисление каждого элемента содержит внутренний цикл. - person Egor Skriptunoff; 03.06.2013
comment
@EgorSkriptunoff: Чего, возможно, можно было бы избежать. Возможно, вы даже можете сделать это в n^2, и, если вы не брезгуете, вы просто проверяете подпоследовательности до определенной длины (допустим, 4000 п.н.) и делаете сохранение в экспоненте. Но я уже потратил слишком много времени на размышления над этим вопросом. Вот почему я упомянул, что есть некоторые детали, которые нужно решить. Эта проблема на самом деле имеет подозрительно линейную структуру, и я подозреваю, что существует гораздо лучшее решение, чем это O (n ^ 3) (или, может быть, n ^ 4, как вы говорите). Вопрос только в том, чтобы найти тот, который был бы достаточно хорош. Экспоненциальное время не годится. - person Boris Stitnicky; 03.06.2013
comment
@EgorSkriptunoff: Но да, я должен вам дать это, кажется, n ** 4, так как придется сравнивать несколько композиций, или, возможно, даже более высокий показатель, до 5, мне действительно очень лень считать. - person Boris Stitnicky; 03.06.2013

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

import Data.Map (fromList, insert, size, (!))

compress s = (foldl f (fromList [(0,([],0)),(1,([s!!0],1))]) [1..n - 1]) ! n  
 where
  n = length s
  f b i = insert (size b) bestCandidate b where
    add (sequence, sLength) (sequence', sLength') = 
      (sequence ++ sequence', sLength + sLength')
    j' = [1..min 100 i]
    bestCandidate = foldr combCandidates (b!i `add` ([s!!i,'1'],2)) j'
    combCandidates j candidate' = 
      let nextCandidate' = comb 2 (b!(i - j + 1) 
                       `add` ((take j . drop (i - j + 1) $ s) ++ "1", j + 1))
      in if snd nextCandidate' <= snd candidate' 
            then nextCandidate' 
            else candidate' where
        comb r candidate
          | r > uBound                         = candidate
          | not (strcmp r True)                = candidate
          | snd nextCandidate <= snd candidate = comb (r + 1) nextCandidate
          | otherwise                          = comb (r + 1) candidate
         where 
           uBound = div (i + 1) j
           prev = b!(i - r * j + 1)
           nextCandidate = prev `add` 
             ((take j . drop (i - j + 1) $ s) ++ show r, j + length (show r))
           strcmp 1   _    = True
           strcmp num bool 
             | (take j . drop (i - num * j + 1) $ s) 
                == (take j . drop (i - (num - 1) * j + 1) $ s) = 
                  strcmp (num - 1) True
             | otherwise = False

Вывод:

*Main> compress "aaagctgctagag"
("a3gct2ag2",9)

*Main> compress "aaabbbaaabbbaaabbbaaabbb"
("aaabbb4",7)


Попытка линейного времени:

import Data.List (sortBy)

group' xxs sAccum (chr, count)
  | null xxs = if null chr 
                  then singles
                  else if count <= 2 
                          then reverse sAccum ++ multiples ++ "1"
                  else singles ++ if null chr then [] else chr ++ show count
  | [x] == chr = group' xs sAccum (chr,count + 1)
  | otherwise = if null chr 
                   then group' xs (sAccum) ([x],1) 
                   else if count <= 2 
                           then group' xs (multiples ++ sAccum) ([x],1)
                   else singles 
                        ++ chr ++ show count ++ group' xs [] ([x],1)
 where x:xs = xxs
       singles = reverse sAccum ++ (if null sAccum then [] else "1")
       multiples = concat (replicate count chr)

sequences ws strIndex maxSeqLen = repeated' where
  half = if null . drop (2 * maxSeqLen - 1) $ ws 
            then div (length ws) 2 else maxSeqLen
  repeated' = let (sequence,(sequenceStart, sequenceEnd'),notSinglesFlag) = repeated
              in (sequence,(sequenceStart, sequenceEnd'))
  repeated = foldr divide ([],(strIndex,strIndex),False) [1..half]
  equalChunksOf t a = takeWhile(==t) . map (take a) . iterate (drop a)
  divide chunkSize b@(sequence,(sequenceStart, sequenceEnd'),notSinglesFlag) = 
    let t = take (2*chunkSize) ws
        t' = take chunkSize t
    in if t' == drop chunkSize t
          then let ts = equalChunksOf t' chunkSize ws
                   lenTs = length ts
                   sequenceEnd = strIndex + lenTs * chunkSize
                   newEnd = if sequenceEnd > sequenceEnd' 
                            then sequenceEnd else sequenceEnd'
               in if chunkSize > 1 
                     then if length (group' (concat (replicate lenTs t')) [] ([],0)) > length (t' ++ show lenTs)
                             then (((strIndex,sequenceEnd,chunkSize,lenTs),t'):sequence, (sequenceStart,newEnd),True)
                             else b
                     else if notSinglesFlag
                             then b
                             else (((strIndex,sequenceEnd,chunkSize,lenTs),t'):sequence, (sequenceStart,newEnd),False)
          else b

addOne a b
  | null (fst b) = a
  | null (fst a) = b
  | otherwise = 
      let (((start,end,patLen,lenS),sequence):rest,(sStart,sEnd)) = a 
          (((start',end',patLen',lenS'),sequence'):rest',(sStart',sEnd')) = b
      in if sStart' < sEnd && sEnd < sEnd'
            then let c = ((start,end,patLen,lenS),sequence):rest
                     d = ((start',end',patLen',lenS'),sequence'):rest'
                 in (c ++ d, (sStart, sEnd'))
            else a

segment xs baseIndex maxSeqLen = segment' xs baseIndex baseIndex where
  segment' zzs@(z:zs) strIndex farthest
    | null zs                              = initial
    | strIndex >= farthest && strIndex > 0 = ([],(0,0))
    | otherwise                            = addOne initial next
   where
     next@(s',(start',end')) = segment' zs (strIndex + 1) farthest'
     farthest' | null s = farthest
               | otherwise = if start /= end && end > farthest then end else farthest
     initial@(s,(start,end)) = sequences zzs strIndex maxSeqLen

areExclusive ((a,b,_,_),_) ((a',b',_,_),_) = (a' >= b) || (b' <= a)

combs []     r = [r]
combs (x:xs) r
  | null r    = combs xs (x:r) ++ if null xs then [] else combs xs r
  | otherwise = if areExclusive (head r) x
                   then combs xs (x:r) ++ combs xs r
                        else if l' > lowerBound
                                then combs xs (x: reduced : drop 1 r) ++ combs xs r
                                else combs xs r
 where lowerBound = l + 2 * patLen
       ((l,u,patLen,lenS),s) = head r
       ((l',u',patLen',lenS'),s') = x
       reduce = takeWhile (>=l') . iterate (\x -> x - patLen) $ u
       lenReduced = length reduce
       reduced = ((l,u - lenReduced * patLen,patLen,lenS - lenReduced),s)

buildString origStr sequences = buildString' origStr sequences 0 (0,"",0)
   where
    buildString' origStr sequences index accum@(lenC,cStr,lenOrig)
      | null sequences = accum
      | l /= index     = 
          buildString' (drop l' origStr) sequences l (lenC + l' + 1, cStr ++ take l' origStr ++ "1", lenOrig + l')
      | otherwise      = 
          buildString' (drop u' origStr) rest u (lenC + length s', cStr ++ s', lenOrig + u')
     where
       l' = l - index
       u' = u - l  
       s' = s ++ show lenS       
       (((l,u,patLen,lenS),s):rest) = sequences

compress []         _         accum = reverse accum ++ (if null accum then [] else "1")
compress zzs@(z:zs) maxSeqLen accum
  | null (fst segment')                      = compress zs maxSeqLen (z:accum)
  | (start,end) == (0,2) && not (null accum) = compress zs maxSeqLen (z:accum)
  | otherwise                                =
      reverse accum ++ (if null accum || takeWhile' compressedStr 0 /= 0 then [] else "1")
      ++ compressedStr
      ++ compress (drop lengthOriginal zzs) maxSeqLen []
 where segment'@(s,(start,end)) = segment zzs 0 maxSeqLen
       combinations = combs (fst $ segment') []
       takeWhile' xxs count
         | null xxs                                             = 0
         | x == '1' && null (reads (take 1 xs)::[(Int,String)]) = count 
         | not (null (reads [x]::[(Int,String)]))               = 0
         | otherwise                                            = takeWhile' xs (count + 1) 
        where x:xs = xxs
       f (lenC,cStr,lenOrig) (lenC',cStr',lenOrig') = 
         let g = compare ((fromIntegral lenC + if not (null accum) && takeWhile' cStr 0 == 0 then 1 else 0) / fromIntegral lenOrig) 
                         ((fromIntegral lenC' + if not (null accum) && takeWhile' cStr' 0 == 0 then 1 else 0) / fromIntegral lenOrig')
         in if g == EQ 
               then compare (takeWhile' cStr' 0) (takeWhile' cStr 0)
               else g
       (lenCompressed,compressedStr,lengthOriginal) = 
         head $ sortBy f (map (buildString (take end zzs)) (map reverse combinations))

Вывод:

*Main> compress "aaaaaaaaabbbbbbbbbaaaaaaaaabbbbbbbbb" 100 []
"a9b9a9b9"

*Main> compress "aaabbbaaabbbaaabbbaaabbb" 100 []
"aaabbb4"
person Community    schedule 02.06.2013
comment
А как насчет aaabbbbaaabbbbaaabbbbaaabbb, где правильный ответ — aaabbb4? - person Boris Stitnicky; 02.06.2013
comment
@BorisStitnicky Спасибо, что заметили это. Я изменил divided = foldr divide [] [div lenOne 2, div lenOne 2 - 1..2] на divided = foldr divide [] [2..div lenOne 2], и теперь отображается aaabbb4. Но может ли это изменение вызвать другие неточности? - person גלעד ברקן; 02.06.2013
comment
@BorisStitnicky .. это было сложнее, чем я думал .. я еще раз попробовал - person גלעד ברקן; 02.06.2013
comment
Вы не должны говорить это мне. Я пытался написать это на Ruby, жадно, но вычислительная сложность без динамического программирования просто дерьмовая. Поскольку проблема не является заданием из учебника, медленные решения не имеют значения, поэтому я сдался. - person Boris Stitnicky; 02.06.2013
comment
@BorisStitnicky Я сделал небольшую модификацию. Теперь он выводит в файл сжатую версию для случайной строки из 10 ^ 6 символов примерно за одну минуту. Должно ли это быть намного быстрее? - person גלעד ברקן; 03.06.2013
comment
1 минута времени выполнения для строки из 10 ^ 6 символов будет означать почти линейное время выполнения. Таким образом, я считаю, что ваш код использует жадный подход, что является отклонением от постановки задачи ОП, но, возможно, долгожданным и удовлетворительным отклонением. (Другими словами, вы решили более простую задачу, которой в действительности может быть достаточно.) Однако позвольте мне предупредить вас, что случайная строка не является хорошим представителем типичных последовательностей ДНК, где короткие и повторений на большие расстояния предостаточно. Количество усилий по кодированию, которые вы приложили к этому вопросу, похвально, +1 от меня. - person Boris Stitnicky; 03.06.2013