Forum: PC-Programmierung C++ Codeoptimierung Tastatureingabe


von Titusthefox (Gast)


Lesenswert?

Guten morgen.

Ich habe in C++ eine Aufgabenstellung zu lösen: Es sollen Beliebige 
Zeichen von der Tastatur eingelesen werden, dann sollen bestimmte 
Zeichen aus der Eingabe herausgefiltert werden und bei Eingabe eines 
bestimmten Zeichens soll das Einlesen abbrechen und das Programm mit 
einer Meldung beenden.

Realisiert habe ich das auf diese Weise, nun würde mich interessieren ob 
man das eleganter lösen kann:
1
#include <iostream>
2
#include <string>
3
using namespace std;
4
5
const string punctuation {".,;-_!?"};
6
const char EXIT {'~'};
7
const char NEWLINE {'\n'};
8
9
void is_punctuation(string& s) {
10
  for(char& ch : s)
11
    for(char w : punctuation)
12
      if(ch==w)
13
        ch = ' ';
14
}
15
16
int main() {
17
  char ch;
18
  string s {};
19
20
  while(cin.get(ch)) {
21
    s+=ch;
22
    if(ch==NEWLINE) {
23
      is_punctuation(s);
24
      cout << s << '\n';
25
      s = "";
26
    }
27
    else if(ch==EXIT) {
28
      is_punctuation(s);
29
      cout << s << '\n';
30
      cout << "Programm wurde mit " << EXIT << " beendet." << '\n';
31
      break;
32
    }
33
  }
34
}

Eingabe: hello world ohne . und , te~st
Ergebnis: hello world ohne   und   te~
Programm wurde mit ~ beendet.

Funktionieren tut es.

von PittyJ (Gast)


Lesenswert?

Cool, eine Programm ohne jeglichen Kommentar.
Schon lange her, dass ich das mal erleben durfte.


Das winchtigste ist nicht die Eleganz, sondern dass du das nach 5 Jahren 
auch noch verstehst.

von (Gast)


Lesenswert?

Stilistisch: Funktionen mit Namen is_irgendwas sollten normalerweise ein 
"Prädikat" sein, also ein bool liefern, und keinen Zustand haben bzw. 
verändern (pure Funktion).

ansonsten wird man das nicht substanziell anders machen können.

von Titusthefox (Gast)


Lesenswert?

PittyJ schrieb:
> Cool, eine Programm ohne jeglichen Kommentar.
> Schon lange her, dass ich das mal erleben durfte.

Ok Optimierung Nr.1 :)

von (Gast)


Lesenswert?

natürlich sollte man wenn man schon "int main()" deklariert dem body 
auch ein "return 0" spendieren.

von Titusthefox (Gast)


Lesenswert?

rµ schrieb:
> Stilistisch: Funktionen mit Namen is_irgendwas sollten normalerweise ein
> "Prädikat" sein, also ein bool liefern, und keinen Zustand haben bzw.
> verändern (pure Funktion).

Ok ich benenne sie um in delete_punctuation()
Nr.2

von MaWin (Gast)


Lesenswert?

Titusthefox schrieb:
> würde mich interessieren ob man das eleganter lösen kann:

Natürlich.
Man kann die doppelten Programmteile im EXIT und NEWLINE Zweig 
einsparen, und die quadratische O(n2) Laufzeit je nach punctuation 
Zeichenanzahl auf lineare Zeit O(n) reduzieren.

Das puffern einer ganzen Zeile vor Ausgabe wird wohl Absicht sein, den 
Puffer s könnte man sonst auch einsparen.

von Titusthefox (Gast)


Lesenswert?

MaWin schrieb:
> Man kann die doppelten Programmteile im EXIT und NEWLINE Zweig
> einsparen, und die quadratische O(n2) Laufzeit je nach punctuation
> Zeichenanzahl auf lineare Zeit O(n) reduzieren

Code Vorschlag?

von (Gast)


Lesenswert?

Titusthefox schrieb:
> quadratische O(n2) Laufzeit je nach punctuation
>> Zeichenanzahl auf lineare Zeit O(n) reduzieren
>
> Code Vorschlag?

bool is_punctiation(char const ch)
{
  // return true iff ch in punctuation
  ...
}

while(cin.get(ch)) {
  if (is_punctuation(ch))
    ch = ' ';
  ...
}

und den std::string könnte man durch ein constexpr std::array ersetzen.

von Marten Morten (Gast)


Lesenswert?

PittyJ schrieb:
> Cool, eine Programm ohne jeglichen Kommentar.
> Schon lange her, dass ich das mal erleben durfte.

Da kann ich dir eimerweise Openssource Programme zeigen. Da wird immer 
noch die alte Einstellung "It was hard to write, it should be hard to 
read" gepflegt.

von Wilhelm M. (wimalopaan)


Lesenswert?

Marten Morten schrieb:
> PittyJ schrieb:
>> Cool, eine Programm ohne jeglichen Kommentar.
>> Schon lange her, dass ich das mal erleben durfte.
>
> Da kann ich dir eimerweise Openssource Programme zeigen. Da wird immer
> noch die alte Einstellung "It was hard to write, it should be hard to
> read" gepflegt.

Code und Comment altern unterschiedlich.
Deswegen sollte man Kommentare auf das absolute Minimum beschränken und 
folgendes beachten:

"Kommentiere nur das, was der Code nicht sagen kann, und nicht das, was 
der Code nicht sagt!".

Daraus erwächst natürlich eine Handlungsanweisung ... die leider oft 
missachtet wird.

von Vincent H. (vinci)


Lesenswert?

Prinzipiell finde ich den Code eigentlich recht gut. Er ist halt noch 
recht imperativ was einige Nachteile mitbringt. Umso vertrauter man mit 
der Standardbibliothek und modernem C++ ist umso deklarativer und 
kompakter ließe sich die Aufgabenstellung lösen.

Die Frage nach "eleganter" ist immer etwas gemein weil ich dich jetzt 
nicht mit dir eventuell unbekannten Dingen erschlagen will. Hier mal 
eine Version die auf moderne Features von C++20 setzt (ranges: 
https://en.cppreference.com/w/cpp/ranges)

Code:
https://godbolt.org/z/MgGMGw

Da Standard-Input mim Online-Compiler etwas schwierig ist ;) gibt es 
oben ein #define zum Ein- und Ausschalten.


rµ schrieb:
> natürlich sollte man wenn man schon "int main()" deklariert dem body
> auch ein "return 0" spendieren.

Es gibt keine Alternative zu int main(), zumindest keine gültige.

von leo (Gast)


Lesenswert?

Vincent H. schrieb:
> Es gibt keine Alternative zu int main(), zumindest keine gültige.

Nein. Gerade fuer uC gilt oft:

5.1.2.1 Freestanding environment

In a freestanding environment (in which C program execution may take 
place without any benefit of an operating system), the name and type of 
the function called at program startup are implementation-defined. / .. 
/ The effect of program termination in a freestanding environment is 
implementation-defined.

leo

von (Gast)


Lesenswert?

Vincent H. schrieb:
> rµ schrieb:
>> natürlich sollte man wenn man schon "int main()" deklariert dem body
>> auch ein "return 0" spendieren.
>
> Es gibt keine Alternative zu int main(), zumindest keine gültige.

naja, mit gespaltener Zunge gesprochen gäbs da natürlich noch die 
Variante mit argc/argv ;-)

Das ändert natürlich nichts am return-wert. Eine Funktion die nicht void 
deklariert ist sollte auch was zurückgeben.

wens interessiert:
https://stackoverflow.com/questions/2108192/what-are-the-valid-signatures-for-cs-main-function/

von Wilhelm M. (wimalopaan)


Lesenswert?

rµ schrieb:
> Eine Funktion die nicht void
> deklariert ist sollte auch was zurückgeben.

Bei main() ist das anders: impizites return 0;

von Vincent H. (vinci)


Lesenswert?

void main()

gcc
error: '::main' must return 'int'

clang
error: 'main' must return 'int'

ellcc
error: 'main' must return 'int'

power64
error: '::main' must return 'int'

zappcc
error: 'main' must return 'int'


Einzig und allein msvc und icc schlucken void...


/edit
Achja, und wir sprechen von C++, nicht von C

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

rµ schrieb:
> naja, mit gespaltener Zunge gesprochen gäbs da natürlich noch die
> Variante mit argc/argv ;-)

Und die mit den Env-Variablen gibt es ggf. auch noch (impl-defined)

von Wilhelm M. (wimalopaan)


Lesenswert?

Und eine etwas "oldschool" Variante:
1
#include <iostream>
2
#include <string>
3
#include <algorithm>
4
#include <iterator>
5
#include <set>
6
7
#define USE_FAKE_STDIN
8
9
int main() {
10
    const std::set<char> punctuation{'.', ',', ';', '-', '_', '!', '?'};
11
#ifdef USE_FAKE_STDIN
12
  std::string in{"hello world ohne . und , te~st"};
13
#else
14
  std::string in;
15
  std::getline(std::cin, in);
16
#endif
17
18
  const auto end = std::find(std::begin(in), std::end(in), '~');
19
  std::transform(std::begin(in), end, std::begin(in), [&](const char c){
20
      if (punctuation.contains(c)) {
21
          return ' ';
22
      }
23
      return c;
24
  });
25
  
26
  std::copy(std::begin(in), end, std::ostream_iterator<char>(std::cout));
27
}

von Rolf M. (rmagnus)


Lesenswert?

Wilhelm M. schrieb:
> rµ schrieb:
>> Eine Funktion die nicht void
>> deklariert ist sollte auch was zurückgeben.
>
> Bei main() ist das anders: impizites return 0;

Sollte man der Vollständigkeit halber trotzdem hinschreiben. Ich würde 
auch mal gerne wissen, was die Standard-Macher da geritten hat, extra 
für main() einen Sonderfall in die Sprachdefinition einzubauen, nur 
damit man dort das sonst nötige return weglassen kann. Das erscheint mir 
wenig sinnvoll.

leo schrieb:
> Gerade fuer uC gilt oft:
>
> 5.1.2.1 Freestanding environment

Allerdings nicht so oft wie man denkt. Einige µC-Implementationen sind 
Hosted Environments.

von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
> Sollte man der Vollständigkeit halber trotzdem hinschreiben.

Nein, denn ohne das return 0; ist es ja nicht unvollständig.

von MaWin (Gast)


Lesenswert?

Titusthefox schrieb:
> Code Vorschlag

State machine, dispatch nach Zeichen '\n', '~', '.', ',', ';', '-', '_', 
'!' und anderen.

von Rolf M. (rmagnus)


Lesenswert?

Wilhelm M. schrieb:
> Rolf M. schrieb:
>> Sollte man der Vollständigkeit halber trotzdem hinschreiben.
>
> Nein, denn ohne das return 0; ist es ja nicht unvollständig.

Bezogen auf Standardkonformität nicht. Allerdings wäre Code nicht 
sonderlich lesbar, wenn immer man nur das technisch allernötigste 
hinschreiben würde.

von zitter_ned_aso (Gast)


Lesenswert?

Titusthefox schrieb:
> if(ch==NEWLINE) {
>       is_punctuation(s);
>       cout << s << '\n';
>       s = "";
>     }
>     else if(ch==EXIT) {
>       is_punctuation(s);
>       cout << s << '\n';

mag ich nicht.  Die gleichen Zeilen stehen mehrmals untereinander.  Von 
Dr. Sommer habe ich hier gelernt, dass man sowas vermeiden sollte 
(DRY-Prinzip: Don't Repeat Yourself).
1
while(cin.get(ch)) {                                                  
2
    s+=ch;                                                              
3
                                                                        
4
    switch(ch){                                                         
5
        case NEWLINE:                                                   
6
        case EXIT:   remove_punctuation(s);                             
7
                cout<<s<<'\n';                                          
8
                s="";                                                   
9
    }                                                                   
10
                                                                        
11
    if(ch==EXIT)                                                        
12
        break;     
13
}

von leo (Gast)


Lesenswert?

zitter_ned_aso schrieb:
> while(cin.get(ch)) {
>     s+=ch;

Jetzt sehe ich's erst: das ist ja voellig verkehrt - zuerst alles 
anfuegen, dann die Sonderzeichen wieder entfernen bzw ersetzen.

leo

von zitter_ned_aso (Gast)


Lesenswert?

dann kann man im Prinzip gleich alle Zeichen einzeln ausgeben (ohne sie 
in einem String zu speichern)

von M.K. B. (mkbit)


Lesenswert?

Wilhelm M. schrieb:
> Und eine etwas "oldschool" Variante:

Warum oldschool?

Ich würde das Transform durch ein replace_if ersetzen. Ist zwar im 
Prinzip das gleiche, aber die Funktion sagt gleich aus, was man damit 
tut.

Das set ist eine elegante Lösung, aber evtl. nicht die performanteste. 
Bei wenigen zu suchenden Zeichen könnte eine lineare Suche auf einem 
String schneller sein. (Indirection beim Zugriff und fehlende Cache 
locality) std::string::contains gibt es leider erst ab C++20, aber man 
kann ja auch einen bestehenden stl Algorithmus nehmen.

von Wilhelm M. (wimalopaan)


Lesenswert?

M.K. B. schrieb:
> Warum oldschool?

Das meinte ich im Vergleich zu der ranges-Variante.

M.K. B. schrieb:
> Ich würde das Transform durch ein replace_if ersetzen. Ist zwar im
> Prinzip das gleiche, aber die Funktion sagt gleich aus, was man damit
> tut.

Das stimmt:
1
#include <iostream>
2
#include <string>
3
#include <algorithm>
4
#include <iterator>
5
#include <array>
6
7
#define USE_FAKE_STDIN
8
9
int main() {
10
    constexpr std::array punctuation{'.', ',', ';', '-', '_', '!', '?'};
11
#ifdef USE_FAKE_STDIN
12
    std::string in{"hello world ohne . und , te~st"};
13
#else
14
    std::string in;
15
    std::getline(std::cin, in);
16
#endif
17
    
18
    const auto end = std::find(std::begin(in), std::end(in), '~');
19
    std::replace_if(std::begin(in), end, [&](const char c){ 
20
        return std::find(std::begin(punctuation), std::end(punctuation), c) != std::end(punctuation);
21
    }, ' ');
22
    
23
    std::copy(std::begin(in), end, std::ostream_iterator<char>(std::cout));
24
}

M.K. B. schrieb:
> Das set ist eine elegante Lösung, aber evtl. nicht die performanteste.

Auch das stimmt: hier war mir die Expressivität des Codes wichtig: 
doppelete Ersetzungszeichen machen keinen Sinn. Im Endergebnis aber auch 
egal.

Der größte Nachteil des std::set ist der non-constexpr ctor.

M.K. B. schrieb:
> std::string::contains gibt es leider erst ab C++20

Dann machen wir es ganz traditionell und können dann auch ein constexpr 
std::array benutzen.

Oder man fügt einen entsprechenden Algorithmus hinzu:
1
#include <iostream>
2
#include <string>
3
#include <algorithm>
4
#include <iterator>
5
#include <array>
6
7
#define USE_FAKE_STDIN
8
9
namespace {
10
    template<typename T>
11
    auto contains(const T& container, typename T::value_type e) {
12
        return std::find(std::cbegin(container), std::cend(container), e) != std::cend(container);
13
    }
14
}
15
16
int main() {
17
    constexpr std::array punctuation{'.', ',', ';', '-', '_', '!', '?'};
18
#ifdef USE_FAKE_STDIN
19
    std::string in{"hello world ohne . und , te~st"};
20
#else
21
    std::string in;
22
    std::getline(std::cin, in);
23
#endif
24
    
25
    const auto end = std::find(std::begin(in), std::end(in), '~');
26
    std::replace_if(std::begin(in), end, [&](const char c){ 
27
        return contains(punctuation, c);
28
    }, ' ');
29
    
30
    std::copy(std::begin(in), end, std::ostream_iterator<char>(std::cout));
31
}

von Titusthefox (Gast)


Lesenswert?

Welchen Vorteil bietet ein std::array gegenüber einem std::string?

von M.K. B. (mkbit)


Lesenswert?

Das Array liegt auf dem Stack und die Größe steht zur Compilezeit fest.
Der String könnte auch auf dem Heap liegen, wenn nicht die small string 
optimization greift.

Ob der String schlechter ist hängt vermutlich vom Optimizer ab.

von Wilhelm M. (wimalopaan)


Lesenswert?

Titusthefox schrieb:
> Welchen Vorteil bietet ein std::array gegenüber einem std::string?

In diesem Fall wird der generierte Code - auch wegen der Deklaration als 
constexpr - wohl ziemlich identisch sein.

Auch ohne constexpr wird bei diesen kleinen Stringlängen SSO greifen, 
und damit ist der Unterschied wieder marginal.

Grundsätzlich bringt man mit std::array eine Intention zum Ausdruck: 
dies ist eine Sammlung fester Länge von logisch zusammengehörigen 
Elementen.

(Die Länge von std::array ist ja im Gegensatz zu std::string oder 
std::vector zur Compilezeit fest und damit unveränderbar)

Ich sehe eher einen semantischen Unterschied: in meinen Augen bringt das 
std::array (neben den Anmerkungen von oben) klar zum Ausdruck, dass 
diese einzelnen Zeichen nicht als zusammenhängende Zeichenkette (wie 
etwa der Text) anzusehen ist, sondern einfach nur als eine Menge von 
Trennzeichen. Genau aus diesem Grund halte ich eben std::set für die 
beste Wahl (das mit dem non-constexpr ctor wird sich ja bald lösen). 
Eine Menge bringt diesen Sachverhalt zusammen mit der Mengensementik am 
besten zum Ausdruck.

von sid (Gast)


Lesenswert?

Titusthefox schrieb:
> Ich habe in C++ eine Aufgabenstellung zu lösen: Es sollen Beliebige
> Zeichen von der Tastatur eingelesen werden, dann sollen bestimmte
> Zeichen aus der Eingabe herausgefiltert werden und bei Eingabe eines
> bestimmten Zeichens soll das Einlesen abbrechen und das Programm mit
> einer Meldung beenden.

das nennt sich Keylogger;
und die eigentliche Aufgabe wird sein das Antiviren programm davon zu 
überzeugen dass Du harmlos bist.

ich bin raus keylogger mag ich nicht
die klauen eh nur Logins und passworte
und sind genau zu garnichts anderem gut.

Alles andere kann ein Eingabefeld haben

von Peter D. (peda)


Lesenswert?

Titusthefox schrieb:
> nun würde mich interessieren ob
> man das eleganter lösen kann:

Ich würde erstmal Standardfunktionen nehmen, ehe ich was selber zu Fuß 
mache, z.B. strtok.

von Oliver S. (oliverso)


Lesenswert?

Vincent H. schrieb:
> Hier mal
> eine Version die auf moderne Features von C++20 setzt (ranges:
> https://en.cppreference.com/w/cpp/ranges)

Wilhelm M. schrieb:
> Und eine etwas "oldschool" Variante:

Modernes, supermodernes, und hypermodernes C++ nutzt nichts, wenn es die 
Aufgabenstellung nicht erfüllt.

Titusthefox schrieb:
> und bei Eingabe eines
> bestimmten Zeichens soll das Einlesen abbrechen und das Programm mit
> einer Meldung beenden.

Oliver

von Titusthefox (Gast)


Lesenswert?

Eine Frage zwischendurch, ist der Wert eines solchen Konstrukts 
definiert oder kann es auch undefiniert sein? Bitte mal von der 
Sinnhaftigkeit absehen.
1
if(QUOTEMARK==false) {
2
   QUOTEMARK = !QUOTEMARK;
3
}

von Titusthefox (Gast)


Lesenswert?

Wie könnte man den Code erweitern wenn alle Punktuationszeichen entfernt 
werden sollen, nicht aber die Punktuationszeichen zwischen 
Anführungszeichen, zB.

Eingabe: Hallo, Test! "Hallo, Test!"
Ausgabe: Hallo  Test  "Hallo, Test!"

von MaWin (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Oder man fügt einen entsprechenden Algorithmus hinzu:

Was für ein irrwitziger Aufwand von durch hyperkomplizierte Konstrukte 
verblendete Programmieranfänger die im ersten Semester nicht aufgepasst 
haben.

Insgesamt wird 4 mal durch den String n iteriert, und davon ein Mal für 
jedes Stringzeichen m durch die punctuation Zeichen, also n*m+3n.

Das ist quadratische Laufzeit und Overhead ohne Ende, typisch für die 
modernen gigabytefressenden Programme die nichtmal einen Mäusefurz 
hinbekommen.
1
while(cin.get(c))
2
{
3
  switch(c)
4
  {
5
  case '~':
6
  case '.':
7
  case ',':
8
  case ';':
9
  case '-':
10
  case '_':
11
  case '!':
12
    c=' ';
13
    break;
14
  case EXIT:
15
    cout << EXIT << '\n' << "Programm wurde mit " << EXIT << " beendet." << '\n'; 
16
    return;
17
  }
18
  cout << c;
19
}
Speicherbedarf: 1 byte. Laufzeit 1 * n. Denses switch wird normalerweise 
(optimize speed) in Sprungtabelle nach Bereichsvergleich kompiliert, es 
ergibt sich also auch im Maschinencode keine Vergleichskette
1
  if(c=='.'||c==','||c==';'||c=='-'||c=='_'||c=='!'||c=='?')
die ja i.A. zu 7 nicht-zutreffenden Vergleichen führt.

Aber all das lernt man in heutigen Programmiervorlesungen wohl nicht 
mehr, man schreibt lieber mit mehr Tipparbeit grössere Programme die 
mehr Speicher brauchen und langsamer laufen, irgendwer muss ja dafür 
sorgen dass man sich ständig neue Computer kaufen muss weil die 
Programmierhansel nicht mehr so fähig sind wie früher.

von Titusthefox (Gast)


Lesenswert?

Und was ist daran schlechter?

1
while(cin.get(ch)) {
2
    s+=ch;
3
    if(ch==NEWLINE || ch==EXIT) {
4
      delete_punctuation(s);
5
      cout << s << '\n';
6
      s = "";
7
      if(ch==EXIT) break;
8
    }
9
}
1
void delete_punctuation(string& s) {
2
  bool convert {true};
3
  for(char& ch : s) {
4
    if(ch==QUOTE)
5
      convert = false;
6
    for(char w : punctuation)
7
      if(ch==w && convert==true)
8
        ch = ' ';
9
  }
10
}

von Heiko L. (zer0)


Lesenswert?

MaWin schrieb:
> switch(...)
> Speicherbedarf: 1 byte. Laufzeit 1 * n.

Richtig. Was der Compiler daraus macht interessiert für solche 
Betrachtungen nicht...

von MaWin (Gast)


Lesenswert?

Titusthefox schrieb:
> Und was ist daran schlechter

Wurde doch gesagt:

Mehrmaliges durchlaufen jeden Zeichens, dann einmal bei jedem Zeichen 
noch komplettes Durchlaufen aller punctuation Zeichen.

Könnt ihr noch nichtmal Programme durchschauen ?

Protokolliert doch einfach mal, welche Instruktionen die CPU für eine 
Beispieleingabe durchführen  muss, um verstehen zu lernen, warum C++ zu 
dermassen schlechten (hier im Beispiel schlimmer als Faktor 3 langsamer) 
Programmen führt, die weder weniger Tipparbeit noch übersichtlicher 
sind.

Was soll QUOTE in deinem Programm ? Nachträgliche Problemänderung ?

von Titusthefox (Gast)


Lesenswert?

MaWin schrieb:
> Was soll QUOTE in deinem Programm ? Nachträgliche Problemänderung ?

Habe weiter dran gearbeitet, das gehört eigentlich nicht hier rein.

MaWin schrieb:
> Protokolliert doch einfach mal, welche Instruktionen die CPU für eine
> Beispieleingabe durchführen  muss, um verstehen zu lernen, warum C++ zu
> dermassen schlechten (hier im Beispiel schlimmer als Faktor 3 langsamer)
> Programmen führt, die weder weniger Tipparbeit noch übersichtlicher
> sind.

Bin ehrlich gesagt schockiert, habe den Code aus einem bekannten C++ 
Buch. Ich nehme mir mal mit, dass man immer so einfach wie möglich beim 
Programmieren denken sollte, danke.

von Titusthefox (Gast)


Lesenswert?

MaWin schrieb:
> Protokolliert doch einfach mal, welche Instruktionen die CPU für eine
> Beispieleingabe durchführen  muss, um verstehen zu lernen, warum C++ zu
> dermassen schlechten (hier im Beispiel schlimmer als Faktor 3 langsamer)
> Programmen führt, die weder weniger Tipparbeit noch übersichtlicher
> sind.

Ich habe diesen Code genommen ohne darüber nachzudenken, eigentlich 
verwendet man ihn nur wenn man ganze Zeilen mit getline() einliest und 
den string dann analysieren/manipulieren muss.

von Titusthefox (Gast)


Lesenswert?

Habe den Code nochmal überarbeitet, jetzt liest es ganze Zeilen als 
string ein und entfernt alle Punktuationen bzw. nicht wenn etwas in 
Anführungszeichen steht. Ich denke nur so macht der Code auch Sinn.

Wäre super wenn ihr euch das nochmal anschaut und eure kritik loswerdet:
1
#include <iostream>
2
#include <string>
3
using namespace std;
4
const string punct {".,;?!_-'"};
5
6
bool is_punct(char ch) {
7
    for(char w : punct)     // macht wirklich nur Sinn wenn man mit strings arbeitet
8
        if(ch==w) return true;
9
    return false;
10
}
11
12
void
13
 convert_punct(string& s) {
14
    bool convert {true};
15
    for(char& ch : s) {     // macht wirklich nur Sinn wenn man mit strings arbeitet
16
        if(ch=='"') {
17
            convert = !convert;
18
        }
19
        else if(is_punct(ch)&&convert)
20
            ch = ' ';
21
    }
22
}
23
24
int main()
25
{
26
    cout << "Beliebige Eingabe machen\n";
27
    cout << "Zum beenden Strg+Z eingeben\n";
28
    for(string line; getline(cin,line);) {
29
        convert_punct(line);
30
        cout << line << '\n';
31
    }
32
}

von foobar (Gast)


Lesenswert?

> Das ist quadratische Laufzeit

Auch wenn du es noch zig mal wiederholst, wird es nicht wahrer.  Die 
Laufzeit ist O(n*m) mit m=konstant, also O(n).

von MaWin (Gast)


Lesenswert?

foobar schrieb:
> Auch wenn du es noch zig mal wiederholst, wird es nicht wahrer.  Die
> Laufzeit ist O(n*m) mit m=konstant, also O(n).

Stimmt.

Also: die Laufzeit steigt soeohl wenn man den Eingabestring als auch 
wenn man den punctuation String erweitert.

von Wilhelm M. (wimalopaan)


Lesenswert?

Titusthefox schrieb:
> Wäre super wenn ihr euch das nochmal anschaut und eure kritik loswerdet:

Die expliziten Schleifen fördern nicht das Verständnis des Codes. Besser 
ist es, Algorithmen zu verwenden. Und lass Dich nicht von solchen Leuten 
verwirren, die so etwas behaupten :

MaWin schrieb:
> Was für ein irrwitziger Aufwand von durch hyperkomplizierte Konstrukte
> verblendete Programmieranfänger die im ersten Semester nicht aufgepasst
> haben.

Oder:

MaWin schrieb:
> Das ist quadratische Laufzeit

Du solltest die freie Funktion nicht unbedingt is_punct(...) nennen, 
sondern contains(...) daraus machen, dann ist sie universeller 
verwendbar.

Auch sollte man Output-Parameter vermeiden. Kann man so machen, aber es 
ist die Frage, welche Probleme man sich damit später einhandelt. Kopien 
sind oft besser.

Solchen Code kann man zwar schreiben, allerdings musst Du Dir im klaren 
sein, dass dieser Code natürlich völlig unflexibel ist.

MaWin schrieb:
> while(cin.get(c))
> {
>   switch(c)
>   {
>   case '~':
>   case '.':
>   case ',':
>   case ';':
>   case '-':
>   case '_':
>   case '!':
>     c=' ';
>     break;
>   case EXIT:
>     cout << EXIT << '\n' << "Programm wurde mit " << EXIT << " beendet."
> << '\n';
>     return;
>   }
>   cout << c;
> }

Die Kunst beim Programmieren ist nicht, ein lauffähiges und für seinen 
begrenzten Einsatzzweck korrekt funktionierendes Programm zu schreiben. 
Das kann man auch mit einer solchen "Erstsemesterlösung (O-Ton MaWin) 
irgendwie hinkriegen. Aber ich glaube, dass Du über dieses Stadium schon 
hinaus bist.

Zudem Reden wir hier von PC-Programmierung:  Speicherbedarf ist nicht 
notwendigerweise ein Gütekriterium und selbst auf µC gibt es für nicht 
benutzten Speicher kein Geld zurück.

Also mach Dir Gedanken über Abstraktionen, und versuche jeweils dir 
höchste Abstraktion zu wählen.
Und mache Dir Gedanken über Namen: naming is hard, es ist vielleicht 
sogar das Schwierigste beim Programmieren.

von Wilhelm M. (wimalopaan)


Lesenswert?

Als Verallgemeinerung von

MaWin schrieb:
> while(cin.get(c))
> {
>   switch(c)
>   {
>   case '~':
>   case '.':
>   case ',':
>   case ';':
>   case '-':
>   case '_':
>   case '!':
>     c=' ';
>     break;
>   case EXIT:
>     cout << EXIT << '\n' << "Programm wurde mit " << EXIT << " beendet."
> << '\n';
>     return;
>   }
>   cout << c;
> }

kannst Du ja dies hier verwenden:
1
#include <iostream>
2
#include <string>
3
4
using namespace std;
5
6
template<typename T, T... CC>
7
struct Replace {
8
    inline static constexpr Replace with(const T& w) {
9
        return Replace(w);
10
    }
11
    T operator()(const T& in) const {
12
        if (((in == CC) || ...)) {
13
            return w;
14
        }
15
        return in;
16
    }
17
private:
18
    constexpr explicit Replace(const T& i) : w{i} {}
19
    const T w;
20
};
21
22
int main() {
23
    constexpr auto exitChar{'~'};
24
    constexpr auto replace = Replace<char, '.', ',', ';', '-', '_', '!', '?'>::with(' ');
25
    
26
    char c{};
27
    while(cin.get(c)) {
28
        c = replace(c);
29
        if (c == exitChar) {
30
            cout << exitChar << '\n' << "Programm wurde mit " << exitChar << " beendet." << '\n';             
31
            return 0;
32
        }
33
        cout << c;
34
    }   
35
}

Dies liefert etwas kürzeren Maschinencode als die switch-Variante.

von Oliver S. (oliverso)


Lesenswert?

Wilhelm M. schrieb:
> Dies liefert etwas kürzeren Maschinencode als die switch-Variante.

Mit -O3 ist der Maschinencode (in erster Näherung) identisch.

Oliver

von MaWin (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Dies liefert etwas kürzeren Maschinencode als die switch-Variante

Zeigen, denn das halte ich für unwahrscheinlich, denn
a) if(c==exitchar) ist ein zusätzlicher Vergleich, der beim switch 
gleich miterledigt wurde.

b) Und dein Template if (((in == CC) || ...)) sieht doch in erster 
Näherung nach if(c=='.'||c==','||c==';'||c=='-'||c=='_'||c=='!'||c=='?') 
aus mit seinem immensen Laufzeitaufwand.

Wenn replace(c) vom Compiler als char replace[256]={'\0', '/1', ... '+', 
' ', ' ', ' ', '/, '0', ... '{', ' ', '}', ... '\x7F'} umgesetzt wird, 
dann könnte es effizienter sein, aber das trau ich dem Compiler nicht 
zu.

von Oliver S. (oliverso)


Lesenswert?

Oliver S. schrieb:
> Mit -O3 ist der Maschinencode (in erster Näherung) identisch.

Mit in erster Näherung meine ich vertauschte Anordnung der Blöcke. Die 
eigentliche Funktionalität wird tatsächlich in völlig identischen Code 
übersetzt.

Oliver

von Wilhelm M. (wimalopaan)


Lesenswert?

MaWin schrieb:
> Zeigen, denn das halte ich für unwahrscheinlich,

Mach selbst (godbolt).

MaWin schrieb:
> Und dein Template if (((in == CC) || ...)) sieht doch in erster
> Näherung nach if(c=='.'||c==','||c==';'||c=='-'||c=='_'||c=='!'||c=='?')
> aus mit seinem immensen Laufzeitaufwand.

Du scheinst eine schlechte Meinung von Compilern zu haben.

MaWin schrieb:
> Wenn replace(c) vom Compiler als char replace[256]={'\0', '/1', ... '+',
> ' ', ' ', ' ', '/, '0', ... '{', ' ', '}', ... '\x7F'} umgesetzt wird,

Mir ist es ehrlich gesagt egal: ich instrumentiere den Compiler mit 
möglichst viel Information, und er macht seinen Job gut. Meine Erfahrung 
ist: fold-expressions werden sehr effizient umgesetzt.
Ich verstehe nicht, warum Du schlauer als der Compiler sein willst. 
Schreibe vernünftige Abstraktionen, und Compiler tut seine Arbeit.

von Wilhelm M. (wimalopaan)


Lesenswert?

Oliver S. schrieb:
> Mit in erster Näherung meine ich vertauschte Anordnung der Blöcke. Die
> eigentliche Funktionalität wird tatsächlich in völlig identischen Code
> übersetzt.

Beim mir ist er mit gcc-trunk wenige Instruktionen kürzer. Aber das ist 
auch völlig egal.
Dieses Gequatsche, man müsste als Programmierer die Optimierungen des 
Compilers erledigen, ist doch einfach nur Blödsinn. Die Compilerbauer 
sind ja nicht blöd. Man muss seinen eigenen Code nur mit genügend 
Informationen für den Compiler ausstatten, und dann tut der seinen Job 
ganz hervorragend.

von MaWin (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Meine Erfahrung ist:

Meine Erfahrung ist: selbst simpelste Dinge werden von Compilern extrem 
mies umgesetzt.

Das war sogar meine Studienarbeit, Vergleich von 8 C Compilern für 8088. 
Und heute schau ich bei AVR und STM32 hin und wieder nach dem 
Assembleroutput und bin ähnlich erschrocken.

von Wilhelm M. (wimalopaan)


Lesenswert?

MaWin schrieb:
> Meine Erfahrung ist: selbst simpelste Dinge werden von Compilern extrem
> mies umgesetzt.

Willkommen im 21-ten Jahrhundert!

MaWin schrieb:
> Das war sogar meine Studienarbeit, Vergleich von 8 C Compilern für 8088.

Da hast Du in 30 Jahren nichts dazugelernt, und schreibst Code, der die 
Optimierungstrategieen der Compiler zunichte macht. Das ist schon hohe 
Kunst.

von MaWin (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Mach selbst (godbolt)

Sprich: du hast gelogen und bist gar nicht in der Lage mal eben die 
Zeilen aus deinem schon generierten Output hierher zu kopieren, weil du 
den nicht hast, sondern ihn dir nur im Koof zusammenphantasiert hast. 
q.e.d.

Mein VS2015 kann den von dir verwendeten neuesten C++ noch gar nicht, 
ich werd mir jetzt aucb keinen neueren Compiler installieren.

von MaWin (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Da hast Du in 30 Jahren nichts dazugelernt

Falsch, die AVR und STM32 sind aktuelle gcc, so strohdoof wie vor, 30 
kommt hin. Compilerbauer haben nichts hinzugelernt.

von Wilhelm M. (wimalopaan)


Lesenswert?

MaWin schrieb:
> Mein VS2015 kann den von dir verwendeten neuesten C++ noch gar nicht,

Tja, Pech für Dich.

Solltest mal über eine aktuelle Entwicklungsumgebung nachdenken.

Kleiner Tipp: in ein paar Wochen haben wir 2020 und C++2a wird zu C++20 
und danach geht es in riesen Schritten zu C++23.

von Wilhelm M. (wimalopaan)


Lesenswert?

MaWin schrieb:
> Sprich: du hast gelogen und bist gar nicht in der Lage mal eben die
> Zeilen aus deinem schon generierten Output hierher zu kopieren, weil du
> den nicht hast, sondern ihn dir nur im Koof zusammenphantasiert hast.

Das Du nicht in der Lage bist den Code zu compilieren oder zu verstehen, 
beweist erstmal gar nichts.

von Wilhelm M. (wimalopaan)


Lesenswert?

MaWin schrieb:
> Falsch, die AVR und STM32 sind aktuelle gcc,

MaWin schrieb:
> Mein VS2015 kann den von dir verwendeten neuesten C++ noch gar nicht,

Ich hatte Dir einen Tipp gegeben, und der heißt Matt Godbolt. Bis Du 
auch dazu nicht in der Lage?

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.