1 Datentransformation - Teil 3

library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.3.2 ──✔ ggplot2 3.3.6      ✔ purrr   0.3.4 
✔ tibble  3.1.8      ✔ dplyr   1.0.10
✔ tidyr   1.2.1      ✔ stringr 1.4.1 
✔ readr   2.1.3      ✔ forcats 0.5.2 ── Conflicts ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
library(nycflights13)

1.1 Weiter: Spalten gruppieren und zusammenfassen

1.1.1 Fehlende Werte

Bei einigen Funktionen muss explizit angegeben werden, dass NAs entfernt werden sollen, damit beispielsweise der Mittelwert berechnet werden kann:

flights %>% 
  group_by(year, month, day) %>% 
  summarise(mean = mean(dep_delay))
`summarise()` has grouped output by 'year', 'month'. You can override using the `.groups` argument.

Falls na.rm = TRUE nicht gesetzt wird, ist das Ergebnis immer NA, sofern fehlende Werte in den Daten vorhanden sind.

Daher haben die Funktionen zur Aggregation immer ein na.rm Parameter:

flights %>% 
  group_by(year, month, day) %>% 
  summarise(mean = mean(dep_delay, na.rm = TRUE))
`summarise()` has grouped output by 'year', 'month'. You can override using the `.groups` argument.

Anstatt bei jeder Funktion na.rm zu setzen, können wir den Datensatz zuvor auch vorbereiten, indem die NAs in einem ersten Schritt entfernt werden. Je nach Fragestellung kann das sinnvoll sein, jedoch gehen hierdurch auch Daten verloren.

nicht_annulliert <- flights %>%
    filter(!is.na(dep_delay), !is.na(arr_delay))

nicht_annulliert %>%
    group_by(year, month, day) %>%
    summarise(mean = mean(dep_delay))
`summarise()` has grouped output by 'year', 'month'. You can override using the `.groups` argument.

Kleiner Hinweis: die Gestaltung, wie Leerzeichen und Zeilenumbrüche gesetzt werden, das obliegt Ihnen.

Der Übersichtlichkeit wegen wird meistens nach %>% ein Zeilenumbruch eingeleitet.

1.1.2 Anzahlen bestimmen

Um gleichzeitig zu den Zusammenfassungen einzusehen, wie sich die Anzahl der Gruppen verhält, kann die Zählfunktion n() oder der Befehl sum(!is.na(x)) verwendet werden. So können Sie beispielsweise direkt einsehen, ob eine Gruppe mit starken Unterschieden gegebenfalls sehr klein ist.

Betrachten wir für einen Moment die durchschnittliche Verspätung für jedes Flugzeug (identifiziert durch tailnum):

delays <- nicht_annulliert %>%
    group_by(tailnum) %>%
    summarise(avg_delay = mean(arr_delay))
delays
# Verteilung der Häufigkeiten der Delays:
ggplot(data = delays, mapping = aes(x = avg_delay)) +
    geom_freqpoly(binwidth = 10)

Wir sehen, dass der Großteil der Flugzeuge sich um Verspätungen im Bereich von 0 Minuten bewegen. Die Kurve ist lecht nach rechts verschoben, denn Verspätungen sind häufiger als frühzeitige Ankünfte. Es ist aber auch einzusehen, dass es Flugzeuge mit einer durchschnittlichen Verspätung von über 5 Stunden gibt.

Wenn wir nun einen Blick auf die Flugzeuge und ihre Flughäufigkeit werfen, sehen wir Folgendes:

delays <- nicht_annulliert %>%
    group_by(tailnum) %>%
    summarise(avg_delay = mean(arr_delay, na.rm = TRUE),
                        anzahl = n())
delays
ggplot(data = delays, mapping = aes(x = anzahl, y = avg_delay)) +
    geom_point(alpha = 1/10)

Sie sehen, dass die Variation (Schwankungen) bei Flügen mit einer geringen Frequenz größer ist. Die Variation (z. B. der Mittelwerte) nimmt mit der Stichprobengröße ab.

Das ist eine typische Beobachtungen und hebt die Wichtigkeit der Stichprobengröße hervor, um aussagekräftige Ergebnisse abzuleiten.

Dementsprechen kann es sinnvoll sein, Gruppen mit einer geringen Häufigkeit zu filtern, sodass “Ausreißer”/seltene Beobachtungen, die eine große Variation verursachen, aus der Verteilung entnommen werden.

Wir könnten nun also zusätzlich nach der Anzahl in unserem Dataframe filtern:

delays %>%
    filter(anzahl > 25) %>%
    ggplot(mapping = aes(x = anzahl, y = avg_delay)) +
        geom_point(alpha = 1/10)

  • Achten Sie auf die Achsen! Die durchschnittliche Verspätung überschreitet 60 Minuten nicht mehr.

  • Es ist hierbei lediglich etwas umständlich, von der Pipe %>% zu dem Operator + für ggplot Grafiken zu wechseln.

1.1.3 Funktionen für summarise()

An dieser Stelle eine Liste von weiteren nützlichen Funktionen mit Hilfe dessen Daten zusammengefasst werden können:

  • Der Median median(x), welcher den Mittelpunkt der Daten beschreibt (50% der Daten liegen ober/unter diesem Wert). Häufig bietet der Median eine solide Alternative zu dem Mittelwert mean(x) an, denn er ignoriert Ausreißer:
nicht_annulliert %>%
    group_by(year, month, day) %>%
    summarise(median_delay = median(arr_delay),
                        avg_delay = mean(arr_delay))
`summarise()` has grouped output by 'year', 'month'. You can override using the `.groups` argument.
  • Maßzahlen für die Streuung: Varianz var(x), Standardabweichung sd(x), Interquartilsbereich IQR(x), Median der absoluten Abweichungen mad(x). Hierbei sind IQR(x) und mad(x) robustere Alternativen zu sd(x):
# Die Distanz zu manchen Flughäfen ist variabler (größere SD) als zu anderen:
nicht_annulliert %>%
    group_by(dest) %>%
    summarise(distanz_sd = sd(distance),
                        distanz_iqr = IQR(distance),
                        distanz_mad = mad(distance)) %>%
    arrange(desc(distanz_sd))
  • Maßzahlen für Ränge: min(x), max(x), quantile(x, 0.25). Das Quantil hier beschreibt die Grenze, bei der x größer als 25% und kleiner als 75% der Daten ist:
# Erster, letzter Flug und der Moment, wo 90% der Flüge am Tag vorbei sind:
nicht_annulliert %>%
    group_by(year, month, day) %>%
    summarise(erster_flug = min(dep_time),
                        letzter_flug = max(dep_time),
                        fast_alle_fluege_vorbei = quantile(dep_time, 0.9)
                        )
`summarise()` has grouped output by 'year', 'month'. You can override using the `.groups` argument.
  • Maßzahlen für Positionen: first(x), nth(x, 10), last(x) geben den ersten, den zehnten und den letzten Wert des Dataframes aus:
nicht_annulliert %>%
    group_by(year, month, day) %>%
    summarise(erster_flug = first(dep_time),
                        letzter_flug = last(dep_time),
                        zehnter_flug = nth(dep_time, 10))
`summarise()` has grouped output by 'year', 'month'. You can override using the `.groups` argument.

Hinweis: diese Funktionen lassen sich ergänzend zu Sortierungen mit min_rank() verwenden, wobei hier die gesamten Einträge erhalten bleiben:

nicht_annulliert %>%
    group_by(year, month, day) %>%
    mutate(abflug_rang = min_rank(desc(dep_time))) %>%
    filter(abflug_rang %in% range(abflug_rang))
  • Zählfunktionen: Zusätzlich zu n() lässt sich die Anzahl der nicht-fehlende Werte durch sum(!is.na(x)) bestimmen und die Anzahl an einzigartigen Werten mit n_distinct(x):
flights %>%
    group_by(dest) %>%
    summarise(einzigartige_flugzeuge = n_distinct(carrier),
                        alle_fluege = n(),
                        annullierte_fluege = sum(is.na(dep_time))) %>%
    arrange(desc(einzigartige_flugzeuge))

Um das zu vereinfachen gibt es zusätzlich die count(x) Hilfsfunktion:

nicht_annulliert %>%
    count(dest)

Falls nicht nur die Häufigkeit der Gruppe gezählt werden soll, sondern die Summe einer Variable für jede Gruppe (z. B. die gesamte Flugzeit aller Flugzeuge zu einem Flughafen), so kann das Argument wt verwendet werden:

nicht_annulliert %>%
    count(dest, wt = air_time)

Für das Zählen bzw. Summen von Variablen für Gruppen kann also mit group_by() gruppiert werden und dann die gewünschte Operation verwendet werden oder direkt count().

  • Bedingte Zählfunktionen: Abschließend lassen sich auch die Anzahl an erfüllten Bedingungen oder deren Proportion bestimmen. Beispielsweise gibt sum(x > 10) aus, wie viele Werte x größer als 10 sind und mean(x == 0), wie viel Prozent der Werte von x einen Wert von 0 haben:
# Anzahl der Flüge, die vor 5 Uhr stattgefunden haben:
nicht_annulliert %>%
    group_by(year, month, day) %>%
    summarise(fruehe_fluge = sum(dep_time < 500))
`summarise()` has grouped output by 'year', 'month'. You can override using the `.groups` argument.
# Verhältnis der Flüge mit einer Verspätung von über 60 Minuten:
nicht_annulliert %>%
    group_by(year, month, day) %>%
    summarise(verspaetung_verhaeltnis = mean(arr_delay > 60))
`summarise()` has grouped output by 'year', 'month'. You can override using the `.groups` argument.

Sie sehen an dieser Stelle aber auch, dass einige Operationen redundant sind. Sie können auf verschiedenen Wegen dieselben Ergebnisse erhalten.

1.1.4 Beispiel Gruppierung mit mehreren Variablen

Wenn mehrere Variablen zur Gruppierung verwendet werden, wird mit jedem summarise() Befehl eine Gruppe verworfen:

taeglich <- group_by(flights, year, month, day)
(pro_tag <- summarise(taeglich, anzahl_fluege = n()))
`summarise()` has grouped output by 'year', 'month'. You can override using the `.groups` argument.
(pro_monat <- summarise(pro_tag, anzahl_fluege = sum(anzahl_fluege)))
`summarise()` has grouped output by 'year'. You can override using the `.groups` argument.
(pro_jahr <- summarise(pro_monat, anzahl_fluege = sum(anzahl_fluege)))

Hierbei muss genau bedacht werden, was in jedem Schritt gemacht wird. Ansonsten entstehen Probleme beispielsweise, wenn hier schrittweise der Median berechnet werden würde.

1.1.5 Gruppierung rückgängig machen

Mit Hilfe von ungroup() wird die Gruppierung rückgängig gemacht:

taeglich %>%
    ungroup() %>%
    summarise(anzahl_fluege = n())

1.2 Spalten gruppieren und weiterverarbeiten

Neben summarise() lassen sich auch mutate() und filter() mit Gruppierungen verwenden:

  • Die 5 Flüge pro Tag mit der längsten Verspätung:
flights %>%
    group_by(year, month, day) %>%
    filter(rank(desc(arr_delay)) < 5)
  • Alle Einträge einer Gruppe, die oberhalb einer Grenze liegen:
beliebte_ziele <- flights %>% 
  group_by(dest) %>% 
  filter(n() > 365)
beliebte_ziele
  • Metriken für Gruppen:
beliebte_ziele %>%
    filter(arr_delay > 0) %>%
    mutate(proportion_delay = arr_delay / sum(arr_delay)) %>%
    select(year:day, dest, arr_delay, proportion_delay)

Abhängig von der Anwendung können gruppierte Filterung unerwartete Nebeneffekte haben, daher ist hierbei Vorsicht geboten.

2 Aufgaben

In diesem Abschnitt soll erneut der diamonds Datensatz betrachtet werden:

library(tidyverse)
diamonds

2.1 Übungsaufgaben

  1. Überlegen Sie zunächst, nach welchen Variablen sich der Datensatz diamonds gruppieren lässt. Denken Sie hierbei besonders an die unterschiedlichen Datentypen.

  2. Welche Farbe der Diamanten hat den geringsten Wert?

    1. Wie erklären Sie sich das Ergebnis?

    2. Erstellen und betrachten Sie hierzu auch ein Streudiagramm mit dem Zusammenhang von carat und price bezüglich der Farben.

  3. Gruppieren Sie die Diamanten nach der Variable cut und berechnen Sie die Mittelwerte für carat, price und depth.

    1. Erkennen Sie hierbei Zusammenhänge?

    2. Stellen Sie die durchschnittlichen Preise durch Balkendiagramme für alle Kategorien von cut dar.

    3. Erstellen Sie einen Boxplot mit den Preisen für jede Kategorie cut. Vergleichen Sie das Ergebnis mit dem Balkendiagramm.

  4. Gruppieren Sie die Diamanten nach carat und berechnen Sie Minimum, Maximum und Mittelwert von price.

    1. Erstellen Sie auf Grundlage der Mittelwerte und der Variable carat ein Streudiagramm.

    2. Erstellen Sie ein Streudiagramm für alle Einträge mit carat und price. Fügen Sie zusätzlich die bestimmten Mittelwerte in die Grafik mit geom_line() ein. Setzen Sie hierzu stat = "smooth".

    3. Entfernen Sie zuletzt alle Einträge, die seltener als 20 mal auftreten.

  5. Finden Sie für jede Kategorie von color die Tiefen (depth), bei denen mehr als 25%, 50% und 75% der Diamanten einen geringen Wert aufweisen.

  6. Finden Sie für jeden cut den teuersten und den günstigsten Diamanten. Entspricht das Ergebnis Ihren Erwartungen? Woran kann dieser Zusammenhang liegen?

  7. Betrachten Sie neben dem cut auch die Klarheit clarity und geben Sie den Anteil und die Anzahl von Diamanten mit einem Preis von über 10000 aus.

    1. Wählen Sie im Anschluss die ersten fünf aller Ergebnisse aus. Stoßen Sie hierbei auf ein Problem?

    2. Erstellen Sie eine Grafik mit Histogrammen der Preise für alle Kategorien von cut und clarity (durch facet_grid()).

    3. Markieren Sie in der Grafik die Bereiche rot, bei denen der Preis über 10000 liegt. Vergleichen Sie die Grafik mit den Anteilen, die Sie zuvor berechnet haben.

LS0tCnRpdGxlOiAiU3RhdGlzdGlrIHVuZCBEYXRhIFNjaWVuY2UgLSBHcnVuZGxhZ2VuIGRlciBEYXRlbmFuYWx5c2UgMyIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOiAKICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIG51bWJlcl9zZWN0aW9uczogeWVzCi0tLQoKIyBEYXRlbnRyYW5zZm9ybWF0aW9uIC0gVGVpbCAzCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkobnljZmxpZ2h0czEzKQpgYGAKCiMjIFdlaXRlcjogU3BhbHRlbiBncnVwcGllcmVuIHVuZCB6dXNhbW1lbmZhc3NlbgoKIyMjIEZlaGxlbmRlIFdlcnRlCgpCZWkgZWluaWdlbiBGdW5rdGlvbmVuIG11c3MgZXhwbGl6aXQgYW5nZWdlYmVuIHdlcmRlbiwgZGFzcyBgTkFzYAplbnRmZXJudCB3ZXJkZW4gc29sbGVuLCBkYW1pdCBiZWlzcGllbHN3ZWlzZSBkZXIgTWl0dGVsd2VydCBiZXJlY2huZXQKd2VyZGVuIGthbm46CgpgYGB7cn0KZmxpZ2h0cyAlPiUgCiAgZ3JvdXBfYnkoeWVhciwgbW9udGgsIGRheSkgJT4lIAogIHN1bW1hcmlzZShtZWFuID0gbWVhbihkZXBfZGVsYXkpKQpgYGAKCkZhbGxzIGBuYS5ybSA9IFRSVUVgIG5pY2h0IGdlc2V0enQgd2lyZCwgaXN0IGRhcyBFcmdlYm5pcyBpbW1lciBgTkFgLApzb2Zlcm4gZmVobGVuZGUgV2VydGUgaW4gZGVuIERhdGVuIHZvcmhhbmRlbiBzaW5kLgoKRGFoZXIgaGFiZW4gZGllIEZ1bmt0aW9uZW4genVyIEFnZ3JlZ2F0aW9uIGltbWVyIGVpbiBgbmEucm1gIFBhcmFtZXRlcjoKCmBgYHtyfQpmbGlnaHRzICU+JSAKICBncm91cF9ieSh5ZWFyLCBtb250aCwgZGF5KSAlPiUgCiAgc3VtbWFyaXNlKG1lYW4gPSBtZWFuKGRlcF9kZWxheSwgbmEucm0gPSBUUlVFKSkKYGBgCgpBbnN0YXR0IGJlaSBqZWRlciBGdW5rdGlvbiBgbmEucm1gIHp1IHNldHplbiwga8O2bm5lbiB3aXIgZGVuIERhdGVuc2F0egp6dXZvciBhdWNoIHZvcmJlcmVpdGVuLCBpbmRlbSBkaWUgYE5Bc2AgaW4gZWluZW0gZXJzdGVuIFNjaHJpdHQgZW50ZmVybnQKd2VyZGVuLiBKZSBuYWNoIEZyYWdlc3RlbGx1bmcga2FubiBkYXMgc2lubnZvbGwgc2VpbiwgamVkb2NoIGdlaGVuCmhpZXJkdXJjaCBhdWNoIERhdGVuIHZlcmxvcmVuLgoKYGBge3J9Cm5pY2h0X2FubnVsbGllcnQgPC0gZmxpZ2h0cyAlPiUKCWZpbHRlcighaXMubmEoZGVwX2RlbGF5KSwgIWlzLm5hKGFycl9kZWxheSkpCgpuaWNodF9hbm51bGxpZXJ0ICU+JQoJZ3JvdXBfYnkoeWVhciwgbW9udGgsIGRheSkgJT4lCglzdW1tYXJpc2UobWVhbiA9IG1lYW4oZGVwX2RlbGF5KSkKYGBgCgpLbGVpbmVyIEhpbndlaXM6IGRpZSBHZXN0YWx0dW5nLCB3aWUgTGVlcnplaWNoZW4gdW5kIFplaWxlbnVtYnLDvGNoZQpnZXNldHp0IHdlcmRlbiwgZGFzIG9ibGllZ3QgSWhuZW4uCgpEZXIgw5xiZXJzaWNodGxpY2hrZWl0IHdlZ2VuIHdpcmQgbWVpc3RlbnMgbmFjaCBgJT4lYCBlaW4gWmVpbGVudW1icnVjaAplaW5nZWxlaXRldC4KCiMjIyBBbnphaGxlbiBiZXN0aW1tZW4KClVtIGdsZWljaHplaXRpZyB6dSBkZW4gWnVzYW1tZW5mYXNzdW5nZW4gZWluenVzZWhlbiwgd2llIHNpY2ggZGllIEFuemFobApkZXIgR3J1cHBlbiB2ZXJow6RsdCwga2FubiBkaWUgWsOkaGxmdW5rdGlvbiBgbigpYCBvZGVyIGRlciBCZWZlaGwKYHN1bSghaXMubmEoeCkpYCB2ZXJ3ZW5kZXQgd2VyZGVuLiBTbyBrw7ZubmVuIFNpZSBiZWlzcGllbHN3ZWlzZSBkaXJla3QKZWluc2VoZW4sIG9iIGVpbmUgR3J1cHBlIG1pdCBzdGFya2VuIFVudGVyc2NoaWVkZW4gZ2VnZWJlbmZhbGxzIHNlaHIKa2xlaW4gaXN0LgoKQmV0cmFjaHRlbiB3aXIgZsO8ciBlaW5lbiBNb21lbnQgZGllIGR1cmNoc2Nobml0dGxpY2hlIFZlcnNww6R0dW5nIGbDvHIKamVkZXMgRmx1Z3pldWcgKGlkZW50aWZpemllcnQgZHVyY2ggYHRhaWxudW1gKToKCmBgYHtyfQpkZWxheXMgPC0gbmljaHRfYW5udWxsaWVydCAlPiUKCWdyb3VwX2J5KHRhaWxudW0pICU+JQoJc3VtbWFyaXNlKGF2Z19kZWxheSA9IG1lYW4oYXJyX2RlbGF5KSkKZGVsYXlzCiMgVmVydGVpbHVuZyBkZXIgSMOkdWZpZ2tlaXRlbiBkZXIgRGVsYXlzOgpnZ3Bsb3QoZGF0YSA9IGRlbGF5cywgbWFwcGluZyA9IGFlcyh4ID0gYXZnX2RlbGF5KSkgKwoJZ2VvbV9mcmVxcG9seShiaW53aWR0aCA9IDEwKQpgYGAKCldpciBzZWhlbiwgZGFzcyBkZXIgR3Jvw590ZWlsIGRlciBGbHVnemV1Z2Ugc2ljaCB1bSBWZXJzcMOkdHVuZ2VuIGltCkJlcmVpY2ggdm9uIDAgTWludXRlbiBiZXdlZ2VuLiBEaWUgS3VydmUgaXN0IGxlY2h0IG5hY2ggcmVjaHRzCnZlcnNjaG9iZW4sIGRlbm4gVmVyc3DDpHR1bmdlbiBzaW5kIGjDpHVmaWdlciBhbHMgZnLDvGh6ZWl0aWdlIEFua8O8bmZ0ZS4gRXMKaXN0IGFiZXIgYXVjaCBlaW56dXNlaGVuLCBkYXNzIGVzIEZsdWd6ZXVnZSBtaXQgZWluZXIgZHVyY2hzY2huaXR0bGljaGVuClZlcnNww6R0dW5nIHZvbiDDvGJlciA1IFN0dW5kZW4gZ2lidC4KCldlbm4gd2lyIG51biBlaW5lbiBCbGljayBhdWYgZGllIEZsdWd6ZXVnZSB1bmQgaWhyZSBGbHVnaMOkdWZpZ2tlaXQKd2VyZmVuLCBzZWhlbiB3aXIgRm9sZ2VuZGVzOgoKYGBge3J9CmRlbGF5cyA8LSBuaWNodF9hbm51bGxpZXJ0ICU+JQoJZ3JvdXBfYnkodGFpbG51bSkgJT4lCglzdW1tYXJpc2UoYXZnX2RlbGF5ID0gbWVhbihhcnJfZGVsYXksIG5hLnJtID0gVFJVRSksCgkJCQkJCWFuemFobCA9IG4oKSkKZGVsYXlzCmdncGxvdChkYXRhID0gZGVsYXlzLCBtYXBwaW5nID0gYWVzKHggPSBhbnphaGwsIHkgPSBhdmdfZGVsYXkpKSArCglnZW9tX3BvaW50KGFscGhhID0gMS8xMCkKYGBgCgpTaWUgc2VoZW4sIGRhc3MgZGllIFZhcmlhdGlvbiAoU2Nod2Fua3VuZ2VuKSBiZWkgRmzDvGdlbiBtaXQgZWluZXIKZ2VyaW5nZW4gRnJlcXVlbnogZ3LDtsOfZXIgaXN0LiBEaWUgVmFyaWF0aW9uICh6LiBCLiBkZXIgTWl0dGVsd2VydGUpCm5pbW10IG1pdCBkZXIgU3RpY2hwcm9iZW5ncsO2w59lIGFiLgoKRGFzIGlzdCBlaW5lIHR5cGlzY2hlIEJlb2JhY2h0dW5nZW4gdW5kIGhlYnQgZGllIFdpY2h0aWdrZWl0IGRlcgpTdGljaHByb2Jlbmdyw7bDn2UgaGVydm9yLCB1bSBhdXNzYWdla3LDpGZ0aWdlIEVyZ2Vibmlzc2UgYWJ6dWxlaXRlbi4KCkRlbWVudHNwcmVjaGVuIGthbm4gZXMgc2lubnZvbGwgc2VpbiwgR3J1cHBlbiBtaXQgZWluZXIgZ2VyaW5nZW4KSMOkdWZpZ2tlaXQgenUgZmlsdGVybiwgc29kYXNzICJBdXNyZWnDn2VyIi9zZWx0ZW5lIEJlb2JhY2h0dW5nZW4sIGRpZQplaW5lIGdyb8OfZSBWYXJpYXRpb24gdmVydXJzYWNoZW4sIGF1cyBkZXIgVmVydGVpbHVuZyBlbnRub21tZW4gd2VyZGVuLgoKV2lyIGvDtm5udGVuIG51biBhbHNvIHp1c8OkdHpsaWNoIG5hY2ggZGVyIEFuemFobCBpbiB1bnNlcmVtIERhdGFmcmFtZQpmaWx0ZXJuOgoKYGBge3J9CmRlbGF5cyAlPiUKCWZpbHRlcihhbnphaGwgPiAyNSkgJT4lCglnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gYW56YWhsLCB5ID0gYXZnX2RlbGF5KSkgKwoJCWdlb21fcG9pbnQoYWxwaGEgPSAxLzEwKQpgYGAKCi0gICBBY2h0ZW4gU2llIGF1ZiBkaWUgQWNoc2VuISBEaWUgZHVyY2hzY2huaXR0bGljaGUgVmVyc3DDpHR1bmcKICAgIMO8YmVyc2NocmVpdGV0IDYwIE1pbnV0ZW4gbmljaHQgbWVoci4KCi0gICBFcyBpc3QgaGllcmJlaSBsZWRpZ2xpY2ggZXR3YXMgdW1zdMOkbmRsaWNoLCB2b24gZGVyIFBpcGUgYCU+JWAgenUKICAgIGRlbSBPcGVyYXRvciBgK2AgZsO8ciBnZ3Bsb3QgR3JhZmlrZW4genUgd2VjaHNlbG4uCgojIyMgRnVua3Rpb25lbiBmw7xyIGBzdW1tYXJpc2UoKWAKCkFuIGRpZXNlciBTdGVsbGUgZWluZSBMaXN0ZSB2b24gd2VpdGVyZW4gbsO8dHpsaWNoZW4gRnVua3Rpb25lbiBtaXQgSGlsZmUKZGVzc2VuIERhdGVuIHp1c2FtbWVuZ2VmYXNzdCB3ZXJkZW4ga8O2bm5lbjoKCi0gICBEZXIgTWVkaWFuIGBtZWRpYW4oeClgLCB3ZWxjaGVyIGRlbiBNaXR0ZWxwdW5rdCBkZXIgRGF0ZW4gYmVzY2hyZWlidAogICAgKDUwJSBkZXIgRGF0ZW4gbGllZ2VuIG9iZXIvdW50ZXIgZGllc2VtIFdlcnQpLiBIw6R1ZmlnIGJpZXRldCBkZXIKICAgIE1lZGlhbiBlaW5lIHNvbGlkZSBBbHRlcm5hdGl2ZSB6dSBkZW0gTWl0dGVsd2VydCBgbWVhbih4KWAgYW4sIGRlbm4KICAgIGVyIGlnbm9yaWVydCBBdXNyZWnDn2VyOgoKYGBge3J9Cm5pY2h0X2FubnVsbGllcnQgJT4lCglncm91cF9ieSh5ZWFyLCBtb250aCwgZGF5KSAlPiUKCXN1bW1hcmlzZShtZWRpYW5fZGVsYXkgPSBtZWRpYW4oYXJyX2RlbGF5KSwKCQkJCQkJYXZnX2RlbGF5ID0gbWVhbihhcnJfZGVsYXkpKQpgYGAKCi0gICBNYcOfemFobGVuIGbDvHIgZGllIFN0cmV1dW5nOiBWYXJpYW56IGB2YXIoeClgLCBTdGFuZGFyZGFid2VpY2h1bmcKICAgIGBzZCh4KWAsIEludGVycXVhcnRpbHNiZXJlaWNoIGBJUVIoeClgLCBNZWRpYW4gZGVyIGFic29sdXRlbgogICAgQWJ3ZWljaHVuZ2VuIGBtYWQoeClgLiBIaWVyYmVpIHNpbmQgYElRUih4KWAgdW5kIGBtYWQoeClgIHJvYnVzdGVyZQogICAgQWx0ZXJuYXRpdmVuIHp1IGBzZCh4KWA6CgpgYGB7cn0KIyBEaWUgRGlzdGFueiB6dSBtYW5jaGVuIEZsdWdow6RmZW4gaXN0IHZhcmlhYmxlciAoZ3LDtsOfZXJlIFNEKSBhbHMgenUgYW5kZXJlbjoKbmljaHRfYW5udWxsaWVydCAlPiUKCWdyb3VwX2J5KGRlc3QpICU+JQoJc3VtbWFyaXNlKGRpc3Rhbnpfc2QgPSBzZChkaXN0YW5jZSksCgkJCQkJCWRpc3RhbnpfaXFyID0gSVFSKGRpc3RhbmNlKSwKCQkJCQkJZGlzdGFuel9tYWQgPSBtYWQoZGlzdGFuY2UpKSAlPiUKCWFycmFuZ2UoZGVzYyhkaXN0YW56X3NkKSkKYGBgCgotICAgTWHDn3phaGxlbiBmw7xyIFLDpG5nZTogYG1pbih4KWAsIGBtYXgoeClgLCBgcXVhbnRpbGUoeCwgMC4yNSlgLiBEYXMKICAgIFF1YW50aWwgaGllciBiZXNjaHJlaWJ0IGRpZSBHcmVuemUsIGJlaSBkZXIgYHhgIGdyw7bDn2VyIGFscyAyNSUgdW5kCiAgICBrbGVpbmVyIGFscyA3NSUgZGVyIERhdGVuIGlzdDoKCmBgYHtyfQojIEVyc3RlciwgbGV0enRlciBGbHVnIHVuZCBkZXIgTW9tZW50LCB3byA5MCUgZGVyIEZsw7xnZSBhbSBUYWcgdm9yYmVpIHNpbmQ6Cm5pY2h0X2FubnVsbGllcnQgJT4lCglncm91cF9ieSh5ZWFyLCBtb250aCwgZGF5KSAlPiUKCXN1bW1hcmlzZShlcnN0ZXJfZmx1ZyA9IG1pbihkZXBfdGltZSksCgkJCQkJCWxldHp0ZXJfZmx1ZyA9IG1heChkZXBfdGltZSksCgkJCQkJCWZhc3RfYWxsZV9mbHVlZ2Vfdm9yYmVpID0gcXVhbnRpbGUoZGVwX3RpbWUsIDAuOSkKCQkJCQkJKQpgYGAKCi0gICBNYcOfemFobGVuIGbDvHIgUG9zaXRpb25lbjogYGZpcnN0KHgpYCwgYG50aCh4LCAxMClgLCBgbGFzdCh4KWAgZ2ViZW4KICAgIGRlbiBlcnN0ZW4sIGRlbiB6ZWhudGVuIHVuZCBkZW4gbGV0enRlbiBXZXJ0IGRlcyBEYXRhZnJhbWVzIGF1czoKCmBgYHtyfQpuaWNodF9hbm51bGxpZXJ0ICU+JQoJZ3JvdXBfYnkoeWVhciwgbW9udGgsIGRheSkgJT4lCglzdW1tYXJpc2UoZXJzdGVyX2ZsdWcgPSBmaXJzdChkZXBfdGltZSksCgkJCQkJCWxldHp0ZXJfZmx1ZyA9IGxhc3QoZGVwX3RpbWUpLAoJCQkJCQl6ZWhudGVyX2ZsdWcgPSBudGgoZGVwX3RpbWUsIDEwKSkKYGBgCgpIaW53ZWlzOiBkaWVzZSBGdW5rdGlvbmVuIGxhc3NlbiBzaWNoIGVyZ8OkbnplbmQgenUgU29ydGllcnVuZ2VuIG1pdApgbWluX3JhbmsoKWAgdmVyd2VuZGVuLCB3b2JlaSBoaWVyIGRpZSBnZXNhbXRlbiBFaW50csOkZ2UgZXJoYWx0ZW4KYmxlaWJlbjoKCmBgYHtyfQpuaWNodF9hbm51bGxpZXJ0ICU+JQoJZ3JvdXBfYnkoeWVhciwgbW9udGgsIGRheSkgJT4lCgltdXRhdGUoYWJmbHVnX3JhbmcgPSBtaW5fcmFuayhkZXNjKGRlcF90aW1lKSkpICU+JQoJZmlsdGVyKGFiZmx1Z19yYW5nICVpbiUgcmFuZ2UoYWJmbHVnX3JhbmcpKQpgYGAKCi0gICBaw6RobGZ1bmt0aW9uZW46IFp1c8OkdHpsaWNoIHp1IGBuKClgIGzDpHNzdCBzaWNoIGRpZSBBbnphaGwgZGVyCiAgICBuaWNodC1mZWhsZW5kZSBXZXJ0ZSBkdXJjaCBgc3VtKCFpcy5uYSh4KSlgIGJlc3RpbW1lbiB1bmQgZGllIEFuemFobAogICAgYW4gZWluemlnYXJ0aWdlbiBXZXJ0ZW4gbWl0IGBuX2Rpc3RpbmN0KHgpOmAKCmBgYHtyfQpmbGlnaHRzICU+JQoJZ3JvdXBfYnkoZGVzdCkgJT4lCglzdW1tYXJpc2UoZWluemlnYXJ0aWdlX2ZsdWd6ZXVnZSA9IG5fZGlzdGluY3QoY2FycmllciksCgkJCQkJCWFsbGVfZmx1ZWdlID0gbigpLAoJCQkJCQlhbm51bGxpZXJ0ZV9mbHVlZ2UgPSBzdW0oaXMubmEoZGVwX3RpbWUpKSkgJT4lCglhcnJhbmdlKGRlc2MoZWluemlnYXJ0aWdlX2ZsdWd6ZXVnZSkpCmBgYAoKVW0gZGFzIHp1IHZlcmVpbmZhY2hlbiBnaWJ0IGVzIHp1c8OkdHpsaWNoIGRpZSBgY291bnQoeClgIEhpbGZzZnVua3Rpb246CgpgYGB7cn0KbmljaHRfYW5udWxsaWVydCAlPiUKCWNvdW50KGRlc3QpCmBgYAoKRmFsbHMgbmljaHQgbnVyIGRpZSBIw6R1Zmlna2VpdCBkZXIgR3J1cHBlIGdlesOkaGx0IHdlcmRlbiBzb2xsLCBzb25kZXJuCmRpZSBTdW1tZSBlaW5lciBWYXJpYWJsZSBmw7xyIGplZGUgR3J1cHBlICh6LiBCLiBkaWUgZ2VzYW10ZSBGbHVnemVpdAphbGxlciBGbHVnemV1Z2UgenUgZWluZW0gRmx1Z2hhZmVuKSwgc28ga2FubiBkYXMgQXJndW1lbnQgYHd0YCB2ZXJ3ZW5kZXQKd2VyZGVuOgoKYGBge3J9Cm5pY2h0X2FubnVsbGllcnQgJT4lCgljb3VudChkZXN0LCB3dCA9IGFpcl90aW1lKQpgYGAKCkbDvHIgZGFzIFrDpGhsZW4gYnp3LiBTdW1tZW4gdm9uIFZhcmlhYmxlbiBmw7xyIEdydXBwZW4ga2FubiBhbHNvIG1pdApgZ3JvdXBfYnkoKWAgZ3J1cHBpZXJ0IHdlcmRlbiB1bmQgZGFubiBkaWUgZ2V3w7xuc2NodGUgT3BlcmF0aW9uCnZlcndlbmRldCB3ZXJkZW4gb2RlciBkaXJla3QgYGNvdW50KClgLgoKLSAgIEJlZGluZ3RlIFrDpGhsZnVua3Rpb25lbjogQWJzY2hsaWXDn2VuZCBsYXNzZW4gc2ljaCBhdWNoIGRpZSBBbnphaGwgYW4KICAgIGVyZsO8bGx0ZW4gQmVkaW5ndW5nZW4gb2RlciBkZXJlbiBQcm9wb3J0aW9uIGJlc3RpbW1lbi4KICAgIEJlaXNwaWVsc3dlaXNlIGdpYnQgYHN1bSh4ID4gMTApYCBhdXMsIHdpZSB2aWVsZSBXZXJ0ZSBgeGAgZ3LDtsOfZXIKICAgIGFscyAxMCBzaW5kIHVuZCBgbWVhbih4ID09IDApYCwgd2llIHZpZWwgUHJvemVudCBkZXIgV2VydGUgdm9uIGB4YAogICAgZWluZW4gV2VydCB2b24gMCBoYWJlbjoKCmBgYHtyfQojIEFuemFobCBkZXIgRmzDvGdlLCBkaWUgdm9yIDUgVWhyIHN0YXR0Z2VmdW5kZW4gaGFiZW46Cm5pY2h0X2FubnVsbGllcnQgJT4lCglncm91cF9ieSh5ZWFyLCBtb250aCwgZGF5KSAlPiUKCXN1bW1hcmlzZShmcnVlaGVfZmx1Z2UgPSBzdW0oZGVwX3RpbWUgPCA1MDApKQojIFZlcmjDpGx0bmlzIGRlciBGbMO8Z2UgbWl0IGVpbmVyIFZlcnNww6R0dW5nIHZvbiDDvGJlciA2MCBNaW51dGVuOgpuaWNodF9hbm51bGxpZXJ0ICU+JQoJZ3JvdXBfYnkoeWVhciwgbW9udGgsIGRheSkgJT4lCglzdW1tYXJpc2UodmVyc3BhZXR1bmdfdmVyaGFlbHRuaXMgPSBtZWFuKGFycl9kZWxheSA+IDYwKSkKYGBgCgpTaWUgc2VoZW4gYW4gZGllc2VyIFN0ZWxsZSBhYmVyIGF1Y2gsIGRhc3MgZWluaWdlIE9wZXJhdGlvbmVuIHJlZHVuZGFudApzaW5kLiBTaWUga8O2bm5lbiBhdWYgdmVyc2NoaWVkZW5lbiBXZWdlbiBkaWVzZWxiZW4gRXJnZWJuaXNzZSBlcmhhbHRlbi4KCiMjIyBCZWlzcGllbCBHcnVwcGllcnVuZyBtaXQgbWVocmVyZW4gVmFyaWFibGVuCgpXZW5uIG1laHJlcmUgVmFyaWFibGVuIHp1ciBHcnVwcGllcnVuZyB2ZXJ3ZW5kZXQgd2VyZGVuLCB3aXJkIG1pdCBqZWRlbQpgc3VtbWFyaXNlKClgIEJlZmVobCBlaW5lIEdydXBwZSB2ZXJ3b3JmZW46CgpgYGB7cn0KdGFlZ2xpY2ggPC0gZ3JvdXBfYnkoZmxpZ2h0cywgeWVhciwgbW9udGgsIGRheSkKKHByb190YWcgPC0gc3VtbWFyaXNlKHRhZWdsaWNoLCBhbnphaGxfZmx1ZWdlID0gbigpKSkKKHByb19tb25hdCA8LSBzdW1tYXJpc2UocHJvX3RhZywgYW56YWhsX2ZsdWVnZSA9IHN1bShhbnphaGxfZmx1ZWdlKSkpCihwcm9famFociA8LSBzdW1tYXJpc2UocHJvX21vbmF0LCBhbnphaGxfZmx1ZWdlID0gc3VtKGFuemFobF9mbHVlZ2UpKSkKYGBgCgpIaWVyYmVpIG11c3MgZ2VuYXUgYmVkYWNodCB3ZXJkZW4sIHdhcyBpbiBqZWRlbSBTY2hyaXR0IGdlbWFjaHQgd2lyZC4KQW5zb25zdGVuIGVudHN0ZWhlbiBQcm9ibGVtZSBiZWlzcGllbHN3ZWlzZSwgd2VubiBoaWVyIHNjaHJpdHR3ZWlzZSBkZXIKTWVkaWFuIGJlcmVjaG5ldCB3ZXJkZW4gd8O8cmRlLgoKIyMjIEdydXBwaWVydW5nIHLDvGNrZ8OkbmdpZyBtYWNoZW4KCk1pdCBIaWxmZSB2b24gYHVuZ3JvdXAoKWAgd2lyZCBkaWUgR3J1cHBpZXJ1bmcgcsO8Y2tnw6RuZ2lnIGdlbWFjaHQ6CgpgYGB7cn0KdGFlZ2xpY2ggJT4lCgl1bmdyb3VwKCkgJT4lCglzdW1tYXJpc2UoYW56YWhsX2ZsdWVnZSA9IG4oKSkKYGBgCgojIyBTcGFsdGVuIGdydXBwaWVyZW4gdW5kIHdlaXRlcnZlcmFyYmVpdGVuCgpOZWJlbiBgc3VtbWFyaXNlKClgIGxhc3NlbiBzaWNoIGF1Y2ggYG11dGF0ZSgpYCB1bmQgYGZpbHRlcigpYCBtaXQKR3J1cHBpZXJ1bmdlbiB2ZXJ3ZW5kZW46CgotICAgRGllIDUgRmzDvGdlIHBybyBUYWcgbWl0IGRlciBsw6RuZ3N0ZW4gVmVyc3DDpHR1bmc6CgpgYGB7cn0KZmxpZ2h0cyAlPiUKCWdyb3VwX2J5KHllYXIsIG1vbnRoLCBkYXkpICU+JQoJZmlsdGVyKHJhbmsoZGVzYyhhcnJfZGVsYXkpKSA8IDUpCmBgYAoKLSAgIEFsbGUgRWludHLDpGdlIGVpbmVyIEdydXBwZSwgZGllIG9iZXJoYWxiIGVpbmVyIEdyZW56ZSBsaWVnZW46CgpgYGB7cn0KYmVsaWVidGVfemllbGUgPC0gZmxpZ2h0cyAlPiUgCiAgZ3JvdXBfYnkoZGVzdCkgJT4lIAogIGZpbHRlcihuKCkgPiAzNjUpCmJlbGllYnRlX3ppZWxlCmBgYAoKLSAgIE1ldHJpa2VuIGbDvHIgR3J1cHBlbjoKCmBgYHtyfQpiZWxpZWJ0ZV96aWVsZSAlPiUKCWZpbHRlcihhcnJfZGVsYXkgPiAwKSAlPiUKCW11dGF0ZShwcm9wb3J0aW9uX2RlbGF5ID0gYXJyX2RlbGF5IC8gc3VtKGFycl9kZWxheSkpICU+JQoJc2VsZWN0KHllYXI6ZGF5LCBkZXN0LCBhcnJfZGVsYXksIHByb3BvcnRpb25fZGVsYXkpCmBgYAoKQWJow6RuZ2lnIHZvbiBkZXIgQW53ZW5kdW5nIGvDtm5uZW4gZ3J1cHBpZXJ0ZSBGaWx0ZXJ1bmcgdW5lcndhcnRldGUKTmViZW5lZmZla3RlIGhhYmVuLCBkYWhlciBpc3QgaGllcmJlaSBWb3JzaWNodCBnZWJvdGVuLgoKIyBBdWZnYWJlbgoKSW4gZGllc2VtIEFic2Nobml0dCBzb2xsIGVybmV1dCBkZXIgYGRpYW1vbmRzYCBEYXRlbnNhdHogYmV0cmFjaHRldAp3ZXJkZW46CgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmRpYW1vbmRzCmBgYAoKIyMgw5xidW5nc2F1ZmdhYmVuCgoxLiAgw5xiZXJsZWdlbiBTaWUgenVuw6RjaHN0LCBuYWNoIHdlbGNoZW4gVmFyaWFibGVuIHNpY2ggZGVyIERhdGVuc2F0egogICAgYGRpYW1vbmRzYCBncnVwcGllcmVuIGzDpHNzdC4gRGVua2VuIFNpZSBoaWVyYmVpIGJlc29uZGVycyBhbiBkaWUKICAgIHVudGVyc2NoaWVkbGljaGVuIERhdGVudHlwZW4uCgoyLiAgV2VsY2hlIEZhcmJlIGRlciBEaWFtYW50ZW4gaGF0IGRlbiBnZXJpbmdzdGVuIFdlcnQ/CgogICAgMS4gIFdpZSBlcmtsw6RyZW4gU2llIHNpY2ggZGFzIEVyZ2VibmlzPwoKICAgIDIuICBFcnN0ZWxsZW4gdW5kIGJldHJhY2h0ZW4gU2llIGhpZXJ6dSBhdWNoIGVpbiBTdHJldWRpYWdyYW1tIG1pdAogICAgICAgIGRlbSBadXNhbW1lbmhhbmcgdm9uIGBjYXJhdGAgdW5kIGBwcmljZWAgYmV6w7xnbGljaCBkZXIgRmFyYmVuLgoKMy4gIEdydXBwaWVyZW4gU2llIGRpZSBEaWFtYW50ZW4gbmFjaCBkZXIgVmFyaWFibGUgYGN1dGAgdW5kIGJlcmVjaG5lbgogICAgU2llIGRpZSBNaXR0ZWx3ZXJ0ZSBmw7xyIGBjYXJhdGAsIGBwcmljZWAgdW5kIGBkZXB0aGAuCgogICAgMS4gIEVya2VubmVuIFNpZSBoaWVyYmVpIFp1c2FtbWVuaMOkbmdlPwoKICAgIDIuICBTdGVsbGVuIFNpZSBkaWUgZHVyY2hzY2huaXR0bGljaGVuIFByZWlzZSBkdXJjaCBCYWxrZW5kaWFncmFtbWUKICAgICAgICBmw7xyIGFsbGUgS2F0ZWdvcmllbiB2b24gYGN1dGAgZGFyLgoKICAgIDMuICBFcnN0ZWxsZW4gU2llIGVpbmVuIEJveHBsb3QgbWl0IGRlbiBQcmVpc2VuIGbDvHIgamVkZSBLYXRlZ29yaWUKICAgICAgICBgY3V0YC4gVmVyZ2xlaWNoZW4gU2llIGRhcyBFcmdlYm5pcyBtaXQgZGVtIEJhbGtlbmRpYWdyYW1tLgoKNC4gIEdydXBwaWVyZW4gU2llIGRpZSBEaWFtYW50ZW4gbmFjaCBgY2FyYXRgIHVuZCBiZXJlY2huZW4gU2llIE1pbmltdW0sCiAgICBNYXhpbXVtIHVuZCBNaXR0ZWx3ZXJ0IHZvbiBgcHJpY2VgLgoKICAgIDEuICBFcnN0ZWxsZW4gU2llIGF1ZiBHcnVuZGxhZ2UgZGVyIE1pdHRlbHdlcnRlIHVuZCBkZXIgVmFyaWFibGUKICAgICAgICBgY2FyYXRgIGVpbiBTdHJldWRpYWdyYW1tLgoKICAgIDIuICBFcnN0ZWxsZW4gU2llIGVpbiBTdHJldWRpYWdyYW1tIGbDvHIgYWxsZSBFaW50csOkZ2UgbWl0IGBjYXJhdGAKICAgICAgICB1bmQgYHByaWNlYC4gRsO8Z2VuIFNpZSB6dXPDpHR6bGljaCBkaWUgYmVzdGltbXRlbiBNaXR0ZWx3ZXJ0ZSBpbgogICAgICAgIGRpZSBHcmFmaWsgbWl0IGBnZW9tX2xpbmUoKWAgZWluLiBTZXR6ZW4gU2llIGhpZXJ6dQogICAgICAgIGBzdGF0ID0gInNtb290aCJgLgoKICAgIDMuICBFbnRmZXJuZW4gU2llIHp1bGV0enQgYWxsZSBFaW50csOkZ2UsIGRpZSBzZWx0ZW5lciBhbHMgMjAgbWFsCiAgICAgICAgYXVmdHJldGVuLgoKNS4gIEZpbmRlbiBTaWUgZsO8ciBqZWRlIEthdGVnb3JpZSB2b24gYGNvbG9yYCBkaWUgVGllZmVuIChgZGVwdGhgKSwgYmVpCiAgICBkZW5lbiBtZWhyIGFscyAyNSUsIDUwJSB1bmQgNzUlIGRlciBEaWFtYW50ZW4gZWluZW4gZ2VyaW5nZW4gV2VydAogICAgYXVmd2Vpc2VuLgoKNi4gIEZpbmRlbiBTaWUgZsO8ciBqZWRlbiBgY3V0YCBkZW4gdGV1ZXJzdGVuIHVuZCBkZW4gZ8O8bnN0aWdzdGVuCiAgICBEaWFtYW50ZW4uIEVudHNwcmljaHQgZGFzIEVyZ2VibmlzIElocmVuIEVyd2FydHVuZ2VuPyBXb3JhbiBrYW5uCiAgICBkaWVzZXIgWnVzYW1tZW5oYW5nIGxpZWdlbj8KCjcuICBCZXRyYWNodGVuIFNpZSBuZWJlbiBkZW0gYGN1dGAgYXVjaCBkaWUgS2xhcmhlaXQgYGNsYXJpdHlgIHVuZCBnZWJlbgogICAgU2llIGRlbiBBbnRlaWwgdW5kIGRpZSBBbnphaGwgdm9uIERpYW1hbnRlbiBtaXQgZWluZW0gUHJlaXMgdm9uIMO8YmVyCiAgICAxMDAwMCBhdXMuCgogICAgMS4gIFfDpGhsZW4gU2llIGltIEFuc2NobHVzcyBkaWUgZXJzdGVuIGbDvG5mIGFsbGVyIEVyZ2Vibmlzc2UgYXVzLgogICAgICAgIFN0b8OfZW4gU2llIGhpZXJiZWkgYXVmIGVpbiBQcm9ibGVtPwoKICAgIDIuICBFcnN0ZWxsZW4gU2llIGVpbmUgR3JhZmlrIG1pdCBIaXN0b2dyYW1tZW4gZGVyIFByZWlzZSBmw7xyIGFsbGUKICAgICAgICBLYXRlZ29yaWVuIHZvbiBgY3V0YCB1bmQgYGNsYXJpdHlgIChkdXJjaCBgZmFjZXRfZ3JpZCgpYCkuCgogICAgMy4gIE1hcmtpZXJlbiBTaWUgaW4gZGVyIEdyYWZpayBkaWUgQmVyZWljaGUgcm90LCBiZWkgZGVuZW4gZGVyCiAgICAgICAgUHJlaXMgw7xiZXIgMTAwMDAgbGllZ3QuIFZlcmdsZWljaGVuIFNpZSBkaWUgR3JhZmlrIG1pdCBkZW4KICAgICAgICBBbnRlaWxlbiwgZGllIFNpZSB6dXZvciBiZXJlY2huZXQgaGFiZW4uCg==