Как удалить гласные из введенного текста с большей эффективностью? - PullRequest
2 голосов
/ 22 сентября 2019

По существу, школьное задание.
Я решил его, но он слишком медленный для них (сайт, который проверяет мой код).
Имейте в виду, что я могу использовать только getchar и putchar длялюбые текстовые задания, так как мы только начали изучать C.
Это мой первый пост, я прошу прощения, если допустил ошибки в этом вопросе.

#include <stdio.h>    
int main(){
    char ch = getchar();

    while(ch != '\n'){

        ch = getchar();

        switch(ch){
        case 'A':
        case 'E':
        case 'I':
        case 'O':
        case 'U':
        case 'a':
        case 'e':
        case 'i':
        case 'o':
        case 'u':continue;
        default: putchar(ch);
        }
    }
}

Ответы [ 4 ]

4 голосов
/ 22 сентября 2019

Единственное, о чем я могу думать быстрее (используя только getchar и putchar):

int main() {
    int ch = getchar();
    while (ch != '\n') {

        if (ch>64)
        {
               if(ch <118)
               {
                    if (((4575140898685201 >> (ch-65)) & 1)==0){
                            putchar(ch);}
               }
        }
        ch = getchar();
    }
}

Попробуйте это с

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

выводит

BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz

Я не знаю, является ли это целью упражнения или нет, но это уменьшает проверку только до трехexclusive if.

Но если вы используете компилятор CLANG в оптимизации O3, компилятор сделает именно это для вас: GodBolt
Поэтому, когда вы говорите, что он слишком медленный для нихmsgstr "Вы имеете в виду только глядя на код или один раз скомпилированный?Потому что, если он один раз скомпилирован, он не должен быть «слишком медленным».

Пояснение
возможно ((4575140898685201 >> (ch-65)) & 1) немного неясно.
4575140898685201 - 64-битное целое число ивыглядит так в памяти 00010000010000010001000100000000000100000100000100010001

Как вы можете видеть, '1' расположены в 0, 4, 8, 14, 20, 32, 36, 40, 46, 52. Если вы добавите 65 к этой серииВы получите 65, 69, 73, 79, 85, 97, 101, 105, 111, 117, который является ASCII для A, E, I, O, U, a, e, i, o, u.

Оператор i >> j сдвигает j битов вправо от i, так что это будет гласный, если j-й бит равен 1, следовательно, & 1 или & 00000000000000000000000000000000000000000000000000000001

Если вы поняли этоответьте, и вам понравилось как упражнение, вы можете попытаться вычислить, какое число вставить, чтобы избавиться от Y и y и поместить его в комментарий (заголовки GodBolt с Y и y не помогут вам,но сгенерированный код интересно посмотреть особенно на ror eax, и он должен заканчиваться на UL, потому что он может быть представлен только как 64-битныйцелое число)!

2 голосов
/ 22 сентября 2019

Очень странно, что такой простой код считается слишком "медленным", особенно если обязательны getchar и putchar.

Если я сделаю что-то подобное с вашей программой (создав файл без \n и перенаправив его в вашу программу):

echo -n "abc" > input.txt
./program < input.txt

Я получу бесконечный цикл.Может быть, ваш сайт сообщает о тайм-ауте, а не медлительности?Вам нужно использовать EOF вместо \n с getchar.

Вот пример, который вы можете попробовать.Я использовал strchr, но корпус переключателя в порядке.

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

int main(){
    int ch;

    while((ch = getchar()) != EOF)
    {
        if (ch == '\n')
            break ;
        else if (!strchr("aeiouAEIOU", ch))
            putchar(ch);

    }
    return (0);
}

1 голос
/ 23 сентября 2019

Нет ничего "медленного" в том, как вы его закодировали.Код "неправильный" , но не "медленный" .getchar() читает из буфера BUFSIZ (8192 байта в LInux, 512 байтов в windows).Ваш код будет оптимизирован так же хорошо или лучше, чем любой более хитрый подход.

Ваши основные проблемы, как отмечено в комментариях, ch должен быть типа int вместо char.Возвращается getchar(), например, EOF типа int.Затем вы дважды звоните getchar(), прежде чем вводить выписку switch().Чтобы решить проблему с getchar(), переместите второй getchar() ниже вашего switch(), чтобы он вызывался после выполнения switch().Вам нужно будет изменить регистр по умолчанию в вашем switch() с continue на break, чтобы не пропускать getchar() после switch().Наконец, удалите тест для '\n' (если это не ограничение присваивания).Вы хотите, чтобы '\n' выводился, чтобы сохранить «строки» ввода вместо того, чтобы выполнять его полностью.

Что такое «хорошо» и «быстро» о вашем коде?Вы подходите с оператором switch() с падением регистра для всех гласных, что очень-очень хорошо оптимизирует, создавая молниеносный код.Убедитесь, что вы указали уровень оптимизации -O3 (gcc / clang) или /Ox (VS, cl.exe).Это скажет компилятору полностью оптимизировать ваш код для скорости.(есть дополнительные оптимизации, но здесь это не важно).Так что ваш подход был действительно, действительно хорош!(вы потерпели неудачу в реализации )

Исправляя реализацию ваш подход может быть следующим:

#include <stdio.h>

int main (void) {

    int ch = getchar();     /* read character */

    while (ch != EOF) {     /* check for EOF */

        switch (ch) {       /* switch w/fallthrough to default */
            case 'A':
            case 'E':
            case 'I':
            case 'O':
            case 'U':
            case 'a':
            case 'e':
            case 'i':
            case 'o':
            case 'u': break;        /* break, not continue */
            default: putchar(ch);   /* output char if not vowel */
        }
        ch = getchar();     /* read next char from file */
    }
}

(и нет, [Yy] являютсяНЕ гласные)

Пример входного файла

$ cat dat/fleas.txt
My dog has fleas
My cat has none
Lucky cat

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

$ ./bin/getchar_skip-vowels < dat/fleas.txt
My dg hs fls
My ct hs nn
Lcky ct

Оставить "магическое число" программирование для вашего компилятора для обеспечения его оптимизации, что и делают компиляторыЕсли вы сбросите приведенный выше код в сборку, чтобы посмотреть действительные инструкции по сборке с оптимизацией -O3 (или -Ofast) под gcc, вы увидите, что компилятор оптимизировал сборку для проверки на магические числа, например (с * 1068)* для синтаксиса Intel) соответствующая часть кода сокращена до:

    mov     rdi, QWORD PTR stdin[rip]
    push    rbx
    movabs  rbx, 4575140898685201
    call    _IO_getc
    cmp     eax, -1
    je      .L8

(фактически это практически весь код, переход к .L8 просто является процедурой выхода на EOFи сравнение и циклы не более 3 mov и cmp и скачок)

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

0 голосов
/ 23 сентября 2019

Единственный способ сделать это быстрее - с помощью таблицы поиска.Главное, что делает это не самым быстрым из возможных, состоит в том, что код превращается в кучу сравнений, что-то вроде:

if(ch == 'A') goto isvowel;
else if(ch == 'E') goto isvowel;
else if(ch == 'I') goto isvowel;
.
.
.
isvowel: putchar(ch);

, но это настолько несущественно, что в реальной жизни кого это волнует?

Но способ сделать это быстрее - использовать таблицу:

int isvowel[] = {1,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0};

Теперь вам просто нужно поменять букву на число от 0 до 26. Во-первых, если вы посмотрите на внутреннюю структуру ASCIIВы видите, что строчная буква отличается от заглавной буквы только одной буквой.Таким образом, вы всегда можете сделать что-то в верхнем регистре всегда так:

ch & 0xFFDF;

Чтобы преобразовать в число, вы просто вычтите значение первой буквы:

(ch & 0xFFDF) - 'A'.

Затем, чтобы увидеть, еслиэто значение, вы просто тестируете массив в этой позиции: 1:

if(isvowel[(ch & 0xFFDF) - 'A')

Обратите внимание, что если ch не символ, наша математика не будет работать, поэтому нам нужноОстерегайтесь этого.

Таким образом, полный код становится примерно таким:

#include <stdio.h>    
int main(){
    char ch = 0;

    int isvowel[] = {1,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0};
    while(ch != '\n'){

        ch = getchar();

        unsigned int ord = (ch & 0xFFDF) - 'A';
        if( ord < 26 && isvowel[ord])
             putchar(ch);
    }
    putchar('\n');
}

Причина, по которой это (очень немного) быстрее (но не быстрее), заключается в том, что вы делаете только два сравненияи не десять.Если вы посмотрите на старую версию, внутренне она выглядит примерно так:

compare to 'A'
compare to 'E'
compare to 'I'
compare to 'O'
compare to 'U'
compare to 'a'
compare to 'e'
compare to 'i'
compare to 'o'
compare to 'u'

С таблицей она становится

and with 0xFFDF
subtract 'A'
compare with 26
dereference table
compare

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

Обратите внимание, что именно так стандартные методы библиотеки, такие как isalpha () обычно реализуются.Для isalpha() существует таблица с 256 записями, в которой каждое буквенное значение в ASCII установлено в значение true, а каждое значение не установлено в значение false.Это делает их чрезвычайно быстрыми, более быстрыми, чем моя реализация здесь, поскольку им не нужно преобразовывать их в верхний регистр или проверять на наличие границ.

Но я хочу подчеркнуть: Вам не следует беспокоиться о такихнесущественные различия. Это то, что вы делаете, проведя недели с профилировщиком в производственном коде, а не в студенческих заданиях!.

Приложение:

Если вы используете gcc, вы можете сгенерировать код ASM с помощью gcc -S.Это полезно для просмотра необработанного кода, до которого компилируется программа.Переключатель в вашем оригинале выглядит следующим образом в ASM:

    movsbl  -5(%rbp), %eax
    movl    %eax, %edx
    subl    $65, %edx
    movl    %eax, -12(%rbp)         ## 4-byte Spill
    movl    %edx, -16(%rbp)         ## 4-byte Spill
    je  LBB0_3
    jmp LBB0_7
LBB0_7:                                 ##   in Loop: Header=BB0_1 Depth=1
    movl    -12(%rbp), %eax         ## 4-byte Reload
    subl    $69, %eax
    movl    %eax, -20(%rbp)         ## 4-byte Spill
    je  LBB0_3
    jmp LBB0_8
LBB0_8:                                 ##   in Loop: Header=BB0_1 Depth=1
    movl    -12(%rbp), %eax         ## 4-byte Reload
    subl    $73, %eax
    movl    %eax, -24(%rbp)         ## 4-byte Spill
    je  LBB0_3
    jmp LBB0_9
LBB0_9:                                 ##   in Loop: Header=BB0_1 Depth=1
    movl    -12(%rbp), %eax         ## 4-byte Reload
    subl    $79, %eax
    movl    %eax, -28(%rbp)         ## 4-byte Spill
    je  LBB0_3
    jmp LBB0_10
LBB0_10:                                ##   in Loop: Header=BB0_1 Depth=1
    movl    -12(%rbp), %eax         ## 4-byte Reload
    subl    $85, %eax
    movl    %eax, -32(%rbp)         ## 4-byte Spill
    je  LBB0_3
    jmp LBB0_11
LBB0_11:                                ##   in Loop: Header=BB0_1 Depth=1
    movl    -12(%rbp), %eax         ## 4-byte Reload
    subl    $97, %eax
    movl    %eax, -36(%rbp)         ## 4-byte Spill
    je  LBB0_3
    jmp LBB0_12
LBB0_12:                                ##   in Loop: Header=BB0_1 Depth=1
    movl    -12(%rbp), %eax         ## 4-byte Reload
    subl    $101, %eax
    movl    %eax, -40(%rbp)         ## 4-byte Spill
    je  LBB0_3
    jmp LBB0_13
LBB0_13:                                ##   in Loop: Header=BB0_1 Depth=1
    movl    -12(%rbp), %eax         ## 4-byte Reload
    subl    $105, %eax
    movl    %eax, -44(%rbp)         ## 4-byte Spill
    je  LBB0_3
    jmp LBB0_14
LBB0_14:                                ##   in Loop: Header=BB0_1 Depth=1
    movl    -12(%rbp), %eax         ## 4-byte Reload
    subl    $111, %eax
    movl    %eax, -48(%rbp)         ## 4-byte Spill
    je  LBB0_3
    jmp LBB0_15
LBB0_15:                                ##   in Loop: Header=BB0_1 Depth=1
    movl    -12(%rbp), %eax         ## 4-byte Reload
    subl    $117, %eax
    movl    %eax, -52(%rbp)         ## 4-byte Spill
    jne LBB0_4
    jmp LBB0_3
LBB0_3:                                 ##   in Loop: Header=BB0_1 Depth=1
    jmp LBB0_1
LBB0_4:                                 ##   in Loop: Header=BB0_1 Depth=1

С таблицей это выглядит так:

    movsbl  -117(%rbp), %eax
    andl    $65503, %eax            ## imm = 0xFFDF
    subl    $65, %eax
    movl    %eax, -124(%rbp)
    cmpl    $26, -124(%rbp)
    jae LBB0_5
## %bb.3:                               ##   in Loop: Header=BB0_1 Depth=1
    movl    -124(%rbp), %eax
    movl    %eax, %ecx
    cmpl    $0, -112(%rbp,%rcx,4)
    je  LBB0_5
## %bb.4:                               ##   in Loop: Header=BB0_1 Depth=1


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