c# abfrage ob char in string vorhanden ist und wie oft?

3 Antworten

Ein paar Dinge, die mir auffallen:

char letter = Convert.ToChar(Console.ReadLine().ToLower());

Damit liest du eine ganze Zeile ein und wandelst die ganze Zeile dann in einen einzelnen Buchstaben um. C# ist schlau genug, in dem Fall nur den ersten Buchstaben umzuwandeln. Andere Programmiersprachen würden hier einfach einen Fehler auswerfen. Ich würde es so umbauen, dass tatsächlich nur ein Buchstabe eingelesen wird:

char letter = char.ToLower(Console.ReadKey().KeyChar);

Deine Schleife hat zwei Fehler:

  1. Du wiederholst die Schleife so oft, wie das Wort lang ist, aber willst dann jedes mal die Variable "amount" erhöhen, wenn der Buchstabe überhaupt im Wort vorkommt. Das heißt: amount kann am Ende nur entweder 0 oder die Länge des Wortes sein.
  2. Die Anweisung "amount = amount++;" funktioniert nicht. Mit amount++ erhöhst du die Variable amount, allerdings schreibst du gleichzeitig den alten Werten von amount wieder zurück zu amount, so dass amount immer 0 bleibt. Du kannst dir eine dieser drei Varianten aussuchen, wenn du eine Zahl um 1 erhöhen willst:
amount++;
amount = amount + 1;
amount += 1;

Ich weiß nicht genau, was du mit "Das Programm crashed" meinst. Wahrscheinlich geht einfach das Konsolenfenster sofort zu, wenn du einen Buchstaben eingegeben hast. Das liegt daran, dass das Programm dann zu Ende ist und halt zu geht. Du kannst unter deinen Code aber noch ein Console.ReadKey() machen, damit das Programm noch offen bleibt. Man kann auch in Visual Studio unter "Extras">"Optionen">"Debugging">"Konsole beim Beenden des Debuggings automatisch schließen" einstellen, dass das Konsolenfenster noch offen bleiben soll, wenn das Programm zu Ende ist.

Ich mag übrigens Linq sehr gerne. Hätte das Problem wahrscheinlich so gelöst:

public static void Main()
{
    string wort = "Klassenarbeit".ToLower();
    Console.Write("Geben Sie einen Buchstaben ein: ");
    char letter = char.ToLower(Console.ReadKey().KeyChar);

    Console.WriteLine();
    Console.WriteLine($"Der Buchstabe \"{letter}\" ist im Wort \"{wort}\" {wort.Count(curLetter => curLetter == letter)} mal enthalten.");

der klassiker:

using System;
class Prog{
    public static void Main(string[] args){
        string source = "Rabenfutteressensvergabestelle";
        //Am Char in ein Array zerlelgen und  die Anzahl der  Elemente  zählen -1
        int count = source.Split('e').Length - 1;
        Console.WriteLine("Anzahl der e:{0}",count);
        Console.ReadKey();
    }
}

mit Linq... https://docs.microsoft.com/de-de/dotnet/api/system.linq.enumerable.count?view=net-6.0

using System;
using System.Linq;
class Prog{
    public static void Main(string[] args){
        string source = "Rabenfutteressensvergabestelle";
        int count = source.Count(f => f == 'e');
        Console.WriteLine("Anzahl der e:{0}",count);
        Console.ReadKey();
    }
}

Naja der Vollständigkeit halber auch noch die Iteration:

using System;
class Prog{
    public static void Main(string[] args){
        string source = "Rabenfutteressensvergabestelle";
        int count = 0;
        foreach (char c in source) {
            if (c == 'e') count++;
        }
        Console.WriteLine("Anzahl der e:{0}",count);
        Console.ReadKey();
    }
}
Palladin007  16.03.2022, 14:19

Wo wir bei der Optimierung sind:

Die foreach-Schleife, die Du im letzten Snippet nutzt, dürfte auch etwas langsamer sein, als die for-Schleife.

Die for-Schleife kann direkt über den Indexer arbeiten und ist damit die performanteste Variante. Die foreach-Schleife braucht aber einen Enumerator, was in dem Fall min. ein Objekt (CharEnumerator) bedeutet und der wiederum tut mehr, als nur Pointer hoch rechnen.

In den meisten Fällen ist das egal, aber es lohnt sich, den Unterschied zu kennen, weniger wegen der Performance, mehr für das Verständnis von IEnumerable, da das ein sehr wichtiger Teil in .NET ist, der häufig missverstanden oder falsch eingeschätzt wird. Es gab schon ganze Projekte, die daran gescheitert sind ^^

Gemessen habe ich es diesmal aber nicht.

0

Dein Algorithmus wird so nicht korrekt zählen. Ich habe den einmal korriegiert. Allerdings habe ich keinen Fehler bekommen. Lauffähig war es schon vorher

Bild zum Beitrag

1) Das Wort zuerst zu einem Array machen.

2) Den jeweiligen Buchstaben im Array mit dem Index i prüfen.

3) Amount entwerder so oder mit "amount = amount + 1" nach oben zählen.

Woher ich das weiß:Berufserfahrung
 - (Computer, programmieren, C Sharp)
Erzesel  14.03.2022, 18:41

eine Möglichkeit. aber ein schreckliches Gemetzel.

an einfachsten ist es das Wort gleich an den zu zählenden Chars als Delimiter zu splitten. dann braucht man nicht selber zu zählen, das erledigt die Methode Count des ergebenden Arrays viel schneller

0
Palladin007  14.03.2022, 22:25
@Erzesel

... hat aber den Nachteil, dass man durch das Zählen gleich ein paar Strings mehr im RAM hat, der GC hat also mehr zu tun.

Wenn das relevant sein sollte, dürfte die for-Variante alles andere schlagen.

Bei mir:

For: 45ns, 0B
Split: 121ns, 408B

Wenn man das an mehreren Stellen im Code braucht, würde ich eine ExtensionMethod daraus machen und darin dann die for-Variante verwenden.

1
Erzesel  16.03.2022, 10:24
@Palladin007

Ich sitz grade in der Bahn und habe meinen Gedanken freien Lauf gelassen... Ich kann aktuell nicht Nachprüfen...

Die 0 Byte können aber nicht stimmen..., schließlich wandelst Du den String weiter oben bereits in ein char Array, was Speicher und Zeit kostet. Noch schlechter dürfte die Bilanz aussehen , wenn der String zur Compilierungszeit nicht bekannt ist und somit die Codeoptimierung durch den Compiler wegfällt?

Um beim Vergleich der Varianten Fairnes herzustellen, sollte die Conversion zum Array mit drin sein...😏, denn das ist Voraussetzung für die Iteration per For-Loop.

0
Palladin007  16.03.2022, 10:51
@Erzesel

Die Messdaten stammen von BenchmarkDotNet, was unter Anderem auch von Microsoft genutzt wird, um ihre Detail-Optimierungen zu überprüfen. Ich denke daher, dass man bedenkenlos auf die Zuverlässigkeit vertrauen kann.

Und doch, die 0 Byte stimmen, dein Denkfehler liegt hier:

schließlich wandelst Du den String weiter oben bereits in ein char Array

Ein Array ist technisch ein Start-Index, das Wissen über die Länge jedes einzelnen Items und die Anzahl. Nichts anderes ist ein String: Ein Start-Index, das Wissen über die Länge jedes Zeichens und die Länge.

Würde ich vorher ToString aufrufen, hast Du Recht, dann wird ein Char-Array allokiert werden, aber das passiert hier nicht. Der Indexer kann direkt auf der originalen String-Referenz (bzw. dem ersten char-Pointer) arbeiten und muss nichts allokieren.

Das kannst Du auch im Code nachlesen: String.this[index]
Die Unsafe-Klasse ist nicht in C# geschrieben, sondern in IL. Ich hatte auch mal den GitHub-Link dazu, finde ihn aber auf Anhieb nicht mehr - steht aber auch in den Kommentaren.

Die String-Implementierung arbeitet also in etwa genauso, wie ein Array oder String für C++: Ein Pointer auf ein erstes Zeichen und die Länge. Jeder Zeichen-Zugriff ist demnach Pointer + (Index * sizeof(char)) und das muss nichts allokieren.

Aber natürlich muss der String einmal in den RAM gezogen werden, das gilt aber für jede Variante, immerhin dreht sich die Arbeit um einen String.
Der Grund, warum da trotzdem 0 Byte steht, ist, dass ich den String als statische Variable deklariert habe und der Benchmark sehr oft misst und einen Mittelwert berechnet. Hätte ich das nicht gemacht oder den String random erzeugen lassen, würde der Benchmark die Performance der String-Implementierung messen und nicht for-Schleife vs. Split, worum es mir aber eigentlich ging.

Um beim Vergleich der Varianten Fairnes herzustellen, sollte die Conversion zum Array mit drin sein...😏, denn das ist Voraussetzung für die Iteration per For-Loop.

Warum? ;)
Fairness zählt nicht, sondern nur die messbare Performance und warum ToArray keine Voraussetzung ist, habe ich oben beschrieben.

1
Erzesel  16.03.2022, 12:32
@Palladin007

Danke für Wissen bzgl. der Vorgänge unter der "Motorhaube".

Gerade die Vorgänge um solch einfache Dinge wie Strings, nimmt man irgendwie als gegeben hin.

Muss wohl, wenn ich mal wieder am Rechner hocke, mich mal tiefer in die Maschinerie von C# graben...

Man lernt eben nie aus...

1
Palladin007  16.03.2022, 13:57
@Erzesel
Gerade die Vorgänge um solch einfache Dinge wie Strings, nimmt man irgendwie als gegeben hin.

... und sie sind wirklich überall wichtig, weshalb selbst wenige Nanosekunden einen relevanten Unterschied machen können.

Schau dir z.B. die letzten Versionen (C# 7, 8, 9, 10) an, da gab's viele Funktionen, die hauptsächlich in Richtung Performance-Optimierung für Framework-Entwickler deuten.

Muss wohl, wenn ich mal wieder am Rechner hocke, mich mal tiefer in die Maschinerie von C# graben...

Guckst Du hier:

Performance Improvements in .NET 5 - .NET Blog (microsoft.com)
Performance Improvements in .NET 6 - .NET Blog (microsoft.com)
(Der Mann ist generell ein guter Anlaufpunkt für Detail-Wissen)

Aber Vorsicht, die Artikel gehen weit über "alles andere als einfach" hinaus - ich habe sie unter der Ausrede, keine Zeit zu haben, noch nicht durchgearbeitet. :D
Vor allem solltest Du, um das zu verstehen, Assembler und CIL zumindest verstehen, weil auf genau der Ebene finden viele Optimierungen statt. Und sie messen mit BenchmarkDotNet.

Mein Wissen habe ich hauptsächlich aus dem SourceCode von .NET, den Link findest Du im vorherigen Kommentar. Oder auf GitHub: dotnet/runtime

Ach ja, für die einfache Kategorie von Optimierungen hilft auch SharpLab.io

1
Palladin007  16.03.2022, 14:01
@Erzesel

PS:

Ich sehe gerade, deine Aussage zum Array stimmt ja doch - wenn man sich die ursprüngliche Antwort wirklich durchliest :D

Das Wort zuerst zu einem Array machen.

Das ist natürlich Quatsch, das Array braucht man nicht.
Aber jetzt verstehe ich, warum Du das geschrieben hast.

1
Palladin007  16.03.2022, 14:03
1) Das Wort zuerst zu einem Array machen.

Das ist überflüssig und verschenkt die Performance.

Der String hat auch einen Indexer, man kann also direkt mit dem String arbeiten:

if (word[i] == letter)
    amount++;
0