Hilfe beim verstehen eines Skripts

Allgemeine Statistik mit R, die Test-Methode ist noch nicht bekannt, ich habe noch keinen Plan!

Moderatoren: EDi, jogo

Sandra95

Hilfe beim verstehen eines Skripts

Beitrag von Sandra95 »

Hallo Zusammen, ich sitze seit einigen Tagen an einem geschrieben Skript und würde das gerne deuten. Dieses Skript läuft im Hintergrund eines Tools und soll bei Eingabe einer Datei und gesetzten Filtern, doppelte Daten ausfindig machen. Ich würde mich über Hilfe, bzw. auch gerne PN freuen.
Viele Grüße

Code: Alles auswählen

 if("cluster" %in% rownames(installed.packages()) == FALSE) { install.packages("cluster") }
 if("stringdist" %in% rownames(installed.packages()) == FALSE) { install.packages("stringdist") }

 library(cluster)
 library(stringdist)
 #library(parallel) 
 #library(plyr)

 counter <- 0

 trim <- function( x ) {
gsub("(^[[:space:]]+|[[:space:]]+$)", "", x)
 }

 assignClusterName <- function (clusterIDsVector, segName) {
clusterIDsVector <- sapply(clusterIDsVector, function (x) return(paste0(segName, ".", x)))
return (as.data.frame(clusterIDsVector)) 
 }

 clusterStrDist <- function (strDist, df.records, segmentName, outputFile, columns.output, cluster_height) {
if (counter > 0)
cols <- TRUE
else
cols <- FALSE
clusterDendro <- hclust(strDist, method="complete")
clusterIDs <- cutree(clusterDendro, h=cluster_height)
clusterIDs <- assignClusterName(as.vector(clusterIDs), segmentName)
result <- cbind(df.records, clusterIDs) 
#write.table(result, file = "clusters_dep_full.csv", sep=";", row.names=FALSE, col.names=FALSE, append=TRUE)
occs <- table(result$clusterIDs)
result$occs <- occs[result$clusterIDs]
result.dubs <- result[result$occs > 1, ]
colout <- c(columns.output, c("clusterIDsVector", "occs"))
write.table(result.dubs[colout], file = outputFile, sep=";", row.names=FALSE, col.names=cols, append=TRUE)
counter <- counter+1
rm(result)
rm(result.dubs)
rm(clusterDendro) 
rm(clusterIDs)
 }

 strdistmatrixSelf <- function (strcmpVector, method, useBytes, weight) {
strdistMatrix <- stringdistmatrix(strcmpVector, strcmpVector, method, useBytes)
strdistMatrix <- strdistMatrix * as.numeric(weight)
return(strdistMatrix)
 }

 multiColStrcmp <- function (segmentName, df.records, outputFile, cols, colWeights, columns.output , method, useBytes, cluster_height) {
strdistMatrixList <- lapply(cols, function(x) return(strdistmatrixSelf(as.vector(unlist(df.records[[x]])), method=method, useBytes=useBytes, weight=colWeights[match(x, cols)])))
strDistMatrix <- as.matrix(Reduce("+", strdistMatrixList)) / as.numeric((rep(1,length(colWeights)) %*% colWeights))
rownames(strDistMatrix) <- df.records[[1]]
strDist <- as.dist(strDistMatrix)
rm(strdistMatrixList)
rm(strDistMatrix)
clusterStrDist(strDist, df.records, segmentName, outputFile=outputFile, columns.output=columns.output, cluster_height = cluster_height)
rm(strDist)
#gc()
 }

 startDedub <- function (inputFile, outputFile, columns.match, columns.strcmp, columns.output, column.filter = " ", column.filterValue = " ", columns.strcmp.weights = c(), strcmpfun="jw", cluster_method="complete", cluster_height=0.1) {

# run script for specified file 
# U+00A7 means " as quote character
df.input <- read.csv(file=inputFile, head=TRUE, sep=";")

# some type checking
if (typeof(strcmpfun) != "character")
stop(sprintf(paste("Illegal strcmpfun type: %s", typeof(strcmpfun))))
if (!is.data.frame(df.input))
stop(sprintf(paste("Illegal data frame: %s", typeof(df.input))))
if (!length(columns.strcmp > 0))
stop(sprintf("Provide at least one column for fuzzy string matching"))
if (length(columns.strcmp.weights) == 0)
columns.strcmp.weights <- rep(1, length(columns.strcmp))
if (length(columns.strcmp.weights) != length(columns.strcmp))
stop(sprintf("Please provide exactly one weight for each column to for string comparison"))

lapply(columns.strcmp, function (x) df.input[x] <- trim(tolower(df.input[[x]])))
lapply(columns.strcmp, function (x) df.input[x] <- gsub("*", "", df.input[[x]]))
if (!(column.filter == " ")) {
df.input <- df.input[df.input[column.filter]==column.filterValue, ]
}
gc()

if (length(columns.match) > 0) {
#matchingFramesList <- split(df.input, df.input[,columns.match])

df.input <- within(df.input, splitKey <- do.call("paste", c(df.input[columns.match], sep = ".")))
names(df.input)[length(names(df.input))] <- "splitKey"
#setkey(df.input, "splitKey")
idx <- split(seq_len(nrow(df.input)), df.input$splitKey)
# Check for segments without entities 
filterList <- lapply(idx, function (x) length(x) > 1) 
idx <- idx[unlist(filterList)]
print(length(idx))
rm(filterList)
gc()
matchingFramesList <- lapply(idx, function(x) { return(as.data.frame(df.input[x,])) } )

# Calculate the number of cores
#no_cores <- detectCores() - 1
# Initiate cluster
#cl <- makeCluster(no_cores)
#combine matching columns in unique key
#clusterExport(cl, varlist=c("idx", "df.input"), envir=environment())
#matchingFramesList <- parLapply(cl, idx, function(x) { return(as.data.frame(df.input[x,])) } )

#stopCluster(cl)
#segmentNames <- unique(df.input[columns.match])

}
else {
matchingFramesList <- list(ALL=df.input)
}

segmentNames <- names(matchingFramesList)
lapply(segmentNames, function (x) multiColStrcmp(x, matchingFramesList[[x]], outputFile = outputFile, cols = columns.strcmp, colWeights = columns.strcmp.weights, columns.output = columns.output, method = strcmpfun, useBytes = TRUE, cluster_height = cluster_height))

gc() 
return(TRUE)
jogo
Beiträge: 2085
Registriert: Fr Okt 07, 2016 8:25 am

Re: Hilfe beim verstehen eines Skripts

Beitrag von jogo »

Hallo Sandra,

willkommen im Forum!
Das ist ja ein ganz schöner Riemen, den Du hier vorlegst :shock:
Ein erster Blick vermittelt mir den Eindruck, dass der Code schlecht formatiert ist und mir auch vom Stil her nicht gefällt.
Meinst Du, Du kannst uns den Code nochmal in einer gefälligeren Form präsentieren?
(Die code-Tags habe ich schon eingefügt; bitte lies viewtopic.php?f=20&t=29 :!: )

Gruß, Jörg
bigben
Beiträge: 2771
Registriert: Mi Okt 12, 2016 9:09 am

Re: Hilfe beim verstehen eines Skripts

Beitrag von bigben »

Hi!

Ich finde die Fragestellung bemerkenswert unkonkret. Was heißt das, sein Skript "deuten"? Willst Du
  • Jede Zeile und jeden Operator verstehen?
  • Eine Bedienungsanleitung für das Skript haben?
  • grob verstehen, was ungefähr wo passiert?
  • verstehen, warum einige Codezeilen geschrieben und dann doch wieder auskommentiert wurden?
  • wissen, warum man ein Skript mit return(TRUE) beendet?
  • einfach nur wissen, wo die Funktion startDedub endet?
  • einen Fehler im Skript korrigieren, von dem wir noch nichts wissen sollen?
Wenn dann klar gestellt ist, was unter "deuten" zu verstehen sei, wäre es gut zu schreiben, was Du selbst verstehst und woran es hängt. Eine Zeile-für-Zeile-ÜBersetzung des Skripts in Deutsche Umgangssprache ist doch hoffentlich nicht das Ziel?

LG,
Bernhard
---
Programmiere stets so, dass die Maxime Deines Programmierstils Grundlage allgemeiner Gesetzgebung sein könnte
Sandra95

Re: Hilfe beim verstehen eines Skripts

Beitrag von Sandra95 »

Hallo,

vielen Dank für die ersten Hilfestellungen.

Ja, die Frage ist sehr allgemein und unkonkret formuliert. Verzeiht mir an dieser Stelle.
Was das Skript im Endeffekt tun soll, das weiß ich ja. Ich bin ziemlich neu in der R-Welt, interessiere mich dafür jedoch zunehmend. Mit meinem Wissensstand weiß ich, dass ziemlich unübersichtlich dargestellt und programmiert wurde. Mein Ziel ist es (,was ich natürlich nicht von euch verlange,)das komplett oder auch teilweise neu aufzusetzen, damit ich das Tool steuern kann.
Ich hake mal die Fragen einzeln ab.

•Jede Zeile und jeden Operator verstehen? Am liebsten das, aber das würde den Rahmen sprengen, ich weiß.
•Eine Bedienungsanleitung für das Skript haben? Ja, das wäre toll.
•grob verstehen, was ungefähr wo passiert? Vielleicht könnte ja jemand die Passagen durchgehen. Das Grobe und Ganze habe ich ja verstanden.
•verstehen, warum einige Codezeilen geschrieben und dann doch wieder auskommentiert wurden? Das ist mir auch aufgefallen und irritiert mich.
•wissen, warum man ein Skript mit return(TRUE) beendet? Ich hoffe ich komme mal soweit, dass ich mir nur noch um die letzte Zeile Gedanken machen muss ;) aber ja, das würde mich natürlich interessieren.
•einfach nur wissen, wo die Funktion startDedub endet? Leider nicht "einfach nur" :cry:
•einen Fehler im Skript korrigieren, von dem wir noch nichts wissen sollen? Ein Fehler ist da soweit ich weiß nicht, alles läuft so ab wie es sein sollte. Ich weiß, jetzt denken sich einige "warum dann verstehen müssen". Es geht darum, dass ich das nun übernehmen möchte und es dazu gehört alles zu verstehen, um dann bei Bedarf Erweiterungen vornehmen zu können.
Ich freue mich und schätze jede noch so kleine Hilfe!
Benutzeravatar
EDi
Beiträge: 1599
Registriert: Sa Okt 08, 2016 3:39 pm

Re: Hilfe beim verstehen eines Skripts

Beitrag von EDi »

Bist du schon Zeile für Zeile durch den Code gegangen? Hast vor jedem Funktionsaufruf geschaut was reingeht? Hast nach jedem Aufruf geschaut was rauskommt?

Das Skript ist nicht gut geschrieben...
Zum Beispiel erwarten einige Funktionen ein Objekt 'counter', dass nicht übergeben wird und angenommen wird dass es im global Environment ist (kann man in Teufels Küche kommen mit solchenAbhängigkeiten).

Die Funktion trim() braucht es heutzutage nicht mehr (gibt trimws()), was auf eine Urspung vor April 2015 hindeutet (dann kam R 3.2.0 mit trimws).

Mir ist nicht klar was wir hier tun sollen.
Lies und führe das skript Zeile für Zeile durch. Schau dir alle Obkekte die in eine Funktion reingehen an.Schau dirwas rauskommt an.Wenn du eine Funktion nicht kennst,lies die Hilfe. Mache die mit der R-Hilfe vertraut. Lerne die Funktionen str(),browser(),debug().
Wenn du dann immer noch hängst,poste ein aufs wesentliche verkürztes reproduzierbares Beispiel hier.
Bitte immer ein reproduzierbares Minimalbeispiel angeben. Meinungen gehören mir und geben nicht die meines Brötchengebers wieder.

Dieser Beitrag ist lizensiert unter einer CC BY 4.0 Lizenz
Bild.
jogo
Beiträge: 2085
Registriert: Fr Okt 07, 2016 8:25 am

Re: Hilfe beim verstehen eines Skripts

Beitrag von jogo »

Hallo Sandra,

ich habe jetzt einfach mal den ganzen Quelltext genommen und nach RStudio kopiert, damit die Einrückungen systematisch werden. Das Ergebnis findest Du unten in dieser Nachricht.
Als Erläuterung zu dem Quelltext möchte ich nur auf einige "Glanzpunkte" eingehen, auf die Bernard schon angespielt hat:
1. Der Quelltext endet mit

Code: Alles auswählen

  return(TRUE)
return(...) wird nur innerhalb der Definition einer Funktion verwendet und ist beim Durchlaufen der Funktion ein Punkt, an der die Ausführung der Funktion beendet wird und ein angegebenes Objekt an die aufrufende Umgebung zurückgeliefert wird. Nach einem return() muss noch irgendwo das Ende der Funktionsdefinition in Form einer schließenden geschweiften Klammer zu finden sein. Dies ist jedoch bei dem von Dir gelieferten Quelltext nicht der Fall. In diesem Punkt ist der Quelltext syntaktisch falsch und kann in dieser Form nie und nimmer laufen.

2. Nachdem das Misstrauen geweckt wurde (siehe 1.), fällt auf, dass der Quelltext viel Überflüssiges enthält. Es ist anzunehmen, dass der Quelltext (wie drücke ich mich jetzt noch halbwegs schmeichelhaft aus) nicht gerade von einem erfahrenen R-Programmierer erstellt wurde. Die Verrenkungen, die hier beim Programmieren gemacht wurde, soll bitte der Autor erklären. Dass ein Dritter erklären soll, was der Autor sich dabei gedacht hat, halte ich für eine Zumutung.

3. Nehmen wir mal an, es würde bei dem Quelltext nur eine Zeile fehlen, nämlich: , um die Definition der letzten Funktion abzuschließen, dann bleibt in der Gesamtschau auf den Quelltext folgendes:
Es werden allerlei Funkionen definiert, die jedoch innerhalb des Quelltextes nicht aufgerufen werden.
Das Einzige, was wirklich getan wird, ist das Setzen des Counters:

Code: Alles auswählen

counter <- 0
Fast kommt es mir so vor, als ob dies ein Quelltext ist, den man am Anfang eines Kurses für Fortgeschrittene den Teilnehmern vorlegt, damit diese möglichst viele Unstimmigkeiten finden.
Insofern würde mich schon die Entstehung dieses Quelltextes interessieren. Kannst Du uns verraten, was es damit auf sich hat?

Gruß, Jörg
hier nochmal der Quelltext mit gefälligen Einrückungen:

Code: Alles auswählen

if("cluster" %in% rownames(installed.packages()) == FALSE) { install.packages("cluster") }
if("stringdist" %in% rownames(installed.packages()) == FALSE) { install.packages("stringdist") }

library(cluster)
library(stringdist)
#library(parallel) 
#library(plyr)

counter <- 0

trim <- function( x ) {
  gsub("(^[[:space:]]+|[[:space:]]+$)", "", x)
}

assignClusterName <- function (clusterIDsVector, segName) {
  clusterIDsVector <- sapply(clusterIDsVector, function (x) return(paste0(segName, ".", x)))
  return (as.data.frame(clusterIDsVector)) 
}

clusterStrDist <- function (strDist, df.records, segmentName, outputFile, columns.output, cluster_height) {
  if (counter > 0)
    cols <- TRUE
  else
    cols <- FALSE
  clusterDendro <- hclust(strDist, method="complete")
  clusterIDs <- cutree(clusterDendro, h=cluster_height)
  clusterIDs <- assignClusterName(as.vector(clusterIDs), segmentName)
  result <- cbind(df.records, clusterIDs) 
  #write.table(result, file = "clusters_dep_full.csv", sep=";", row.names=FALSE, col.names=FALSE, append=TRUE)
  occs <- table(result$clusterIDs)
  result$occs <- occs[result$clusterIDs]
  result.dubs <- result[result$occs > 1, ]
  colout <- c(columns.output, c("clusterIDsVector", "occs"))
  write.table(result.dubs[colout], file = outputFile, sep=";", row.names=FALSE, col.names=cols, append=TRUE)
  counter <- counter+1
  rm(result)
  rm(result.dubs)
  rm(clusterDendro) 
  rm(clusterIDs)
}

strdistmatrixSelf <- function (strcmpVector, method, useBytes, weight) {
  strdistMatrix <- stringdistmatrix(strcmpVector, strcmpVector, method, useBytes)
  strdistMatrix <- strdistMatrix * as.numeric(weight)
  return(strdistMatrix)
}

multiColStrcmp <- function (segmentName, df.records, outputFile, cols, colWeights, columns.output , method, useBytes, cluster_height) {
  strdistMatrixList <- lapply(cols, function(x) return(strdistmatrixSelf(as.vector(unlist(df.records[[x]])), method=method, useBytes=useBytes, weight=colWeights[match(x, cols)])))
  strDistMatrix <- as.matrix(Reduce("+", strdistMatrixList)) / as.numeric((rep(1,length(colWeights)) %*% colWeights))
  rownames(strDistMatrix) <- df.records[[1]]
  strDist <- as.dist(strDistMatrix)
  rm(strdistMatrixList)
  rm(strDistMatrix)
  clusterStrDist(strDist, df.records, segmentName, outputFile=outputFile, columns.output=columns.output, cluster_height = cluster_height)
  rm(strDist)
  #gc()
}

startDedub <- function (inputFile, outputFile, columns.match, columns.strcmp, columns.output, column.filter = " ", column.filterValue = " ", columns.strcmp.weights = c(), strcmpfun="jw", cluster_method="complete", cluster_height=0.1) {
  
  # run script for specified file 
  # U+00A7 means " as quote character
  df.input <- read.csv(file=inputFile, head=TRUE, sep=";")
  
  # some type checking
  if (typeof(strcmpfun) != "character")
    stop(sprintf(paste("Illegal strcmpfun type: %s", typeof(strcmpfun))))
  if (!is.data.frame(df.input))
    stop(sprintf(paste("Illegal data frame: %s", typeof(df.input))))
  if (!length(columns.strcmp > 0))
    stop(sprintf("Provide at least one column for fuzzy string matching"))
  if (length(columns.strcmp.weights) == 0)
    columns.strcmp.weights <- rep(1, length(columns.strcmp))
  if (length(columns.strcmp.weights) != length(columns.strcmp))
    stop(sprintf("Please provide exactly one weight for each column to for string comparison"))
  
  lapply(columns.strcmp, function (x) df.input[x] <- trim(tolower(df.input[[x]])))
  lapply(columns.strcmp, function (x) df.input[x] <- gsub("*", "", df.input[[x]]))
  if (!(column.filter == " ")) {
    df.input <- df.input[df.input[column.filter]==column.filterValue, ]
  }
  gc()
  
  if (length(columns.match) > 0) {
    #matchingFramesList <- split(df.input, df.input[,columns.match])
    
    df.input <- within(df.input, splitKey <- do.call("paste", c(df.input[columns.match], sep = ".")))
    names(df.input)[length(names(df.input))] <- "splitKey"
    #setkey(df.input, "splitKey")
    idx <- split(seq_len(nrow(df.input)), df.input$splitKey)
    # Check for segments without entities 
    filterList <- lapply(idx, function (x) length(x) > 1) 
    idx <- idx[unlist(filterList)]
    print(length(idx))
    rm(filterList)
    gc()
    matchingFramesList <- lapply(idx, function(x) { return(as.data.frame(df.input[x,])) } )
    
    # Calculate the number of cores
    #no_cores <- detectCores() - 1
    # Initiate cluster
    #cl <- makeCluster(no_cores)
    #combine matching columns in unique key
    #clusterExport(cl, varlist=c("idx", "df.input"), envir=environment())
    #matchingFramesList <- parLapply(cl, idx, function(x) { return(as.data.frame(df.input[x,])) } )
    
    #stopCluster(cl)
    #segmentNames <- unique(df.input[columns.match])
    
  }
  else {
    matchingFramesList <- list(ALL=df.input)
  }
  
  segmentNames <- names(matchingFramesList)
  lapply(segmentNames, function (x) multiColStrcmp(x, matchingFramesList[[x]], outputFile = outputFile, cols = columns.strcmp, colWeights = columns.strcmp.weights, columns.output = columns.output, method = strcmpfun, useBytes = TRUE, cluster_height = cluster_height))
  
  gc() 
  return(TRUE)
Sandra95

Re: Hilfe beim verstehen eines Skripts

Beitrag von Sandra95 »

Hallo,

vielen Dank für die vielen Tipps.

Das Skript ist für ein Tool geschrieben worden. In diesem Tool soll eine Datei eingegeben werden und die Daten in der hiesigen Datei sollen auf Dubletten überprüft werden.
Könnt ihr mir sagen, an welche Stelle des Skripts erkennbar ist, dass er diese Prüfung durchführt?
Und, worin liegt der Unterschied, wenn ich zb. ein Dataframe habe und damit Prüfungen durchführe zu Situationen, wenn ich allgemein formulieren muss für Daten, die eingegeben werden und die ich beim Programmieren noch nicht kenne.

VG
Sandra
bigben
Beiträge: 2771
Registriert: Mi Okt 12, 2016 9:09 am

Re: Hilfe beim verstehen eines Skripts

Beitrag von bigben »

Naja, nicht einfach Dubletten. Da steht was von " fuzzy string matching". Es geht also nicht einfach darum, gleiche, sondern auch ähnliche Spalten herauszufischen. Dafür verwendet der Programmierer hierarchisches Clustern (bist Du damit vertraut?). Das ganze wird dann sehr durcheinander und mir wird nicht klar, welche Daten da wo rein und raus gehen.

Ich denke, man sollte Zeit und Mühe lieber darin investieren, eigene Anforderungen zu erstellen und das neu zu programmieren.

Wer das hier geschrieben hat

Code: Alles auswählen

  counter <- counter+1
  rm(result)
  rm(result.dubs)
  rm(clusterDendro) 
  rm(clusterIDs)
}
Der kommt von einer ganz anderen Programmiersprache. Dessen Hintergrund ist irgendeine Programmiersprache, in der man ständig aufpassen muss, keine memory leaks zu basteln und in der man kein Vertrauen in den Garbage collector gewinnt. Kommt der von C/C++ oder gibt es da noch andere Kandidaten?

LG,
Bernhard
---
Programmiere stets so, dass die Maxime Deines Programmierstils Grundlage allgemeiner Gesetzgebung sein könnte
jogo
Beiträge: 2085
Registriert: Fr Okt 07, 2016 8:25 am

Re: Hilfe beim verstehen eines Skripts

Beitrag von jogo »

Sandra95 hat geschrieben: Do Nov 15, 2018 2:37 pm Das Skript ist für ein Tool geschrieben worden.
Ist denn der Autor noch greifbar?
In diesem Tool soll eine Datei eingegeben werden und die Daten in der hiesigen Datei sollen auf Dubletten überprüft werden.
Könnt ihr mir sagen, an welche Stelle des Skripts erkennbar ist, dass er diese Prüfung durchführt?
So wie ich das sehe, wird eine Clusterananlyse durchgeführt (hclust()) in der Funktion clusterStrDist(). Mit dieser Zeile werden die Anzahlen der Elemente pro Cluster gezählt:

Code: Alles auswählen

  occs <- table(result$clusterIDs)
Und, worin liegt der Unterschied, wenn ich zb. ein Dataframe habe und damit Prüfungen durchführe zu Situationen, wenn ich allgemein formulieren muss für Daten, die eingegeben werden und die ich beim Programmieren noch nicht kenne.
:?: :?: :?:

Hier ist noch ein kleines Lehrstück zu den sinnfreien rm()-Aufrufen
(bitte nach den Definitionen der Funktionen schön Zeile für Zeile laufen lassen):

Code: Alles auswählen

f1 <- function() { x <- 3; return(101); rm(x) }
f2 <- function() { x <- 4; rm(x); return(102) }
f3 <- function() { return(103); rm(x) }
f4 <- function() { rm(x); return(104) }
f5 <- function() { x <- 3; return(105) }  # ohne rm(x)


f1()
x
f2()
x
f3()
x
f4()
x
f5()
x

x <- "Hallo"
f2()
x
f4()
x
Gruß, Jörg
Sandra95

Re: Hilfe beim verstehen eines Skripts

Beitrag von Sandra95 »

Hallo jogo,

der Autor ist leider nicht mehr greifbar, was auch hier das Problem darstellt.

Okay, ins Clustering muss ich mich sowieso weiter vertiefen.


Zwei Fragen:
Was hat es mit dem Counter auf sich? Zählt man die Loops damit und verwendet das dann bei If- Anweisungen? (ich finde die R Definition nicht verständlich)

ausserhalb der Thematik: welchen Befehl benötige ich, wenn ich aus einem Datensatz ein Element herauslöschen möchte. Es handelt sich dabei um keine Variable. Ich würde mit remove arbeiten. Ist es wichtig, dann die genauen Daten zu nennen? Bsp: das Wort "World", welches man bei Ausgabe von 'coal_region§region' soll nicht mehr angezeigt werden.
Antworten