21 Востаннє редагувалося wander (16.05.2022 00:10:44)

Re: Обробка текстів, символьні рядки

Lata написав:

Виходить, буква а - це багатосимвольна константа %)

Загалом - ні. Якби у нас стояла задача дати загальне визначення, то ми б могли сказати, що буква може займати >= 1 байт. А те, скільки байт займатиме одна буква залежатиме від кодування. Власне у сучасному світі в якому ми живемо (майже) так воно і є. Пан koala вже дещо згадав про UTF-8, зараз усі сучасні операційні системи в своїх "нутрощах" користуються саме UTF кодуваннями. Наприклад, Linux користується UTF-8, a Windows UTF-16. Тож раджу детально познайомитися з даними кодуваннями, дещо я вже писав тут на форумі тиць.

Отже, у двох словах в UTF-кодуванні кириличні букви займають 2 байти, а не звичний один. Відповідно, коли ви намагаєтеся "порівняти" дві букви отак:

word=='а'

, то отримуєте помилку, що а multi-character character constant. А одинарні лапки в С/С++ вказують на те, що символ однобайтний, тобто має тип char, проте для букви а, як ми вже вияснили тре як мінімум два байти (char'и). Тобто символ а можна записати як:

char a[] = "a"; // << подвійні лапки

Чому так? Як ви вже, напевне, знаєте тип char займає 1 байт, а в 1 байт влізе не дуже багато даних, цифри, латинський алфавіт (включаючи капс), та ще деякі символи. Кирилиці місця не вистачить. В utf-кодуванні це вирішили банально, просто сказали, що, а давайте тоді все що не влізло в один байт, ми засунемо у два байти, а якщо і у два не влізе, тоді в 4 засунемо і т.д. Саме так буква "а" почала кодуватися, як "\xd0\xb0". Для більших деталей читайте моє посилання вище.

Тож, щоб вам вирішити ваше завдання, вам прийдеться розібратися в тому, як працює UTF-кодування. Ось, що вдалось мені накидати на С:

Прихований текст

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

#include <stdio.h>
#include <uchar.h>
#include <stdint.h>
#include <assert.h>

char32_t utf8_to_utf32(int const(*symbol)[2]) {
    char32_t cp = (uint8_t)(0xFF & *symbol[0]);
    if(0 != *symbol[1])
        cp = ((cp << 6) & 0x7FF) + (*symbol[1] & 0x3F);
    return cp;
}

char32_t next(char** const utf8_iter_begin, char* const utf8_iter_end) {
    assert(*utf8_iter_begin < utf8_iter_end);
    int first_byte = **utf8_iter_begin;
    int last_byte  = 0;
    switch ((first_byte & 0xF0) >> 4) {
        case 0xC: // control
        case 0xD:
           *utf8_iter_begin += 2;
            last_byte        = **utf8_iter_begin;
            break;
    }        
    int symbol[] = {first_byte, last_byte};
    return utf8_to_utf32(&symbol);
}

#define NEXT(begin, end) (next(&begin, end))

int main()
{
    char  str[] = "привітати з новим роком і побажати здати роботи та отримати бали";
    char  aty[] = "ати";        
    
    int   counter = 0;
    char* token   = strtok(str, " ");
    while (token != NULL) {
        char* pstr = token;
        while (*pstr) {
            char32_t c = NEXT(pstr, pstr + 2);
            if (0 == memcmp(pstr, aty, sizeof aty - 1)) {
                counter++;
                printf("%s\n", token);
            }
        }
        token = strtok(NULL, " ");
    }
    printf("Кількість слів, що закінчуються на 'ати': %d", counter);
}

https://rextester.com/YKLF82312

Я не став використовувати так звані широкі символи, вони ж wchar_t, тому що цей тип є доволі слизьким і його розмір сильно залежить від платформи, тож я утримався від його використання.

Скажу ще пару слів про Windows, бо там все трохи цікавіше. По-перше, використання UTF-8 в віндовс — ідея спірна, тому що всередині все одно буде UTF-16 і перекодування.

Виходить, що спосіб з мінімальними присіданнями полягає у двох ключових моментах:
1) Використовувати setmode з актуального msvc runtime (тобто використовувати, вважай, тільки компілятор майкрософт)
2) Використовувати нативне кодування — UTF-16.
При дотриманні умов і введення і виведення працюють однаково добре з мінімальними присіданнями:

_setmode(_fileno(stdout), _O_U16TEXT);
_setmode(_fileno(stdin) , _O_U16TEXT);

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

Щодо локальних однобайтних кодувань вінди і чому і їх НЕ варто використовувати.
За замовчуванням віндова консоль налаштована на однобайтове кодування DOS, яке відповідає поточній мові системи. Для української Windows - це кодування 866. Так існує два системних локальних кодування: одна для додатків в псевдо-DOS режимі, інша для всього іншого (1251). DOS-кодування для консолі залишена з міркувань сумісності. Тому runtime для консольного виведення пробує перекодувати те, що йому передають в 866. Це перекодування працює на основі налаштувань поточної локалі. За замовчуванням програма стартує в стандартній локалі мови С, яка підтримує тільки символи ASCII. Тому ви бачите порожній вивід (UTF-16 кодується використовуючи налаштування локалі в "cирої" ASCII, що призводить до втрати інформації, далі швидше за все процес не йде, тому що перекодування завершилося з помилкою).

Якщо ми додамо рядок setlocale(LC_ALL, ""), то ми змусимо змінити локаль відповідно до поточних установок системи. Для української Windows в Visual Studio воно вибере локаль: "Ukrainian_Ukraine.1251". Це можна побачити ось таким кодом:

char const * loc = setlocale(LC_ALL, "");
std::cout << loc << '\n';

І це "несподівано" полагодить (настільки, наскільки це можливо для cp866, букви і все ще не буде) вивід, але за цією легкою дією буде стояти дуже багато. По-перше, кодування виведення самої консолі все ще DOS 866. Завдяки деякій "магічній" внутрішній "силі", runtime вміє кодувати те, що передається на вивід з одного однобайтового кодування, в інше однобайтове. Тому під капотом цієї простої дії ми отримуємо ось що:

  • UTF-16 з програми користувача кодується в 1251 (використовуючи налаштування локалі),

  • Потім "магічна" сила всередині runtime кодує 1251 до 866,

  • Потім відбувається "коректний" вивід.

А ось з введенням все ще складніше, тому що "магічна сила" тут чомусь уже не працює. Вводимо ми в консолі все ще 866. Але поточна локаль встановлена в 1251, тому перетворення в UTF-16 виконується так, як ніби-то ми ввели рядок в 1251. В результаті ми отримуємо некоректний UTF-16 рядок.

Можна змінити кодування консолі на 1251 за допомогою SetConsoleCP і SetConsoleOutputCP. Це зазвичай радять дуже часто в інтернеті. Тоді вийде:

Прихований текст
int main()
{
    SetConsoleCP(1251); // ввід
    SetConsoleOutputCP(1251); // вивід
 
    setlocale(LC_ALL, ""); // ставить 1251
 
    std::cout << "Привіт, світе!" <<std::endl;
}
int main()
{
    SetConsoleCP(1251); // ввід
    SetConsoleOutputCP(1251); // вивід
 
    setlocale(LC_ALL, ""); // ставить 1251
 
    std::wstring wstr;
    std::getline(std::wcin, wstr); // 1251 -> UTF16
 
    std::wcout << wstr; // UTF16 -> 1251 -> 1251
}

Це створить ілюзію працездатності, якої, в принципі, достатньо більшості новачків на форумі. Але якщо ми таки пишемо інтернаціональний продукт, то цей спосіб тільки додає проблем. По-перше, тому що ми примусово виставили локаль прямо в коді, а по-друге, тому що саме по собі виставлення поточної локалі позбавляє можливості вводити текст декількома мовами одночасно. Саме тому явне використання в коді setlocale як з пустими скобками, так і з якимось конкретним ім'ям - не підходить для справжніх інтернаціональних додатків: наприклад, в такій конфігурації в французькій Windows працюватимуть тільки перекодування "французьке дос-кодування <-> юнікод UTF-16 "і ніякі інші варіанти.

P.S. - багато нюансів було спеціально спрощено, для розуміння початківцям, а також, щоб не роздувати й так великий пост.

Подякували: ch0r_t, leofun01, Lata, lucas-kane4

22

Re: Обробка текстів, символьні рядки

Частину завдання вже зроблено. Воно правильно рахує слова. ;)

#include <stdio.h>
#include <string.h>
int main()
{
char text[]="pryvitaty z novym rokom i pobazhaty zdaty vsi roboty i otrymaty vysoki baly";
char aty[]="ати";
char *word=text;
char *p1, *p2;
int n=0;
while(*word){
    if(*word!=' '){
       if(*word=='a'){
       word++;
       if(*word=='t'){
           word++;
           if(*word=='y'){
               word++;
               if(*word==' '){
                   word++;
                   n++;
               }
           }
       }
    }
    }
       word++;
    }
printf("\nСлова, які закінчуються буквосполученням 'ати':");

printf("\nЇх кількість: %i",n);
    
return 0;
}

23

Re: Обробка текстів, символьні рядки

А якщо останнє слово буде на aty закінчуватися?

24

Re: Обробка текстів, символьні рядки

Спосіб, який запропонував wander, працює, але він надто складний. Ми ще цього не вчили.
Треба щось простіше. Нехай буде латинницею, але зрозуміліше.
Я пробувала користуватися функціями, які вже більш-менш знаю. Вийшло  - не дуже.

#include <stdio.h>
#include <string.h>
int main()
{
char text[]=" pryvitaty z novym rokom i pobazhaty zdaty vsi roboty i otrymaty vysoki baly";
char *word=text;
char *p1, *p2;
int n=0;
printf("pryvitaty z novym rokom i pobazhaty zdaty vsi roboty i otrymaty vysoki baly");
printf("\nСлова, які закінчуються буквосполученням 'aty':");
while(*word){
    p1=strchr(text, ' ');
    p2=strchr(text, ' ');
    if(*word!=' '){
       if(*word=='a'){
       word++;
       if(*word=='t'){
           word++;
           if(*word=='y'){
               word++;
               if(*word==' '){
                   word++;
                   n++;
                   strcpy(p1,p2);
                   printf("%s", text);
               }
           }
       }
    }
    }
       word++;
    }


printf("\nЇх кількість: %i",n);
    
return 0;
}

Компілятор просто виводить все підряд.
pryvitaty z novym rokom i pobazhaty zdaty vsi roboty i otrymaty vysoki baly                                                     
Слова, які закінчуються буквосполученням 'aty': pryvitaty z novym rokom i pobazhaty zdaty vsi roboty i otrymaty vysoki baly pr 
yvitaty z novym rokom i pobazhaty zdaty vsi roboty i otrymaty vysoki baly pryvitaty z novym rokom i pobazhaty zdaty vsi roboty 
i otrymaty vysoki baly pryvitaty z novym rokom i pobazhaty zdaty vsi roboty i otrymaty vysoki baly                             
Їх кількість: 4                                                                                                                 
                                                                                                                               
...Program finished with exit code 0                                                                                           
Press ENTER to exit console.

25 Востаннє редагувалося Lata (20.12.2020 14:31:44)

Re: Обробка текстів, символьні рядки

А якщо останнє слово буде на aty закінчуватися?

То буде закінчуватися) Я для таких випадків там і пропуск додала.

#include <stdio.h>
#include <string.h>
int main()
{
char text[]=" pryvitaty z novym rokom i pobazhaty zdaty vsi roboty i otrymaty vysoki baly ";
char *word=text;
char *p1, *p2;
int n=0;
printf("pryvitaty z novym rokom i pobazhaty zdaty vsi roboty i otrymaty vysoki baly");
printf("\nСлова, які закінчуються буквосполученням 'aty':");
while(*word){
    p1=strchr(text, ' ');
    p2=strchr(text, ' ');
    if(*word!=' '){
       if(*word=='a'){
       word++;
       if(*word=='t'){
           word++;
           if(*word=='y'){
               word++;
               if(*word==' '){
                   word++;
                   n++;
                   strcpy(p1,p2);
                   printf("%s", text);
               }
           }
       }
    }
    }
       word++;
    }


printf("\nЇх кількість: %i",n);
    
return 0;
}

26 Востаннє редагувалося wander (20.12.2020 13:42:03)

Re: Обробка текстів, символьні рядки

Lata написав:

Спосіб, який запропонував wander, працює, але він надто складний. Ми ще цього не вчили.
Треба щось простіше. Нехай буде латинницею, але зрозуміліше.
Я пробувала користуватися функціями, які вже більш-менш знаю. Вийшло  - не дуже.

Це все, звісно, чудово, але ви вирішили лікувати симптоми, а не причини. Те, що ви цього не вчили в коледжі не означає, що не можна це вчити самому. Я надіюсь ви розумієте, що на лекціях вам не дадуть всієї потрібної інформації, а самоосвіта - це можна сказати кредо програмістів (раз ви вже обрали цю професію) :)

Мої повідомлення, звісно, не пояснюють в деталях, як все працює, але це і не було моєю метою. Я лише хотів продемонструвати роботу деяких кодувань, та заохотити вас прочитати про це більше у всесвітній павутині. Все що я там написав не є чимось складним, просто я знаю, як працює utf-8, наприклад, а ви - ні. Тож пропоную вам у цьому розібратись теж.

————————————
Хоча, можливо, я вимагаю забагато від вас, бо у вас схоже виникли проблеми з латинськими літерами, хоча це вже тривіальна задача. Можете погуглити, як працює strtok.

Подякували: Lata1

27 Востаннє редагувалося Lata (20.12.2020 14:33:12)

Re: Обробка текстів, символьні рядки

Звісно, самоосвіта - це важливо)
Лекцій навіть на простіші завдання не достатньо.
Я лише цікавилася, чи є інші способи рішення цієї задачі.
Отже, немає.

28

Re: Обробка текстів, символьні рядки

#include <stdio.h>
#include <string.h>
int main()
{
char text[]=" pryvitaty z novym rokom i pobazhaty zdaty vsi roboty i otrymaty vysoki baty ";
char sep[]=" ";
char *instr;

char *word=text;
char *p1, *p2;
int n=0;
printf("pryvitaty z novym rokom i pobazhaty zdaty vsi roboty i otrymaty vysoki baty");
printf("\nСлова, які закінчуються буквосполученням 'aty':");
while(*word){
    instr=strtok(text,sep);
    p1=strchr(text, ' ');
    p2=strchr(text, ' ');
    if(*word!=' '){
       if(*word=='a'){
       word++;
       if(*word=='t'){
           word++;
           if(*word=='y'){
               word++;
               if(*word==' '){
                   word++;
                   n++;
                   printf("%s", text);
               }
           }
       }
    }
    }
       word++;
    }


printf("\nЇх кількість: %i",n);
    
return 0;
}

З функцією strok виводить такий результат.

pryvitaty z novym rokom i pobazhaty zdaty vsi roboty i otrymaty vysoki baty                                                   
Слова, які закінчуються буквосполученням 'aty': pryvitaty pryvitaty pryvitaty pryvitaty                                       
Їх кількість: 4  

29

Re: Обробка текстів, символьні рядки

1. Прочитайте, як саме працює функція strtok. Це зовсім не очевидно, читайте уважно, там є кілька заплутаних моментів.
2. Спробуйте декомпозицію.

Подякували: Lata1

30

Re: Обробка текстів, символьні рядки

Добре, прочитаю уважніше. Коли буде якийсь результат, скажу.

Подякували: FakiNyan1

31 Востаннє редагувалося Lata (20.12.2020 19:52:58)

Re: Обробка текстів, символьні рядки

Я читала про функцію strok на різноманітних англомовних ресурсах. Так, справді не все очевидно. Зараз покажу новий код.

#include <stdio.h>
#include <string.h>
int main()
{
char text[]=" pryvitaty z novym rokom i pobazhaty zdaty vsi roboty i otrymaty vysiki baly ";
char *word=text;
char *result;

int n=0;
printf("pryvitaty z novym rokom i pobazhaty zdaty vsi roboty i otrymaty vysoki baly");
printf("\nСлова, які закінчуються буквосполученням 'aty':");
while(*word){
    
    if(*word!=' '){
       if(*word=='a'){
       word++;
       if(*word=='t'){
           word++;
           if(*word=='y'){
               word++;
               if(*word==' '){
                   word++;
                   n++;
                   result=strtok(text," ");
                   printf("%s\n", result);
                   
               }
           }
       }
    }
    }
       word++;
    }


printf("\nЇх кількість: %i",n);
    
return 0;
}

Я, мабуть, погано зрозуміла принцип роботи цієї функції, бо компілятор видає перше слово (pryvitaty) 4 рази, але наступні потрібні слова не виводяться.
Що саме я не врахувала? *SORRY*

32

Re: Обробка текстів, символьні рядки

Може, я застосувала функцію правильно, але треба створити ще один масив символьного типу? Спробую.

33 Востаннє редагувалося wander (20.12.2020 22:36:23)

Re: Обробка текстів, символьні рядки

Lata написав:

Що саме я не врахувала?

Функція strtok повертатиме вам вже ціле слово, вона розбиває ваш текст на токени, а те як вона це робитимете ви їй задаєте другим параметром. Тобто вона розділятиме ваш текст по пробілу:

Прихований текст
#include <stdio.h>
#include <string.h>
int main()
{
    char  text[] = "pryvitaty z novym rokom";
    char *token  = strtok(text, " ");
    while (token != NULL) {
        printf("%s\n", token);
        token = strtok(NULL, " "); // Якщо першим параметром ми передамо нульовий вказівник,
                                   // то виклик розглядатиметься як наступний виклик strtok:
                                   // функція продовжиться з того місця, де вона завершилась
                                   // у попередньому виклику.
    }    
    return 0;
}

Out:
pryvitaty
z
novym
rokom

https://rextester.com/ZAC52046

Тобто token - це вже і будем вашим словом, все що вам залишається, то це пройтися по ньому і знайти закінчення "ати".

Подякували: Lata1

34 Востаннє редагувалося Lata (21.12.2020 09:50:23)

Re: Обробка текстів, символьні рядки

#include <stdio.h>
#include <string.h>
int main()
{
char text[]=" pryvitaty z novym rokom i pobazhaty zdaty vsi roboty i otrymaty vysiki baly ";
char *letter=text;
char *token  = strtok(text, " ");
int n=0;
printf("pryvitaty z novym rokom i pobazhaty zdaty vsi roboty i otrymaty vysoki baly");
printf("\nСлова, які закінчуються буквосполученням 'aty':");
while(*letter){
    while(*token){
       if(*letter=='a'){
       letter++;
       if(*letter=='t'){
           letter++;
              if(*letter=='y'){
                   letter++;
                   n++;
                   token = strtok(NULL, " ");
                   printf("%s\n", token);
           }
       }
    }
      token++;
    }
     letter++;
    }


printf("\nЇх кількість: %i",n);
    
return 0;
}
}

Компілюється, але видає:

pryvitaty z novym rokom i pobazhaty zdaty vsi roboty i otrymaty vysoki baly                                                     
Слова, які закінчуються буквосполученням 'aty':                                                                                 
Їх кількість: 0  


Чому?

35

Re: Обробка текстів, символьні рядки

Ледь не забула уточнити. Я замінила word на letter, щоб не плуталося.

36

Re: Обробка текстів, символьні рядки

летер це лист

Подякували: Lata1

37

Re: Обробка текстів, символьні рядки

A word - це слово.

38 Востаннє редагувалося wander (21.12.2020 11:11:55)

Re: Обробка текстів, символьні рядки

Lata написав:

Чому?

Як я розумію, ви досі не розібрались, що робить strtok і не розумієте нащо вона є? Давайте так, якщо воно вам непотрібно і хочеться просто здати лабу чи що там, то вже так і бути я вам скину готовий код, і на цьому можна бути зупинити ці муки? :)

39

Re: Обробка текстів, символьні рядки

Якби я хотіла просто "здати лабу", я би створила тему так: дано текст. знайти і надрукувати... Якщо я вже починала це робити, то я хочу це зрозуміти. В усіх колись не виходить.

#include <stdio.h>
#include <string.h>
int main()
{
char text[]=" pryvitaty z novym rokom i pobazhaty zdaty vsi roboty i otrymaty vysiki baly ";
char *token=strtok(text," ");
int n=0;
printf("pryvitaty z novym rokom i pobazhaty zdaty vsi roboty i otrymaty vysoki baly");
printf("\nСлова, які закінчуються буквосполученням 'aty':");
    while(token){
    char *p=token;
    while(p){
       if(p="a"){
           p++;
           if(p="t"){
               p++;
               if(p="y"){
                   p++;
                   if(p="\0"){
                       printf("%s", token);
                   }
               }
           }
       }
    }


printf("\nЇх кількість: %i",n);
    
return 0;
}

Тут я спробувала пройтися по токенах.
main.c: In function ‘main’:
main.c:31:1: error: expected declaration or statement at end of input
}
^
Якщо можете скинути код, скидайте. Проаналізую правильний код.

40

Re: Обробка текстів, символьні рядки

Можете переглянути мої попредні теми. Я ніколи не писала Розв'яжіть задачу, бо треба здати на завтра. До того ж, не до кожної "лаби" створювала тему. Але цієї функції я справді погано розумію. Не бачу сенсу писати, що мені "треба лише правильного коду".
Прошу ставитися до користувачів з повагою. Можете не допомагати, але зневажати себе я не дозволю.
Дякую за допомогу і пояснення.