C char Array, Zeichenkette

  • Ersteller Ersteller Taxotic
  • Erstellt am Erstellt am
T

Taxotic

Gast
Hallo, ich versuche ein Programm zu schreiben, das einen Text einliest und dann die Anzahl der vorkommenden Buchstaben ausgibt.

Wenn ihr Lust habt, schauts euch doch mal an.
Mein Problem: Die Buchstaben und die Anzahl werden teilweise doppelt ausgegeben.


Bsp: Hallo

Ausgabe:
h=1
a=1
l=2
l=2
o=1

=1

Code:
#include <stdio.h>
#include <stdlib.h> 
#include <string.h>

int main()
{	 char speicher[20];
	 int anzahl[20]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
	 int i=0;
	 int j=0;
  
			printf("Bitte geben Sie ein Wort ein\n");
			fgets(speicher, 20, stdin);

for (i=0;i<strlen(speicher);i++)								//von Zeichenkette Anfang bis Ende
			{		for (j=0;j<strlen(speicher);j++)			
						{
						if (speicher[i]==speicher[j])			//vergleiche das Zeichen mit allen anderen, wenn es übereinstimmt zähle +1
						anzahl[i]=anzahl[i]+1;						
						}
			}


for (i=[COLOR="Red"]0[/COLOR];i<=strlen(speicher);i++)        //danke an asdfman
	if (speicher[i] != ' ' && anzahl[i]!=0)
	printf("%c = %i \n",speicher[i],anzahl[i]);
return 0;
}
 
Zuletzt bearbeitet:
Arrays fangen bei 0 und nicht bei 1 an. Außerdem gehst du die eingegebene Zeichenkette einfach durch. Wenn du doppelte Buchstaben nicht angezeigt haben willst, solltest du sie überspringen.
€: Das "=1" am Ende kommt daher, dass die Schleife ein Zeichen zu weit läuft und beim Terminator \0 landet, der nicht ausgegeben werden kann.
 
Code:
for (i=1;i<=strlen(speicher);i++) // i=1 (Indizes der Zeichen: h=0, a=1, l=2, l=3, o=4, \0=5)
if (speicher[i] != ' ' && anzahl[i]!=0) //du prüfst nicht ob ein zeichen schonmal erwähnt wurde => die anzahl eines Zeichens wird sooft angegeben wie sie vorkommt (=> l=2 l=2)
printf("%c = %i \n",speicher[i],anzahl[i]);
 
Arrays fangen bei 0 an.

Du kannst einfach ein Array (letters[]) mit der Anzahl der Buchstaben (also 26) anlegen und jede Position für die gefundene Anzahl nutzen, dann z.B. gleich ohne Groß- und Kleinbuchstaben zu unterscheiden und (Steuer-)zeichen filtern. "c" ist dann das gerade untersuchte Zeichen.
Code:
if ((c > 64 && c < 91) || (c > 96 && c < 123))
{
    if (c < 91)
        c+=32;
    letters[c-97]++;
}
 
Zuletzt bearbeitet:
finde den Code hier deutlich leserlicher so:
Code:
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
{
    if (c > 'Z') c -= 32;
    letters[c - 'A']++;
}

Im Übrigen könnte man auch statt dem inneren if eine binäre Verknüpfung machen können, so in die Richtung... aber das geht am Thema vorbei ;-)
Code:
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
{
    letters[c & 31 - 1]++;
}
 
Weil dein Programm insgesamt schlecht durchdacht ist und grundlose Einschränkungen hat
(z.B. dass die Eingabe nur 20 Zeichen haben kann), habe ich eine sinnvolle Variante gebaut.

Vielleicht hilft dir das ja weiter.

€: Paar Kommentare.

Code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXBUF 16

int main(void) {
    char *speicher = NULL, *buf;
    int anzahl[26], size = 0, oldsize, pos, i;

    memset(anzahl, 0, 26 * sizeof(int));

    printf("Bitte geben Sie ein Wort ein\n");

/* Einlesen der Zeichenkette */

    if((buf = malloc(MAXBUF)) == NULL) {
        fprintf(stderr, "Fehler: malloc() fehlgeschlagen.\n");
        return EXIT_FAILURE;
    }

    fgets(buf, MAXBUF, stdin);
    do {
        oldsize = size;
        size += strlen(buf);
        if((speicher = realloc(speicher, size + 1)) == NULL) {
            fprintf(stderr, "Fehler: realloc() fehlgeschlagen.\n");
            free(buf);
            if(speicher)
                free(speicher);
            return EXIT_FAILURE;
        } else {
            memset(speicher + oldsize, 0, size - oldsize + 1);
        }
        strcat(speicher, buf);
        if(speicher[size - 1] != '\n')
            fgets(buf, MAXBUF, stdin);
    } while(speicher[size - 1] != '\n');
    speicher[size - 1] = '\0';

/* Auswerten der Zeichenkette */

    for(i = 0; i < strlen(speicher); i++) {
        pos = tolower(speicher[i]) - 'a';
        if((pos < 0) || (pos > 25))
            fprintf(stderr, "Fehler: '%c' an Position %d ist kein Buchstabe.\n",
                speicher[i], i);
        else {
            anzahl[pos]++;
        }
    }

/* Ausgabe des Ergebnisses. */

    for(i = 0; i < 26; i++)
        if(anzahl[i])
            printf("%c = %d\n", i + 'a', anzahl[i]);

/* Aufräumen. */

    free(buf);
    if(speicher)
        free(speicher);
    return EXIT_SUCCESS;
}
 
Zuletzt bearbeitet:
Code:
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) // wenn das zu untersuchende Zeichen größer als A und kleiner als Z ist, bzw größer a, kleiner z
    {
    if (c > 'Z') c -= 32;
    letters[c - 'A']++; 
    }

könntest Du mir die verbleibenden 2 Zeilen noch erklären? Danke!
 
3. Zeile:
Wenn es dann noch größer als 'Z' ist, muss es sich um einen Kleinbuchstaben handeln. Von seinem Wert wird 32 subtrahiert und er wird so zu einem Großbuchstaben (nach ASCII-Tabelle).

4. Zeile:
Die entsprechende Position im Array wird inkrementiert.
 
@Asdfman: Danke für den Code, leider kenne ich viele Befehle noch garnicht.
Ich werde gleich mal etwas recherchieren und dann kommentieren, wie ich deinen Code verstehe.
 
Ich denke, die Unkenntnis betrifft hauptsächlich den Teil, der die Zeichenkette einliest. In dem Fall kümmere
dich darum halt erstmal nicht, sondern beachte den Rest, der eigentlich trivial zu verstehen sein sollte.
 
Dein eigentliches Problem liegt zunächst nicht in der Qualität deines Quellcodes, sondern in einem schlichten Denkfehler. Du zählst mehrfach vorkommende Buchstaben leider auch mehrfach. Deine beiden For-Schleifen durchlaufen die komplette Länge des Speichers und das ist der Fehler. Die innere Schleife darf natürlich immer nur ab der Position starten an der die Variable der äußeren Schleife gerade steht. Außerdem verwaltest du für jede Position deines untersuchten Speichers einen Zähler, das ist aber nicht das was du möchtest. Du möchtest für jedes vorkommende Zeichen ein Zähler verwalten.

In deinem Beispiel passiert nämlich mit den beiden 'l' folgendes:

Speicher enthält das Wort 'hallo'
i steht an Position 2 (Zählweise bei 0 beginnend) auf dem ersten 'l', j ebenfalls, Vergleichsbedingung ist erfüllt und das erste 'l' wird gezählt.

i steht an Position 2 auf dem ersten 'l', j steht auf Position 3 auf dem 2. 'l', Vergleichsbedingung ist erfüllt und das 2. 'l' wird gezählt.

etwas später...

i steht an Position 3 auf dem zweiten 'l', j steht auf Position 2 auf dem 1. 'l', Vergleichsbedingung ist erfüllt und das zweite 'l' wird gezählt.

i steht an Position 3 auf dem zweiten 'l', j ebenfalls, Vergleichsbedingung ist erfüllt und das zweite 'l' wird gezählt.

Somit wird mit deinem Code, das erste 'l' zweimal gezählt und das zeite 'l' separat auch. Abgelegt werden beide 'l' in deinem Array anzahl[], einmal an Index 2 und einmal an Index 3 und beide mal steht die gezählte Anzahl 2 drin.

Cleverer wäre es also ein Array anzulegen, für jeden der 26 Buchstaben des Alphabets einen Zähler enthält, so wie es hier im Thread schon desöfteren vorgeschlagen wird.

@adsfman: Nix für ungut, aber ich fürchte mit deiner 16-Zeichen-weisen Einleseschleife und der daraus resultierenden für meinen Geschmack unschönen dynamischen Speicherverwaltung stiftest du beim OP wahrscheinlich mehr Verwirrung als du zum Verständnis des eigentlichen Problems beiträgst.

Hier mein zugegebenermaßen auf statischen Speicher beschränkter Lösungsvorschlag:

Code:
#include <stdio.h>
#include <stdlib.h> 
#include <string.h>
 
int main()
{	
    char speicher[20];
    unsigned int anzahl[26];
    int i=0;
    int j=0;

    memset(anzahl, 0, 26 * sizeof(unsigned int));
    
    printf("Bitte geben Sie ein Wort ein\n");
    fgets(speicher, 20, stdin);

    
    for (i=0; i < strlen(speicher); i++)  //von Zeichenkette Anfang bis Ende
    {
        if ((speicher[i] >= 'A' && speicher[i] <= 'Z') || (speicher[i] >= 'a' && speicher[i] <= 'z'))
        {
            anzahl[ (speicher[i] & ~0x60) - 1]++;
        }       
    }

    for (i=0; i<26; i++) 
    {
        if ( anzahl[i] ) {
            printf("%c = %i \n",(i | 0x40) + 1, anzahl[i]);
        }
    }
	
    return 0;
}

j o e
 
Zuletzt bearbeitet:
Ich habe darauf hingewiesen, dass er sich darum, wie das Einlesen bei mir funktioniert, nicht kümmern muss.
Er könnte den Teil komplett weglassen und so behalten, wie er es hat, ohne dass sein eigentliches Vorhaben
davon berührt wird.

Bei dir hat er so lustige Dinge wie & ~0x60 und | 0x40 drin, die natürlich sofort (nicht) einleuchten und dafür
noch für das Funktionieren essentiell sind. Muss das sein, wenn du es schon besser machen willst?

€: Abgesehen von OP bin ich auch sehr offen für eine Möglichkeit, beliebig viele Zeichen in ein Array zu lesen,
das deiner Meinung nach schöner ist als meine Variante. Ich lerne ja gerne dazu.
 
Zuletzt bearbeitet:
@asdfman:
Bevor ich den Rechenaufwand mit realloc spendieren würde, würde ich wohl das Mitzählen der Buchstaben "live" machen, also jeden eingelesenen 16-Zeichen-Block direkt bearbeiten. Somit könnte ich auf dynamische Speicherverwaltung an der Stelle komplett verzichten. Ich bin kein Fan von malloc und free, weil man da ganz schnell mal Fehler macht und realloc verwende ich eigentlich ohnehin nie.

Bei meiner Implementierung der Arrayzugriffe mit Bitmasken gebe ich dir Recht, die versteht man leider auch erst wenn man sich die ASCII Tabelle ansieht. Aber ein bisschen effizient wollte ich das an der Stelle halt machen.

j o e
 
Zurück
Oben