Erste Schritte String: Alle doppelten Leerzeilen entfernen

Diskutiere String: Alle doppelten Leerzeilen entfernen im Java Basics - Anfänger-Themen Bereich.
X

Xyz1

Vielleicht etwas blöd meine Frage, aber muss ich das wirklich so machen:
Java:
	private String removeLines(String s) {
		int emptyCount = 1;
		StringWriter sw = new StringWriter();
		try (BufferedReader r = new BufferedReader(new StringReader(s)); PrintWriter w = new PrintWriter(sw)) {
			String l;
			while ((l = r.readLine()) != null) {
				if (l.isBlank()) {
					emptyCount++;
				} else {
					emptyCount = 0;
				}
				if (emptyCount < 2) {
					w.println(l);
				}
			}
		} catch (IOException e1) {
			append(e1.getMessage());
		}
		sw.flush();
		return sw.toString();
	}


Es ist endlich einmal ein Einsatz für den StringWriter... aber gibt es nicht einen schlauen Regex oder Stream o.Ä.?
 
J

JustNobody

Was spricht denn gegen ein einfaches String.replaceAll?
replaceAll("(?m)(\r?\n){3,}", "\n\n")

Hier wird erst mit (?m) der Multiline Mode gesetzt, dann suchen wir nach mindestens 3 Zeilenumbrüchen um diese dann mit zwei Zeilenumbrüche zu ersetzen.

Bei den Zeilenumbrüchen nehmen wir sowohl Unix als auch Windows und setzen dann aber nur ein spezifischen. Hier den reinen \n. Das macht Dein Code aber auch nicht anders, denn println setzt ja den Zeilenumbruch der Plattform. Wenn man den Windows Zeilenumbruch bräuchte, dann wäre der hat entsprechend zu setzen.
 
X

Xyz1

Ich finde den Regex etwas zu kompliziert... und weiß auch nicht wie es bezüglich Performance aussieht. Aber vielen Dank für Deine Antwort einer möglichen Variante.
 
J

JustNobody

aber gibt es nicht einen schlauen Regex oder Stream o.Ä.?

Ich finde den Regex etwas zu kompliziert...

YMMD! Du fragst nach einem Regex, kriegst einen kurzen und übersichtlichen Regex, der sogar direkt mit String verwendet werden kann ganz ohne Pattern und so und dann ist es zu kompliziert....

Also das war jetzt ein richtig schöner Lacher - auch wenn schon der 3. und nicht der 1. ist :)
 
J

JustNobody

Ja wir sind richtig schlimm und ich schäme mich zutiefst, dass ich Dir auf Deine Frage nach einem regulären Ausdruck für ein einfaches Problem einen doch einfachen regulären Ausdruck liefere....
Und da dir dieser viel zu komplex ist, sind wir jetzt so böse und geben Dir für ein Problem, welches relativ ungeeignet für Streams ist, keine Stream-Frickellösung.

Aber dann ist es doch super: Du hast dann doch jetzt die sichere Bestätigung, dass Du den perfekten Code hast. Den könnte man maximal noch etwas kürzen ... also "emptyCount" könnte man zu "e1" verkürzen. Das sticht sonst so blöd hervor da es nicht Deinen Naming Conventions folgt.

Evtl. noch ein Hinweis: sw.flush() dürftest Du dir sparen können, denn Dein w ist ja ein PrintWriter auf sw und der ist schon geschlossen da Du hinter dem try-with-resources bist. Formell ist das ein Fehler, denn die Beschreibung des Writer Interface besagt, dass ein flush oder write nach einem close zu einer IOException führt.
(StringWriter ist aber diesbezüglich ein Fall für sich. Die JCL sieht vor, dass close und flush nichts machen und damit entspricht es streng genommen nicht dem Writer Vertrag bzw. die Dokumentation müsste angepasst werden.)

War das jetzt hilfreich? Klare Bestätigung, das Dein Code perfekt ist mit klitzekleinen Anmerkungen um den perfekten Code noch perfekter zu machen?
 
J

JustNobody

Das ist gut, da Experten zu Rate zu ziehen. Aber die Stream Lösung hat Bereiche, in denen sie extrem stark ist. Das ist (mal ganz trivial ausgedrückt) vor allem da der Fall, bei denen man die einzelnen Elemente ohne einen State zu verändern, durchgehen kann. Da kann man dann auch schön durch Parallelisierung der Bearbeitung vieles machen. Aber bei Deinen Elementen (Zeilen, Zeichen, was auch immer) ist jetzt auch die Beziehung untereinander wichtig. Das bringt automatisch Probleme mit sich.

Aber man kann sich da schnell eine Stream Lösung bauen. Da ich aber immer Zeilen mit dem Vorgänger oder Nachfolger betrachten muss, baue ich mir dies einfach einmal auf.
String Array mit allen Zeilen.
Int-Stream machen: 0... n-1 (mit n: Anzahl Zeilen)
-> Filtern: Nur Elemente, die nicht leer sind oder die Leer sind und die einen Nachfolger haben der nicht leer ist oder die keinen Nachfolger haben
-> map i -> array-Element
-> Einsammeln mit Concat und Trennelement "\n";

Aus meiner Sicht aber nicht wirklich Zielführend. Zumal ein kleiner .replaceAll ausreichend ist :)

Schneller Edit: Das Map fehlte natürlich noch - wir wollen ja nicht die Element-Nummern sondern die Elemente selbst.
 
mrBrown

mrBrown

Man könnte auch nen CharStream raus machen, mit peek das Zeichen merken, danach mit filter nur die durchlassen, wenn nicht vorheriges und aktuelles eine neue Zeile ist. Ist aber schon ziemlicher Missbrauch.
 
J

JustNobody

Man könnte auch nen CharStream raus machen, mit peek das Zeichen merken, danach mit filter nur die durchlassen, wenn nicht vorheriges und aktuelles eine neue Zeile ist. Ist aber schon ziemlicher Missbrauch.
Aber da brauchst Du einige Zeichen im Überblick. Denn Du willst ja nicht alle Leerzeilen raus haben sondern nur die, die vor und nach sich ein Zeilenumbruch haben. Und ein Zeilenumbruch kann ein Zeichen (\n) oder zwei Zeichen (\r\n) sein.
 
mrBrown

mrBrown

Aber da brauchst Du einige Zeichen im Überblick. Denn Du willst ja nicht alle Leerzeilen raus haben sondern nur die, die vor und nach sich ein Zeilenumbruch haben. Und ein Zeilenumbruch kann ein Zeichen (\n) oder zwei Zeichen (\r\n) sein.
Oh, dann lines() statt chars() nutzen und mit isBlank arbeiten.
 
J

JustNobody

Woran scheitert es? Ist doch schon sehr genau herunter geschrieben worden.

Aber einfach einmal in Code herunter geschrieben:
Code:
        String[] lines = input.split("\n");
        String result = IntStream.range(0, lines.length)
                .filter( i -> {
                    if (i == lines.length - 1) return true; // Letztes Element bleibt.
                    return !(lines[i].isEmpty() && lines[i+1].isEmpty());
                })
                .mapToObj(i -> lines[i])
                .collect(Collectors.joining("\n"));

Edit: Einfach einmal die Lösung runter geschrieben. Tippfehler oder so ggf entschuldigen....
 
Zuletzt bearbeitet:
mrBrown

mrBrown

Java:
private String removeLinesWithFilter(String s) {
    String[] lastSeen = new String[] {"NOT_BLANK"};
    return s.lines().filter(line -> {
        try {
            return !lastSeen[0].isBlank() || !line.isBlank();
        } finally {
            lastSeen[0] = line;
        }
    }).collect(Collectors.joining("\n"));

}

private String removeLinesWithReduce(String s) {
    return s.lines().reduce((s1, s2) -> s1.matches(".*\\n\\s*\\z") && s2.isBlank() ? s1 : s1+"\n"+s2).orElseThrow();
}
 
Zuletzt bearbeitet:
X

Xyz1

@mrBrown zur ersten Lösung: Du verletzt mit Capturing Lambdas das Constraint, dass locale variables effectively final sein müssen (https://www.baeldung.com/java-lambda-effectively-final-local-variables , S. "5. Avoid Workarounds": "In general, these kinds of workarounds are error-prone and can produce unpredictable results, so we should always avoid them.")
Die zweite Lösung verwendet Regexes... (Dazu muss ich nicht mehr viel schreiben)
@JustNobody : "IntStream.range(0, lines.length)" ist einfach nur eine verborgene for Schleife...
 
J

JustNobody

@JustNobody : "IntStream.range(0, lines.length)" ist einfach nur eine verborgene for Schleife...
Das hast du doch generell. Das andere ist ein iterieren über die Werte. Streams sind doch nur eine zusätzliche Funktionalität des Compilers und nix magisch Neues ....
 
mihe7

mihe7

Endlich mal wieder was unleserliches:
Java:
    public static void main(String[] args) {
        String input = "Dies\n\nist\n\n\nein\nTest";
        String expected = "Dies\n\nist\n\nein\nTest";
        String actual = new BufferedReader(new StringReader(input)).lines()
            .reduce("", (b, s) -> b.isEmpty() ? s :
                    s.isEmpty() && b.endsWith("\n") ? b : b+"\n"+s);
        System.out.println(actual.equals(expected));
    }
 
mrBrown

mrBrown

Du verletzt mit Capturing Lambdas das Constraint, dass locale variables effectively final sein müssen
Nö, die Variablen sind alle "effectively final", ansonsten wäre es ein Compile-Fehler.


[...] (https://www.baeldung.com/java-lambda-effectively-final-local-variables , S. "5. Avoid Workarounds": "In general, these kinds of workarounds are error-prone and can produce unpredictable results, so we should always avoid them.")
Wie ich sagte: "Ist aber schon ziemlicher Missbrauch."
 
Thema: 

String: Alle doppelten Leerzeilen entfernen

Passende Stellenanzeigen aus deiner Region:
Anzeige

Neue Themen

Anzeige

Anzeige
Oben