"Aus klein mach groß": Teil2 - Zahlen und Satzzeichen

CDW

Moderator
Mitarbeiter
Teilaufgabe2:
Schreibe ein Programm, welches aus einer Benutzereingabe Zahlen und Satzzeichen extrahiert und getrennt ausgibt. Beachte: die Ausgabe soll in der "verkehrten" Reihenfolge des Vorkommens erfolgen. Z.B
"Ich habe 1 webseite, mit 35 Zeilen code: in nur 5 Tagen erstellt!"
Ausgabe: 5 35 1 ! : ,

Benutze zum Zwischenspeichern den Stack und die Stackroutinen aus dieser Aufgabe:
"Aus klein mach groß": Teil1 - Stack
Wobei Zahlen im einen Stack landen und Satzzeichen im anderen, so dass am Ende nur die Inhalte der Stacks nacheinander ausgegeben werden müssen.


Eingabe:
Zahlenformat: "normales" Dezimalformat: 123;123.321,0.321
d.h entweder ganze Zahlen oder Fließkommazahlen mit einem "Punkt"
maximale Anzahl der Stellen in einer einzelnen Zahl: 255 (nur für Leute interessant, die Stacks mit festen Datentypen umgesetzt haben).

Satzzteichenformat: ": , ! ?" (also kein Punkt ".", damit es keine Verwechslungsgefahr gibt)

Einfachheitshalber gehe von dieser Form der Eingabe aus: Eine einzelne Zeile wie:
"mein rechner hat 3500Mhz und deiner nur 3400, also bin ich um 2.4% 1337er als du"

Tipp: Ihr müsst nicht unbedingt Stacks mit unterschieldichen Datentypen verwenden - nur sichergehen, dass in einem Stack eben nur Zahlen und im anderen nur Satzzeichen stehen. Ansonsten wären z.B unterschiedliche Datenstrukturen wie "stack_float" und "stack_string" und entsprechend angepasste Routinen eine mögliche Lösung.

Betrachte dazu die Möglichkeiten der genutzen Programmiersprache:
z.B C: atof,strtof
C#: Float.Parse
Java: Float.parseFloat
C++: die netten >> << Operatoren der istringstream Klasse
Pascal: Val
Delphi: StringToFloat

Alternativ bieten viele Programmiersprachen sowas wie isNum()/isNaN

Es sollten keine "Sonderformen" der Zahlen erkannt werden, sondern nur das, was die Standardroutinen der jeweiligen Programmiersprache bieten. D.h ihr müsst nur die Zahlen "lokalisieren"

Wie schon in der vorherigen Aufgabe: trennt euere Main und die Funktionen voneinander. Ob ihr die Eingabe aus einer GUI oder Konsole einlest, ist egal.

Code:
String zahl="";
eingabe="mein rechner hat 3500Mhz ..."

For Laenge der Eingabe do
  lese zeichen ein
  if istNummer_oder_Punkt(zeichen) then zahl=zahl+zeichen; (Sting zusammenbauen)
 else_block    
    if istEinSatzzeichen(zeichen)=TRUE then push(satzzeichen_stack,zeichen)
    if istEineZahl(zahl)=TRUE then push(zahlen_stack,zahl)
    zahl=""; (string wieder leer machen)
  else_block_end
od
 

Eydeet

Member
Code:
#include <iostream>
#include <sstream>

#include "stack.h"

using namespace std;

int main()
{
    try
    {
        Stack<float> numbers(10);
        Stack<char> punctations(10);
        string input;

        cout << "Enter string: " << flush;
        getline(cin, input);

        string numBuffer;

        for (unsigned i = 0; i < input.length(); i++)
        {
            char c = input[i];

            if (c >= '0' && c <= '9' || c == '.')
            {
                numBuffer += c;
            }
            else if (c == '!' || c == '?' || c == ',' || c == ':')
            {
                punctations.push(c);
            }
            else if (numBuffer.length() > 0)
            {
                stringstream numStream(numBuffer);
                float f;
                numStream >> f;
                numbers.push(f);
                numBuffer = "";
            }
        }

        while (!numbers.isEmpty())
        {
            cout << numbers.pop() << ' ';
        }

        while (!punctations.isEmpty())
        {
            cout << punctations.pop() << ' ';
        }

        cout << endl;
    }
    catch (const char *error)
    {
        cerr << "Error: " << error << endl;
        return 1;
    }
}
Code:
#include <iostream>
#include <sstream>

#include "stack.h"

using namespace std;

int main()
{
    try
    {
        Stack<double> numbers(20);
        Stack<char>   punctations(20);
        string input;

        cout << "Enter string: " << flush;
        getline(cin, input);

        // Damit auch eine Nummer am Ende des Strings erkannt
        // wird, müssen wir ein Text-Zeichen (also !0-9 und .)
        // anhängen
        input += ' ';

        // Hier wird eine erkannte Zahl abgespeichert
        string numBuffer;

        // Die Anzahl an Punkten in der
        // momentan aufgezeichneten Zahl
        int    numOfDots = 0;

        for (unsigned i = 0; i < input.length(); i++)
        {
            char c = input[i];

            // Ziffer an Buffer anhängen
            if (c >= '0' && c <= '9')
            {
                numBuffer += c;
            }
            // Punkt anhängen und Punkt-Counter erhöhen
            else if (c == '.' && numBuffer.length() > 0)
            {
                numOfDots++;
                numBuffer += c;
            }
            // Satzzeichen speichern
            else if (c == '!' || c == '?' || c == ',' || c == ':')
            {
                punctations.push(c);
            }
            // Nummern-Buffer auslesen und in double-Wert speichern
            else if (numBuffer.length() > 0 && numOfDots <= 1)
            {
                stringstream numStream(numBuffer);
                double converted;
                numStream >> converted;
                numbers.push(converted);

                // Zurücksetzen für nächste Zahl
                numBuffer = "";
                numOfDots = 0;
            }
        }

        // Ausgabe der Nummern
        while (!numbers.isEmpty())
        {
            cout << numbers.pop() << ' ';
        }

        // Ausgabe der Satzzeichen
        while (!punctations.isEmpty())
        {
            cout << punctations.pop() << ' ';
        }

        cout << endl;
    }
    catch (const char *error)
    {
        cerr << "Error: " << error << endl;
        return 1;
    }
}
 

CDW

Moderator
Mitarbeiter
@Eydeet
hast Du auch den Fall berücksichtigt, dass ein ungültiges Zahlenformat vorkommen könnte? z.B "123.123.2". Wenn die Konvertierung fehlschlägt, sollte optimalerweise kein "defaultwert" von f gespeichert werden (was es auch sein mag - auf die Schnelle wurde ich aus der Doku nämlich nicht schlau). Es wurde zwar nicht ausdrücklich verlangt, sollte aber das Leben in späteren Teilaufgaben leichter machen ;).
 

Eydeet

Member
Ich hab den Code noch mal etwas überarbeitet, unter anderem Kommentiert und das Problem gelöst, dass eine Zahl am Zeilenende nicht gespeichert werden kann (allerdings mit einem kleinen Hack).

Ungültige Zahlen erkennt die stringstream-Klasse freundicherweise automatisch, sodass 123.123.2 in 123.123 umgewandelt wird. Ich hab es jetzt so programmiert, dass diese Zahl einfach verworfen wird.
Eine andere Möglichkeit wäre es, die Zahl in 3 getrennte Zahlen aufzusplitten. Das wäre allerdings komplizierter, also hab ich's gelassen ;)
 

Boar

New member
Hier meine Java-Lösung:
Code:
import java.util.Vector;

public class Stack{

	private Vector<Object> elemente;
	private int anzahl;

	public Stack() {
		anzahl = 0;
		elemente = new Vector<Object>();
	}
	//Ablegen von Objekten auf dem Stack
	public void push(Object obj) {
		elemente.add(obj);
		anzahl++;
	}
	//Oberstes Objekt vom Stack nehmen
	public Object pop(){
		Object o = null;
		if(!isEmpty()) {
			anzahl--;
			o = elemente.get(anzahl);
			elemente.remove(anzahl);
		}
		return o;
	}
	//Oberstes Objekt vom Stack lesen
	public Object top(){
		return elemente.get(anzahl-1);
	}
	//Prüft, ob der Stack leer ist
	public boolean isEmpty() {
		return anzahl == 0;
	}
}
Code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class ExtractDigits extends JFrame implements ActionListener {

	//GUI-Elemente
	private JPanel inputPanel, outputPanel;
	private JTextField input, output;
	private JLabel inputLabel, outputLabel;
	private JButton ok;
	
	//Zwei Stacks, einer für Zahlen und einer für Satzzeichen
	private Stack numbers, punctuation;
	
	//Im Konstruktor wird das JFrame zusammengebastelt und die Stacks werden initialisiert
	public ExtractDigits() { 
		super("ExtractDigits");
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		input = new JTextField(40);
		output = new JTextField(15);
		inputLabel = new JLabel("Eingabe: ");
		outputLabel = new JLabel("Ausgabe: ");
		ok = new JButton("Enter");
		
		inputPanel = new JPanel(new FlowLayout(FlowLayout.LEFT,10,5));
		inputPanel.add(inputLabel);
		inputPanel.add(input);
		inputPanel.add(ok);
		
		outputPanel = new JPanel(new FlowLayout(FlowLayout.LEFT,10,5));
		outputPanel.add(outputLabel,BorderLayout.NORTH);
		outputPanel.add(output,BorderLayout.SOUTH);
		
		ok.addActionListener(this);
		input.addActionListener(this);
		
		this.setLayout(new BorderLayout());
		this.add(inputPanel,BorderLayout.NORTH);
		this.add(outputPanel, BorderLayout.SOUTH);
		
		this.pack();
		this.setLocationRelativeTo(null);
		this.setVisible(true);
		
		numbers = new Stack();
		punctuation = new Stack();
	} //TeileString
	
	public void actionPerformed(ActionEvent e) {
		if(!input.getText().equals("")) {
			extract(input.getText());
		}
	} //actionPerformed

	// Extrahiert Zahlen und Satzzeichen aus dem Input-String und gibt sie in umgekehrter Reihenfolge aus
	private void extract(String line) {
		String number = ""; //Zwischenspeicher, um eine komplette Zahl zusammenzusetzn
		boolean floatFound = false; //Prüft, ob die Zahl schon einen Punkt hat

		for(int i=0; i<line.length(); i++) {
			
			char c = line.charAt(i); // Das aktuelle Zeichen
			
			// Ziffern oder der erste Punkt (Alle weiteren Punkte gehören nicht zur Zahl)
			if(Character.isDigit(c) || (c == '.' && !floatFound)) {
				if(number.equals("") && c == '.') number+="0"; //Wenn die Zahl mit einem Punkt beginnt setze eine Null davor
				number+=c; //Aktuelle Ziffer zum Zwischenspeicher hinzufügen
				if(c=='.') floatFound = true;
			}
			//Satzzeichen
			else if(c == '!' || c == ',' || c=='?' || c==':') {
				punctuation.push(c);
				if(number.equals("")) continue;
				numberInStack(number, floatFound);
				floatFound = false;
				number = "";	
			//Alle anderen Zeichen
			} else {
				if(number.equals("")) continue;
				numberInStack(number, floatFound);
				floatFound = false;
				number = "";	
			}
			
		}
		// Falls der Input-String mit einer Zahl aufhört, steht diese noch im Zwischenspeicher
		if(!number.equals("")){
			numberInStack(number,floatFound);
			number = "";
		}
		//Alle Zahlen vom Stack nehmen
		while(!numbers.isEmpty()) {
			number+=numbers.pop() + " ";
			
		}
		//Alle Satzzeichen vom Stack nehmen
		String punc = "";
		while(!punctuation.isEmpty()) {
			punc +=punctuation.pop() + " ";		
		}
		//Ausgabe der Zahlen und Satzzeichen
		output.setText(number+" "+punc);
	} //extract
	
	//Schreibt eine Zahl als Float oder Integer in den Stack
	public void numberInStack(String number, boolean floatFound) {
		//Wenn die Zahl mit einem Punkt aufhört wird der Punkt abgeschnitten
		if(number.endsWith(".")) {
			number = number.substring(0,number.length()-1);
			floatFound = false;
		}
		//Wenn es eine Kommazahl ist wird sie als Float auf den Stack gelegt, ansonsten als Integer
		if(floatFound) {
			numbers.push(Float.parseFloat(number));
		} else {
			numbers.push(Integer.parseInt(number));
		}	
	} //numberInStack
	
	public static void main(String[] args) {
		new ExtractDigits();
	} //main
}

"123.123.2" wird zu "123.123" und "2", die Ziffer hinter dem zweiten Punkt wird als neue Zahl interpretiert.
Außerdem wird ".45" zu "0.45" formatiert und bei Zahlen, die mit einem Punkt enden wird der Punkt abgeschnitten. ("12." wird zu "12")

EDIT: .jar-Datei im Anhang.

Feedback ist erwünscht :)

Gruß, Boar
 

CDW

Moderator
Mitarbeiter
Feedback ist erwünscht
Falls Du schon mit Exceptions gearbeitet hast: imho wäre es besser hier:
Code:
public Object pop(){
		Object o = null;
		if(!isEmpty()) {
			anzahl--;
			o = elemente.get(anzahl);
			elemente.remove(anzahl);
		}
		return o;
	}{
sowas zu machen:
Code:
public Object pop() throws Exception{
		Object o = null;
		if(!isEmpty()) {
			anzahl--;
			o = elemente.get(anzahl);
			elemente.remove(anzahl);
			return o;
		}
		else throw new Exception("Stack ist leer!!!") ;
		
	}


weil der Programmierer z.B beim POPen (->Stack ;) ) die isEmpty() Abfrage weglassen/vergessen könnte und sich dann über falsche Ergebnisse oder gar Runtimeexceptions wundert (z.B wenn im Stack Strings abgelegt waren, die dann ausgegeben werden). Die Fehlerursache ist nicht gleich ersichtlich.
Hierdurch wird er aber sozusagen auf den möglichen Fehlerfall aufmerksam gemacht und gezwungen, diesen zu berücksichtigen.


Und
Code:
//Oberstes Objekt vom Stack lesen
	public Object top(){
		return elemente.get(anzahl-1);
	}
was passiert, wenn ein Stack angelegt wird und gleich darauf "geTOPt" wird? Ich meckere ja nur, weil Du beim pop() schon eine Überprüfung machst, ob der Stack leer ist, top aber offenbar auslässt ;)

Was ich aber persönlich unschön finde ist, dass Du GUI und Programm zusammenmischst - ExtractDigits() legt z.B GUI Elemente an, dazu gleich auch die Stacks. Aber die eigentliche Extraction wird in einem Listener aufgerufen.
Vielleicht wäre es besser, eine eigene GUI Klasse zu haben, die auf Useraktionen reagiert und die Ausgabe regelt und die Klasse ExtractDigits, die nach außen sichtbar z.B nur die extract(String s, Stack1,Stack2) Methode anbietet bzw. alternativ extract(String s) und zusätzlich getDigStack() und getPunctuationStack() Methoden, die entsprechende Stacks liefern. Wäre zwar etwas mehr Aufwand, der sich aber später beim mehrmaligen Anpassen (falls Du die nachfolgenden Aufgabenteile machen möchtest ;) ) wieder rentiert.
 

Boar

New member
Wow, erstmal ein Dankeschön für den ausführlichen Kommentar.

Zu den Exceptions: So hab ich das noch gar net gesehen, guter Einwand.

Ich finds auch unschön GUI und Programm in eine Klasse zu packen. Aber da es bis jetzt ein Mini-Programm hab ich alles in eine Klasse geklatscht (pure Faulheit ^^). Hab nicht daran gedacht, dass das Programm noch wächst. Aber bis zum nächsten Aufgabenteil werd ich das anpassen ;)
 

System.I/O

New member
warum das rad neu erfinden? :D
hab den stack aus dem .net framework einfach um die function IsEmpty erweitert.
Code:
using System;

namespace langeweile {
	
	public class Stack : System.Collections.Stack {
		public Stack() : base()	{}
		
		public Stack(System.Collections.ICollection elements) : base(elements) {}
		
		public Stack(Int32 size) : base(size) {}
		
		
		public bool IsEmpty() {
			return base.Count == 0;
		}
		
		public object Top() {
			return base.Peek();
		}		
	}
}
Regex ist doch was feines.
Code:
using System;
using System.Text.RegularExpressions;

namespace langeweile
{
	public class ParserResult {
		private Stack numbers;
		private Stack punctuationMarks;
		
		public Stack Numbers {
			get { return this.numbers; }
			set { this.numbers = value; }
		}
		
		public Stack PunctuationMarks {
			get { return this.punctuationMarks; }
			set { this.punctuationMarks = value; }
		}
				
		public ParserResult() {
			this.numbers = new Stack();
			this.punctuationMarks = new Stack();
		}
	}
	
	
	public class Parser	{		
		public static ParserResult Parse(string document) {			
			ParserResult result = new ParserResult();
			MatchCollection matches = Regex.Matches(document, @"(\d{1,}\.\d{1,})|(\d{1,})");
			result.Numbers = MatchCollectionToStack(matches);
			
			matches = Regex.Matches(document, @"!|,|:|\?");
			result.PunctuationMarks = MatchCollectionToStack(matches);
			return result;
		}
		
		private static Stack MatchCollectionToStack(MatchCollection matches) {
			Stack result = new Stack();
			foreach(Match match in matches) {
				result.Push(match.Value);
			}
			return result;
		}		
	}
}
Main ohne den GUI code, der ist ausgelagert (partial->InitializeComponent())
Code:
using System;
using System.Drawing;
using System.Windows.Forms;

namespace langeweile
{
	public partial class MainForm : Form
	{
		[STAThread]
		public static void Main(string[] args) {
			Application.EnableVisualStyles();
			Application.SetCompatibleTextRenderingDefault(false);
			Application.Run(new MainForm());
		}
		
		public MainForm() {
			InitializeComponent();			
		}
		
		void Button1Click(object sender, EventArgs e) {
			ParserResult result = Parser.Parse(this.richTextBoxInput.Text);
			
			while(!result.Numbers.IsEmpty()) {
				this.richTextBoxOutput.AppendText(result.Numbers.Pop()+" ");
			}
			
			while(!result.PunctuationMarks.IsEmpty()) {
				this.richTextBoxOutput.AppendText(result.PunctuationMarks.Pop()+" ");
			}
		}
	}
}
achja, ist in c# gecodet
 

CraHack

New member
Code:
    Public Function Einlesen(ByVal DerString As String, ByRef StackNum As Object, ByRef StackZei As Object) As Integer
        Dim Suchstring As String
        Dim ZeichenNummer As Integer
        Dim GefundeneZeichen As Integer
        Dim IstInZahl As Boolean
        Dim DieZahl As String
        DieZahl = ""

        Suchstring = ":,!?"

        For ZeichenNummer = 1 To Len(DerString)
            If (IstInZahl) Then
                If (IsNumeric(Mid(DerString, ZeichenNummer, 1))) Then
                    DieZahl = DieZahl & Mid(DerString, ZeichenNummer, 1)
                Else
                    StackNum.push(DieZahl)
                    IstInZahl = False
                    GefundeneZeichen = GefundeneZeichen + 1
                End If
            Else
                If (IsNumeric(Mid(DerString, ZeichenNummer, 1))) Then
                    DieZahl = Mid(DerString, ZeichenNummer, 1)
                    IstInZahl = True
                Else
                    If (InStr(Suchstring, Mid(DerString, ZeichenNummer, 1)) > 0) Then
                        StackZei.push(Mid(DerString, ZeichenNummer, 1))
                        GefundeneZeichen = GefundeneZeichen + 1
                    End If
                End If
            End If
        Next

        Return GefundeneZeichen
    End Function
End Class

Erwartet als Parameter 2 + 3 je eine "Stack-Klasse" die ich in Teil 1 gepostet habe.
Editiert von CDW: CODE-Tags kann man auch in SPOILER-Tags verschachteln ;)


Edit. Danke CDW :D
 

v2.0

Stammuser
Java Code

Ich habe meinen Stack nun etwas umgeändert, so dass er nun Chars benutzt statt Strings.
Um die Zahlen zu lokalisieren benutze ich eine Integervariable namens ascii,
die den Ascii-Wert eines Chars speichert und überprüft ob es sich bei diesem Char um eine Zahl handelt.
Code:
import javax.swing.JOptionPane;

public class Stack {
    int nummer = -1;
    char inhalt[] = new char[200];
    
    void push(char eingabe) {
        nummer++;
        inhalt[nummer] = eingabe;
    }
    
    char pop() {
        if (nummer >= 0) {
            char ausgabe = inhalt[nummer];
            inhalt[nummer] = '0';
            nummer--;
            return ausgabe;
        } else {
            return '0';
        }
    }
    
    char top() {
        return inhalt[nummer];
    }
    
    boolean isEmpty() {
        if (nummer < 0) {
            return true;
        } else {
            return false;
        }
    }
    
}    
public class ZeichenTrennen {

    public static void main(String[] args) {
        StringBuffer Zahlen = new StringBuffer();
        StringBuffer Buchstaben = new StringBuffer();
        String eingabe;
        int anzahl;
        int anzahlZahlen = 0, anzahlBuchstaben = 0;
        Stack ZahlenStack = new Stack();
        Stack BuchstabenStack = new Stack();
        
        eingabe = JOptionPane.showInputDialog("Geben Sie einen Text ein");
        anzahl = eingabe.length();
        
        for (int i = 0; i < anzahl; i++) {
            char zeichen = eingabe.charAt(i);
            int ascii = (int)zeichen;
            
            if (ascii < 58 &  ascii > 47) {
                ZahlenStack.push(zeichen);
                anzahlZahlen++;
            } else {
                BuchstabenStack.push(zeichen);
                anzahlBuchstaben++;
            }
        }
        
        for (int i = 0; i < anzahlZahlen; i++) {
            Zahlen.append(ZahlenStack.pop());
        }
        
        for (int i = 0; i < anzahlBuchstaben; i++) {
            Buchstaben.append(BuchstabenStack.pop());
        }
        
        System.out.println(Zahlen);
        System.out.println(Buchstaben);
        
    }

}
[LEFT][/LEFT]
 
Oben