java string replacement ohne replace()

AMD_Rulez

Lieutenant
Registriert
Feb. 2010
Beiträge
582
hi

bin gerade dabei eine methode für das ersetzen vom umlaut ä in ae zu schreiben. dabei sollte die replace() funktion bewusst nicht verwendet werden.

das hier ist bis ejtzt mein code. er funktioniert mit jedem string außer eben mit umlauten. kann mir das jemand erklären?

Code:
public class StringManipulation {

    static StringBuilder replaceUmlauts(String old) {
		String[] arr = old.split("ä");
		StringBuilder neu = new StringBuilder();
		
		int i=0;
		for (; i<arr.length -1; i++) {
			neu.append(arr[i]).append("ae");
		}
		neu.append(arr[i]);
		return neu;
    }  

	public static void main(String[] args) {
    
	String org = args[0];
	StringBuilder neu = replaceUmlauts(org);
	System.out.println(neu);
	
    }


}
 
Hey warum machst du kein Char-array aus dem String?

char[] arr = old.toCharArray;
StringBuilder neu =newStringBuilder();


so und mit arr machst du dann einfach eine for each char schleife. if char != ä neu.append char else neu.append ae.
​(hab grade eclipse nicht am mann, daher hier mal nur pseudo code :) )
 
return neu.toString();

Die Methode gibt dann String zurück und nicht mehr StringBuilder.
Alle Variablen, die du in der Methode lokal erzeugst, sind nach dem Verlassen weg.
 
Die For-Schleife dürfte falsch sein. Das muss doch bis i <= array.length -1 laufen. (oder i < array.length)
Ansonsten würd ichs so machen wie Nuck_Chorris, ein Char Array bietet sich da an.
 
Eventuell auch so?

Aufruf:
Code:
String s = "Das ähnelt allmählich einer Funktion.";
s = replaceUmlauts(s);
System.out.println(s);

Methode:
Code:
private String replaceUmlauts(String s) {

    StringBuilder sb = new StringBuilder();

    int i1 = 0;
    int i2 = -1;

    while ((i2 = s.indexOf("ä", i1)) != -1) {
        sb.append(s.substring(i1, i2));
        sb.append("ae");
        i1 = i2 + 1;
    }

    if (i1 < s.length()) {
        sb.append(s.substring(i1, s.length()));
    }

    return sb.toString();
}
 
Zuletzt bearbeitet: (Optimierung: Eine arithmetische Operation weniger.)
Ich würde es so machen:
Code:
  public void replaceUmlaut(String s)
    {
        char [] array = new char[s.length()];
        String neu ="";
        for(int i =0;i<s.length();i++)
        {
            array[i]=s.charAt(i);
        }
        for(int i=0;i<s.length();i++)
        {
            String buchstabe = ""+array[i];
            if(buchstabe.equals("ä"))
            {
                neu+="ae";
            }
            else
            {               
                    neu=neu+buchstabe;                
            }
        } 
        System.out.println(neu);       

    }

Funktioniert auch, aber ich habe wahrscheinlich wieder zu kompliziert gedacht. :p
 
AMD_Rulez schrieb:
er funktioniert mit jedem string außer eben mit umlauten. kann mir das jemand erklären?
Man muss erstmal genau feststellen, wann er nicht funktioniert. Dein Code ist an sich richtig gedacht - er funktioniert nur nicht, wenn der String mit einem ä endet. Und das liegt einfach daran, dass die split-Methode keinen abschließenden leeren String erzeugt.

Meiner Meinung nach ein gravierender und unglaublich dämlicher Fehler, aber das Verhalten ist auch dokumentiert.


Im Grunde ist Killkrogs Vorschlag schon sehr gut, weil er die vorhandenen indexOf- und substring-Methoden nutzt. Kann man aber sehr viel einfacher formulieren:

Code:
  StringBuilder replace(String string, String toReplace, String replacement) {
    int occurence = string.indexOf(toReplace);
    return occurence >= 0
      ? replace(string.substring(occurence + toReplace.length()), toReplace, replacement)
          .insert(0, replacement)
          .insert(0, string.substring(0, occurence))
      : new StringBuilder(string);
  }

Zur Erinnerung: indexOf gibt -1 zurück, wenn der gesuchte String nicht gefunden wird, und substring mit einem Parameter gibt den Substring bis zum Ende des Strings zurück.


Ich werfe generell mal die Empfehlung in den Raum, eher rekursiv zu programmieren als mit Schleifen, wo es sich anbietet - dadurch eliminiert man nicht selten einen ganzen Haufen potentieller Fehlerquellen, und in diesem Fall ist auch der Code viel leichter zu verstehen.


PS:
spamarama schrieb:
Die For-Schleife dürfte falsch sein. Das muss doch bis i <= array.length -1 laufen. (oder i < array.length)
Eben nicht, weil er ja nach dem letzten Durchlauf der For-Schleife noch den Teil des Strings zum Ergebnis hinzufügt, der kein ä mehr enthält. Mit deiner Variante würde er immer ein "ae" zu viel bekommen.
 
Zuletzt bearbeitet:
Eben nicht, weil er ja nach dem letzten Durchlauf der For-Schleife noch den Teil des Strings zum Ergebnis hinzufügt, der kein ä mehr enthält. Mit deiner Variante würde er immer ein "ae" zu viel bekommen.
In der Tat. Hatte Code von ihm umgeschrieben und den Teil bei mir geändert.

Ich werfe generell mal die Empfehlung in den Raum, eher rekursiv zu programmieren als mit Schleifen, wo es sich anbietet
Im Embedded-Bereich wird davon streng abgeraten und das sicherlich nicht zu unrecht.
 
Wenn du die Gründe dann auch noch nennen würdest - im Desktop-/Smartphone-/Sonstwas-Bereich sind wahrscheinlich 0% davon in irgendeiner Weise relevant, zumal es auch funktionale Sprachen wie Haskell gibt, in denen nur so programmiert wird.

Wenn man sich Mühe gibt, kann das Problem von oben auch auf Tail Call Recursion hin umschreiben - indem man den StringBuilder nicht "ganz unten" erzeugt und dann prepended, sondern von oben nach unten durchgereicht und die Strings appended. Dann optimiert dir auch jeder gute Compiler die Rekursion weg.
 
VikingGe schrieb:
Wenn du die Gründe dann auch noch nennen würdest - im Desktop-/Smartphone-/Sonstwas-Bereich sind wahrscheinlich 0% davon in irgendeiner Weise relevant, zumal es auch funktionale Sprachen wie Haskell gibt, in denen nur so programmiert wird.

Der Grund wird der altbekannte sein: Um Rekursion zu verstehen muß man erst einmal Rekursion verstehen.

Ganz im ernst: Rekursion ist in den meisten Fällen eben nicht so leicht durchschaubar wie eine simple Schleife, es entspricht nicht den Denkgewohnheiten der meisten Leute. Ansonsten könntest Du ja auch sagen, daß man alles Texthandling gleich mit regulären Ausdrücken machen kann, da braucht man nur noch einen Hammer, mit dem man alles erschlägt ...
 
Ich persönlich hatte in der Vergangenheit schon öfters das Problem, dass bei zu tiefer Rekursion der Stack übergelaufen ist. Besonders in diesem Fall hier, bei dem in einem String uns unbekannter Länge eine unbekannte Anzahl an Ersetzungen durchgeführt werden soll, können wir (außer bei Endrekursion) nicht sicherstellen, dass uns das Programm nicht abschmiert.

Rekursion beinhaltet also Zusatzaufwand wegen Implementierung als Endrekursion.

Der Gewinn dadurch für mich im Vergleich zu einer Schleife? Keiner.

Natürlich kann Rekursion in Situationen in welchen ich mir über die Eingabemenge im Klaren bin extrem sinnvoll sein. Gutes Beispiel dafür ist die Bildung von Permutationen.

Aber eine Methode gegenüber der anderen als "generell empfehlenswerter" darzustellen halte ich für kühn.
 
Rekursion zu nutzen, resultiert bei bestimmten Aufgabenstellungen (z.B. Baumtraversierung) in einer vermeintlich (das englische arguably wäre hier passender) eleganteren/intuitiveren Lösung. Kompakter normalerweise schon, aber kompakt heißt in aller Regel auch komplexer (siehe Statement-Dichte bei Scala), d.h. jemand, der nicht oft mit Rekursion konfrontiert ist (und das ist man im nicht-akademischen Umfeld wohl eher nicht), muss erst einen Moment darüber nachdenken. Für gewöhnlich arbeiten die Leute iterativ.

Man kann das eine in das andere umwandeln und läuft mit der iterativen Lösung weniger Gefahr, dass das Programm durch einen stack overflow abraucht (stack size ist übrigens ein JVM-Parameter, kann man also erhöhen). Der einzige Ausweg ist die genannte Endrekursivität (tail recursion). Und warum ist das so? Na weil der Compiler das in eine Schleife umwandeln kann (aber bei Java z.B. nicht tut!), womit wir wieder bei der iterativen Lösung wären.

Ich würde die iterative Lösung schon als generell empfehlenswerter ansehen, aber gibt sicher Aufgabenstellungen, wo eine iterative Lösung endlos hässlich wird. Sehe ich aber eher in speziellen (bzw. akademischen) Bereichen.
 
Rekursion ist weniger intuitiv zu Erlernen als ein gleichwertiger iterativer Ansatz, ist aber deswegen nicht weniger intuitiv zu Benutzen, ganz im Gegenteil. Was man nicht richtig in Fleisch und Blut hat ist nunmal komplizierter als andere Dinge, ist das nicht mit allem so?

Bei vorhandenem Verständnis fällt mir nichts ein, was rekursiv nicht schneller zu schreiben und auch schneller zu verstehen wäre. Es ist doch immer das gleiche Schema. Außerdem kann man rekursiv komplett immutable agieren, das geht mit Iteration nicht. Zählvariablen und indices sind auch kein lästiger Overhead (sofern man diese nicht explizit braucht, z.B. in einem Koordinatensystem).

Ich sage nicht, dies ist besser als jenes, sondern nur, dass es sich lohnt, mit Rekursion umgehen zu lernen.

spamarama schrieb:
Im Embedded-Bereich wird davon streng abgeraten und das sicherlich nicht zu unrecht.

Das ist ungefähr so als würdest du sagen, du fährst nie über 30 weil in 30er Zonen nicht schneller gefahren werden darf und das sicherlich nicht zu unrecht.

Tumbleweed schrieb:
(siehe Statement-Dichte bei Scala)

Was meinst du damit genau?
 
Damit meinte ich, dass du in Scala in einer Zeile meist mehrere statements hast. Wurde mir das erste Mal bewusst, als ich dafür einen code coverage check ins CI eingebunden habe. Coverage per line of code zu messen (wie in Java üblich) bringt es bei Scala nicht, weil du da eben mehrere pro line hast und daher coverage pro statement messen musst.

Der Code wird dadurch kürzer, hat es aber in sich. Wobei mir beim Schreiben des Beitrags hier gerade auffiel, dass diese Erkenntnis aus Java7 Zeiten stammt und mit der Einführung der Lambdas in Java8 müsste man eigentlich in Java eine ähnliche Dichte erreichen.

Zu dem von dir erwähnten overhead, würde ich noch in den Ring werfen, dass der durch Lambdas und die klassischen map-/filter-/usw.-Operationen auch gar nicht mehr so gegeben ist. Zugegeben, das hat man sich aus der funktionalen Ecke abgeschaut, aber im Endeffekt ist es nur Syntax-Zucker und Rekursion gibt es da meines Erachtens keine.
 
Zuletzt bearbeitet:
Das ist ungefähr so als würdest du sagen, du fährst nie über 30 weil in 30er Zonen nicht schneller gefahren werden darf und das sicherlich nicht zu unrecht.
Ich spreche hier unter Anderem vom MISRA:
https://de.wikipedia.org/wiki/MISRA-C
Dieser verbietet Rekursion. Zum einen wegen dem Verständnisproblem und dann natürlich wegen Stack Overflow. Man will sauberen und einfach wartbaren Code.
Vor allem gehts darum sowas zu vermeiden:
http://www.safetyresearch.net/blog/...ed-acceleration-and-big-bowl-“spaghetti”-code
(Gott sei dank siehts nicht in allen Bereichen der Autoindustrie so aus...)
 
Tumbleweed schrieb:
Damit meinte ich, dass du in Scala in einer Zeile meist mehrere statements hast [...] weil du da eben mehrere pro line hast und daher coverage pro statement messen musst.

Verstehe, das stimmt wohl. Wobei du bei Java doch eigentlich auch schon seit Jahren in jeder besseren Lib/API sowas wie fluent Interfaces vorfindest, um Aufrufe verketten zu können. Oder ist es bei Java Konvention, jeden Aufruf in eine eigene Zeile zu schreiben?

Ich habe eigentlich nur nachgefragt, weil mich der Begriff Statement verwirrt hat. Es mag zunächst kleinlich klingen, aber in Scala gibt es eigentlich keine Statements, nur Expressions (man hat also in einer Zeile mehrere Expressions). Scoverage redet natürlich trotzdem von Statement Coverage, der Begriff hat sich im Testumfeld eben eingebürgert.

Tumbleweed schrieb:
Der Code wird dadurch kürzer, hat es aber in sich.

Das "hat es aber in sich" ist, wenn man damit vertraut ist, aber doch gerade der Vorteil. Verborgene Funktionlität bedeutet auch, dass man dort keine Bugs mehr einbauen kann und auch keine Tests dafür schreiben muss (nur für den eigenen Code, der davon Gebrauch macht). Es passiert zwar sehr viel auf wenig Raum, das bedeutet im Gegenzug aber auch, dass das Wesentliche schneller überblickt ist. Ein höheres Abstraktionsniveau eben.

Tumbleweed schrieb:
... und Rekursion gibt es da meines Erachtens keine.

Es ist einfach ein höheres Abstraktionsniveau als von Hand geschriebe Rekursion oder Iteration. Wobei Funktionen wie foldLeft/Right klar den Charakter einer vereinfachten Rekursion haben.

spamarama schrieb:
Ich spreche hier unter Anderem vom MISRA [...]

Das Problem mit Stack Overflow liegt bei embedded-Systemen auf der Hand (wenig Speicher -> kleiner Call Stack), ist aber in anderen Umgebungen weniger ein Problem (das wollte ich mit der 30er Zone sagen).
Der von dir verlinkte Artikel (ach du heilige Sch..., das klingt vernichtend) redet von Spaghetti-Code, aber das ist eigentlich genau das, wofür Rekursion ein Gegenmittel darstellt. Wenn das Problem dazu passt, dann geht es kaum sauberer. Was bleibt, sind die Verständnisschwierigkeiten, die manch einer zu haben scheint, und das ist dann natürlich schon ein Problem.
 
Ergänzung

Es ist einfach ein höheres Abstraktionsniveau als von Hand geschriebe Rekursion oder Iteration.
In der Tat verfolgt ja die Rekursion den Ansatz ein komplexes Problem auf einfache Teilprobleme zu zerlegen und ist damit natürlich grundsätzlich erst mal begrüßenswert.

Im übrigen ist auf häufig die Beweisbarkeit das ein Algorithmus quasi korrekt funktioniert einfacher als bei einer iterativen Lösung.

Hinzu kommt, das insbesondere in funktionalen Sprachen (anders als Java) möglichst mit unveränderlichen Werten (sprich Konstanten) gearbeitet wird was dann auch gerne die Rekursion bevorzugt. Und unveränderliche Werte helfen nicht nur bei der Fehlervermeidung, sondern auch bei der Optimierung für Multithreading.

An Rekursion hängt also letztlich noch viel mehr dran. EIn Baustein von einem Gesamtkonzept sozusagen.

Java kann davon aber eben durch fehlende Eigenschaften nicht in dem Maße profitieren wie es funktionale Programmiersprachen tun. Dementsprechend wird Rekursion aus Java-Sicht auch oftmals als "eine Möglichkeit ganz bestimmte Algorithmen eleganter zu schreiben" wahrgenommen, aber nicht das breitere Konzept was man drum herum aufbauen kann.

Und aus dieser (verkürzten) Sicht sind die Vorteile natürlich erst mal überschaubar.

Auch das vorgebrachte Argument (bezüglich Endrekursion) "letztlich macht der Compiler eh ne Iteration draus" kann man so nicht gelten lassen. Aus dem gleichen Grund könnte man sagen OOP ist sinnlos weil der Compiler schmeißt letztlich den ganzen OOP-Overhead über Bord.

Tumbleweed schrieb:
Ich würde die iterative Lösung schon als generell empfehlenswerter ansehen, aber gibt sicher Aufgabenstellungen, wo eine iterative Lösung endlos hässlich wird. Sehe ich aber eher in speziellen (bzw. akademischen) Bereichen.
Na rein akademisch ist Rekursion oder überhaupt der ganze funktionale Kram schon längst nicht mehr. Du hast ja mit Scala selbst einen Vertreter genannt der ja auch in der Industrie (und da reden wir hier von Firmen wie Twitter und keine Klitschen, die keine Sau kennt) sich einer gewissen Beliebtheit erfreut
(während "in der Uni" ironischerweise nach wie vor das elendige Java angesagt ist :) ).

Gruß
Andy
 
Zurück
Oben