C zufälligen Buchstaben generieren?

5 Antworten

Vom Fragesteller als hilfreich ausgezeichnet

Deine Frage ist gar nicht so leicht zu beantworten. Das liegt aber weniger am gestellten Problem selbst, sondern weil ich nicht einschätzen kann, auf welchem Lernstand du dich befindest, und welche Features der Programmiersprache für dich nachvollziehbar sind. :)

Außerdem gibt es gerade bei C unendlich viele Aspekte, die berücksichtigt werden wollen, vor allem den Stil, Entwurfsmuster und Paradigmen betreffend. (Soll dein Code auf einem eingebetteten System laufen, oder eher auf einer Workstation? Im Folgenden gehe ich mal realistischerweise von einem Desktop-Computer aus.)

Trotzdem versuche ich es mal, möchte allerdings noch auf etwas hinweisen: In der Standardbibliothek findest du zwar die Funktionen srand() und rand(), mit denen man angeblich (!) Pseudozufallszahlen erzeugen können soll, allerdings ist die im Standard seit jeher vorgeschriebene Implementierung eines deterministischen Pseudozufallszahlengenerators qualitativ äußerst dürftig und erzeugt ausgesprochen schlechte Zufallszahlen. (Die sind sogar so dermaßen schlecht, dass immer dieselbe Person gewinnen wird, falls du damit ein "Würfelspiel" schreiben würdest!)

Es ist aber total einfach, einen wirklich überdurchschnittlichen Zufallszahlen-Generator selbst zu schreiben, indem man auf gängige Xor-Shift-Implementierungen zurück greift. Deshalb implementiert folgender Code einen solchen Mini-Pseudo-Zufallszahlen-Generator, der exzellente Ergebnisse liefert. (Hinweis für Interessierte: Er besteht sogar die statistischen Tests der dieharder-Testsuite!)

Unter dem Code gibt es noch eine ganze Latte weiterer Anmerkungen, aber hier erst mal das Miniprogramm, welches zehn zufällige Strings ausgibt:

#include <stdio.h> /* printf() */
#include <stdlib.h> /* EXIT_SUCCESS, size_t */
#include <time.h> /* clock(), time() */

typedef unsigned prng;

prng prng_rand(prng state) {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;

return state;
}

#define PRNG_INIT() \
prng_init((prng)(time(NULL) ^ clock()))

prng prng_init(const prng seed) {
const prng pi_hex = 0x40490FDB;

return prng_rand(pi_hex ^ seed);
}

#define PRNG_RAND(state, max) \
((state) = prng_rand(state), (state) % (max))

#define PRNG_CHAR(state) \
('a' + PRNG_RAND((state), 26))

prng prng_cstr(prng state, char * cstr, size_t len) {
if (cstr && len) {
--len;

for (size_t i = 0; i < len; ++i) {
cstr[i] = PRNG_CHAR(state);
}

cstr[len] = '\0';
}

return state;
}

#define PRNG_CSTR(state, cstr, len) \
(state) = prng_cstr((state), (cstr), (len))

#define BUFLEN 8

int main(void) {
char cstr[BUFLEN] = {'\0'};
prng state = PRNG_INIT();

for (size_t i = 0; i < 10; ++i) {
PRNG_CSTR(state, cstr, BUFLEN);

printf("#%lu: '%s'\n", i, cstr);
}

return EXIT_SUCCESS;
}

Wie gesagt, noch einige Anmerkungen und Disclaimer bzgl. des Codes:

  • EXIT_SUCCESS aus <stdlib.h> könntest du auch durch "0" ersetzen, aber ich denke, es verdeutlich die Intention des Entwicklers besser.
  • size_t sollte in Schleifen für den Index anstelle von "int" verwendet werden. Ich weiß nicht, ob du das schon gelesen hast, aber bei großen Arrays wäre mit einem normalen "int" bei knapp 2GB Schluss und dir würde dein Programm danach abstürzen. Mithilfe von size_t kannst du auf so viel RAM zugreifen, wie du niemals im Leben haben wirst. :)
  • "prng" zeigt die Absicht auf, die hinter einer Variablen steht, "unsigned" hingegen nur den Typen. Normalerweise würde ich bei nur wenig komplexeren Typen auch in C einen objektorientierten Ansatz wählen (Ja, das geht tatsächlich in C und nicht nur in C++!), aber hier geht es nur um einen "unsigned", also wollen wir mal nicht mit Kanonen auf Spatzen schießen. :)
  • Alle Funktionen für unseren "Pseudo Random Number Generator" beginnen mit dem Präfix "prng_", da C ja leider keine Namensräume kennt. In einem "richtigen" Projekt würde ich ein sprechenderes Präfix wählen, aber hier reicht das völlig aus. Wie gesagt, Kanonen und Spatzen und so ... :)
  • Viele Leute werden sagen "Makros sind böse", und damit haben sie tatächlich oft recht, aber eben nicht immer. Und im obigen Falle verhindern die Hilfsmakros, dass man vergisst den Status des Generators zu aktualisieren. Alternativ hätte man auch eine globale oder eine statische lokale Variable nehmen können, aber das würde ich in diesem Falle als noch ekliger als Makros erachten. Das ist ein bisschen wie die Wahl zwischen Pest und Cholera und unter anderen Umständen würde ich sicher eine Referenz per Zeiger, globale Variable oder im Idealfall gar ein vernünftiges opakes Objekt bevorzugen, aber wie ich eingangs schon erwähnt habe, muss man immer anhand der Anforderungen abwägen.
  • Parameter sollten normalerweise immer const sein, sofern sie nicht weiter verwendet werden, aber um unnötige lokale Variablen zu sparen, habe ich die Parameter an einigen Stellen "recycled".
  • Ich gehe mal davon aus, dass du deinen Code mit einem Compiler kompilierst, der halbwegs aktuelles C kennt, und Variablendeklarationen nicht am Funktionsanfang erwartet. Der obige Code lässt sich zwar auch sehr leicht an C89 anpassen, aber C99 bzw. C11 ist häufig schon angenehmer.
  • Die Funktion prng_cstr() ist so geschrieben, dass man möglichst nichts kaputt machen kann, es keine Überläufe gibt und das Ergebnis ordentlich nullterminiert ist. Vielleicht sieht die Funktion noch etwas kompliziert für dich aus, aber wenn du weiter übst, wirst du sie vermutlich spätestens in ein paar Wochen problemlos verstehen.
  • Der Code ist auf Einfachheit und nocht auf Portabilität ausgelegt. Das betrifft insbesondere den Teil im Makro PRNG_CHAR(), welches einen einzigen zufälligen Buchstaben erzeugt. Der Code aus den anderen Antworten mit dem langen Stringliteral "abcdefghijklmnopqrstuvwxyz" ist hier deutlich Portabler! Aber da man heutzutage kaum noch auf eine Nicht-ASCII-Plattform stoßen wird, ist die Berechnung mit Offset von 'a' aus vermutlich auch akzeptabel. Merke dir aber bitte, dass Annahmen zum Zeichensatz laut C-Standard niemals portabel sein werden!
  • Ich weiß, dass der obige Code-Schnipsel nicht dein Problem erschlägt, aber es ist auch nicht meine Absicht, dir deine Ideen zu entwickeln. Programmieren lernen musst du selbst und der Quelltext von oben wird dich hoffentlich etwas weiter bringen, inspirieren, anregen oder interessieren.

Naja, es gibt noch weitere Kleinigkeiten, aber ich glaube, das reicht jetzt so langsam. Vermutlich überfordert dich der obige Code und die Anmerkungen etwas, aber mach dir da nichts draus! Einfach immer schön weiter lernen, dann verstehst du automatisch irgendwann alles! :)

Schönen Abend noch! :)
TeeTier  28.10.2017, 02:20

PS: Die Ausgabe des Programms sollte bei (fast) jedem Start anders aussehen, zum Beispiel so:

#0: 'hpxtzzp'
#1: 'fpyrrgi'
#2: 'zlruzpl'
#3: 'geskudw'
#4: 'jhqqdeq'
#5: 'teueodt'
#6: 'lwgjoqw'
#7: 'npdgkij'
#8: 'fvkfegq'
#9: 'bvtaqnk'

Allerdings sind time() und clock() auch keine zuferlässigen Entropiequellen, aber für einen Hangman-Klon reicht es allemal! Fang aber bitte nicht an, damit die Schlüsselgenerierung für eine RSA-Bibliothek zu entwickeln! Danke im Voraus! :)

0

Ich weiß nicht, wie die Syntax in C genau ist, aber du könntest eine Methode zum Konvertieren von Integer nach Char benutzen. Dann lässt du eine zufällige Zahl zwischen 65 ( A  ) und 90 ( Z ) erzeugen und wandelst die dann wieder in einen Char um.

Woher ich das weiß:eigene Erfahrung – Ich habe selber lange im PC gearbeitet
PWolff  27.10.2017, 15:26

In C sind Konvertierungen überflüssig. Ein char ist nichts weiter als ein 8-Bit-Integer. Ob es als Buchstabe oder Zahl ausgegeben wird, entscheidet die Ausgaberoutine.

2

du kannst char und int ineinander konvertieren. wenn du mit rand() eine zahl generiert hast, caste sie in einen char. Schau dir die ASCII Tabelle an. Kleinbuchstaben beginnen ab 65. Also generiere ne Zahl zwischen 65 und 90 (Großbuchstaben) sowie 97 und 122 (Kleinbuchstaben) und wandle sie in einen char um.

Woher ich das weiß:Berufserfahrung – Berufserfahrung
PWolff  27.10.2017, 15:27

In C sind Konvertierungen überflüssig. Ein char ist nichts weiter als ein 8-Bit-Integer. Ob es als Buchstabe oder Zahl ausgegeben wird, entscheidet die Ausgaberoutine.

3
TeeTier  28.10.2017, 02:28
@PWolff Sehr guter und wichtiger Kommentar!

Das ist einer der Gründe, warum ich denke, dass Einsteiger eher früher als später mit der Assemblerprogrammierung in Berührung kommen sollten!

Dann wird einem nämlich erst mal so richtig klar, dass beliebig definierte Speicherabschnitte entweder ein Zeichen, eine Ganzzahl, ein Bitfeld, ein Zeiger, eine Gleitpunktzahl oder im Falle von SIMD-Erweiterungen gerne auch mal alles zusammen sein können / dürfen / sollen / müssen.

Oder wenn man sich anguckt, wie so die gängigen ABIs über Stack und / oder Register funktionieren.

C ist zwar sehr "low-level", aber mit Assembler versteht der gemeine Einsteiger hier normalerweise deutlich schneller, dass eigentlich alles nur "Ansichtssache" ist. :)

1
TeeTier  28.10.2017, 02:52

Bei ASCII gilt sogar:

  • 'a' ^ 0x20 == 'A'

... und daraus resultierend ...

  • 'A' ^ 0x20 == 'a'

... lustig, oder? Wenn man also weiß, dass es sich um einen (Groß- oder Klein-) Buchstaben handelt, kann man mit einem XOR die Größe ändern:

char swap_case(const char c) { return c ^ 0x20; }

Ist zwar nicht portabel und ein Kandidat für ernste Sicherheitslücken, aber schön um Leute zum Grübeln zu bringen. :)

0

Das Wort solltest du als Array mit definierter Länge speichern (denk ans Endezeichen \0).

char randomletter = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[random () % 26];

Das wäre eine Möglichkeit zufällige Buchstaben zu generieren.

Nun kannst du überprüfen, ob randomletter dem Zeichen an der entspr. Stelle des Wortes entspricht, bzw mit einer weiteren Schleife, ob randomletter im gesamten Wort vorkommt.

Schwieriger wird es einzubasteln, ob der PC bereits z.B. "D" "erraten" hat.

Dann könntest du noch ein int erstellen für 11 Galgenelemente, was bei flaschem Buchstaben um eins erhöht wird und bei Galgen = 11 -> x.x .

Willst du den Code so verwenden, solltest du alle Buchstaben des Wortes "uppern"

Du solltest auch einen Exception Fall einbauen, falls User ein Sonderzeichen eingegeben hat. 

Joa das war's von meiner Seite erstmal.

Viel Spaß :)

Hab ich mal in mein Projekt eingebaut

string GetRandomString(int length)
{
srand(GetTickCount());
auto randchar = []() -> char
{
const char charset[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" ;
const size_t max_index = (sizeof(charset) - 1);
return charset[rand() % max_index];
};
std::string str(length, 0);
std::generate_n(str.begin(), length, randchar);
return str;
}

Als Länge dann einfach eine 1 übergeben

TeeTier  28.10.2017, 02:45

Das ist C++ und kein C! Aber C++ bietet im Gegensatz zu C aber eine sehr ausgereifte Bibliothek für Zufallszahlen, weshalb ich nicht nachvollziehen kann, warum du auf srand() / rand() zurück greifst, die katastrophale Ergebnisse liefern.

Zumal du ansonsten recht modernes C++ inkl. Lambdas und Helferlein aus <algorithm> nutzt.

Mach es doch lieber so:

using namespace ::std; // da lokaler Scope

default_random_engine prng { random_device {} () };
uniform_int_distribution<char> dist { 'a', 'z' };

char randchar = dist(prng);

Damit nutzt du als Seed sogar den Hardware-Zufallszahlengenerator, was deutlich besser ist, als time() ^ clock() aus meiner, oder dem nicht portablen GetTickCounter() aus deiner Antwort. :)

Ansonsten ein guter Ansatz mit dem Offset im Stringliteral und schön dass du moderne C++ Features nutzt!

0