Ответ C: Высокий контроль потока / Замените только первый случай * звездочкой * и удалите каждый дубликат - PullRequest
0 голосов
/ 11 июня 2018

Вот еще один тизер мозга У меня проблемы с.

Вопрос довольно прост для понимания:

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

, т. Е .: A[N]= A B C e A C f B;После вызова функции replace (A) она изменится на: A[N]= * * * e f;

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

#define N 8

void replace(char *A)
{
    int i=0,j=0,flag=0;

    while(i<N){
        flag=0;
        j=0;
        while(flag==0 && j<N){     
            if(A[i]!=A[j]){       //if the values are different
                flag=1;               //i quit the loop and the flag is
                // set to 'true'
            } 

            j++;                 //if i'm here the values are equal, i can keep going
            // till i reach the end or two different values.
        }   
        if(flag==1){           //if i quitted the loop -so the flag is 1-
            A[i]='*';             //i change the value in an *
        };

        i++;                //i can keep going. 
    }   
}

Вывод, который я получаю: ********. Я думаю, это потому, что я сравниваю каждую букву с одной и той же. Как она должна быть построена?

Ответы [ 3 ]

0 голосов
/ 12 июня 2018

Предполагая, что вы запрашиваете подсказки дизайна,

и

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

Я бы предложил описать желаемое поведение следующим образом:

for each character in the array
    test if a duplicate exists in the rest of the array;
    if so,
        remove all duplicates
        and replace the first occurence with '*'

Однако не ясно, что это значит«Удалить дубликаты».Если мы считаем, что входные данные являются строкой , то есть (потенциально) цепочкой символов переменной длины, заканчивающейся NUL, то удаление символа из строки эквивалентно копированию хвоста строки над предыдущими местами вдольс завершающим байтом NUL.OTOH, если нам говорят, что вход является массивом , то есть структурой фиксированной длины, мы можем удалить символы, заменив их, например, пробелами, или скопировав их в начало массива иперезаписывает освобожденные хвостовые позиции пробелом.Мы также можем сохранить значение «фактической» длины массива и уменьшить его на символ «удаление».Тогда нам нужно будет вернуть в результате как измененный массив, так и его новую «фактическую длину».

Увы, ваше описание проблемы не согласуется ни с одной из приведенных выше интерпретаций.
Во-первых, вы используетенотация A[N], которая обозначает N -й элемент (считая с нуля!) массива A на языке C, не N -элемент массива.
Second,вы представляете «одинаковые» A[N], которые имеют разную длину (8 символов до изменения и 5 символов после него).
Кроме того, вы представляете строки с пробелами между буквами - не совсем понятно, являются ли они действительной частьюданные (и как они должны обрабатываться?) или просто трюк представления.
В-третьих, вы не показываете C-подобные разделители строк, что предполагает, что это обычный массив символов, а не строка ...
И в-четвертых, вы не определяете фактический способ обработки «сокращающейся длины» массива.

В следующем коде я предполагаю, что длина массива передается явно как параметра к функции, и функция возвращает новый размер сокращенных данных.

int replace(char *A, int N)
{
    int first, next; // positions of 1st & any next occurence
    for(first = 0; first < N; ++first)
    {
        for(next = first+1; next < N; ++next) // seek a duplicate
        {
            if(A[next] == A[first]) // found?
            {
                int del = next;    // position to be removed
                while(++next < N)  // scan the rest of A[]
                {
                    if(A[next] != A[first]) // not a duplic.
                        A[del++] = A[next]; // retain it
                }
                N = del;        // a new size
                A[first] = '*'; // replace the 1st occurence
            }
        }
    }
    return N;
}
0 голосов
/ 12 июня 2018

Подобные головоломки всегда должны быть хитрыми.И это делает.

Хитрость заключается в следующем: вывод этой функции должен содержать не более UCHAR_MAX символов.Это связано с тем, что для каждого отдельного входного символа имеется только один выходной символ, а для всех возможных символов * UCHAR_MAX.Кроме того, одно из этих значений представляет NUL, и поскольку ни один символ внутри строки не может быть NUL, мы можем сказать, что максимальная длина строки в выводе должна быть меньше UCHAR_MAX символов.

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

Теперь давайте представим, что сканируем входную строку, более или менее так, как предлагает@ Шверн в этом отличный ответ .Оставляя в стороне механику того, как мы на самом деле принимаем решение, мы можем сказать, что есть только два возможных действия для каждого персонажа:

  1. Если мы никогда не видели этого персонажа раньше, мы добавимэто к выводу

  2. Если мы видели этот символ раньше, мы изменим его экземпляр, который мы уже добавили к выводу, на *.

Для того, чтобы сделать обе эти вещи, нам нужно отслеживать

  1. Видели ли мы этот персонаж раньше, и

  2. Если мы видели это раньше, куда мы его поместили.

Очевидный способ сделать это - сохранить массив из UCHAR_MAX указателей ввыходной массив, один на каждый возможный отдельный символ.Изначально все эти указатели были бы равны NULL, что указывает на то, что мы никогда не видели персонажа раньше.Когда мы впервые видим символ и добавляем его в выходной массив, мы устанавливаем соответствующий указатель на текущую позицию вывода;позже это будет не только указывать на то, что мы видели символ раньше, но и сообщать нам, где поставить звезду.

Для этого потребуется массив UCHAR_MAX указателей, который обычно равен 2 КБ на64-битная машина.Но, как вы, возможно, уже поняли, существует только UCHAR_MAX возможных различных значений указателя, поэтому нам действительно нужен только один байт для каждого отдельного символа.Есть два очевидных способа сделать это отображение;в следующем коде я использую фактическое смещение в выходном массиве для каждого символа, который находится в выходном массиве, и UCHAR_MAX (что не может быть возможным смещением выходного символа) для символов, которые еще не были замечены.(Другое очевидное решение заключается в увеличении всего на 1, что позволяет использовать ноль в качестве маркера «никогда не был замечен».)

Итак:

int replace(char* A) {
  unsigned char offset[UCHAR_MAX];
  memset(offset, UCHAR_MAX, UCHAR_MAX); /* Set all offsets to UCHAR_MAX */
  int out = 0;     /* The position to add the next distinct character */
  char* scan;      /* The next character to read */
  for (scan = A; *scan; ++scan) {  /* For each input character */
    unsigned char ch = *scan;  /* chars might be negative; offsets must be positive */
    if (offset[ch] == UCHAR_MAX) {
      /* Never seen this character before */
      offset[ch] = out;       /* Record where it is */
      A[out++] = *scan;       /* Put it there and up the count */
    }
    else {
      /* We've already seen this character */
      A[offset[ch]] = '*';   /* Overwrite it with a star */
    }
  }
  /* NUL-terminate the output and return its length */
  A[out] = 0;
  return out;
}
0 голосов
/ 12 июня 2018

Ваш алгоритм в основном такой.

  1. Посмотрите на букву.
  2. Отметьте, если какая-либо следующая буква отличается.
  3. Если это так, замените исходную буквус *

Если мы напишем ваш код с петлями for, код станет более понятным.И если мы добавим несколько отладочных отпечатков, то увидим проблему.

void replace( char *A ) {
    for(int i = 0; i<N; i++) {
        int flag = 0;
        printf("i = %d, A[i] = %c\n", i, A[i]);

        for(int j = 0; flag==0 && j<N; j++) {
            printf("    j = %d, A[j] = %c\n", j, A[j]);
            if( A[i]!=A[j] ) {
                flag=1;
            } 
        }

        if( flag==1 ) {
            A[i]='*';
        }
    }
}

Запуск этого ...

$ ./test
i = 0, A[i] = A
    j = 0, A[j] = A
    j = 1, A[j] = B
i = 1, A[i] = B
    j = 0, A[j] = *
i = 2, A[i] = C
    j = 0, A[j] = *
i = 3, A[i] = e
    j = 0, A[j] = *
i = 4, A[i] = A
    j = 0, A[j] = *
i = 5, A[i] = C
    j = 0, A[j] = *
i = 6, A[i] = f
    j = 0, A[j] = *
i = 7, A[i] = B
    j = 0, A[j] = *
********

Первая проблема - вместо просмотра следующих букв, вывсегда заново сканируешь всю строку.Буква всегда видит себя.

Во-вторых, вы помечаете буквы, которые отличаются, A[i] != A[j].Вы ищете дубликаты, чтобы получить одинаковые буквы, A[i] == A[j].Если все символы не уникальны, всегда будет дубликат.

Когда мы исправим эти две проблемы, начнем внутренний цикл с j = i+ и отметим, если A[i] == A[j], мы получим лучший результат.

$ ./test
i = 0, A[i] = A
    j = 1, A[j] = B
    j = 2, A[j] = C
    j = 3, A[j] = e
    j = 4, A[j] = A
i = 1, A[i] = B
    j = 2, A[j] = C
    j = 3, A[j] = e
    j = 4, A[j] = A
    j = 5, A[j] = C
    j = 6, A[j] = f
    j = 7, A[j] = B
i = 2, A[i] = C
    j = 3, A[j] = e
    j = 4, A[j] = A
    j = 5, A[j] = C
i = 3, A[i] = e
    j = 4, A[j] = A
    j = 5, A[j] = C
    j = 6, A[j] = f
    j = 7, A[j] = B
i = 4, A[i] = A
    j = 5, A[j] = C
    j = 6, A[j] = f
    j = 7, A[j] = B
i = 5, A[i] = C
    j = 6, A[j] = f
    j = 7, A[j] = B
i = 6, A[i] = f
    j = 7, A[j] = B
i = 7, A[i] = B
***eACfB

Это не сработает.Даже если мы каждый раз заново сканируем всю строку, стараясь пропустить текущую букву A[i], алгоритм разрушителен.Последний А не увидит более ранний А, потому что они будут превращены в *.

Есть лучший способ.


Циклы в циклах, как правило, плохие новости, потому что они оченьнеэффективно очень быстро.Это решение O (N ^ 2), означающее, что, поскольку длина строки удваивается, она должна выполнять работу в 4 раза больше.

Вместо этого мы можем сделать это за два прохода.Сначала посчитайте количество каждого символа в строке.В нем всего 255 символов, поэтому мы можем хранить его всего в 255 байтах.(Обратите внимание, что если строка содержит более 255 дубликатов одного и того же символа, это будет переполнено. Чтобы быть в безопасности, вы просто должны хранить 0, 1 или 2, представляющие невидимые, уникальные и дубликаты. Но для простоты я просто увеличу.)

unsigned short counts[255] = {0};
for(size_t i = 0; i < size; i++) {
    size_t this_char = (size_t)A[i];
    counts[this_char]++;
}

Затем выполните второй проход во втором цикле, чтобы выполнить преобразование на основе счетчиков.

for(size_t i = 0; i < size; i++ ) {
    size_t this_char = (size_t)A[i];
    if( counts[this_char] > 1 ) {
        A[i] = '*';
    }
}

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

Это также более эффективно.Нам нужно только дважды просмотреть строку, O (2N) намного лучше, чем O (N ^ 2).И мы используем только небольшой и фиксированный объем стековой памяти.

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

***e**f*

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

Мы можем написать на месте.Это потребует отслеживания двух позиций в строке одновременно, где мы читаем и где пишем.Это работает, потому что мы никогда не пишем больше, чем читаем, поэтому писатель всегда будет рядом с читателем или позади него.

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

void replace(char *A) {
    unsigned short counts[255] = {0};
    for( char *reader = A; *reader != '\0'; reader++ ) {
        size_t this_char = (size_t)*reader;
        counts[this_char]++;
    }

    for( char *reader = A; *reader != '\0'; reader++ ) {
        size_t this_char = (size_t)*reader;
        if( counts[this_char] > 1 ) {
            *reader = '*';
        }
    }
}

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

char *writer = A;
for( char *reader = A; *reader != '\0'; reader++ ) {
    size_t this_char = (size_t)*reader;
    if( counts[this_char] > 1 ) {
        *writer = '*';
        writer++;
    }
    else {
        *writer = *reader;
        writer++;
    }
}

Обратите внимание, что writer продвигается только тогда, когда он записывается.В приведенном выше коде он всегда записывается в, и обычно вы помещаете writer++ вне if / else.Но следующий шаг это меняет.

Следующий шаг - отследить, какие буквы уже были заменены, и заменять только в первый раз, когда мы его увидим.Это означает, что другой массив отслеживания.Также как counts у нас есть replaced.

unsigned short replaced[255] = {0};
char *writer = A;
for( char *reader = A; *reader != '\0'; reader++ ) {
    size_t this_char = (size_t)*reader;

    // It's a duplicate and it hasn't been replaced yet
    if( counts[this_char] > 1 && !replaced[this_char] ) {
        // Replace it. Note that we replaced it. Advance the writer position.
        *writer = '*';
        replaced[this_char] = 1;
        writer++;
    }
    // It's unique.
    else if( counts[this_char] == 1 ) {
        // Write it. Advance the writer position.
        *writer = *reader;
        writer++;
    }
}

Это дает нам ***efCfB, почти там!

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

*writer = '\0';

И мы здесь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...