C program för frekvens av bokstäver läst från fil

Permalänk

C program för frekvens av bokstäver läst från fil

Hej jag försöker skriva ett program i C som räknar frekvensen av bokstäver i en text och skriver ut resultatet. så outputen ska bli: a occurs 2 times b occurs 0 times etc etc.
tyvärr så räknar aldrig mitt program några bokstäver är ny på det här med filhantering och skulle uppskatta all hjälp väldigt mkt. så här ser min (havererade) kod ut:
#include<stdio.h>

void frequency()
{
int count [256] = {0};
int c = 0;
int ch;

FILE * text;
text = fopen("c:\\temp\\textfile.txt","r");
if (text==NULL)
{
printf("error opening file");
return NULL;

}
while((ch=getc(text)) != EOF)
{

{
count[c - 'a']++;
}
c++;
}
for (int c =0;c<256;c++)
printf("%c occurs %d times \n ",c+'a',count[c]);
fclose(text);

}
int main (void)
{
//FILE * fp;
//fp = fopen("C:\\temp\\textfile.txt","r");
frequency();

}
tack på förhand

Permalänk

I slingan där du läser tecken från filen, vad indexerar du med i "count" då? Jag vill påstå att du gör en massa ogiltiga negativa indexeringar där.

Fundera över varför du aldrig använder värdet av ch som du läser.

Sen har jag lite dålig koll på C-standarden, men initieringen av arrayen "count" kanske du måste göra en loop av; jag befarar att bara första elementet blir 0.

Permalänk
Medlem

Filhanteringen som sådan ser väl inte så tokig ut. men övrig programlogik finns det vissa brister i.

När du räknar upp frekvensen, borde du inte använda <ch> istället för <c> ?
Det är väl trots allt det inlästa tecknet som skall styra vad som räknas upp.

Hur hade du tänkt hantera inlästa tecken som ligger utanför 'a'-'z' ?
Nuvarande kod ser inte ut att hantera sådana.

Permalänk
Medlem
Skrivet av AndersG74:

Sen har jag lite dålig koll på C-standarden, men initieringen av arrayen "count" kanske du måste göra en loop av; jag befarar att bara första elementet blir 0.

Den initieringen är faktiskt korrekt. Om man anger färre värden än platser i arrayen så kommer övriga platser i arrayen att få värdet 0.

Permalänk
Skrivet av Erik_T:

Den initieringen är faktiskt korrekt. Om man anger färre värden än platser i arrayen så kommer övriga platser i arrayen att få värdet 0.

Det var som 17, det stämmer faktiskt.

Permalänk
Medlem

Använd gärna code-taggar när du postar kod, så blir den lättare att läsa:
[code]
Kod här
[/code]

Felet är som sagt att du har både c och ch, där du läser in till ch och sen använder c istället.

Eftersom du ändå har en array med 256 element så behöver du inte hålla på med c - 'a' heller, det är enklare att bara använda ch som index rakt av istället. Sen blir utskriften lite konstigt eftersom alla 256 tecken inte är synliga tecken, så du kanske vill börja utskriftsloopen på t.ex. 33 istället (d.v.s. '!', det första synliga tecknet i ASCII). Eventuellt kanske hoppa över att skriva ut tecken som inte förekommer också.

Permalänk

@Erik_T: tack för hjälpen nu har jag modifierat min kod efter hur ni tänkte så den verkar kunna räkna vanliga tecken men inte mellanslag och & tecknet osv. Det känns som jag inte förstår hur fgets returnerar en unsigned char castad till en int. när den typecastas så får man tillbaka ASCII värdet. och sen ökar man counts värde med 1 på rätt ASCII plats i arrayen. Vad måste jag göra för att få med alla möjliga ASCII tecken i frekvenstabellen?

#include<stdio.h>

void frequency()
{
int count [256] = {0};
//int c = 0;
int ch;

FILE * text;
text = fopen("c:\\temp\\textfile.txt","r");
if (text==NULL)
{
printf("error opening file");
return NULL;

}
while((ch=getc(text)) != EOF)
{

{
count[ch - 'a']++; // MODIFIERAD KOD
}
ch++; // MODIFIERAD KOD
}
for (int c =0;c<256;c++)
printf("%c occurs %d times \n ",c+'a',count[c]);
fclose(text);

}
int main (void)
{
//FILE * fp;
//fp = fopen("C:\\temp\\textfile.txt","r");
frequency();

}

Permalänk
99:e percentilen

Jag undrar framförallt om C verkligen är det bästa verktyget för att lösa detta problem. Finns det någon särskild anledning till att du använder just C?

Visa signatur

Skrivet med hjälp av Better SweClockers

Permalänk

@perost: vad menas med att tecken ej är synliga? ber om ursäkt ska använda kod tag nästa gång.

Permalänk

#include<stdio.h> void frequency() { int count [256] = {0}; int ch; FILE * text; text = fopen("c:\\temp\\textfile.txt","r"); if (text==NULL) { printf("error opening file"); return NULL; } while((ch=getc(text)) != EOF) { { count[ch]++; } ch++; } for (int c =0;c<256;c++) printf("%c occurs %d times \n ",c,count[c]); fclose(text); } int main (void) { frequency(); }

detta verkar fungera tack vare er. postar denna. Är inte duktig med C-ASCII märker jag.

Permalänk
Medlem

För att fixa buggar och göra koden mer portabel, prova följande små ändringar och tillägg:

#include <limits.h> #include <ctype.h> // .... int count[UCHAR_MAX+1] = {0};

Det finns inga garantier att char är exakt 8 bitar, även om så oftast är fallet.
UCHAR_MAX är definierad som det största värdet en unsigned char kan ha.

//.... while((ch=getc(text))!=EOF) { count[ch]++; // Inget "ch++" här; det är helt onödigt. } for(int c=0; c <= UCHAR_MAX; c++) { if(isprint(count[c]) printf("The character _%c_ occurs %d times\n", c, count[c]); else printf("The character <%03d> occurs %d times\n", c, count[c]; } // .....

Det finns datorer som inte använder sig av ASCII. Det finns många tecken-värden som inte motsvarar en skrivbar symbol (ex. Retur, tab, m.fl.)
isprint() returnerar sant om argumentet är ett skrivbar tecken.
Koden ovan skall skriva ut tecknet om det är skrivbart, annars det numeriska värdet för tecknet.

(Reservation för att jag inte kompilerat och testkört ovanstående, så det kan finnas småfel.)

Permalänk
99:e percentilen

Som ett tillägg till min fråga tidigare i tråden: Här är ett Haskell-program som jag tror löser samma eller ett liknande problem.

filename :: String filename = "textfile.txt" characters :: [Char] characters = ['a'..'z'] ++ "åäö" ++ ['A'..'Z'] ++ "ÅÄÖ" showOcc :: (Char, Int) -> String showOcc (c, n) = c : " occurs " ++ show n ++ " times" occurrences :: String -> [(Char, Int)] occurrences text = map (\c -> (c, howMany c)) characters where howMany :: Char -> Int howMany c = length $ filter (== c) text main :: IO () main = readFile filename >>= putStrLn . unlines . map showOcc . occurrences

Det tuggar sig igenom den här drygt 6 MB stora filen på ungefär en sekund på min laptop.

occurences → occurrences
Visa signatur

Skrivet med hjälp av Better SweClockers

Permalänk
Medlem

C känns mer och mer förlegat för dagliga funktioner varje år.
Detta funkar utmärkt med perl:

open(FILE, shift); map {foreach(split(//, $_)) { $hChars{ $_ }++}} <FILE>; print "$_ occurs $hChars{$_} times\n" foreach keys %hChars;

Permalänk
Medlem
Skrivet av Fey:

C känns mer och mer förlegat för dagliga funktioner varje år.

Det beror vad du skall använda det till.
För att skriva lågnivå kod eller programmering på inbyggda system med begränsade resurser så är C ett alldeles utmärkt val. I många fall det bästa valet.
C är också bra om du vill förstå vad som faktiskt händer när programmet körs.

För högnivå programmering på vanliga datorer så finns det många bättre alternativ.

Permalänk
Datavetare
Skrivet av Alling:

Som ett tillägg till min fråga tidigare i tråden: Här är ett Haskell-program som jag tror löser samma eller ett liknande problem.

filename :: String filename = "textfile.txt" characters :: [Char] characters = ['a'..'z'] ++ "åäö" ++ ['A'..'Z'] ++ "ÅÄÖ" showOcc :: (Char, Int) -> String showOcc (c, n) = c : " occurs " ++ show n ++ " times" occurences :: String -> [(Char, Int)] occurences text = map (\c -> (c, howMany c)) characters where howMany :: Char -> Int howMany c = length $ filter (== c) text main :: IO () main = readFile filename >>= putStrLn . unlines . map showOcc . occurences

Det tuggar sig igenom den här drygt 6 MB stora filen på ungefär en sekund på min laptop.

Här är en orsak till varför saker fortfarande skrivs i C (även om utanför OS-kärnor är det rätt få saker som idag inte använder C++ i stället).

Använder filen du länkar ovan, big.txt

#include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #define FILENAME "verybig.txt" int main() { int fd = open(FILENAME, O_RDONLY); struct stat st; stat(FILENAME, &st); unsigned char * content = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); unsigned count[256] = { 0, }; #pragma omp parallel for reduction(+:count[:256]) for (size_t i = 0; i < st.st_size; i++) { count[content[i]]++; } for (unsigned i = 0; i < sizeof(count) / sizeof(count[0]); i++) { if (count[i] > 0) { printf("The character '%c' occured %u times\n", i, count[i]); } } munmap(content, st.st_size); close(fd); }

Tiden det tar på min laptop är 8 millisekunder (utan OpenMP stöd, med OpenMP se edits nedan)

real 0m0,008s user 0m0,007s sys 0m0,001s

Edit: vill man gå än mer wild-n-crazy kan man använda alla CPU-trådar på systemet. 6 MB är dock alldeles för lite data, så går faktiskt ungefär tio gånger långsammare just här (ungefär plus-minus noll efter lite OpenMP-tweak). Men givet tillräckligt stor indata-fil kommer det gå snabbare med fler kärnor

Edit2: replikerade indata-filen 100 gånger, d.v.s. den är nu 600 MB, blir nu ca tre gånger så snabbt med 4C mot 1C (~100 ms mot ~300 ms).

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
99:e percentilen
Skrivet av Yoshman:

Här är en orsak till varför saker fortfarande skrivs i C (även om utanför OS-kärnor är det rätt få saker som idag inte använder C++ i stället).

Använder filen du länkar ovan, big.txt

#include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #define FILENAME "big.txt" int main() { int fd = open(FILENAME, O_RDONLY); struct stat st; stat(FILENAME, &st); char *content = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); unsigned count[256] = { 0, }; for (size_t i = 0; i < st.st_size; i++) { count[content[i]]++; } for (unsigned i = 0; i < sizeof(count) / sizeof(count[0]); i++) { if (count[i] > 0) { printf("The character '%c' occured %u times\n", i, count[i]); } } munmap(content, st.st_size); close(fd); }

Tiden det tar på min laptop är 8 millisekunder

real 0m0,008s user 0m0,007s sys 0m0,001s

Snyggt! Det förvånar mig inte att detta C-program är snabbare än den helt naiva Haskell-koden ovan, som är skriven helt utan prestanda i åtanke. Jag inkluderade prestandasiffran endast för att ge ett hum om hur stora filer det går att använda programmet på.

Några frågor som uppstår:

  • Hur lätt är respektive program att skriva, förstå och modifiera?

  • Hur lätt är det att försäkra sig om att respektive program gör precis det man vill?

  • Hur lätt är det att plocka ut delar ur respektive program och återanvända dem?

  • Hur viktig är exekveringstiden för det aktuella användningsområdet?

Svaren varierar förstås beroende på vem man frågar.

Visa signatur

Skrivet med hjälp av Better SweClockers

Permalänk
Medlem
Skrivet av Erik_T:

Det beror vad du skall använda det till.
För att skriva lågnivå kod eller programmering på inbyggda system med begränsade resurser så är C ett alldeles utmärkt val. I många fall det bästa valet.
C är också bra om du vill förstå vad som faktiskt händer när programmet körs.

För högnivå programmering på vanliga datorer så finns det många bättre alternativ.

Det var därför jag skrev dagliga funktioner. Allting utanför seriösa program är bortkastade med C, och jag skulle dessutom säga att C++ är bättre i 90% av fallen där C är ett bra val.

Permalänk
Medlem
Skrivet av Lemieux66:

@perost: vad menas med att tecken ej är synliga? ber om ursäkt ska använda kod tag nästa gång.

De första tecknen i ASCII-tabellen är så kallade styrtecken. 8 är t.ex. backspace, 12 är ny rad, och 13 är vagnretur (återgår till radens början på en skrivmaskin. Jo, ASCII är rätt gammalt ). D.v.s. det är tecken som utför saker när de skrivs ut, istället för att vara synliga som t.ex. bokstäver.

Och du behöver inte be om ursäkt för att du inte använde code-taggar, det är forumets fel som inte har någon knapp att infoga kod med.

Skrivet av Alling:

Jag undrar framförallt om C verkligen är det bästa verktyget för att lösa detta problem. Finns det någon särskild anledning till att du använder just C?

Anledningen lär nog vara att det med största sannolikhet är en skoluppgift, det är ju en typisk nybörjaruppgift i C. Så att lämna in ett Haskell-program istället kan nog vara något problematiskt

Men om man nu egentligen bara vill räkna bokstäver så kan man ju bara använda t.ex. ett bash-script:

for c in {{a..z},{å,ä,ö},{A..Z},{Å,Ä,Ö}}; do echo -n "$c "; tr -cd $c < big.txt | wc -c; done

Jag har svårt att komma på något mindre effektivt sätt att lösa det på eftersom den tuggar igenom hela filen för varje tecken, men det tar ändå bara ~0.4s på min dator för big-filen.

Permalänk
99:e percentilen
Skrivet av perost:

Anledningen lär nog vara att det med största sannolikhet är en skoluppgift, det är ju en typisk nybörjaruppgift i C. Så att lämna in ett Haskell-program istället kan nog vara något problematiskt

Lustigt. Jag uppfattade det inte alls som en skoluppgift, antagligen på grund av hur trådstarten är formulerad, att C inte är så vanligt bland skoluppgiftsfrågorna här i forumet och att TS är registrerad 2010. Men jag hade det i åtanke; om det var en skoluppgift kunde min kod ändå inte hjälpa TS att fuska.

Visa signatur

Skrivet med hjälp av Better SweClockers

Permalänk
Medlem

@Yoshman: Jag har för vana att skriva ++i i for-loopen, har dock inte koll på hur kompilatorn uppfattar detta jämfört med det "vanliga" viset nuförtin'. Skulle det kunna ge en fördel i detta program?

Permalänk
Medlem
Skrivet av Dalton Sleeper:

@Yoshman: Jag har för vana att skriva ++i i for-loopen, har dock inte koll på hur kompilatorn uppfattar detta jämfört med det "vanliga" viset nuförtin'. Skulle det kunna ge en fördel i detta program?

Om du har en någorlunda vettig kompilator så gör det ingen skillnad alls prestandamässigt.
Däremot så har i++ och ++i något olika betydelse.
i++ räknar upp i med ett, och returnerar det gamla värdet av i.
++i räknar upp i med ett, och returnerar det nya värdet av i.

När man använder det i en for-loop endast för att räkna upp loop-variabeln, så gör det ingen som helst skillnad om man använder i++ eller ++i.

Permalänk
Medlem

@Erik_T: Yes, jag vet vad de gör. Tänkte mest på optimeringen, om det nu krävs en extra kopia för i++ (bortser från C++ objekt och operatorer). Blir maskinkoden likadan?

Permalänk
Medlem
Skrivet av Dalton Sleeper:

@Erik_T: Yes, jag vet vad de gör. Tänkte mest på optimeringen, om det nu krävs en extra kopia för i++ (bortser från C++ objekt och operatorer). Blir maskinkoden likadan?

Om man inte använder själva värdet av uttrycket, utan det endast finns med för sidoeffekten (t.ex. när man räknar upp loop-variabeln i en for-loop) så kommer de flesta kompilatorer att generera helt identisk kod för både ++i och i++

I de fall där värdet faktiskt används så kan det variera beroende på hur omgivande kod ser ut, men man kan inte säga rent generellt att det ena är snabbare än det andra - och oftast så är det ingen prestandaskillnad alls.

Om vi t.ex. jämför uttrycken a = i++; och a = ++i; så kan man tänka sig att en kompilator för den första varianten generar något i stil med följande pseudo-maskin kod:

MOV a,i ADD i,1

Medan för det andra fallet så kan den generera

ADD i,1 MOV a,i

Exakt samma instruktioner kommer att utföras i bägge fallen, bara i olika ordning.

Permalänk
Datavetare
Skrivet av Alling:

Snyggt! Det förvånar mig inte att detta C-program är snabbare än den helt naiva Haskell-koden ovan, som är skriven helt utan prestanda i åtanke. Jag inkluderade prestandasiffran endast för att ge ett hum om hur stora filer det går att använda programmet på.

Några frågor som uppstår:

  • Hur lätt är respektive program att skriva, förstå och modifiera?

  • Hur lätt är det att försäkra sig om att respektive program gör precis det man vill?

  • Hur lätt är det att plocka ut delar ur respektive program och återanvända dem?

  • Hur viktig är exekveringstiden för det aktuella användningsområdet?

Svaren varierar förstås beroende på vem man frågar.

Hur lätt är respektive program att skriva, förstå och modifiera?
Här man skilja på "lätt" och "enkelt". För mig är det ju betydligt lättare att både skriva, förstå och modifiera C-programmet då jag inte programmerat Haswell på rätt många år och har aldrig varit bra på det. I fallet C (och C++) är det språk jag jobbat med i >20 år, inklusive jobbat med utveckling av deras standardbibliotek.

För just det här exemplet ser jag inte att något av språket ger ett enklare program.

Hur lätt är det att försäkra sig om att respektive program gör precis det man vill?
Även här finns två svar då det beror väldigt mycket på vad man menar.

C (och kanske än mer C++) ger dig som programmerar superkrafter utan att ge speciellt mycket i form av skyddsutrustning
Innan man lärt sig tygla dessa krafter, vilket man som människa i praktiken aldrig kommer lyckas med till 100 % då människor tappar fokus ibland och gör misstag, kommer saker gå fel, rejält fel!

Fast ändå lär du aldrig hittat något annat än program skrivna i just C (och inget annat än C, möjligen någon rad assembler!) i den typ av applikationer som under inga omständigheter får göra fel då fel i dessa miljöer ofta betyder stor ekonomisk skada eller stor skada/dödsfall av människa.

Enda sättet att med önskvärd nivå säkerställa att programmet är korrekt räcker det inte med att verifiera att programmet i sig är rätt, man måste verifiera att även kompilatorer, operativsystem etc är korrekt. I C finns här en väldigt stor fördel i att inget är implicit, finns massor med saker som är implicita i C++, Java, Haskell, JS vilket i normalfallet är väldigt trevligt då det underlättar men det skulle rejält öka kostnaden för att certifiera resultatet för korrekthet.

Det skrivet: ser noll anledning att skriva något utanför OS-kärnor och programvara avsedd för "safety critical" i C 2019. Framförallt givet hur hopplöst det är att hitta folk under 40-50 år som kan C

Hur lätt är det att plocka ut delar ur respektive program och återanvända dem?
Detta har rätt lite med val av programspråk att göra. Går att skapa big-ball-of-mud i alla språk och går att göra väldigt återanvändbara delar i ren assembler.

I "modern" C++ (C++11 och senare) finns stora skäl att hålla så stor andel som möjligt av sina funktioner "pure" då det ger kompilatorn långt fler möjligheter till riktigt coola optimeringar, bl.a. kommer dagens C++ kompilatorer evaluera de delar av ditt program där indata är känt vid kompilering och funktionen är "pure" direkt när man kompilerar. I ett större program betyder det att inte ens den bästa assembler guru matchar prestanda då inget är snabbare än kod som aldrig behöver köras (eller som i C++ fallet, redan körts).

Hur viktig är exekveringstiden för det aktuella användningsområdet?
Är en direkt funktion av:

  • hur många gånger kommer jag behöva köra programmet

  • med vilken frekvens kommer jag behöva köra programmet

  • hur stor är förväntat genomsnittlig indata

Är svaret "ofta+frekvent+väldigt stor" kommer prestanda vara den mest kritiska egenskapen. Men i många fall är det bättre att göra det som går fortast att skriva.

Målet är ju typiskt att minimera TOC.

Skrivet av Dalton Sleeper:

@Yoshman: Jag har för vana att skriva ++i i for-loopen, har dock inte koll på hur kompilatorn uppfattar detta jämfört med det "vanliga" viset nuförtin'. Skulle det kunna ge en fördel i detta program?

Med optimeringar påslagna så kvittar det helt om man kör ++i eller i++, blir samma resultat.

Du kan testa detta

int pre_inc(int i) { ++i; return i; } int post_inc(int i) { i++; return i; }

i compiler-explorer. På x86 blir det samma resultat vare sig man har optimeringar på eller ej

Att skriva ++it är typisk C++-ism (och det var en relevant optimering på 90-talet!). Men faktum är att moderna C++ kompilatorer fattar att optimera även det fallet när de ser att returvärdet inte används, detta blir också samma sak med optimeringar på

#include <vector> void pre_inc(std::vector<int>::iterator &it) { ++it; } void post_inc(std::vector<int>::iterator &it) { it++; }

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer