Hi zusammen,
ich möchte euch heute ein paar ATML-Funktionen höherer Ordnung vorstellen. Wir haben sie vor langer Zeit entwickelt, um die Ereignisse in Fußballspielen besser auswerten zu können, und gestern bin ich wieder über sie gestolpert, als ich eine Reihe von Daten zur Sonntagsfrage (Wenn am Sonntag Bundestagswahl wäre, welche Partei würden Sie wählen?) auswerten wollte.
Sie alle beschäftigen sich damit, dass man Daten in eine Liste packt, und darüber herausfindet, wie sie zueinander stehen. Das ist praktisch für Fußballtore, Flugpreise, Wertpapiere in einem Markt oder auch nur, um herauszufinden, welcher KPI diesen Monat aus der Reihe tanzt.
Macht euch also bereit in die wunderbare Welt von Listenverarbeitung mit filter(), list(), sort() und map()
Mein Problem:
Im Datensatz habe ich diverse Datenfelder, die den Stimmenanteil der Parteien enthalten. Im Feld selbst steht nur eine Zahl.
Aus dieser Menge würde ich gerne ableiten: Welche Partei hat den höchsten Stimmenanteil? Welche den niedrigsten? Welche Parteien sind unter der 5%-Hürde? Und das bitte nur für diejenigen Parteien, die auch einen gültigen Eintrag haben, der nicht “null” ist.
1. Schritt: Alles in eine Liste einlesen, und die Werte mit Namen verknüpfen.
In ATML3 Properties kann man fast alle Datenobjekte verwenden, die JSON erlaubt. Man kann also Listen, Strings, Zahlen oder Dictionaries direkt “hart” in die Mapping Expression schreiben. Außerdem kann man sie mit Variablen oder Datenfeldaufrufen verknüpfen.
In diesem Beispiel habe ich für jedes Datenfeld zwei key-value-Paare in einem Element angeordnet:
{"key name": "Name der Partei als String", "key percent": "Das Datenfeld mit dem Stimmenanteil der Partei"}
.
Diese Elemente kann ich später sortieren und filtern und weiß trotzdem immer, welcher Name zu welchem Prozentwert gehört.
Ich schreibe also:
Die Kenzeichner für Anfang und Ende einer Liste:
[ ]
Die Kennzeichner für Anfang und Ende eines JSON Objekts.
[{ }]
Das Komma als Trennzeichen zwischen den Objekten in der Liste.
[{ }, { }]
Innerhalb der Objekte alle Texte, die ich hart setzen möchte, als Strings in Anführungszeichen
Als Verbinder eines Key-Value Paares den Doppelpunkt.
Für Datenfelder, deren Inhalt ich direkt aus dem Datensatz in die Liste holen möchte, die Raute
[{"name": "AfD", "percent": #AfD}, {"name": "SPD", "percent": #SPD}]
Name:
LIST_Parteienwerte_Parteiennamen
Truth Expression:
true
Mapping Expression:
[{ "name": "AfD", "percent": #AfD},{ "name": "CDU und CSU", "percent": #CDU_CSU},{ "name": "FDP", "percent": #FDP},{ "name": "freie Wähler", "percent": #Freie_Waehler},{ "name": "Grüne", "percent": #Gruene},{ "name": "Linke", "percent": #Linke},{ "name": "Piraten", "percent": #Piraten},{ "name": "Rechte", "percent": #Rechte},{ "name": "sonstige Parteien", "percent": #Sonstige},{ "name": "SPD", "percent": #SPD}]
Schritt 2: Die Liste sortieren, damit wir den größten und den kleinsten Eintrag auslesen können
Ich will erreichen, dass meine Einträge nach dem Stimmanteil sortiert werden. Dazu benutze ich eine Funktion namens “sort”, die ein Listenobjekt sortieren kann. Wenn die Liste nur aus Zahlen bestünde, könnte ich einfach sort($Listenobjekt) schreiben. Da sie aus Wertepaaren besteht, muss ich der Funktion zusätzlich sagen, dass sie die Einträge nach dem Wert sortieren soll, der im Feld “percent” steht.
Heraus kommt hier übrigens immer eine aufwärts sortierte Liste, und die Elemente, die gar keinen Eintrag haben, werden als 0 gezählt und stehen am Anfang.
Name:
LIST_Sortierte_Parteien
Truth Expression
true
Mapping Expression
sort(list($LIST_Parteienwerte_Parteiennamen), "percent")
Schritt 3. Die Liste um ungültige Einträge bereinigen
Meine Liste enthält noch Einträge, die ich nicht verwenden möchte: Meine Daten reichen zurück bis in die 1960er ud einige Parteien gab es in den früheren Umfragen noch nicht. Bei ihnen enthält mein JSON Import den Wert null
Name
LIST_Gefilterte_Parteien
Truth Expression
true
Mapping Expression
filter(list($LIST_Sortierte_Parteien), [x -> str(#x.percent) != ""])
Ups! In welchem Handbuch steht denn das?
Was wir hier sehen, ist eine Funktion, die mit Lambda-Ausdrücken arbeitet (der Teil in den eckigen Klammern).
Sie geht durch die Einträge in einem Listenobjekt und erfüllt in ATML3 eine ähnliche Funktion wie for --> next Schleifen in prozeduralen Programmiersprachen.
Man gibt der Funktion erst an, durch welches Objekt sie gehen soll. Sicherheitshalber wickeln wir noch die Anweisung darum, dass dieses Objekt auf jeden Fall als Liste behandelt werden soll.
filter(list($LIST_Sortierte_Parteien))
^-- in Prosa heißt das: Geh durch jedes Element der Liste LIST_Sortierte_Parteien
Im zweiten Teil findet die eigentliche Lambda-Expression statt. Hier wird definiert, was die Funktion in jedem Element tun soll:
[x -> str(#x.percent) != ""])
Das Statement x -->
bedeutet: Fortan heißt jedes Listenelement x. Und für jedes x machst du bitte folgendes:
str(#x.percent) != ""
Hier formuliere ich die Bedingung, nach der das Element gefiltert weren soll: Wenn es ein Kind-element namens “percent” hat, das nicht leer ist.
filter(list($LIST_Sortierte_Parteien), [x -> str(#x.percent) != ""])
In Summe also: Gehe mit der Funktion “filter” durch die Liste “LIST_Sortierte_Parteien” und schaue in jedem Element (x) nach, ob es nicht leer ist.
4. Die Wackelparteien finden, die unter der 5%-Hürde herumkrebsen
Als nächstes habe ich eigentlich das selbe nochmal gemacht: Die Liste habe ich gefilter, um die Parteien zu finden, die unter 5% der Stimmen bekommen haben. Für die wird es schließlich spannend, und eine Aussage wie “die FDP muss um den Einzug ins Parlament bangen” gibt dem Text ein wenig Farbe.
Name
LIST_Parteien_unter_5
Truth Expression
true
Mapping Expression
filter(list($LIST_Gefilterte_Parteien), [x -> numeric(#x.percent) < 0.05])
Wie gesagt, die filter-funktion tut hier das selbe. Das Zweite Beispiel würde ich gerne nutzen, um etwas über die Syntax in unseren Lambda-Expressions zu erzählen:
Dieser Ausdruck macht nämlich einen mathematischen Vergleich, und ist etwas anders zusammengesetzt als der letzte:
Im ersten haben wir geprüft,
str(#x.percent) != ""
die Variable wird als Text-String behandelt und in str()
gewrappt.
Die gängigsten Operatoren für Strings sind:
!=
für ungleich
==
für gleich.
Der Vergleichsgegenstand muss in ""
gehüllt sein, denn wir suchen ja nach einem String.
In diesem prüfen wir, ob eine Zahl rein rechnerisch kleiner ist als 0.05.
numeric(#x.percent) < 0.05
Hier gehen mehr Operatoren:
!=
für ungleich
==
für gleich.
>=
für größer gleich.
<=
für kleiner gleich.
<
für kleiner als.
>
für größer als.
Da der Vergleichsgegenstand eine Zahl ist, muss er “nackt” bleiben. Statt eines Kommas muss ich den Dezimalpunkt benutzen.
0.05
Ich haben also eine Liste mit allen Parteien unter 5%. Die kann ich aber noch nicht richtig im Text verwenden, denn jeder Eintrag hat nochmal Untereinträge, nämlich “name” und “percent”.
Daher greife ich tief in die Mottenkiste und komme mit unserer absoluten Supergeheimwaffe und einigen Spinnweben im Haar wieder hervor. Mit der Funktion map()
Das ist eine reine for-next-Schleife, die ein Listenobjekt durchgehen und etwas für jedes Objekt tun kann. Heraus kommt eine eindimensionale Liste, die etwas für jedes Objekt enthält. Ich habe mich entschieden, mit map() eine kleine Phrase nach dem Schema “SPD mit 22 %” aus den Werten in “name” und “percent” zu bauen.
Name
GROUP_Parteien_unter_5
Truth Expression
true
Mapping Expression
map(list($LIST_Parteien_unter_5), [x -> #x.name + " mit " + #x.percent + " %"])
Die Syntax im Lambda-Ausdruck ist die selbe wie bei filter().
Ich definiere meine Variable x
und sage mit dem ->
"tu folgendes.
#x.name + " mit " + #x.percent + " %"
Ich rufe die beiden Bestandteile von x auf. Mit +
sagem ich der Lambda-Expression, dass sie sie zu einem String zusammenfügen soll. Genauso verfahre ich mit den festen Textebestandteilen " mit " und " %".
Fertig.
Jetzt habe ich eine eindimensionale Liste. Das ist gut, denn die kann ich endlich in einem ATML3-Container aufrufen und sie so in einem Satz benutzen. Und genau das tue ich auch.
[GROUP_Parteien_unter_5.All(),conj=und] müssen um den Einzug ins Parlament bangen.
Jetzt kann ich sagen: “AfD mit 2% und FDP mit 4,5% müssen um den Einzug ins Parlament bangen.”
5. Partei mit dem kleinsten und größten Stimmenanteil ausgeben
Jetzt haben ich also eine Liste und habe mit ihren diversen Teilmengen Hexenwerk gewirkt:
- Alle Ergebnisse sind mit sort() nach Stimmanteil sortiert
- Die ungültigen Ergebnisse sind mit filter() ausgemerzt
- Die Teilmenge der Parteien unter 5% ist ebenfalls mit filter() in ein neues Objekt ausgeleitet
- Für diese Parteien haben wir mit map() schönere Formulierungen gebaut und in einer Gruppenproperty gesammelt.
Als letztes möchte ich noch die sortierte und bereinigte Liste nutzen, um über die Sortierung an das kleinste und größte Ergenbis zu kommen.
Hierbei mache ich mir zunutze, dass ich sie als JSON-Liste aufgebaut habe.
In ATML3 kann man mit einem kleinen Trick auf jedes Element einer Liste direkt zugreifen, wenn man seine Position kennt.
Name
MAX_Partei_Name
Truth Expression
true
Mapping Expression
$LIST_Sortierte_Parteien[9].name
Der Trick ist: Einfach die Position des gewünschten Elements in eckigen Klammern direkt an den Namen des Objekts anhängen. Der Ausdruck in diesem Beispiel gibt mir den Namen des 10. Elements in der Liste. Achtung. Die Zählung beginnt bei 0, wie in jeder ordentlichen Programmiersprache.
Ich benutze in dem Beispiel noch einen kleinen Exploit: Ich weiß, dass die Liste maximal 10 Einträge haben kann, denn so habe ich sie gebaut. Durch das Filtern kann sie zwar kleiner werden. Wenn man eine ATML3-Liste zu hoch addressiert (und zwar als Variable), gibt sie einfach den letzten Eintrag ausgibt, den sie hat. Sehr hacky
Auf die selbe Weise hole ich mir in einer anderen Variable noch den Prozentwert der Partei mit den meisten Stimmen.
$LIST_Sortierte_Parteien[9].percent
Für die Partei mit den wenigsten Stimmen setze ich einfach die kleinstmögliche Zahl ein, um das erste Element der Liste zu bekommen:
$LIST_Sortierte_Parteien[0].name
.
$LIST_Sortierte_Parteien[0].percent
.
Wir erinnern uns: Der Entwickler zählt “Null, Eins, Zwei, Drei, Vier”.
Und damit habe ich einen Haufen einzelner Zahlenwerte mit Hilfe einer Liste in Bezug zueinander gesetzt und analysiert.