Program liczący słowa w tekście

0

Dziś jako ćwiczenie napisałem prosty program zliczający wystąpienia słów w pliku tekstowym i chciałbym się nim podzielić w celu uzyskania komentarzy (a może nawet ktoś użyje tego programu).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define WORDLEN 16
struct  wordent
{       char    word[WORDLEN];
        int     count;
        struct  wordent *next;
};
struct  wordent *wordlist = NULL;
void    regword (char *word)
{       struct  wordent *ent;
        for (ent = wordlist; ent; ent = ent->next)
                if (!strcmp (word, ent->word))
                {       ++(ent->count);
                        return;
                }
        ent = malloc (sizeof (struct wordent));
        strcpy (ent->word, word);
        ent->count = 1;
        ent->next = wordlist;
        wordlist = ent;
}
void    freelist (struct wordent *top)
{       if (top)
        {       freelist (top->next);
                free (top);
        }
}
int     main (int argc, char **argv)
{       FILE    *fp = stdin;
        char    c, wordbuf[WORDLEN], wlen = 0;
        int     maxcount, n = 0;
        struct  wordent *ent, *maxent;
        if (argc > 1)
                fp = fopen (argv[1], "r");
        if (!fp)
        {       puts ("Can't read the input file.");
                return 1;
        }
        do
        {       c = fgetc (fp);
                if (c >= 'A' && c <= 'Z')
                        c |= 32;
                if (c >= 'a' && c <= 'z')
                {       if (wlen < (WORDLEN - 1))
                                wordbuf[wlen++] = c;
                } else if (wlen)
                {       wordbuf[wlen] = 0;
                        regword (wordbuf);
                        wlen = 0;
                }
        }       while (c != EOF);
        list:
                maxent = NULL; maxcount = 0;
                for (ent = wordlist; ent; ent = ent->next)
                        if (ent->count > maxcount)
                        {       maxent = ent;
                                maxcount = maxent->count;
                        }
                if (maxent)
                {       printf ("Word \"%s\" %d times.\n", maxent->word, maxcount);
                        maxent->count = 0;
                        ++n;
                        goto list;
                }
        printf ("Total %d unique words.\n", n);
        freelist (wordlist);
        return 0;
}
1
goto hell;

Cześć, mam pytanie, dlaczego tak często korzystasz z goto? Trochę pachnie to spaghetti code :(


Jak/czym to kompilujesz?
screenshot-20230224195453.png


Poniżej podsyłam Ci jak ja bym to zrobił :)

#include <fstream>
#include <iostream>
#include <string>
#include <cstdlib>
#include <iostream>
#include <unordered_map>
#include <chrono>

int main(const int argc, char** argv) {
    if (argc < 2) {
        std::cout << "Can't read the input file." << std::endl;

        return EXIT_FAILURE;
    }

    std::ifstream input_file(argv[1], std::ios_base::in);
    std::unordered_map<std::string, int> dictionary = {};
    std::vector<char> break_character { '\r', '\n', '\t', 32 };

    std::cout << "File '"<< argv[1] << "' processing started." << std::endl;
    const auto start = std::chrono::high_resolution_clock::now();

    char c;
    std::string buffer;
    while (input_file.get(c)) {
        if (std::ranges::find(break_character, c) != break_character.end()) {
            if (auto dictionary_element = dictionary.find(buffer); dictionary_element != dictionary.end()) {
                dictionary_element->second++;
            } else {
                dictionary.insert(std::make_pair(buffer, 1));
            }

            buffer = {};
        }
        else {
            if (std::isalpha(c)) {
                buffer.push_back(c);
            }
        }
    }

    if(!buffer.empty())
    {
        if (const auto dictionary_element = dictionary.find(buffer); dictionary_element != dictionary.end()) {
            dictionary_element->second++;
        }
        else {
            dictionary.insert(std::make_pair(buffer, 1));
        }
    }

    const auto stop = std::chrono::high_resolution_clock::now();
    std::cout << "File '" << argv[1] << "' processing finished in "<< duration_cast<std::chrono::milliseconds>(stop - start).count() <<"ms." << std::endl << std::endl;

    for (auto [key, value] : dictionary) {
        std::cout << "Word \"" << key << "\" " << " occurred " << value << " times." << std::endl;
    }

    std::cout << "Total " << dictionary.size() << " unique words." << std::endl;
}
0

A on liczy np czerwono-zielony jako pojedyncze słowo czy dwa słowa?

0

Używam strcpy, gdyż na etapie przekazania ciągu znaków do funkcji regword (z main) jest pewne, że ciąg ma poniżej WORDLEN znaków, tak więc nie trzeba tego sprawdzać dwa razy.
Typ void dla pliku - rzeczywiście miałem źle, zmieniam na FILE. Co do innych (przy alokacji) to trzeba by dodać explicit casty, wydawało mi się, że standard ANSI rzutuje typy domyślnie przy wywołaniu funkcji i powinno się skompilować. Na screenie widać, że kompilujesz to jako C++, może dlatego są błędy.
Jeśli chodzi o tę instrukcję goto to istnienie maxent warunkuje zarówno operacje z linii 62-64 jak i powtórzenie pętli. W przypadku użycia strukturalnego do-while warunek byłby sprawdzany dwa razy - raz w instrukcji if i drugi raz by zdecydować o powtórzeniu pętli.

0

Odmianę słów, jak Otworzyłem youtube'a, to youtube'a to powinno być jedno słowo, też zaliczysz je jako osobne?

0

Odmiana słów jest inna dla każdego języka (a w j. polskim wyjątkowo kłopotliwa), nie jest zaimplementowana. W tym konkretnym przykładzie apostrof nie jest literą, więc rozdzieli to na słowa youtube i a.

1
Manna5 napisał(a):

Odmiana słów jest inna dla każdego języka (a w j. polskim wyjątkowo kłopotliwa), nie jest zaimplementowana. W tym konkretnym przykładzie apostrof nie jest literą, więc rozdzieli to na słowa youtube i a.

To dla jakiego języka to zrobiłeś? Angielskiego?

Bo jeśli tak to np Let's go też rozdzieli? Albo Mark's books?

0

Z angielskim działa w miarę poprawnie, bo tam raczej nie ma jako takiej odmiany. Właściwie to forma skrócona 's' jest semantycznie innym słowem.

1

Jakbym miał grymasić, struktura danych byłaby Map / Dictionary a nie List.
Powiem wiecej, oprócz technologii wyodrębniania słów z ciagu literek - jeśli nie zajmujemy się odmianami i gramatyką - to BYŁABY wiodąca część pokazania poweru progrmaisty

Choć rozumiem, że w szkolnym C (mam na myśli ze sam jezyk ma poziom szkolny, niezależnie od użytkownika ) Map /Dictionary jest przerażająco złożone, i (w tym wypadku naprawdę) szkolna lista nasuwa sie jako ratunek.
Szkolna m.in. w tym sensie, że na stałej długości pola itd... profesjonalna by była na zmienną

Generalnie w C złożoność wystrzela w niebo z każdym współczesnym kontenerem. Modlitwy i palenie świętych kadzidełek aby symetrycznie do pary alokować - zwolnić itd...
Drzewa ? Dla mnie osobiście przerażenie (i to wcale nie w sensie nie istniejace w bibliotece / istniejące gotowce, tylko możliwość napisania, zdebugowania, konserwacji i nie wylądowania w Tworkach)

Dobry projekt, aby dostrzec, jak szybko ten język się kończy w swojej ekspresji, i coś z ta nowa wiedzą zrobić.

3

Jesli ten projekt ma czegos nauczyć to tego, żeby nie robić tego w C.

Dlaczego nie splitujesz na białych znakach? To wydaje się bardziej naturalne.

0

Dlaczego nie splitujesz na białych znakach? To wydaje się bardziej naturalne.

Gdyby tak robić, program zaliczyłby np. słowo znakach? - ze znakiem zapytania, który jest niebiały. Pewne znaki (myślnik, apostrof) rzeczywiście można by rozważyć jako część słowa.

Szkolna m.in. w tym sensie, że na stałej długości pola itd... profesjonalna by była na zmienną

Zmienny bufor słowa trzeba by na etapie przetwarzania pliku realokować co pewną porcję liter (chyba że powinno się to zrobić w jescze inny sposób), spróbuję to dodać w ramach ćwiczenia.

1
Manna5 napisał(a):

Szkolna m.in. w tym sensie, że na stałej długości pola itd... profesjonalna by była na zmienną

Zmienny bufor słowa trzeba by na etapie przetwarzania pliku realokować co pewną porcję liter (chyba że powinno się to zrobić w jescze inny sposób), spróbuję to dodać w ramach ćwiczenia.

Skoro podjąłeś moją wypowiedź: w stosunku do innych problemów tego projektu, to naprawdę detal *)

Dosłysz to, z @Saalin chcemy ci powiedzieć: to nie projekt na C. Można mieć do C stosunek religijny niezaleznie od dopasowania do projektu, ale z inżynierią to nie ma ci wspólnego.
W RAMACH ĆWICZENIA - jeśli ma być naprawdę pożyteczne, zrób ten temat w innym języku (obojętne: C++, Python, Java, C#, i zrób sobie analizę porównawczą).

Jak jednak uparcie C, zamiast kopiowania szkolnej listy zrób wytestowane drzewo, za to uzyskasz mój szacunek (choć ze 20x łatwiej to bezpieczniej zrobić w C++)

*) mina, jaką trawą przysypałem: ciekawe jak elegancko to będziesz zwalniał.

0
Manna5 napisał(a):

Dlaczego nie splitujesz na białych znakach? To wydaje się bardziej naturalne.

Gdyby tak robić, program zaliczyłby np. słowo znakach? - ze znakiem zapytania, który jest niebiały. Pewne znaki (myślnik, apostrof) rzeczywiście można by rozważyć jako część słowa.

znakach? to jedno słowo. To nie wpływa na liczbę słów przecież czy znaki takie jak kropka, przecinek czy znak zapytania zaliczymy do słowa.

0

FILE *fp = stdin;

*fp?

Tak powinna wyglądać nazwa wg Ciebie?

Zobacz poniżej jakie ja dawałem nazwy zmiennych i funkcji.

void freelist (struct wordent *top)

Dajesz rade po polsku widzę. to freelist mi brzmi jak "new" "malloc" "int".

Będziesz dawał polskie nazwy albo chociaż z "_" w nazwie to szybciej ludzie się domyślą co do czego służy.

Ładnie te nawiasy klamrowe dajesz

void    freelist (struct wordent *top)
{       if (top)
       {       freelist (top->next);
               free (top);
       }
}

Ja to raczej robię tak

void    freelist (struct wordent *top){
                                       if (top){
                                               freelist (top->next);
                                               free (top);}}

Tak ładnie to porobiłeś, że strach literki zmieniać.

Twój sposób chyba czytelniejszy.
Rzadko miewam kłopoty z oceną.

Dałbyś jakiś plik tekstowy przykładowy i wynik użycia to mógłbym spróbować to zrobić dużo prościej, a tak ...

Wbrew temu co wypisują wyżej C ma wszystko czego potrzebujesz.

Staraj się dzielić zadania na mniejsze części.

Ja bym zaczął tak:

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

int ilosc_zdan;
char tablica[300][500];
int tablica_slow[300];
char tekst[500];

int wczytaj_zdania(){
                    char znak;
                    int pozycja=0;
                    int pozycja_nowa=0;
                    znak=tekst[0];

                        do{
                            znak=tekst[pozycja];
   
                            if(znak==46){ilosc_zdan++;pozycja_nowa=0;}else{tablica[ilosc_zdan][pozycja_nowa]=znak;pozycja_nowa++;}
  
                            pozycja++;
                            
                            
                            }while(znak!='\0');
                            return 0;}


void wylicz_slowa(){
                    int znak;   
                    int pozycja=0;    
    
                    for(int i=0;i<ilosc_zdan;i++){
                                                    tablica_slow[i]=0; 
                                                    pozycja=0; 
     
                                                do{
            
                                                    znak=tablica[i][pozycja];
        
                                                    if(znak==32){tablica_slow[i]++;}
                                                    pozycja++;
                                                    }while(znak!='\0');
                                                    tablica_slow[i]++;}
    
    
    
    
}

int main(){
    
ilosc_zdan=0;
// przykladowy tekst wczytany z pliku (normalnie odczytujemy rozmiar, alokujemy pamiec i tam wczytujemy jego zawartosc)
strcpy(tekst,"Ala ma kota.Janusz ma psa.Heniek lubi kasztany jesienia.");


printf("%c%s%c\n\n",34,tekst,34);

wczytaj_zdania();
wylicz_slowa();

printf("ilosc zdan to %d\n",ilosc_zdan);


for(int i=0;i<ilosc_zdan;i++){
    
    printf("\nzdanie numer %d  to %c%s%c  ilosc slow w zdaniu to %d",i,34,tablica[i],34,tablica_slow[i]);}



return 0;}

Tutaj masz tekst w tablicy (którą w swojej wersji możesz zaalokować przez malloc i do niej wczytać plik).

Z tego co na to patrzę to już porobiłem zdania, i policzyłem słowa w nich.

Czyli już wiesz ile razy po zdaniu pętlą potrzebujesz po każdym zdaniu przejechać.

Czyli jedna pętla uruchomiona tyle razy ile jest zdań, druga wewnętrzna tyle razy ile jest słów.

Masz policzone słowa i zdania, to chyba już Ci pozwala na stworzenie tablicy wskaźników o określonej wielkości.
(właściwie to struktur, bo chcesz coś tam sobie policzyć).

Bo jak tworzysz listę to każdy węzeł to wskaźnik do następnego + wskaźnik na słowo + int na przechowanie rozmiaru.

// o to mi dokładnie chodziło - a tutaj masz 16 bajtowe słowo tak?
struct  wordent
{       char    word[WORDLEN];
        int     count;
        struct  wordent *next;
};

Tutaj masz tylko wskaźnik na słowo + rozmiar.

struct lista_slow{
            char* slowo; 
            int   ilosc;
            };

To już chyba połowa sukcesu.

Listy łączone są super, kiedy nie wiesz czego będziesz potrzebował.

Zaraz zaczną marudzić że po co to dzielić na zdania, ale np. analizie występowania określonych słów czasem potrzebny jest kontekst, tutaj już go masz, właśnie poprzez przechowanie zdan w tablicy.

Wtedy możesz do struktury dodać typ int do wskazania którego zdania dane słowo dotyczy.
(wskaźnik może lepiej żeby tworzyć tylko tablice które mają coś oznaczać)

To tylko jedna możliwość. Jest ich ogromna ilość.

Nie przejmuj się moją surową krytyką, pierwsze kroki w technikach masowej inwigilacji bywają trudne, a i ludzie na to jakoś niechętnie patrzą. Ważne się nie przejmować i wszystko zapisywać.

Dobra, wygrałeś, możesz na początku przejechać po pliku bez kopiowania zdań, po prostu wszystko policzyć i wtedy zaalokować tablicę zdań takimi wartościami jakie odczytasz.

Tam dalej, są już jedynie metody kompresji, ale je lepiej przećwicz oddzielnie.

Ostatnio pytałem kogoś na priv czy wie co potrzebuje pisać, ten się spytał 'w sensie?'.

Tutaj widzisz, że np. list się powinno uczyć na przykładach które są odpowiednie.

I tak jest ze wszystkim.

Wszystko co "właściwe" w C jest proste, przejrzyste, estetyczne.

Takie powinny być nazwy zmiennych, nazwy struktur, nazwy funkcji.

Im bardziej przejrzyście napisane, tym większe projekty można bezproblemowo wdrozyć.

Jak ktoś tego nie rozumie znaczy, że nie zna tego języka.

0
johnny_Be_good napisał(a):

FILE *fp = stdin;

*fp?

Tak powinna wyglądać nazwa wg Ciebie?

Dobre nazwy, czego od niego chcesz?

FILE - nazwa standardowa
stdin - nazwa standardowa
fp - nazwa zgodna z powszechnie przyjętą konwencją

Jakbyś nie wiedział: * przed fp nie jest częścią nazwy zmiennej...

0

Te rady w większości które Ci udzielono niestety nie są z tych najlepszych.

Masz tu najgłupsze rozwianie, tak.testujesz:

./program < plik
wc -w < plik
#include <stdio.h>
#include <string.h>

int main( void ) {
		
  size_t count = 0;
  char word[8192] = {0};
  while( scanf( "%8191s", word ) == 1  )  {

    count++;
    if( strlen( word ) == 8191u )
      perror( "too big word!!" );

  }
					
  if( ferror( stdin ))  printf( "scanf fail" );
  printf( "%zu\n", count );

  return 0;

}

Gdybyś np. chciał inaczej traktować pewne znaki to robisz swoją analizę na końcu pętli albo np innym programem.
Co może być lepsze bo Twój wyliczacz słów robi się wszechstronniejszy.

tr -s  "'" ' ' < plik | ./program

pzdr.

1 użytkowników online, w tym zalogowanych: 0, gości: 1