Erstellen Sie narrative Diagramme im xkcd-Stil

45

Randall Munroe hat in einem der ikonischeren xkcd-Strips die Zeitleisten mehrerer Filme in narrativen Diagrammen visualisiert:

Bildbeschreibung hier eingeben (Klicken Sie für eine größere Version.)

Quelle: xkcd Nr. 657 .

Wenn Sie die Zeitachse eines Films (oder eine andere Erzählung) spezifizieren, müssen Sie ein solches Diagramm erstellen. Dies ist ein Beliebtheitswettbewerb, daher gewinnt die Antwort mit den meisten (Netto-) Stimmen.

Mindestanforderungen

Um die Spezifikation ein wenig zu verschärfen, sind hier die Mindestfunktionen aufgeführt, die jede Antwort implementieren muss:

  • Nehmen Sie als Eingabe eine Liste von Charakternamen, gefolgt von einer Liste von Ereignissen. Jedes Ereignis ist entweder eine Liste sterbender Zeichen oder eine Liste von Zeichengruppen (die angeben, welche Zeichen gerade zusammen sind). Hier ist ein Beispiel dafür, wie die Jurassic Park-Erzählung codiert werden könnte:

    ["T-Rex", "Raptor", "Raptor", "Raptor", "Malcolm", "Grant", "Sattler", "Gennaro",
     "Hammond", "Kids", "Muldoon", "Arnold", "Nedry", "Dilophosaurus"]
    [
      [[0],[1,2,3],[4],[5,6],[7,8,10,11,12],[9],[13]],
      [[0],[1,2,3],[4,7,5,6,8,9,10,11,12],[13]],
      [[0],[1,2,3],[4,7,5,6,8,9,10],[11,12],[13]],
      [[0],[1,2,3],[4,7,5,6,9],[8,10,11,12],[13]],
      [[0,4,7],[1,2,3],[5,9],[6,8,10,11],[12],[13]],
      [7],
      [[5,9],[0],[4,6,10],[1,2,3],[8,11],[12,13]],
      [12],
      [[0, 5, 9], [1, 2, 3], [4, 6, 10, 8, 11], [13]], 
      [[0], [5, 9], [1, 2], [3, 11], [4, 6, 10, 8], [13]], 
      [11], 
      [[0], [5, 9], [1, 2, 10], [3, 6], [4, 8], [13]], 
      [10], 
      [[0], [1, 2, 9], [5, 6], [3], [4, 8], [13]], 
      [[0], [1], [9, 5, 6], [3], [4, 8], [2], [13]], 
      [[0, 1, 9, 5, 6, 3], [4, 8], [2], [13]], 
      [1, 3], 
      [[0], [9, 5, 6, 3, 4, 8], [2], [13]]
    ]
    

    ZB bedeutet die erste Zeile, dass T-Rex am Anfang der Karte einsam ist, die drei Raptoren zusammen sind, Malcolm allein ist, Grant und Sattler zusammen sind usw. Das vorletzte Ereignis bedeutet, dass zwei der Raptoren sterben .

    Wie genau Sie die Eingabe erwarten, bleibt Ihnen überlassen, solange diese Art von Informationen angegeben werden kann. Sie können beispielsweise ein beliebiges Listenformat verwenden. Sie können auch erwarten, dass die Zeichen in den Ereignissen wieder die vollständigen Zeichennamen usw. sind.

    Sie können (müssen aber nicht) davon ausgehen, dass jede Gruppenliste jedes lebende Zeichen in genau einer Gruppe enthält. Sie sollten jedoch nicht davon ausgehen, dass sich die Gruppen oder Zeichen innerhalb eines Ereignisses in einer besonders günstigen Reihenfolge befinden.

  • Rendern Sie auf dem Bildschirm oder in einer Datei (als Vektor- oder Raster-Grafik) ein Diagramm, das für jedes Zeichen eine Zeile enthält. Jede Zeile muss am Zeilenanfang mit einem Zeichennamen versehen werden.

  • Für jedes normale Ereignis muss es in der richtigen Reihenfolge einen Querschnitt der Tabelle geben, in dem die Zeichengruppen durch die Nähe ihrer jeweiligen Linien eindeutig ähneln.
  • Für jedes Todesereignis müssen die Zeilen der relevanten Zeichen in einem sichtbaren Klecks enden.
  • Sie müssen weder andere Merkmale von Randalls Zeichnungen reproduzieren, noch müssen Sie seinen Zeichenstil reproduzieren. Gerade Linien mit scharfen Kurven, alle in Schwarz, ohne weitere Beschriftungen und Titel, sind für die Teilnahme am Wettbewerb völlig in Ordnung. Es ist auch nicht erforderlich, den Platz effizient zu nutzen. Sie könnten beispielsweise Ihren Algorithmus vereinfachen, indem Sie immer nur Linien nach unten bewegen, um sich mit anderen Zeichen zu treffen, solange eine erkennbare Zeitrichtung vorliegt.

Ich habe eine Referenzlösung hinzugefügt , die genau diese Mindestanforderungen erfüllt.

Schön machen

Dies ist jedoch ein Beliebtheitswettbewerb. Darüber hinaus können Sie jede gewünschte Fantasie implementieren. Die wichtigste Ergänzung ist ein anständiger Layout-Algorithmus, der das Diagramm besser lesbar macht - z. B. das Verfolgen von Biegungen in den Linien erleichtert und die Anzahl der erforderlichen Linienkreuzungen verringert. Dies ist das zentrale algorithmische Problem dieser Herausforderung! Die Stimmen entscheiden darüber, wie gut Ihr Algorithmus das Diagramm aufgeräumt hält.

Aber hier sind noch einige Ideen, die meisten basieren auf Randalls Diagrammen:

Dekorationen:

  • Farbige Linien.
  • Ein Titel für die Handlung.
  • Beschriftungszeile endet.
  • Automatische Neuetikettierung von Leitungen, die einen belegten Abschnitt durchlaufen haben.
  • Handgezeichneter Stil (oder wie gesagt, Randalls Stil muss nicht reproduziert werden, wenn Sie eine bessere Idee haben) für Linien und Schriftarten.
  • Anpassbare Ausrichtung der Zeitachse.

Zusätzliche Ausdruckskraft:

  • Benannte Ereignisse / Gruppen / Todesfälle.
  • Verschwindende und wieder auftauchende Linien.
  • Zeichen, die spät eintreffen.
  • Hervorhebungen, die Eigenschaften von Zeichen anzeigen (übertragbar?) (Siehe z. B. Ringträger in der LotR-Tabelle).
  • Codierung zusätzlicher Informationen in der Gruppierungsachse (z. B. geografische Informationen wie im LotR-Diagramm).
  • Zeitreise?
  • Alternative Realitäten?
  • Aus einer Figur wird eine andere?
  • Zwei Charaktere verschmelzen? (Eine Charakteraufteilung?)
  • 3D? (Wenn Sie wirklich so weit gehen, stellen Sie sicher , dass Sie tatsächlich die zusätzliche Dimension mit some sichtbar zu machen!)
  • Alle anderen relevanten Funktionen, die nützlich sein können, um die Erzählung eines Films (oder eines Buches usw.) zu visualisieren.

Natürlich erfordern viele davon zusätzliche Eingaben, und Sie können Ihr Eingabeformat nach Bedarf erweitern. Bitte dokumentieren Sie jedoch, wie Daten eingegeben werden können.

Bitte fügen Sie ein oder zwei Beispiele bei, um die von Ihnen implementierten Funktionen zu demonstrieren.

Ihre Lösung sollte in der Lage sein, alle gültigen Eingaben zu verarbeiten. Es ist jedoch absolut in Ordnung, wenn sie für bestimmte Arten von Erzählungen besser geeignet ist als für andere.

Wahlkriterien

Ich habe keine Illusionen, dass ich den Leuten sagen könnte, wie sie ihre Stimmen ausgeben sollen, aber hier sind einige Richtlinienvorschläge in der Reihenfolge ihrer Wichtigkeit:

  • Downvote-Antworten, die Lücken ausnutzen, Standardantworten oder andere, oder ein oder mehrere Ergebnisse fest codieren .
  • Stimmen Sie keine Antworten hoch, die die Mindestanforderungen nicht erfüllen (egal wie ausgefallen der Rest sein mag).
  • In erster Linie sollten Sie nette Layout-Algorithmen verbessern. Dies schließt Antworten ein, die nicht viel vertikalen Raum beanspruchen, während das Überkreuzen von Linien minimiert wird, um das Diagramm lesbar zu halten, oder die es schaffen, zusätzliche Informationen in die vertikale Achse zu codieren. Das Visualisieren der Gruppierungen ohne große Verwirrung sollte das Hauptaugenmerk dieser Herausforderung sein, so dass dies ein Programmierwettbewerb mit einem interessanten algorithmischen Problem bleibt.
  • Verbessere optionale Funktionen, die Ausdruckskraft verleihen (dh nicht nur reine Dekoration sind).
  • Zum Schluss noch eine schöne Präsentation.
Martin Ender
quelle
7
weil code-golf nicht genug xkcd hat
stolzer haskeller
8
@ proudhaskeller PPCG kann nie genug xkcd haben. ;) Aber ich denke, wir haben noch nicht versucht, seine übergroßen Infografiken / Visualisierungen herauszufordern, also hoffe ich, dass ich damit etwas Neues auf den Tisch bringe. Und ich bin sicher, dass einige der anderen auch ganz andere und interessante Herausforderungen stellen würden.
Martin Ender
Ist es in Ordnung, wenn meine Lösung nur 12 verärgerte Männer, Duell (Spielberg, 1971, normaler Autofahrer gegen verrückten Trucker) und Flugzeuge, Züge und Automobile handhabt? ;-)
Level River St
4
Ich frage mich, wie die Eingabe für Primer aussehen würde ...
Joshua
1
@ping Ja, das war die Idee. Wenn ein Ereignis weitere Listen enthält, handelt es sich um eine Listengruppierung. Das [[x,y,z]]würde bedeuten, dass alle Charaktere derzeit zusammen sind. Wenn das Ereignis jedoch keine Listen, sondern nur direkte Charaktere enthält, ist es sogar ein Tod. In derselben Situation [x,y,z]bedeutet dies, dass diese drei Charaktere sterben. Sie können auch ein anderes Format verwenden und explizit angeben, ob es sich um ein Todes- oder Gruppierungsereignis handelt, wenn dies für Sie hilfreich ist. Das obige Format ist nur ein Vorschlag. Solange Ihr Eingabeformat mindestens so aussagekräftig ist, können Sie etwas anderes verwenden.
Martin Ender

Antworten:

18

Python3 mit Numpy, Scipy und Matplotlib

Jurassic Park

bearbeiten :

  • Ich habe versucht, die Gruppen in der gleichen relativen Position zwischen den Ereignissen zu halten, daher die sorted_eventFunktion.
  • Neue Funktion zur Berechnung der y-Position der Zeichen ( coords).
  • Jedes lebendige Ereignis wird jetzt zweimal aufgezeichnet, damit die Charaktere besser zusammenhalten.
  • Legende hinzugefügt und Achsenbeschriftung entfernt.
import math
import numpy as np
from scipy.interpolate import interp1d
from matplotlib import cm, pyplot as plt


def sorted_event(prev, event):
    """ Returns a new sorted event, where the order of the groups is
    similar to the order in the previous event. """
    similarity = lambda a, b: len(set(a) & set(b)) - len(set(a) ^ set(b))
    most_similar = lambda g: max(prev, key=lambda pg: similarity(g, pg))
    return sorted(event, key=lambda g: prev.index(most_similar(g)))


def parse_data(chars, events):
    """ Turns the input data into 3 "tables":
    - characters: {character_id: character_name}
    - timelines: {character_id: [y0, y1, y2, ...],
    - deaths: {character_id: (x, y)}
    where x and y are the coordinates of a point in the xkcd like plot.
    """
    characters = dict(enumerate(chars))
    deaths = {}
    timelines = {char: [] for char in characters}

    def coords(character, event):
        for gi, group in enumerate(event):
            if character in group:
                ci = group.index(character)
                return (gi + 0.5 * ci / len(group)) / len(event)
        return None

    t = 0
    previous = events[0]
    for event in events:
        if isinstance(event[0], list):
            previous = event = sorted_event(previous, event)
            for character in [c for c in characters if c not in deaths]:
                timelines[character] += [coords(character, event)] * 2
            t += 2
        else:
            for char in set(event) - set(deaths):
                deaths[char] = (t-1, timelines[char][-1])

    return characters, timelines, deaths


def plot_data(chars, timelines, deaths):
    """ Draws a nice xkcd like movie timeline """

    plt.xkcd()  # because python :)

    fig = plt.figure(figsize=(16,8))
    ax = fig.add_subplot(111)
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
    ax.set_xlim([0, max(map(len, timelines.values()))])

    color_floats = np.linspace(0, 1, len(chars))
    color_of = lambda char_id: cm.Accent(color_floats[char_id])

    for char_id in sorted(chars):
        y = timelines[char_id]
        f = interp1d(np.linspace(0, len(y)-1, len(y)), y, kind=5)
        x = np.linspace(0, len(y)-1, len(y)*10)
        ax.plot(x, f(x), c=color_of(char_id))

    x, y = zip(*(deaths[char_id] for char_id in sorted(deaths)))
    ax.scatter(x, y, c=np.array(list(map(color_of, sorted(deaths)))), 
               zorder=99, s=40)

    ax.legend(list(map(chars.get, sorted(chars))), loc='best', ncol=4)
    fig.savefig('testplot.png')


if __name__ == '__main__':
    chars = [
        "T-Rex","Raptor","Raptor","Raptor","Malcolm","Grant","Sattler",
        "Gennaro","Hammond","Kids","Muldoon","Arnold","Nedry","Dilophosaurus"
    ]
    events = [
        [[0],[1,2,3],[4],[5,6],[7,8,10,11,12],[9],[13]],
        [[0],[1,2,3],[4,7,5,6,8,9,10,11,12],[13]],
        [[0],[1,2,3],[4,7,5,6,8,9,10],[11,12],[13]],
        [[0],[1,2,3],[4,7,5,6,9],[8,10,11,12],[13]],
        [[0,4,7],[1,2,3],[5,9],[6,8,10,11],[12],[13]],
        [7],
        [[5,9],[0],[4,6,10],[1,2,3],[8,11],[12,13]],
        [12],
        [[0,5,9],[1,2,3],[4,6,10,8,11],[13]],
        [[0],[5,9],[1,2],[3,11],[4,6,10,8],[13]],
        [11],
        [[0],[5,9],[1,2,10],[3,6],[4,8],[13]],
        [10],
        [[0],[1,2,9],[5,6],[3],[4,8],[13]],
        [[0],[1],[9,5,6],[3],[4,8],[2],[13]],
        [[0,1,9,5,6,3],[4,8],[2],[13]],
        [1,3],
        [[0],[9,5,6,3,4,8],[2],[13]]
    ]
    plot_data(*parse_data(chars, events))
pgy
quelle
Hah, sehr schöner xkcd-Look:) ... gibt es eine Chance, die Zeilen zu beschriften?
Martin Ender
Beschriften Sie die Linien, haben Sie unterschiedliche Linienbreiten (mit abnehmender / zunehmender Breite zwischen einigen Punkten) und machen Sie die Linien schließlich horizontaler, wenn Sie sich einem Scheitelpunkt nähern, während Sie interpolieren, eher wie eine Bezier-Kurve. Dies wäre der beste Eintrag, IMO: )
Optimierer
1
Danke, aber xkcd style ist in matplotlib enthalten, also war es nur ein Funktionsaufruf :) Nun, ich habe eine Legende erstellt, die aber fast ein Drittel des Bildes einnahm, also habe ich sie auskommentiert.
Pgy
Ich habe meine Antwort geändert, ich denke, es sieht jetzt besser aus.
Pgy
6

T-SQL

Ich bin mit diesem Eintrag nicht zufrieden, aber ich denke, dass diese Frage zumindest einen Versuch wert ist. Ich werde versuchen, dies zu einem späteren Zeitpunkt zu verbessern, aber die Beschriftung wird in SQL immer ein Problem sein. Die Lösung erfordert SQL 2012+ und wird in SSMS (SQL Server Management Studio) ausgeführt. Die Ausgabe erfolgt auf der Registerkarte mit den räumlichen Ergebnissen.

-- Variables for the input
DECLARE @actors NVARCHAR(MAX) = '["T-Rex", "Raptor", "Raptor", "Raptor", "Malcolm", "Grant", "Sattler", "Gennaro", "Hammond", "Kids", "Muldoon", "Arnold", "Nedry", "Dilophosaurus"]';
DECLARE @timeline NVARCHAR(MAX) = '
[
   [[1], [2, 3, 4], [5], [6, 7], [8, 9, 11, 12, 13], [10], [14]],
   [[1], [2, 3, 4], [5, 8, 6, 7, 9, 10, 11, 12, 13], [14]],
   [[1], [2, 3, 4], [5, 8, 6, 7, 9, 10, 11], [12, 13], [14]],
   [[1], [2, 3, 4], [5, 8, 6, 7, 10], [9, 11, 12, 13], [14]],
   [[1, 5, 8], [2, 3, 4], [6, 10], [7, 9, 11, 12], [13], [14]],
   [8],
   [[6, 10], [1], [5, 7, 11], [2, 3, 4], [9, 12], [13, 14]],
   [13],
   [[1, 6, 10], [2, 3, 4], [5, 7, 11, 9, 12], [14]],
   [[1], [6, 10], [2, 3], [4, 12], [5, 7, 11, 9], [14]],
   [12],
   [[1], [6, 10], [2, 3, 11], [4, 7], [5, 9], [14]],
   [11],
   [[1], [2, 3, 10], [6, 7], [4], [5, 9], [14]],
   [[1], [2], [10, 6, 7], [4], [5, 9], [3], [14]],
   [[1, 2, 10, 6, 7, 4], [5, 9], [3], [14]],
   [2, 4],
   [[1], [10, 6, 7, 5, 9], [3], [14]]
]
';

-- Populate Actor table
WITH actor(A) AS ( SELECT CAST(REPLACE(STUFF(REPLACE(REPLACE(@actors,', ',','),'","','</a><a>'),1,2,'<a>'),'"]','</a>') AS XML))
SELECT ROW_NUMBER() OVER (ORDER BY(SELECT \)) ActorID, a.n.value('.','varchar(50)') Name
INTO Actor
FROM actor CROSS APPLY A.nodes('/a') as a(n);

-- Populate Timeline Table
WITH Seq(L) AS (
    SELECT CAST(REPLACE(REPLACE(REPLACE(REPLACE(@timeline,'[','<e>'),']','</e>'),'</e>,<e>','</e><e>'),'</e>,','</e>') AS XML)
    ),
    TimeLine(N,Exerpt,Elem) AS (
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) N
        ,z.query('.')
        ,CAST(REPLACE(CAST(z.query('.') AS VARCHAR(MAX)),',','</e><e>') AS XML)
    FROM Seq 
        CROSS APPLY Seq.L.nodes('/e/e') AS Z(Z)
    ),
    Groups(N,G,Exerpt) AS (
    SELECT N, 
        ROW_NUMBER() OVER (PARTITION BY N ORDER BY CAST(SUBSTRING(node.value('.','varchar(50)'),1,ISNULL(NULLIF(CHARINDEX(',',node.value('.','varchar(50)')),0),99)-1) AS INT)), 
        CAST(REPLACE(CAST(node.query('.') AS VARCHAR(MAX)),',','</e><e>') AS XML) C
    FROM TimeLine 
        CROSS APPLY Exerpt.nodes('/e/e') as Z(node)
    WHERE Exerpt.exist('/e/e') = 1
    )
SELECT * 
INTO TimeLine
FROM (
    SELECT N, null G, null P, node.value('.','int') ActorID, 1 D 
    FROM TimeLine CROSS APPLY TimeLine.Elem.nodes('/e') AS E(node)
    WHERE Exerpt.exist('/e/e') = 0
    UNION ALL
    SELECT N, G, DENSE_RANK() OVER (PARTITION BY N, G ORDER BY node.value('.','int')), node.value('.','int') ActorID, 0
    FROM Groups CROSS APPLY Groups.Exerpt.nodes('/e') AS D(node)
    ) z;

-- Sort the entries again
WITH ReOrder AS (
            SELECT *, 
                ROW_NUMBER() OVER (PARTITION BY N,G ORDER BY PG, ActorID) PP, 
                COUNT(P) OVER (PARTITION BY N,G) CP, 
                MAX(G) OVER (PARTITION BY N) MG, 
                MAX(ActorID) OVER (ORDER BY (SELECT\)) MA
            FROM (
                SELECT *,
                    LAG(G,1) OVER (PARTITION BY ActorID ORDER BY N) PG,
                    LEAD(G,1) OVER (PARTITION BY ActorID ORDER BY N) NG
                FROM timeline
                ) rg
    )
SELECT * INTO Reordered
FROM ReOrder;
ALTER TABLE Reordered ADD PPP INT
GO
ALTER TABLE Reordered ADD LPP INT
GO
WITH U AS (SELECT N, P, LPP, LAG(PP,1) OVER (PARTITION BY ActorID ORDER BY N) X FROM Reordered)
UPDATE U SET LPP = X FROM U;
WITH U AS (SELECT N, ActorID, P, PG, LPP, PPP, DENSE_RANK() OVER (PARTITION BY N,G ORDER BY PG, LPP) X FROM Reordered)
UPDATE U SET PPP = X FROM U;
GO

SELECT Name, 
    Geometry::STGeomFromText(
        STUFF(LS,1,2,'LINESTRING (') + ')'
        ,0)
        .STBuffer(.1)
        .STUnion(
        Geometry::STGeomFromText('POINT (' + REVERSE(SUBSTRING(REVERSE(LS),1,CHARINDEX(',',REVERSE(LS))-1)) + ')',0).STBuffer(D*.4)
        )
FROM Actor a
    CROSS APPLY (
        SELECT CONCAT(', '
            ,((N*5)-1.2)
                ,' ',(G)+P
            ,', '
            ,((N*5)+1.2)
                ,' ',(G)+P 
            ) AS [text()]
        FROM (
            SELECT ActorID, N,
                CASE WHEN d = 1 THEN
                    ((MA+.0) / (LAG(MG,1) OVER (PARTITION BY ActorID ORDER BY N)+.0)) * 
                    PG * 1.2
                ELSE 
                    ((MA+.0) / (MG+.0)) * 
                    G * 1.2
                END G,
                CASE WHEN d = 1 THEN
                (LAG(PPP,1) OVER (PARTITION BY ActorID ORDER BY N) -((LAG(CP,1) OVER (PARTITION BY ActorID ORDER BY N)-1)/2)) * .2 
                ELSE
                (PPP-((CP-1)/2)) * .2 
                END P
                ,PG
                ,NG
            FROM Reordered
            ) t
        WHERE a.actorid = t.actorid
        ORDER BY N, G
        FOR XML PATH('')
        ) x(LS)
    CROSS APPLY (SELECT MAX(D) d FROM TimeLine dt WHERE dt.ActorID = a.ActorID) d
GO

DROP TABLE Actor;
DROP TABLE Timeline;
DROP TABLE Reordered;

Die resultierende Zeitleiste sieht wie folgt aus Bildbeschreibung hier eingeben

MickyT
quelle
4

Mathematica, Referenzlösung

Als Referenz stelle ich ein Mathematica-Skript zur Verfügung, das genau die Mindestanforderungen erfüllt, nicht mehr und nicht weniger.

Es wird erwartet, dass die Zeichen eine Liste des Formats in der Frage in charsund der Ereignisse in sind events.

n = Length@chars;
m = Max@Map[Length, events, {2}];
deaths = {};
Graphics[
 {
  PointSize@Large,
  (
     linePoints = If[Length@# == 3,
         lastPoint = {#[[1]], #[[2]] + #[[3]]/(m + 2)},
         AppendTo[deaths, Point@lastPoint]; lastPoint
         ] & /@ Position[events, #];
     {
      Line@linePoints,
      Text[chars[[#]], linePoints[[1]] - {.5, 0}]
      }
     ) & /@ Range@n,
  deaths
  }
 ]

Hier ist als Beispiel das Jurassic Park-Beispiel mit dem Listentyp von Mathematica:

chars = {"T-Rex", "Raptor", "Raptor", "Raptor", "Malcolm", "Grant", 
   "Sattler", "Gennaro", "Hammond", "Kids", "Muldoon", "Arnold", 
   "Nedry", "Dilophosaurus"};
events = {
   {{1}, {2, 3, 4}, {5}, {6, 7}, {8, 9, 11, 12, 13}, {10}, {14}},
   {{1}, {2, 3, 4}, {5, 8, 6, 7, 9, 10, 11, 12, 13}, {14}},
   {{1}, {2, 3, 4}, {5, 8, 6, 7, 9, 10, 11}, {12, 13}, {14}},
   {{1}, {2, 3, 4}, {5, 8, 6, 7, 10}, {9, 11, 12, 13}, {14}},
   {{1, 5, 8}, {2, 3, 4}, {6, 10}, {7, 9, 11, 12}, {13}, {14}},
   {8},
   {{6, 10}, {1}, {5, 7, 11}, {2, 3, 4}, {9, 12}, {13, 14}},
   {13},
   {{1, 6, 10}, {2, 3, 4}, {5, 7, 11, 9, 12}, {14}},
   {{1}, {6, 10}, {2, 3}, {4, 12}, {5, 7, 11, 9}, {14}},
   {12},
   {{1}, {6, 10}, {2, 3, 11}, {4, 7}, {5, 9}, {14}},
   {11},
   {{1}, {2, 3, 10}, {6, 7}, {4}, {5, 9}, {14}},
   {{1}, {2}, {10, 6, 7}, {4}, {5, 9}, {3}, {14}},
   {{1, 2, 10, 6, 7, 4}, {5, 9}, {3}, {14}},
   {2, 4},
   {{1}, {10, 6, 7, 4, 5, 9}, {3}, {14}}
};

wir werden .. bekommen:

Bildbeschreibung hier eingeben

(Klicken Sie für eine größere Version.)

Das sieht nicht allzu schlecht, aber das ist vor allem , weil die Eingangsdaten mehr oder weniger geordnet. Wenn wir die Gruppen und Charaktere in jedem Ereignis mischen (unter Beibehaltung der gleichen Struktur), kann Folgendes passieren:

Bildbeschreibung hier eingeben

Welches ist ein bisschen ein Chaos.

Wie gesagt, dies erfüllt nur die Mindestanforderungen. Es versucht nicht, ein schönes Layout zu finden und es ist auch nicht schön, aber da kommt ihr rein!

Martin Ender
quelle
Ich dachte nur, Sie können es vielleicht "verschönern", indem Sie quadratische oder kubische Splines verwenden, um die scharfen Ecken zu entfernen. (Ich würde es so machen, dass die Tangente an den angegebenen Punkten immer 0 ist.)
Fehler
@flawr Sicher, oder ich könnte einige dieser Tricks anwenden , aber das war nicht der Zweck dieser Antwort. ;) Ich wollte eigentlich nur eine Referenz für das absolute Minimum liefern.
Martin Ender
3
Oh, tut mir leid,
ich