Approximate string/text matching algorithm gesucht

String s1 = "Es regnet viel"; und String s2 = "Viel regnet es"; soll zu 100% übereinstimmen, gleiche Länge und gleiche Wörter, bis auf die Groß- und Kleinschreibung.

String s3 = "Der Regen ist stark"; und String s4 = "Stark das Regen ist"; soll zu etwa zu 81% übereinstimmen, gleiche Länge, nur ein Wort ist anders.

Als Lib verwende ich apache commons text, die einzelnen Wörter liegen in zwei Listen l1 und l2. So könnte man jedes Wort vergleichen:

for (int i=0; i<l1.size()-1; i++)
  for (int j=i+1; j<l2.size(); j++)
    //...

Aber was passiert, wenn zwei Wörter „matchen“? Aus der Liste removen?

Danke für jede Idee. :smiley:

/e: Hintergrund der Sache ist der, dass ich eine Art Vokabel(n)-Lernprogramm brauche, nur nicht mit einzelnen Vokabeln, sondern ganzen Sätzen - und es eine „Abfragen“-Funktion geben soll (zu einer Frage soll eine Antwort eingegeben werden).

Für so etwas nutzt man in der Regel die levenshtein distance. Vielleicht hilft dir das weiter.

Ja, die nutze ich bereits. Bitte beachte auch meine Frage.

Booyer-Moore wäre etwas, was mir einfallen würde. Das sucht nicht ganze Wörter, sondern matched beliebige Zeichenketten. Allerdings wüsste ich nicht, wie man damit eine prozentuelle Angabe hinbekommen will.

Also, ich hatte mir das so ungefähr gedacht (Idee):

Eingabe: one two three four five und one five four thre

Ausgabe:
100 one one
0 two five
0 two four
25 two thre
20 three five
0 three four
80 three thre
25 four five
100 four four
100 five five
i1 = 80

Code:

	mi_learn.addActionListener((e) -> {
		ArrayList<Eintrag2> l = new ArrayList<>();
		for (int i = 0; i < list.size(); i++) {
			l.add(new Eintrag2(list.get(i), i));
		}
		Collections.shuffle(l);
		Collections.sort(l);
		for (int i = 0; i < l.size(); i++) {
			int sum = 0;
			Eintrag ein = l.get(i).eintrag;
			String line = JOptionPane.showInputDialog(ein.frage);
			if (line == null) {
				break;
			}
			String[] split1 = ein.antwort.split("\\s+");
			String[] split2 = line.split("\\s+");
			for (int j = 0; j < split1.length; j++) {
				for (int k = 0; k < split2.length; k++) {
					if (split1[j] != null && split2[k] != null) {
						String s1 = split1[j].toLowerCase();
						String s2 = split2[k].toLowerCase();
						while (s1.length() < s2.length()) {
							s1 += " ";
						}
						while (s2.length() < s1.length()) {
							s2 += " ";
						}
						HammingDistance hd = new HammingDistance();
						int i1 = hd.apply(s1, s2);
						int i3 = Math.round( (1f - (float) (i1) / (float) (s1.length())) * 100f);
						System.out.println(i3 + " " + s1 + " " + s2);
						if (i3 >= 75) {
							split1[j] = null;
							split2[k] = null;
							sum++;
						}
					}
				}
			}
			int i1 = Math.round((float) (sum) / (float) Math.max(split1.length, split2.length) * 100f);
			System.out.println("i1 = " + i1);
			if (i1 >= 75) {
				ein.richtig++;
			} else {
				ein.falsch++;
			}
			atm.fireTableDataChanged();
		}
	});

Also zwei Wörter stimmen bei >=75% überein, eine Antwort ist bei >=75% übereinstimmenden Wörtern richtig. Wäre das aber sinnvoll?

Sorry, hier noch mal in schön:

	static int getPercent(String s1, String s2) {
		int sum1 = getScore(s1, s1);
		int sum2 = getScore(s1, s2);
		return Math.round((float) sum2 / (float) sum1 * 100f);
	}

	static int getScore(String s1, String s2) {
		int sum = 0;
		String[] split1 = s1.toLowerCase().split("\\s+");
		String[] split2 = s2.toLowerCase().split("\\s+");
		for (int i = 0; i < split1.length; i++) {
			sum += getHighestScore(split1, split2);
		}
		return sum;
	}

	static int getHighestScore(String[] split1, String[] split2) {
		int maxindex = 0;
		int max = 0;
		for (int i = 0; i < split1.length; i++) {
			for (int j = 0; j < split2.length; j++) {
				if (split1[i] != null && split2[j] != null) {
					String s1 = split1[i];
					String s2 = split2[j];
					while (s1.length() < s2.length()) {
						s1 += " ";
					}
					while (s2.length() < s1.length()) {
						s2 += " ";
					}
					HammingDistance hd = new HammingDistance();
					int i1 = hd.apply(s1, s2);
					int i2 = Math.round((1f - (float) (i1) / (float) (s1.length())) * 100f);
					System.out.println(i2 + " " + s1 + " " + s2);
					if (i2 > max) {
						maxindex = j;
						max = i2;
					}
				}
			}
		}
		if (max >= 50) {
			split2[maxindex] = null;
			return max;
		}
		return 0;
	}

Das Ergebnis ist jetzt bei one two three four five und one five four thre 76, bei one two three four five und one two thre four five 96, und bei 1 und 2 0.
Ist das richtig? - Die null-Elemente bereiten mir Kopfschmerzen, da gibt es doch bestimmt etwas „adäquateres“.

	static int getPercent(String s1, String s2) {
		int sum1 = getScore(s1, s1);
		int sum2 = getScore(s1, s2);
		return Math.round((float) sum2 / (float) sum1 * 100f);
	}

	static int getScore(String s1, String s2) {
		int sum = 0;
		String[] split1 = s1.toLowerCase().split("\\s+");
		String[] split2 = s2.toLowerCase().split("\\s+");
		for (int i = 0; i < split1.length; i++) {
			sum += getHighestScore(split1[i], split2);
		}
		return sum;
	}

	static int getHighestScore(String s1, String[] split2) {
		int maxindex = 0;
		int max = 0;
		for (int i = 0; i < split2.length; i++) {
			if (split2[i] != null) {
				String s2 = split2[i];
				while (s1.length() < s2.length()) {
					s1 += " ";
				}
				while (s2.length() < s1.length()) {
					s2 += " ";
				}
				HammingDistance hd = new HammingDistance();
				int i1 = hd.apply(s1, s2);
				int i2 = Math.round((1f - (float) i1 / (float) s1.length()) * 100f);
				// System.out.println(i2 + " " + s1 + " " + s2);
				if (i2 > max) {
					maxindex = i;
					max = i2;
				}
			}
		}
		if (max >= 50) {
			split2[maxindex] = null;
			return max;
		}
		return 0;
	}

Da war eine überflüssige Schleife… Ich hab das jetzt so gemacht, für jedes Wort aus s1 wird das Wort mit der maximalen Übereinstimmung aus s2 gesucht, das zu mindestens 50 Prozent übereinstimmt. Das wird dann null-gesetzt und beim nächsten Mal übersprungen. Die Scores werden aufaddiert und zurückgegeben.

So weit ich das erkennen kann, werden immer sinnvolle Scores zurückgegeben, egal ob s2 länger, kürzer oder leer ist.

Prinzipiell könnten damit Text- Prüfungsfragen und -Antworten (also Textaufgaben in Schulen usw., wie es ja auch schon geschieht) bewertet werden. Punkte für wichtige Begriffe, Punkte für vollständige Sätze und kein Punktabzug, wenn mehr als gefordert geschrieben wurde.

Anderes Thema… und unabhängig davon, dass es schwer umzusetzen wäre: Haltet ihr eine Auto-Complete-Funktion, wenn man z. B. die ersten 4 Buchstaben eintippt, aber nicht genau weiß, wie ein Wort geschrieben wird, beim Lernen/ in Prüfungen für sinnvoll?

(Und man kann endlich Yodas Sprache verstehen. :sweat_smile: )

Nein. Studien zeigen, dass wenn einem ein Wort nicht einfällt, man eine Weile grübelt, und dann doch noch darauf kommt, diese Erinnerung deutlich gestärkt wird, während das nicht passiert, wenn einem jemand aushilft und „vorsagt“.

Dazu kommt, dass zumindest bei den europäischen Sprachen das Wortende für das Lernen am wichtigsten ist - der Wortstamm ist meist schneller gelernt als die richtige Flexion.