Bernhard Häussner
Journal: Neueste Artikel erscheinen hier (Seite 3 von 23)

SQL Window-Functions

29.05.2014, 12:27
Ein englischsprachiger Text über Window-Functions hinter LEGO-Fenstern

Ein englischsprachiger Text über Window-Functions hinter LEGO-Fenstern

Vor kurzem bin ich über die Window-Functions gestolpert, eine eher selten verwendete Funktionalität der Abfragesprache SQL. Man kann damit die vorherigen und folgenden Zeilen einer Zeile eines Datenbank-Abfrageergebnisses mit einbeziehen und Aggregatfunktionen auf einen Teil des Ergebnisses anwenden. Da ich sie bisher nicht kannte, habe ich sogleich etwas damit herum gespielt. Dabei sind zwei beispielhafte Anwendungen entstanden. Und weil ich denke, dass viele noch nicht von den Window-Functions gehört haben, werde ich sie im folgenden vorstellen.

Was sind die Window-Functions?

Window-Functions haben einige für SQL eher ungewöhnliche Eigenschaften:

  • Sie wenden eine Aggregatfunktion an, ohne dabei Zeilen im Ergebnis zu gruppieren. Hat man z.B. SUM(foo) OVER () in der Feldliste der Abfrage, erhält man in jeder Ergebniszeile die Summe der foos aller Zeilen.
  • Sie können auf weitere Zeilen relativ zur Position der aktuellen Zeile zugreifen. So gibt etwa LEAD(foo, 3) OVER(ORDER BY id) den Wert von foo drei Zeilen nach der aktuellen Zeile bei Sortierung nach id aus.
  • Sie können Aggregatfunktionen auf Bereiche einschränken. Hat man z.B. SUM(foo) OVER (ORDER BY id) in der Feldliste der Abfrage, erhält man in jeder Ergebniszeile die Summe der foos bis zur aktuellen Zeile.
  • Für die Angabe des Bereichs nach OVER können auch Bereiche von Zeilen oder Partitionen angegeben werden.
  • Window-Functions werden von den WHERE-Bedingungen der Abfrage beeinflusst.
  • Zusätzlich zu Aggregatfunktionen gibt es noch eine Liste spezieller Window-Functions, die nur in diesem Kontext Sinn machen.

An zwei Beispielen zu typischen Anwendungsfällen aus dem Bereich Finanzen und aus dem Bereich Biologie kann die genaue Funktionsweise besser nachvollzogen werden. Wer auch experimentieren will findet hier ein Gist mit lauffähigem Beispielcode für PostgreSQL und Beispieldaten oder ein SQL-Fiddle.

Beispiel Finanzwesen: Kumulative Summe

Das erste Beispiel soll die kumulative Summe sein. In alten Sparbüchern findet sich eine Liste von Kontobewegungen und am Ende stehen zwei Spalten: Eine für den Betrag der Transaktion, also die Veränderung des Kontostandes durch diese Kontobewegung, und eine für den neuen Kontostand nach der Transaktion, also die Summer der Beträge der bisherigen Kontobewegungen. Das wiederholte Aufführen der Summe stammt noch aus vor-digitaler Zeit, um das Aufsummieren zu erleichtern. Die Summen stellen allerdings eine Redundante Information dar, da sie leicht aus den Veränderungen berechnet werden können. In SQL würde man die Transaktionstabelle also z.B. so modellieren:

CREATE TABLE transactions
  ( id         SERIAL primary key
  , account_id INT    not null references accounts(id)
  , value_date DATE   not null
  , amount     MONEY
  )
  ;

Hier steht value_date für den Buchungstag und amount für die vorzeichenbehaftete Veränderung. Die Transaktionen aller Konten werden in einer Tabelle gespeichert, daher die account_id.

Sollen beispielsweise alle Kontostände angezeigt werden, hilft eine einfache Abfrage:

   SELECT t.account_id
        , SUM(t.amount) balance
     FROM transactions  t
 GROUP BY t.account_id
        ;
 account_id | balance  
------------+----------
          1 |   €87,39
          3 | -€134,56
          2 |    €7,66

Hier ist noch nichts überraschendes, nur eine ganz normale Aggregation der Daten. Jetzt soll ein Kontoauszug erstellt werden, bei dem in der letzten Spalte die jeweils der Kontostand nach jeder Transaktion angezeigt wird:

 id | account_id | value_date |  amount  | balance  
----+------------+------------+----------+----------
  1 |          1 | 2014-04-26 |  €100,00 |  €100,00
  2 |          1 | 2014-04-28 |   -€3,96 |   €96,04
  3 |          1 | 2014-04-28 |  -€18,65 |   €77,39
  4 |          1 | 2014-04-29 |   €10,00 |   €87,39
  5 |          2 | 2014-04-26 |   €20,00 |   €20,00
  6 |          2 | 2014-04-29 |  -€12,34 |    €7,66
  8 |          3 | 2014-04-27 | -€234,56 | -€234,56
  7 |          3 | 2014-04-28 |  €100,00 | -€134,56

Die Transaktionen sind jeweils nach Konto und Valuta sortiert. Der naive Ansatz, eine solche Tabelle zu erstellen, wäre es, ein Subquery für jede Zeile auszuführen, in dem die Summe bestimmt wird:

   SELECT t.*
        , (SELECT SUM(u.amount)
             FROM transactions u
            WHERE u.account_id = t.account_id
              AND (u.value_date, u.id)
               <= (t.value_date, t.id)
             ) AS balance
     FROM transactions t
 ORDER BY t.account_id
        , t.value_date
        , t.id
        ;

Dies ist die einfachste und naheliegendste Lösung. Doch bei etwas größeren Datenmengen zeigen sich schnell Performance-Probleme. Das Subquery muss für jeden Datensatz einzeln ausgeführt werden, wo jedesmal alle Datensätze aufs neue aufsummiert werden, was zu einer O(n²)-Laufzeit führt.

Theoretisch müssten wir aber nur den aktuellen Wert von amount auf die balance der vorherigen Zeile summieren. Um das der Datenbank-Engine beizubringen, kommt eine Window-Function ins Spiel:

   SELECT t.*
        , SUM(t.amount) OVER
              ( PARTITION BY t.account_id
                    ORDER BY t.value_date
                           , t.id
               RANGE BETWEEN unbounded preceding
                         AND current row
                           )
                          AS balance
     FROM transactions  t
 ORDER BY t.account_id
        , t.value_date
        , t.id
        ;

Anstatt eines Subqueries wird nun direkt die Aggregatfunktion SUM als Window-Function verwendet. Dazu wird sie eingeschränkt. Das PARTITION übernimmt die Aufgabe von WHERE und filtert die Datensätze von anderen Konten heraus. Mit RANGE gibt man die gewünschten relativen Zeilenkoordinaten an, hier von unbounded preceding (vom Anfang) bis current row (zur aktuellen Spalte).

Mit dieser Optimierung kann auf das Subquery verzichtet werden und die Lauzeit wird akzeptabel.

Beispiel Biologie: Gleitender Mittelwert

Das zweite Beispiel kommt aus der Bioinformatik. Bei der Analyse von Proteinen betrachtet man Bereiche mit kleinerer und größerer Hydophobizität. Dazu werden die einzelnen Aminosäuren der Kette einem Hydophobizitätswert zugeordnet. Betrachtet man nun die Reihe dieser Werte je in Isolation ergibt sich ein starkes Rauschen. Daher bildet man für jede Position mit den umgebenden Positionen den Mittelwert, ein guter Anwendungsfall für eine Window-Function.

Ich habe hier kurzerhand die Analyse vom Aquaporin-4 Isoform A, die ich schon vorher in R implementiert hatte, neu in SQL implementiert. Natürlich wird es in der Praxis meistens einfacher sein, einfach in R die entsprechenden Daten zu analysieren, gerade weil Plots auch viel leichter erstellt werden können. Für die Eingabedaten habe ich diese zwei Tabellen verwendet:

CREATE TABLE amino_acids
  ( letter         CHAR  primary key
  , name           VARCHAR(40) not null
  , hydrophobicity REAL
  )
  ;
 
CREATE TABLE aqp4
  ( id         SERIAL primary key
  , nucleotide CHAR   not null references amino_acids(letter)
  )
  ;

Die Tabelle amino_acids enthält die Hydophobizitätswerte der einzelnen Aminosäuren nach der Kyte and Doolittle scale, aqp4 die Abfolge der Aminosäuren im Aquaporin. Nun sollen jeweils mittels einer Window-Function die gleitenden Mittelwerte berechnet werden.

   SELECT amino_acids.*
        , AVG(hydrophobicity)
     OVER (ROWS BETWEEN 5 preceding AND 5 following)
     FROM aqp4
LEFT JOIN amino_acids
       ON amino_acids.letter = aqp4.nucleotide

Da nur ein Protein in der Tabelle gespeichert ist, wird dieses Mal keine Partitionierung benötigt. Interessant ist die Einschränkung der Zeilen mit den Zahlenangaben: Mit OVER (ROWS BETWEEN 5 preceding AND 5 following) werden die fünf vorhergehenden und die fünf nachfolgenden Zeilen in die AVG-Funktion einbezogen.

Mit dem SQL-Code erhält man tatsächlich die selben Werte wie mit der Referenzimplementierung in R:

 letter |     name     | hydrophobicity |         avg          
--------+--------------+----------------+----------------------
 M      | Methionin    |            1.9 |    -1.53333334128062
 S      | Serin        |           -0.8 |    -1.05714287076678
 D      | Aspartat     |           -3.5 |    -1.48750001192093
 -- [...]

Durch die Verwendung einer Window-Function konnte die Einschränkung der Mittelwert-Funktion auf die umliegenden Zeilen sehr elegant ausgedrückt werden.

Zusammenfassung

Die Window-Functions erweitern den Einsatzbereich von SQL-Analysen erheblich um Berechnungen, die sonst so einfach nicht programmiert werden könnten. Wer die genaue Dokumentation nachlesen will, wird beim PostgreSQL-Tutorial zu Window-Functions fündig, im letzen Abschnitt dort finden sich auch die Links zu den entsprechenden Kapiteln im Handbuch. Auf die Window-Functions gekommen bin ich durch einen jOOQ-Artikel zum Thema „Calculate Running Totals“, in diesem Blog finden sich auch Texte zu anderen eher exotischen SQL-Funktionalitäten. Außerdem finden sich hier die Folien zum Vortrag über Window-Functions, den ich am 6. Juni 2014 bei den Sophisticates gehalten habe.

Kommentare: keine

Rando – „asoziales“ Fotonetzwerk

24.03.2014, 12:34

Das schwedische Studio ustwo ließ vor gut einem Jahr die kostenlose Photosharing-App Rando auf die Welt los. Seither wurden über 10 Millionen Fotos („Randos“) zugestellt, zu hochzeiten gar 27000 per Stunde. Inzwischen ist wurde die App gelöscht, es wird sich herausstellen, ob sie jemals wieder Aufleben wird. Das Alleinstellungsmerkmal dieser App zum Fototausch ist es, das keine Likes, Freunde, Kommentare oder ähnliches verteilt und gelesen werden. Für jedes Foto, das man sendet, bekommt man genau ein Foto zurück - von einem zufälligen Anonymen irgendwo. Allein, man erfährt den ungefähren Ort (Stadt, Land). Was macht also den Reiz dieser App aus?

Edit 2014-03-29: Da es ganz so aus sieht, als wäre die App für immer tot, habe ich hier meine ganzen gesendeten Randos eingestellt.

Rando ist minimalistisches Fotografieren

Zunächst einmal ist das minimalistische Design auffällig: In zwei Timelines für gesendete und empfangene Fotos reihen sich die runden Bildchen untereinander. Mit einem Klick auf das Foto erscheint eine Karte, von wo es gesendet wurde, bzw. bei eigenen Fotos, wohin es versendet wurde. Mit einem Doppelklick auf ein empfangenes Foto erreicht man die Funktionen zum Download, löschen und markieren („flag“). Es gibt eine Kamera-Funktion, mit der sich Fotos in dem charakteristischen runden Format aufnehmen und hochladen lassen. Leider ist der Fokus der Kamera manchmal etwas schlecht steuerbar. Aber das war es dann auch schon an Funktionalität.

Asozial? Introvertiert? Oder fair…?

Dadurch, dass es so wenige Interaktionsmöglichkeiten gibt, setzt sich die App ab von Instagram, Snapchat, Tumblr und ähnlichem. Die Fotografen sind vom Zwang befreit, immer mehr Likes, +1, Shares, Herzen order Sterne zu sammeln. Die Empfänger können sich nicht durch geistreiche Kommentare profilieren. Statt immer mit dem selben Kreis von Freunden oder Followern zu kommunizieren, stammen die Fotos von zufälligen Rando-Usern aus der ganzen Welt. Auch die eigenen Fotos werden an zufällige Personen verschickt. Es sind nicht alle jemals hochgeladenen Fotos erreichbar und mit Tags, Suchmaschinen und Explore-Seiten kategorisiert und erreichbar. Stattdessen bekommt man nur genau so viele Fotos zurück, wie man verschickt hat.

Nun habe ich viel Aufgezählt, das Rando nicht macht, nicht kann, nicht sein soll. Aber was sind die positiven Einflüsse?

Das machen die Nutzer daraus:

Jeder Rando-Nutzer erhält eine Sammlung einzigartiger Fotos. Jedes erhaltene Foto hat niemand sonst erhalten, und stellt einen Moment im Leben eines anderen Rando-Nutzers dar, gesehen wie durch einen Türspion. Ein Teil der erhaltenen Randos sind mehr oder weniger abstrakt, ästhetische Alltagssituationen oder schwer erkennbare Szenen. Diese sind manchmal erstaunlich belanglos, aber oft auch sehr schön und farbenfroh.

Weiterhin beliebt sind Motive von Alltagsgegenständen, die den Fotografen wohl irgenwie am Herzen liegen, ins Auge stechen oder sonstwie interessieren. Ich erhielt teilweise Fotos von sehr einzigartigen Gegenständen, Souvenirs, Waffen, Kleidungsstücke, uvm.

Daneben erhält man Fotos von schönen Innenräumen, Personen, Mahlzeiten, Zeichnungen, Texten und einiges mehr. Sehr oft erhält man auch Fotos von Füßen/Schuhen, den ich manchmal nicht so viel abgewinnen kann.

Anfangs hatte ich natürlich bedenken, dass das ganze schnell in Pornographie ausartet. Anders als bei Chatroulette, sieht man jedoch sehr selten Penisse. Das liegt vielleicht daran, dass die Report-Funktion für Fotos eine von 3 Funktionen ist, und damit sehr leicht erreichbar.

F.A.Q.

Warum erhalte ich keine Position zu den empfangenen Fotos?
Die Position erhälst Du nur, wenn Du Deinerseits Deine Position schickst. Da die Position nur ca. auf die Stadt genau ist, sollte dies kaum Bedenken wecken. Etwa 82% der Fotos werden mit Ortsangabe verschickt.

Warum dauert es etwas, bis meine Fotos in der Liste der gesendeten Fotos auftauchen?
Es dauert manchmal zwischen 10 Minuten und einigen Stunden, bis von Dir hochgeladenen Fotos in deiner Liste der gesendeten Fotos stehen. Zum einen stellt der Rando-Server die Fotos in einer Warteschlange, von wo aus sie nach und nach versendet werden. Zum anderen tauchen sie erst in der Auflistung auf, wenn sie von dem Empfänger gesehen wurden. Darum kann hier auch die Reihenfolge eine andere sein, als die Aufnahmereihenfolge. In der Zwischenzeit kannst Du die Fotos in der Galerie im Rando-Album finden (nur Android).

Aus welchen Ländern kommen die meisten Fotos?
Laut offizieller Statistik vom Juli: Südkorea 39%, USA 17%, Russland 10%. Ich erhalte aber oft auch Fotos aus diversen anderen Ländern.

Wie wahrscheinlich ist es, dass ich unangemessene Fotos erhalte?
Die Anzahl der als unangebracht markierten Randos liegt unter 1%. Selbst von diesen markierten sind nur wenige richtig anstößig. Es mussten nur rund tausend Benutzerkonten wegen regelmäßiger Verstöße gesperrt werden.

Wohin wird sich die App entwickeln? Welche neuen Features sind geplant?
Die Entwickler sind sehr darauf bedacht, keine sozialen Features einzubauen. Stattdessen wurde eine "Collections"-Funktion angekündigt, sodass man erhaltene Schnappschüsse sortieren und kategorisieren kann. Außerdem solle es eine globale Karte geben, auf der du alle Positionen deiner Randos sehen kannst.

Werden meine Fotos immer an die selbe Person geschickt? Erhalte ich die Fotos immer von der selben Person?
Nein. Die Fotos werden an zufällige verschiedene Personen gesendet. Es gibt keine Möglichkeit ein Foto zu beantworten.

Warum werden meine Fotos manchmal nicht richtig Scharf?
Der Fokus der eingebauten Kamera ist manchmal etwas schlecht. Es hilft oft, kurz das Bild zu berühren, dann wird der Fokus neu berechnet.

Was mich an Rando begeistert

Viele meiner Freunde (und „Freunde“) auf Facebook laden fast keine Fotos hoch, manchmal begründet mit „das interessiert ja doch niemanden“ oder „so toll ist das jetzt auch nicht“. Rando zwingt die Nutzer zu gleichen Teilen Produzenten wie Konsumenten zu sein.

Wenn man keine Kontrolle hat, wer die Fotos erhält, muss man beim Fotografieren keine Zielgruppe im Hinterkopf haben. Man kann sich auch keine Gedanken machen, für wen dieses Foto interessant sein könnte.

Da man nicht nur die tollsten und poliertesten Fotos erhält, setzt man auch für sich selbst die Latte etwas niedriger. Jeder Fotografiert mit der eingebauten Handykamera, keiner bringt eine DSLR oder Scheinwerfer mit. Der Fotograf in mir freut sich: Es wird nicht erwartet, dass ein interessantes Motiv gewählt wurde, es aus einer Aussagekräftigen Perspektive eingefangen wird, perfekt ausgeleuchtet wird und die schönsten Farben mittels Bildbearbeitung herausgesucht wurden. Es genügen auch mal 50%, auch semi-interessante Motive sind mal ein Foto wert, und Bearbeitung der Fotos ist nicht möglich. Das ist stressfreies Fotografieren, verbunden mit der Herausforderung, in einem kreisrunden Format zu fotografieren.

Als Lohn erhält man Fotos von Füßen.

Kommentare: keine

Scrum-Workshop mit Marshmallow-Challenge

06.11.2013, 13:32

Ich habe heute zum ersten Mal mit agilen Methoden gearbeitet!

Zugegeben, natürlich werden agile Methoden angewendet in den Unternehmen, in denen ich gearbeitet habe und arbeite. Denn sie haben sich inzwischen immer öfter in der Praxis bewährt. Jedoch habe ich bisher nie mit einem standardisierten Vorgehensmodell wie Scrum gearbeitet.

Ich hatte nun die Gelegenheit einen Scrum-Workshop bei einem zertifizieren Scrum-Master mitzuerleben, bei der Exkursion am Mittwoch, den 30. Oktober zu DATEV in Nürnberg, dem zweitgrößten in Deutschland entwickelnden Softwarehaus.

Was ich dabei über Scrum gelernt habe, und wie die Marshmallow-Challenge auch deinem Team spielend helfen kann, will ich hier notieren.

Agile Methode: Scrum

Agile Methoden basieren alle auf dem Manifesto for Agile Software Development, unter diesen ist Scrum eine der beliebtesten. Es kann nicht annähernd in einem Blogeintrag beschrieben werden. Als Einstig empfehle ich den Wikipedia-Artikel über Scrum zu lesen. Wer sich nicht eingehend mit Scrum beschäftigen will, aber dennoch den Rest des Textes verstehen will, findet hier eine ganz kurze Zusammenfassung, worum es geht:

Die Kommunikation mit dem Kunden läuft über den Product Owner (PO), der die Anforderungen User Stories im Product Backlog sammelt und diese gegenüber dem Entwicklungsteam kommuniziert. Im Entwicklungsteam hat der Scrum Master ein Auge darauf, dass die Methoden richtig durchgeführt werden, und steht mit konstruktiver Kritik zur Seite. Im Verlauf der wiederholten Sprints werden in 2 bis 4 Wochen jeweils funktionierende Produkte gebaut, die immer weitere User Stories implementieren.

Für jeden Sprint werden die User Stories in feinkörnige Tasks aufgespalten. Die Tasks werden am Taskboard als Kärtchen gesammelt (ähnlich Kanban) und dort von den Teammitgliedern während der Bearbeitung „reserviert“. Im 15-minütigen Daily Scrum werden zu Beginn jedes Arbeitstags Fortschritte kommuniziert und Hindernisse, sogenannte Impediments, angesprochen.

Das Taskboard kann komplett handschriftlich bzw. nicht-digital ausgeführt werden, um einerseits ausreichend Kommunikation zu gewährleisten, und andererseits, um die Task-Bearbeitung eine private Angelegenheit des Teams zu machen, welche nicht vom Management eingesehen wird.

Nach jedem Sprint wird festgestellt, ob die User Stories implementiert wurden und inwiefern das Verfahren für die Zukunft verbessert werden kann.

Die Grundzüge von Scrum wurden uns zunächst erklärt und dann von uns anhand eines kleinen Projekts in die Praxis umgesetzt:

Scrum-Marshmallow-Challenge

Die Marshmallow-Challenge wurde für Scrum angepasst. Die Materialien lassen sich für kleines Budget besorgen und werden zunächst an das Teams vergeben:

  • 20 Spaghetti-Halme
  • 2 Marshmallows
  • 1 Meter Paketschnur
  • 1 Meter Maler-Kreppklebeband
  • 1 Analoge Uhr

Jetzt tritt der PO vor die Teams und verkündet die User-Story für den ersten Sprint: Es soll ein möglichst hoher Turm errichtet werden, mit einem Marshmallow auf der Spitze. Der Turm darf nicht am Tisch/Boden verankert werden. Die Dauer des Sprints ist unbekannt.

Bereits nach dem ersten Sprint sollen die Teams ein funktionierendes Produkt präsentieren. Jedoch, nachdem die 5 Minuten abgelaufen sind, zeichnet sich ab: Viele Teams haben lange geplant und keinen Turm gebaut, oder nur einen sehr kleinen.

Für den zweiten Sprint schafft der PO ein Impediment: Es darf nur ein Marshmallow verwendet werden. Somit musste mein Team den Turm wieder komplett einreißen, denn wir haben einen Marshmallow zerteilt und als Mörtel verwendet. Andere Teams können ungehindert weiter arbeiten. Je nach Kreativität der Teilnehmer muss nun auch verboten werden, die Uhr in den Turm einzubauen.

Weil wir nun in der zweiten Phase die verfügbare Zeit schon besser einschätzen konnten, haben wir nun – mehr schlecht als recht – einen standfähigen, ca. 60 cm hohen Turm zusammengezimmert, sodass wir recht zuverlässig auf den dritten Sprint schauen konnten.

Dieser sollte jedoch noch ein überraschend herausforderndes Impediment bieten: Im dritten Sprint darf keiner mehr reden! Alles muss in völliger Stille erledigt werden. Zum Glück war unser Turm schon konzeptionell ausgereift, sodass wir uns auf die Stabilisierung konzentrieren konnten. Mit einigem Gestikulieren und vorläufigem Platzieren von Nudeln konnten die Ideen auch stumm kommuniziert und beraten werden.

Ein Arbeitsmaterial wurde von uns unterschätzt: Kaum jemand hatte während der Sprints auf die Uhr gesehen! Damit hätte vielleicht etwas besser geplant werden können.

Es dämmerte wohl schon den meisten Teams, dass der dritte auch der letzte Sprint sein könnte, die End-Ergebnisse konnten sich durchaus sehen lassen.

Ein Team hatte eine Marshmallow-Challenge-Erfahrene dabei, und konnte die Sprints sehr gut meistern: Bereits am Ende des ersten Sprints hatten sie einen vergleichsweise hohen Turm gebaut, während andere noch im zweidimensionalen planten. Ihr Turm hatte eine sehr stringente Form und hätte sicherlich gewonnen, wäre er nicht gerade zum Ende des letzten Sprints umgefallen. Vielleicht eine Allegorie des IT-Projekts mit erfahrenen Programmieren, welches am Ende an einer Kleinigkeit scheitert?

Unser Turm wurde zweiter und machte einen stabilen Eindruck. Er sah etwas chaotisch aus, was bestimmt daran lag, dass wir viele Ideen zugelassen und ausprobiert haben.

Das Gewinner-Team hatte einen Turm, der unserem erstaunlich ähnlich sah, jedoch sehr ordentlich aufgebaut war.

Und was hat das mit Scrum zu zun?

Da der praktische Teil des Workshops nur rund eine Stunde dauerte, konnte Scrum natürlich nicht vollständig durchgeführt werden. Die Artefakte wurden nur rudimentär Erstellt, und die Daily Scrums mit dem Taskboard entfallen ganz.

Doch trotz dieser Einschränkungen, konnten die Werte der agilen Software-Entwicklung in kurzer Zeit veranschaulicht werden.

Dass funktionierende Produkte viel gelten, wurde uns nach der ersten Runde klar, als unser Team nur einen instabilen, zusammengefallenen Turm vorweisen konnte. Obwohl es eine einfache Aufgabe und nur ein Spiel war, fanden wir unseren „Entwicklerstolz“ angekratzt, da wir unser Commitment nicht erfüllten.

Wie schwer stetige Zusammenarbeit mit dem Kunden und das Eingehen auf Veränderungen ist, wurde uns klar, nachdem wir unseren Turm wieder einreißen mussten, als wir nur noch ein Marshmallow zur Verfügung hatten, und somit das Folgen unseres ursprünglichen Plans unmöglich wurde.

Für die Bedeutung von Individuen und Interaktionen war der dritte, stumme Sprint eine prägende Erfahrung. Sogar ohne Sprache war die Abstimmung im Team noch wichtiger als das eigentliche Bauen.

Abseits von Scrum und Softwareentwicklung

Natürlich kann die Marshmallow-Challenge auch für beliebige Teams aus allen Branchen verwendet werden. Hier kam ich auf ähnliche Rückschlüsse wie Tom Wujec in seinem TED-Talk „Build a tower, build a team“:

In der Diskussion mit den Teamkollegen wird sehr schnell klar, wer welche Rolle einnimmt, ohne dass hierfür ein reales Projekt mit all den, bei der Analyse hinderlichen, Vertraulichkeiten und Machtverstrickungen beobachtet werden muss.

Auch wurde der vorher noch eher anonyme Konferenzraum während der Bastelei zu unserem Arbeitszimmer und es machte sich eine produktive und motivierte Stimmung breit.

Aus dem Vergleich der Turmhöhen mit verscheiden zusammengestellten Teams, lassen sich Rückschlüsse auf die Erfolgschancen bilden. Uns wurde von einer Reihe von Fällen berichtet:

  • Eine Gruppe von Business-School-Absolventen ging so erfolgsbesessen vor, dass ihr fertiger Turm deutlich niedriger war, als der von Kindergarten-Absolventen.
  • Im TED-Talk wird das Erfolgskonzept von Kindergartenkindern, die besser als der Durchschnitt agieren, im iterativen Vorgehen gefunden, immer mit dem Marshmallow an der Spitze: Wir werden also mit Scrum geboren!
  • Eine Gruppe mit mit ausschließlich CEOs wurde von einer mit einem als Leiter eingesetzten CEO übertroffen. Dabei denke ich an „Viele Köche verderben den Brei“.
  • Erwartungsgemäß baute eine Gruppe mit Ingenieuren den höchsten Turm.

Die Materialien können kostengünstig angeschafft werden. Vielen Büro-Menschen macht es auch Spaß, einmal einer Handwerklicheren Aufgabe nachzugehen. Die Tatsache, dass auch Kindergartenkinder hohe Türme zustande bringen, beweist wohl, dass hierbei keiner überfordert wird.

Somit kann ich jedem Team empfehlen, einmal selbst die Marshmallow-Challenge zu probieren!

Kommentare: keine

Javascript in Photoshop

22.10.2011, 13:32
Photoshop-Skripting-Beispielbild

Photoshop-Skripting-Beispielbild

Als ich kürzlich eine ganze Reihe Ausweise mit Namen ausfüllen musste, habe ich mir angeschaut, wie man Photoshop CS3 automatisieren kann. Ich war erstaunt herauszufinden, das PS mit Javascript gesteuert werden kann, neben den zusammenklickbaren Makros, VBScript und AppleScript.

Hello-Word-Beispieldateien

Da der komplette Code etwas länger ist, habe ich eine Beispiel-PSD und alle Skripte auf Github online gestellt. Dort unter Downloads findet sich eine zip-Datei mit allem Nötigen, um das hier beschriebene Hello-World-Skript auszuführen.

Dokumentation

Das Photoshop-Scripting ist sehr ausführlich dokumentiert. Zunächst gibt es im Ordner C:\Programme\Adobe\Adobe Utilities\ExtendScript Toolkit 2\SDK zwei Dokumente: Einmal Adobe Intro to Scripting.pdf, welches man lesen sollte, wenn man „noch nie programmiert hat“ oder nicht so genau weiß, was Anwendungen skripten allgemein ist.

Außerdem JavaScript Tools Guide CS3.pdf. Hier findet sich zunächst ein Überblick über das IDE von Adobe (ich habe aber alles direkt in kwrite geschrieben) und dann folgen die Dokumentationen zu den Libraries für Dateisystem, UI, HTTP, Sockets, und vieles weitere. In dem Dokument wird regelmäßig auf Beispieldateien aus dem Brige SDK hingewiesen, dieses findet sich hier.

Die Photoshop-Spezifischen APIs stehen in der Photoshop Javascript Referenz. Photoshop stellt ein DOM zur Verfügung.

Erstellen der Photoshop-Datei

Zuerst kann man eine ganz normale Photoshop-Datei erstellen. Gute Ebenen-Organisation erleichtert später das Skripten. Wenn möglich, packt man alle Ebenen, die bearbeitet werden sollen, zusammen in eine Gruppe (Ordner). Dann findet man sie im Skript leichter wieder. Wenn man, wie ich, mehrere Ausweise o.ä. auf eine DIN-A4-Seite zusammenstellt, kann man jeden Ausweis gleich und in einer eigenen Gruppe anlegen, auch das macht das Skripten einfacher.

Skripte ausführen

Um ein fertiges Skript auszuführen klickt man im Photoshop-Menü Datei > Skripten > Durchsuchen und wählt die .jsx Datei. Das Skript läuft sofort los, eventuell erscheinen Fehlermeldungen als Dialogbox.

Exkurs: Eingabe-Daten verarbeiten

Man könnte zwar rein technisch gesehen mit Javascript die Daten von einem FTP-Server laden, dann irgendwie verarbeiten und letztendlich daraus die Bilder generieren. Ich habe mich jedoch darauf beschränkt, die Daten von einer Tabelle auf einer Webseite zu kopieren (sie sind dann Tabulator-Getrennt in der Zwischenablage) und sie mit awk in ein entsprechendes JSON-Format zu bringen:

awk 'BEGIN{RS="\r\n";FS="\t+"} {print "{planet:\"" $1 "\",comment:\"" $2 "\"},"}' planeten.raw.txt > planeten.json.txt

Da ich auf Windows arbeite (awk läuft auf Linux, die Dateien liegen in einem Samba-Share), musste ich awk sagen, dass die Zeilen auch noch einen Wagenrücklauf beinhalten. Die entstandenen JSON-Daten habe ich dann in das Skript hart kodiert:

var data=[
  {name:"world"},
  {name:"venus"},
  {name:"mars"},
  {name:"pluto",
   comment:"You're not a real planet though. But still, welcome. "}
];

Photoshop-Ebenen bearbeiten und speichern

Hauptsächlich soll das Skript Photoshop-Textebenen mit neuen Texten versehen. Hierzu gibt es in der Dokumentation auch Beispiele. Im Wesentlichen iteriert man durch die Daten, findet in layerSets den Ebenen-Ordner mit den Texten, und bearbeitet dann die Eigenschaft textItem.contents der verschiedenen artLayers (Ebenen). Der Code sieht dann so aus:

for (var dataI = 0; dataI < data.length; dataI++) {
    
  var textGroup = activeDocument.layerSets[0];
  
  textGroup.artLayers[0].textItem.contents=
    "Hello, "+data[dataI].name+"!";
  
  if(data[dataI].comment) {
    textGroup.artLayers[1].textItem.contents=
      data[dataI].comment;
  } else {
    textGroup.artLayers[1].textItem.contents=
      "Welcome to Photoshop scripting.";
  }
  
  // code zum speichern
  
}

Speichern und Exportieren

Ich bearbeite immer das activeDocument und speichere das neue Bild dann als Kopie. Hier überschreibt Photoshop bereits generierte Dateien ohne nachzufragen. Die Speicher-Optionen können als XxxSaveOptions-Objekte mitgegeben werden.

// Erstellen der Optionen fuer JPEG-Export:
var jpgOpts = new JPEGSaveOptions();
jpgOpts.embedColorProfile=true;
jpgOpts.quality=12; // hoechste Q

// Dokument speichern:
activeDocument.saveAs(filename,jpgOpts,true/*=als Kopie*/);

Den neuen Pfad generiere ich aus dem Dateinamen das aktuellen Dokuments und einer fortlaufenden Nummer. Dazu benutze ich eine Reihe nützlicher Funktionen. Damit ich diese nicht in jeder Skript-Datei einfügen muss, benutze ich die include-Funktion von Adobes ExtendedScript:

#include util.jsxinc

Der vollständige Code der util.jsxinc findet sich im Github-Repository.

Benutzeroberfläche

Bisher wird einfach nur eine Sanduhr angezeigt während das Skript läuft, also erstelle ich ein Fenster mit einem Fortschrittsbalken. Man kann auch komplexere UIs zur Eingabe von Parametern usw. erstellen, aber dies lohnt sich vermutlich selten. Ein Fortschrittsbalken mit Cancel-Funktion ist jedoch nicht so schwer zu erstellen. Im Wesentlichen benötigt man dies:

var dlg = new Window('window', _(names));
dlg.progressbar = dlg.add('progressbar');
dlg.progressbar.preferredSize = [400,20];
dlg.show();

// Zur Haelfte fertig:
dlg.progressbar.value = 50;

Hilfslinen-Skript

Ein weiteres Skript kann Hilfslinien erstellen. Am Besten kopiert man es in den Ordner Adobe Photoshop CS3\Vorgaben\Skripten, damit es immer im Skript-Menü auftaucht. Ich finde es immer extrem nervig, für Druckränder und Mittellinien die ganzen Hilfslinien zu erstellen. Genauso nervig ist es, für jedes Druckformat eine Vorlage zu basteln. Lieber generiere ich die Hilfslinien mit diesem kurzen Skript.

ExtendScript hat auch noch weitere Features, wie Reflection und Operator Overloading, welches dann bei den UnitValues Rechnen mit Einheiten ermöglicht. In ExtendScript geht das:

alert( (UnitValue(3,"mm") + UnitValue(7,"pt")*0.9).as("cm") );

Da es für die Hilfslinienerstellung keine Funktionen gibt, habe ich die Methode createGuide(offs,orientation) mit dem ScriptListener erstellt. Man kopiert Adobe Photoshop CS3\Skript Handbuch\Hilfsprogramme\ScriptListener.8li nach Adobe Photoshop CS3\Zusatzmodule\Automatisieren und startet PS neu. Dieses Plugin erstellt auf dem Desktop ein ScriptingListenerJS.log, in dem die u.A. die aufgenommenen Aktionen als Javascript-Code gespeichert werden. Diesen Code muss man dann noch parametrisieren und erhält so für alles, was man in Photoshop anklicken kann, den entsprechenden Code.

Auch die Ressourcen-Deskriptoren habe ich im Skript verwendet. Mit ihnen kann man aus einem String direkt einen ganzen UI-Baum erstellen.

Zusammenfassung

Ressourcen zum Text:

Das Skripten von Photoshop ist schon nach kurzem Einarbeiten für den Javascript-Kenner wohl wesentlich einfacher als das zusammenklicken von Aktionen und Stapelverarbeitungen. Wer sich intensiver mit der Dokumentation befasst kann darüber hinaus sehr flexibel mit Daten aus verschiedenen Quellen automatisiert Dokumente erstellen.

Kommentare: keine

Über Pythons kurze Listennotation

03.07.2011, 20:34
Python-Code mit Listennotation

Python-Code mit Listennotation

Als ich mir Python (3.2) anschaute, musste ich mich zunächst an das Fehlen traditioneller for-Schleifen gewöhnen, doch inzwischen finde ich großen Gefallen an den Iterationsmethoden von Python und an der Listennotation (List Comprehensions).

Zunächst ein einfaches Programm mir dieser Listenverarbeitungssyntax. Folgendes Python-Stück erstellt eine Liste mit den ersten 5 Quadratzahlen von ungeraden Zahlen:

>>> [i**2 for i in range(10) if i%2==1]
[1, 9, 25, 49, 81]

Jetzt ein Beispiel aus der Python-Dokumentation, wie man zwei Vektoren multiplizieren kann:

>>> vec1 = [2, 4, 6]
>>> vec2 = [4, 3, -9]
>>> [vec1[i]*vec2[i] for i in range(len(vec1))]
[8, 12, -54]

Wobei ich anhand dieses Codefetzens zeigen will, dass sich Dinge auf vielerlei Arten und mitunter einfacher machen lassen. Man kann unter Verwendung von zip() über zwei Listen gleichzeitig iterieren: (mit enumerate() ließe sich auch noch über den Index iterieren)

>>> [e1*e2 for e1,e2 in zip(vec1,vec2)]
[8, 12, -54]

An den nächsten Codezeilen sehen wir erstmals, wie schön in Python ähnliche Datentypen die gleichen Operatoren verwenden: Der Intervall-Itarator, welcher von range() zurückgegeben wird, kann als Sequenztyp wie eine Liste zerstückelt werden:

>>> a=5
>>> [i for i in range(200-a*2,200,2)]
[190, 192, 194, 196, 198]
>>> [i*2 for i in range(100)[-a:]]
[190, 192, 194, 196, 198]

Sehr praktisch sind auch die Aggeregatfunktionen all(), any(), max(), min(), sum(), len() da sich mit ihnen viel Boilerplate-Code vermeiden lässt, der so ähnlich aussieht wie dieser:

>>> testlist=[1,8,9]
>>> test=False
>>> for x in testlist:
...     if(x>4):
...         test=True
...         break
...
>>> test
True

Nach dramatischer Verkürzung:

>>> any(x>1 for x in testlist)
True

Interessanterweise bleibt der Effekt von break erhalten. Das kann man prüfen, indem man mit yield-Syntax ein eigenes, iterierbares Objekt erstellt, welches beim iteriert-werden Statusmeldungen ausgibt. Hier wird die 9 nicht mehr getestet:

>>> def worker(l):
...     for i in l:
...         print("Proccessing %02d" % i)
...         yield i>4
...
>>> w2=worker(testlist)
>>> any(w2)
Proccessing 01
Proccessing 08
True

PS: In Haskell (von welchem Python seine Listennotation hat) kann man den selben Effekt noch schöner an unendlichen Listen sehen: any (> 5) [0..] ergibt True.

Man kann die Aggregatfunktionen auch auf Strings anwenden, da auch diese iterierbar sind:

>>> any(""),any("ha")
(False, True)
>>> max("Panzer")
'z'

Für eine Suchfunktion (theoretische Informatik...) brauchte ich eine Methode herauszufinden, wie lange das längste Präfix eines Strings ist, welches ein Suffix eben jenes Strings und eines weiteren Buchstaben ist. Was ich zunächst hier mit traditionellen Methoden als 10-zeilige Funktion geschrieben, habe ich dann hier als eine Listenverarbeitung schreiben können:

>>> v="abcabd"
>>> i=5
>>> b="c"
>>> max([0]+[k for k in range(0,i+2) if (v[:i]+b)[-k:]==v[:k]])
3

Oder ich habe gemessen, dass die Diagonale in einem Rechteck 29,4° geneigt ist, aber wie war das Seitenverhältnis? Da sorted() eine lexikographische Ordnung herstellt, kann man mehrere Tupel sortieren, wobei der erste Eintrag das Sortierkriterium ist und die weiteren die Ausgangsdaten. Dann baut man noch eine zweite Listennotaton außen herum zum Formatieren. Hiermit findet man also heraus, dass es wohl 16:9 war:

>>> import math,fractions
>>> ", ".join(["{}:{}".format(b,a) for diff,a,b in sorted(
...     [(abs(math.atan(a/b)-((29.4/360)*2*math.pi)),a,b)
...         for a in range(20) for b in range(20)
...       if b>a>0 and fractions.gcd(a,b)==1 ]
... )[:3]])
'16:9, 7:4, 9:5'

Hier sieht man schon ein Problem der Listennotation: Sie wird schnell unübersichtlich, da dutzende Befehle in einem Ausdruck stehen, und man auch eher von hinten nach vorne lesen muss.

Da man mit dem +-Operator Listen konkatenieren kann, kann auch die sum()-Funktion beliebig viele Listen aus einer Liste konkatenieren. Hiermit werden je 3 Zahlen startend bei 0,9 und 18 in eine Liste geschrieben:

>>> sum([range(i,i+3) for i in range(0,27,9)],[])
[0, 1, 2, 9, 10, 11, 18, 19, 20]

Das einzige, was man hier tun muss, ist sum() eine leere Liste als zweites Argument zu übergeben, da sonst die Listen zum Standartanfangswert 0 addiert werden würden (Exception). Etwas komisch ist dann leider das:

>>> sum((str(i) for i in [1,2,345,6,78]),"")
Traceback (most recent call last):
  File "", line 1, in 
TypeError: sum() can't sum strings [use ''.join(seq) instead]

Und wenn man denkt, man weiß alles über Listen, fängt man mit Mengen an:

>>> noprimes={j for i in range(2, 8) for j in range(i*2, 50, i)}
>>> set(range(2, 50))-noprimes # Primzahlen!
{2, 3, 5, 7, 41, 11, 13, 47, 17, 37, 19, 43, 23, 29, 31}
>>> {chr(i) for i in range(97,97+26)} - set( # Pangram powers!
... "The quick brown fox jumps over the lazy dog" )
set() 

Wie man sieht bilden die Iterator- und Sequenztypen ein mächtiges Instrument, wenn man Python programmiert. Wie vieles kann man sie aber auch missbrauchen.

Kommentare: keine
 
Χρόνογραφ
© 2008-2017 by Bernhard Häussner - Impressum - Login
Kurz-Link zu dieser Seite: http://1-co.de/bj