Best Practice BASH ähnliche Variablenersetzung

Bernd Hohmann

Top Contributor
Ich habe eine Map mit Key/Value Paaren. Der Key ist lowercase abgelegt.

Jetzt möchte ich in einem String der Sorte "Eine $Sonne, viele $Sonnen" alle mit $ beginnenden Variablen ersetzen.

Bislang ist mir nur eingefallen, nach einem $ zu suchen und dann solange weiterzugehen, bis das Zeichen nicht 0-9 a-z A-Z ist, die Variable zu extrahieren, toLowerCase() und schauen ob ich den Key in der Map habe.

Geht das auch bisserl schlauer?

Bernd
 

Camill

Bekanntes Mitglied
Mir ist nichts anderes bekannt, würde von daher genauso vorgehen:
- mithilfe eines Regex nach '$...' suchen
- ersetzen des gefundenen
 

Bernd Hohmann

Top Contributor
Mir ist nichts anderes bekannt, würde von daher genauso vorgehen:
- mithilfe eines Regex nach '$...' suchen
- ersetzen des gefundenen

Für meine bescheidenen Bedürfnisse langt auch ein indexOf("$").

Danke für die Info!

Wenn jemanden noch was einfällt wie ich die Prüfung auf 0-9,a-z,A-Z kürzen kann...

Bernd

Java:
	private String replace_vars(String str) {
		while (true) {
			int pp1 = str.indexOf("$");
			if (pp1 == -1) return str;
			int pp2 = str.length();
			for (int i = pp1 + 1; i < str.length(); i++) {
				char ch = str.charAt(i);
				if (ch >= '0' && ch <= '9') continue;
				if (ch >= 'a' && ch <= 'z') continue;
				if (ch >= 'A' && ch <= 'Z') continue;
				pp2 = i;
				break;
			}
			String strKey = str.substring(pp1 + 1, pp2);
			str = str.substring(0, pp1) + getVariable(strKey) + str.substring(pp2);
		}
	}
 

Camill

Bekanntes Mitglied
[...]
Wenn jemanden noch was einfällt wie ich die Prüfung auf 0-9,a-z,A-Z kürzen kann...
[...]

Die Character Klasse bietet dafür Methoden an:
Java:
		if (Character.isLetter(ch) || Character.isDigit(ch)) {
		    continue;
		}

Habe mal eine andere mögliche Lösung erstellt, Patterns müssten ggf. noch angepasst werden:
Java:
    public static void main(String[] args) {
        Map<String, String> vars = new HashMap<String, String>();
        vars.put("$Sonne", "Maus");
        vars.put("$Sonnen", "Maeuse");
        String aString = "Eine $Sonne, viele $Sonnen";
        String varEndPattern = "[., ]";
        System.out.println(replaceVars("(\\$\\w+)((" + varEndPattern + ")|(\\z))", varEndPattern, aString, vars));
    }

    private static String replaceVars(String pattern, String varEndPattern, String text, Map<String, String> vars) {
        Matcher m = Pattern.compile(pattern).matcher(text);
        while (m.find()) {
            String match = m.group().replaceAll(varEndPattern, "");
            text = text.replaceFirst("\\" + match, vars.get(match));
        }
        return text;
    }
 

Ark

Top Contributor
Für meine bescheidenen Bedürfnisse langt auch ein indexOf("$").
Je länger und öfter ich mich mit Parsern beschäftige (und sei es durch solche Themen wie diesem hier), desto mehr stelle ich fest, wie nutzlos Methoden wie
Code:
String.indexOf()
in diesem Zusammenhang sind.

Wenn jemanden noch was einfällt wie ich die Prüfung auf 0-9,a-z,A-Z kürzen kann...
Außer der Verwendung von
Code:
||
sehe ich keine sinnvollere/bessere Möglichkeit zur "Vereinfachung". Dafür hat, wie oben angedeutet, dein Algorithmus eine Schwachstelle. Zum Vergleich hier eine kleine Demo in bash:
Code:
$ blubbvar=asd\$blubbvar
$ echo $blubbvar
asd$blubbvar
$
Dein Algorithmus würde in einem solchen Fall (Variable wird durch einen Wert ersetzt, der direkt oder indirekt rekursiv wie der entsprechende Platzhalter für dieselbe Variable aussieht) nicht terminieren, sondern in einer Endlosschleife immer mehr Platz beanspruchen.

Die einfachste Lösung hast du im Prinzip schon in deinem ersten Beitrag angedeutet, hier eine hoffentlich korrekte Implementierung:
Java:
	private static String replace_vars_neu(final String str){
		final StringBuilder res = new StringBuilder();
		final StringBuilder varbuf = new StringBuilder();
		boolean invar = false;
		for(int i = 0; i < str.length(); i++){
			final char c = str.charAt(i);
			if(invar){
				final boolean b = c >= '0' && c <= '9'
					|| c >= 'a' && c <= 'z'
					|| c >= 'A' && c <= 'Z';
				if(b) varbuf.append(c);
				if(!b || i == str.length() - 1){
					final String value = getVariable(varbuf.toString());
					if(value == null) throw new IllegalArgumentException();
					// Beispiel für Alternative:
					// if(value == null) res.append(varbuf); else
					res.append(value);
					invar = false;
					if(i != str.length() - 1) i--;
				}
			}
			else{
				if(c == '$') invar = true;
				else res.append(c);
			}
		}
		return res.toString();
	}
Die Stellen, wo
Code:
str.length() - 1
auftaucht, gefallen mir nicht wirklich; auch das
Code:
b
ist etwas unsauber. Leichter bzw. sauberer ginge es z.B. so, dass
Code:
c
tatsächlich vom Typ
Code:
int
wäre und als Terminierungszeichen z.B. -1 zum Einsatz kommt. Außerdem kann diese Implementierung nicht mit Ersatzstrings für $ (z.B.
Code:
$$
oder
Code:
\$
) umgehen.

Ark
 

Bernd Hohmann

Top Contributor
Je länger und öfter ich mich mit Parsern beschäftige (und sei es durch solche Themen wie diesem hier), desto mehr stelle ich fest, wie nutzlos Methoden wie
Code:
String.indexOf()
in diesem Zusammenhang sind.

Findest Du? Für den Hausgebrauch langt es eigentlich und es ist auch recht übersichtlich.

Dein Algorithmus würde in einem solchen Fall (Variable wird durch einen Wert ersetzt, der direkt oder indirekt rekursiv wie der entsprechende Platzhalter für dieselbe Variable aussieht) nicht terminieren, sondern in einer Endlosschleife immer mehr Platz beanspruchen.

Stimmt - Du hast vollkommen Recht, dass mein Schnellschuss empfindlich gegen Rekursionen ist. Kurze Überlegung, ob Rekursion in meinem Anwendungsfall einen Sinn macht... Eindeutig nein - also verhindern wir die Rekursion dadurch, dass wir nach dem "Suchen und Ersetzen" hinter der Ersetzungsstelle neu aufsetzen statt am Anfang:

Java:
	private String replace_vars(String str) {
		int pp1 = 0, pp2 = 0;
		while (true) {
			pp1 = str.indexOf("$", pp2);
			if (pp1 == -1) return str;
			pp2 = str.length();
			for (int i = pp1 + 1; i < str.length(); i++) {
				int ch = str.codePointAt(i);
				if (Character.isDigit(ch)) continue;
				if (Character.isLetter(ch)) continue;
				pp2 = i;
				break;
			}
			String strKey = str.substring(pp1 + 1, pp2);
			str = str.substring(0, pp1) + getVariable(strKey) + str.substring(pp2);
		}
	}

Hätte ich auch gleich richtig machen können, das Problem tritt ja nicht zum ersten Mal auf.

Spricht irgendwas gegen die Verwendung von Codepoints an dieser Stelle?

Bernd
 

Marco13

Top Contributor
Nur überflogen, aber nebenbei: Das ganze ist auch abhängig von der Ersetzungsstrategie, und man muss ggf. aufpassen: Wenn man "sequentiell" ersetzt, muss man z.B. die Variablen der Länge nach ersetzen (längste zuerst) ... Ansonsten würde, wenn im Beispiel ein mapping wie
$Sonne = SONNE
$Sonnen = STERNE
definiert wäre, ja ggf. sowas wie
Eine SONNE, viele SONNEn
rauskommen, obwohl
Eine SONNE, viele STERNE
rauskommen sollte (aber da ich mich mit der Bash nicht auskenne, weiß ich gerade nicht, ob der Hinweis hier angebracht oder das nicht vielleicht "selbsverständlich" ist :oops: )

Ein etwas schwereres Geschütz für solche Aufgaben könnte Apache Velocity Site - The Apache Velocity Project sein, was ich mir mal ganz kurz (aber eben noch nicht im Detail) angesehen habe, weil ich dachte, dass es für etwas, was ich vorhatte, passend sein könnte ... aber das hatte sich dann doch von selbst erledigt, und ... String#indexOf und ein paar Replacements sind (oder erscheinen?) für einfache Aufgaben dann meistens sooo viel leichter ... :oops:
 

Bernd Hohmann

Top Contributor
Nur überflogen, aber nebenbei: Das ganze ist auch abhängig von der Ersetzungsstrategie, und man muss ggf. aufpassen: Wenn man "sequentiell" ersetzt, muss man z.B. die Variablen der Länge nach ersetzen (längste zuerst) ...
Das hab ich auch zuerst gedacht. Wenn man sich aber darauf festlegt, dass Variablennamen mit einem $ beginnen und nur 0-9, a-z, A-Z enhalten ist man aus dem Schneider da man nun Delimiter hat.

Was ich nicht berücksichtigt habe ist die Zuweisung einer Variablen zu einer anderen ($Copyright=$Author, $CopyrightNotice), da muss ich in der Praxis schauen ob das überhaupt vorkommt.

Ein etwas schwereres Geschütz für solche Aufgaben könnte Apache Velocity Site - The Apache Velocity Project sein, was ich mir mal ganz kurz (aber eben noch nicht im Detail) angesehen habe, weil ich dachte, dass es für etwas, was ich vorhatte, passend sein könnte ... aber das hatte sich dann doch von selbst erledigt, und ... String#indexOf und ein paar Replacements sind (oder erscheinen?) für einfache Aufgaben dann meistens sooo viel leichter ... :oops:

Die Diskussion "selber (ab)schreiben" oder fertig nehmen muss jeder für sich selber entscheiden.

Ich kenne viele Entwickler die nur daran denken die gestellte Aufgabe mit einem von ihnen überschaubaren Aufwand zu lösen. Und "überschaubar" heisst für diese Kollegen "nimm was fertiges". Das bedeutet nicht, dass das Problem damit schneller, günstiger oder gar mit geringen Wartungskosten für/in der Zukunft gelöst wird.

Von den Apache-Tools halte ich mich in meinen eigenen Projekten nach Möglichkeit fern weil selbst kleinere Sachen dort einen Rattenschwanz an anderen Apache-Tools nachziehen und die Fehlerquellen gefühlsmässig exponentiell ansteigen. Auch Releasewechsel sind dort nicht so ohne wenn man das mal über einen Zeitraum von 5-10-20 Jahren betrachtet.

Bernd
 

Neue Themen


Oben