I want to draw the graph (ladder track representation) as follows:
i.e. without node shapes and with two edge labels: at head and at tail of edge.
What should I do to achieve this?
(14, 15 — nodes with two edges each)
Almost ugly solution (do edit if you can improve correctness):
graph G {
rankdir = LR;
node [shape = point];
edge [minlen = 2, fontsize = 8]
a -- b [taillabel = "1", headlabel = "2"];
b -- c [taillabel = "4", headlabel = "15"];
b -- e [taillabel = "3", headlabel = "5"];
e -- d [taillabel = "6", headlabel = "8"];
e -- f [taillabel = "7", headlabel = "14"];
c -- d [taillabel = "15", headlabel = "9"];
f -- g [taillabel = "14", headlabel = "13"];
d -- g [taillabel = "10", headlabel = "11"];
g -- h [taillabel = "12", headlabel = "16"];
}
which leads to
Related
I'm trying to draw a tree but have a problem with the following approach:
Use of 'invisible' nodes to connect levels of tree,
Use 'rank same' to draw nodes on the same level
Using this code I get following result
graph G{
edge [arrowhead = none];
splines = ortho;
rankdir = LR;
node [ shape="box" fixedsize = true width = 4 height = 1];
{ rank = same; "C" }
{ rank = same;
"B"
"A"
}
{ rank = same;
"F"
"D"
"E"
}
node [ shape="cricle" width = 0 height = 0 style=invis];
{ rank = same;
"B_Inv_Parent_1"
"C_Inv_Even_Children_0"
"A_Inv_Parent_1"
}
{ rank = same;
"F_Inv_Parent_2"
"D_Inv_Parent_2"
"A_Inv_Even_Children_1"
"E_Inv_Parent_2"
}
"C" -- "C_Inv_Even_Children_0";
"B_Inv_Parent_1" -- "C_Inv_Even_Children_0" -- "A_Inv_Parent_1";
"B_Inv_Parent_1" -- "B";
"A_Inv_Parent_1" -- "A";
"B" -- "F_Inv_Parent_2";
"F_Inv_Parent_2" -- "F";
"A" -- "A_Inv_Even_Children_1";
"D_Inv_Parent_2" -- "A_Inv_Even_Children_1" -- "E_Inv_Parent_2";
"D_Inv_Parent_2" -- "D";
"E_Inv_Parent_2" -- "E";
}
I have a problem in the 3rd level: D is drawn on top of the picture thus making a connection with E not ideal.
I would like to have the same results as with C, B and A.
I think the problem is with the order of nodes definition however, I can't manage to get it working whatever order I define them in.
Can anyone spot another problem with my code and suggest a fix?
I have cleaned up your code and re-arranged a few lines - after all, I think that introducing
F_Inv_Parent_2 -- D_Inv_Parent_2 -- A_Inv_Even_Children_1 -- E_Inv_Parent_2;
has been the key. You don't need to define edge arrows since you don't have a directed graph, and there is a typo in shape="cricle".
Here my edited version
graph G
{
splines = ortho;
rankdir = LR;
// node definitions
node [ shape="box" fixedsize = true width = 4 height = 1];
C
{ rank = same; B A }
{ rank = same; F D E }
node [ shape="point" width = 0 height = 0 ];
{ rank = same;
B_Inv_Parent_1
C_Inv_Even_Children_0
A_Inv_Parent_1 }
{ rank = same;
F_Inv_Parent_2
D_Inv_Parent_2
A_Inv_Even_Children_1
E_Inv_Parent_2 }
// edges
C -- C_Inv_Even_Children_0;
B_Inv_Parent_1 -- C_Inv_Even_Children_0 -- A_Inv_Parent_1;
B_Inv_Parent_1 -- B -- F_Inv_Parent_2;
A_Inv_Parent_1 -- A -- A_Inv_Even_Children_1;
F_Inv_Parent_2 -- D_Inv_Parent_2 -- A_Inv_Even_Children_1 -- E_Inv_Parent_2;
F_Inv_Parent_2 -- F;
D_Inv_Parent_2 -- D;
E_Inv_Parent_2 -- E;
}
and the result:
EDIT: I may have misunderstood your intention how you want to connect the third level - if so, replace
F_Inv_Parent_2 -- D_Inv_Parent_2 -- A_Inv_Even_Children_1 -- E_Inv_Parent_2;
with
F_Inv_Parent_2 -- D_Inv_Parent_2[ style = invis ];
D_Inv_Parent_2 -- A_Inv_Even_Children_1 -- E_Inv_Parent_2;
which gives you
EDIT No. 2, in response to yr comment:
Adding weight to the edge helps straightening it - I give the full code even though only two lines have changed (plus comments), for easier copy & paste:
graph G
{
splines = ortho;
rankdir = LR;
// node definitions
node [ shape="box" fixedsize = true width = 4 height = 1];
C
{ rank = same; B A }
{ rank = same; F D E }
node [ shape="point" width = 0 height = 0 ];
{ rank = same;
B_Inv_Parent_1
C_Inv_Even_Children_0
A_Inv_Parent_1 }
{ rank = same;
F_Inv_Parent_2
D_Inv_Parent_2
A_Inv_Even_Children_1
E_Inv_Parent_2 }
// edges
C -- C_Inv_Even_Children_0;
B_Inv_Parent_1 -- C_Inv_Even_Children_0 -- A_Inv_Parent_1;
// add extra weight to the continouous connection between four levels:
B_Inv_Parent_1 -- B -- F_Inv_Parent_2 -- F[ weight = 10 ];
// no weight here:
A_Inv_Parent_1 -- A -- A_Inv_Even_Children_1;
F_Inv_Parent_2 -- D_Inv_Parent_2[ style = invis ];
D_Inv_Parent_2 -- A_Inv_Even_Children_1 -- E_Inv_Parent_2;
// F_Inv_Parent_2 -- F; ### moved
D_Inv_Parent_2 -- D;
E_Inv_Parent_2 -- E;
}
Which gives you the disired straight line from B via F_Inv_Parent_2 to F which is actually the grandchild:
If every letter in the following represents a name. What is the best way to sort them by how common the ancestors are?
A B C D
E F G H
I J K L
M N C D
O P C D
Q R C D
S T G H
U V G H
W J K L
X J K L
The result should be:
I J K L # Three names is more important that two names
W J K L
X J K L
A B C D # C D is repeated more than G H
M N C D
O P C D
Q R C D
E F G H
S T G H
U V G H
EDIT:
Names might have spaces in them (Double names).
Consider the following example where each letter represents a single word:
A B C D M
E F G H M
I J K L M
M N C D M
O P C D
Q R C D
S T G H
U V G H
W J K L
X J K L
The output should be:
A B C D M
M N C D M
I J K L M
E F G H M
W J K L
X J K L
O P C D
Q R C D
S T G H
U V G H
First count the number of occurrences for each chain. Then rank each name according to that count. Try this:
from collections import defaultdict
words = """A B C D
E F G H
I J K L
M N C D
O P C D
Q R C D
S T G H
U V G H
W J K L
X J K L"""
words = words.split('\n')
# Count ancestors
counters = defaultdict(lambda: defaultdict(lambda: 0))
for word in words:
parts = word.split()
while parts:
counters[len(parts)][tuple(parts)] += 1
parts.pop(0)
# Calculate tuple of ranks, used for sorting
ranks = {}
for word in words:
rank = []
parts = word.split()
while parts:
rank.append(counters[len(parts)][tuple(parts)])
parts.pop(0)
ranks[word] = tuple(rank)
# Sort by ancestor count, longest chain comes first
words.sort(key=lambda word: ranks[word], reverse=True)
print(words)
Here's how you could do it in Java - essentially the same method as #fafl's solution:
static List<Name> sortNames(String[] input)
{
List<Name> names = new ArrayList<>();
for (String name : input)
names.add(new Name(name));
Map<String, Integer> partCount = new HashMap<>();
for (Name name : names)
for (String part : name.parts)
partCount.merge(part, 1, Integer::sum);
for (Name name : names)
for (String part : name.parts)
name.counts.add(partCount.get(part));
Collections.sort(names, new Comparator<Name>()
{
public int compare(Name n1, Name n2)
{
for (int c, i = 0; i < n1.parts.size(); i++)
if ((c = Integer.compare(n2.counts.get(i), n1.counts.get(i))) != 0)
return c;
return 0;
}
});
return names;
}
static class Name
{
List<String> parts = new ArrayList<>();
List<Integer> counts = new ArrayList<>();
Name(String name)
{
List<String> s = Arrays.asList(name.split("\\s+"));
for (int i = 0; i < s.size(); i++)
parts.add(String.join(" ", s.subList(i, s.size())));
}
}
Test:
public static void main(String[] args)
{
String[] input = {
"A B C D",
"W J K L",
"E F G H",
"I J K L",
"M N C D",
"O P C D",
"Q R C D",
"S T G H",
"U V G H",
"X J K L" };
for (Name name : sortNames(input))
System.out.println(name.parts.get(0));
}
Output:
I J K L
W J K L
X J K L
A B C D
M N C D
O P C D
Q R C D
E F G H
S T G H
U V G H
I need to draw a diagram with graphviz/dot where there are common edge types between nodes and am trying to find a way to define a label for each type of edge and then use that label multiple times in the diagram.
For example imagine the traditional ceiling fan FSM example where it's initially in state OFF and every time someone pulls the cord it changes to a new state based on the speed of the fan:
Pull Pull Pull
OFF ------> HIGH ------> MED ------> LOW
^ |
| Pull |
+------------------------------------+
Every edge is named "Pull" and I can define that in dot by using:
digraph fan {
OFF -> HIGH [label="Pull"];
HIGH -> MED [label="Pull"];
MED -> LOW [label="Pull"];
LOW -> OFF [label="Pull"];
}
BUT I don't want to keep specifying the same textual label every time because
My labels can get quite long so that's error-prone, and
My edges have other attributes like color in addition to label, and
I have a selection of multiple different types of edge so I want to make SURE that edge type "A" used in different contexts in the diagram always has all the same attributes.
I expected dot to have a syntax that would let me define names for my edge types, something like:
digraph fan {
edge_a [label="Pull"];
OFF -> HIGH edge_a;
HIGH -> MED edge_a;
MED -> LOW edge_a;
LOW -> OFF edge_a;
}
but of course what the really does is create a node named "Pull" and unlabeled edges.
I've been searching online for a few hours with no success. Anyone know how to define edge types up front to be used in multiple locations?
Update: #vaettchen had suggested defining an edge type then listing all of the transitions for that edge type, then defining the next edge type followed by it's transitions. While that would technically solve my problem, it would introduce a couple of others because my graphs today can look like:
digraph {
subgraph cluster_1 {
a -> b [label="type x", color=red, style=solid];
b -> a [label="type y", color=green, style=dashed];
b -> c [label="type x", color=red, style=solid];
c -> b [label="type y", color=green, style=dashed];
c -> d [label="type z", color=blue, style=dotted];
}
subgraph cluster_2 {
d -> e [label="type x", color=red, style=solid];
e -> d [label="type y", color=green, style=dashed];
e -> f [label="type x", color=red, style=solid];
f -> e [label="type y", color=green, style=dashed];
f -> c [label="type z", color=blue, style=dotted];
}
}
and to rearrange that by edge type I'd lose the immediate visual clarity in the code of having the bidirectional edges next to each other (a->b and b->a) and I'd have to explicitly list the nodes within each subgraph and I'd have to pull the subgraph-internal edge definitions up into the main graph:
digraph {
edge [label="type x", color=red, style=solid];
a -> b;
b -> c;
d -> e;
e -> f;
edge [label="type y", color=green, style=dashed];
b -> a;
c -> b;
e -> d;
f -> e;
edge [label="type z", color=blue, style=dotted];
c -> d;
f -> c;
subgraph cluster_1 {
a; b; c;
}
subgraph cluster_2 {
d; e; f;
}
}
So while it would solve the problem I asked about and I appreciate the suggestion, I'm not sure it's worth the tradeoff as you end up with the equivalent of a C program where you had to define all of your variables outside of the functions and organize them by their type rather than logical associations.
To be clear, given the above example what I was really hoping for would look like the following if such an "edge_type" definition keyword existed:
digraph {
edge_type edge_x [label="type x", color=red, style=solid];
edge_type edge_y [label="type y", color=green, style=dashed];
edge_type edge_z [label="type z", color=blue, style=dotted];
subgraph cluster_1 {
a -> b edge_x;
b -> a edge_y;
b -> c edge_x;
c -> b edge_y;
c -> d edge_z;
}
subgraph cluster_2 {
d -> e edge_x;
e -> d edge_y;
e -> f edge_x;
f -> e edge_y;
f -> c edge_z;
}
}
I think I got your solution, using m4 (thanks to Simon). Using and adapting your sample, I created a file called gv.m4:
digraph {
define(`edge_x',`[label="type x", color=red, style=solid]')
define(`edge_y',`[label="type y", color=green, style=dashed]')
define(`edge_z',`[label="type z", color=blue, style=dotted]')
subgraph cluster_1 {
a -> b edge_x;
b -> a edge_y;
b -> c edge_x;
c -> b edge_y;
c -> d edge_z;
}
subgraph cluster_2 {
d -> e edge_x;
e -> d edge_y;
e -> f edge_x;
f -> e edge_y;
f -> c edge_z;
}
}
and converted it with the simple command
m4 gv.m4 > gv.dot
which now contains your defined edges
digraph {
subgraph cluster_1 {
a -> b [label="type x", color=red, style=solid];
b -> a [label="type y", color=green, style=dashed];
b -> c [label="type x", color=red, style=solid];
c -> b [label="type y", color=green, style=dashed];
c -> d [label="type z", color=blue, style=dotted];
}
subgraph cluster_2 {
d -> e [label="type x", color=red, style=solid];
e -> d [label="type y", color=green, style=dashed];
e -> f [label="type x", color=red, style=solid];
f -> e [label="type y", color=green, style=dashed];
f -> c [label="type z", color=blue, style=dotted];
}
}
and yields the expected graph:
You can do much more with m4 - stuff that is missing in graphViz, like maintaining and (even conditionally) including subfiles. For example, if you put your two subgraphs into two separate files gv1.txt and gv2.txt, this would work nicely:
digraph incl
{
define(`edge_x',`[label="type x", color=red, style=solid]')
define(`edge_y',`[label="type y", color=green, style=dashed]')
define(`edge_z',`[label="type z", color=blue, style=dotted]')
include(gv1.txt)
include(gv2.txt)
e -> d[ color = yellow, label = "this is new!"];
}
Not really an answer but "food for thought" as I don't think named labels exist in graphviz: You could define a default label for the following edges. That works well if your workflow allows to define edges all in one place. Example:
digraph rs
{
node[ shape = box, style = rounded]
edge[ label = "pull" ];
{ A B } -> C;
G -> H;
C -> D[ label = "stop" ];
edge[ label = "push"];
D -> { E F };
edge[ color = red, fontcolor = red ];
{ E F } -> G;
}
which yields
I have also tried to implement your diagramm with
digraph fan
{
splines = ortho;
node [ shape=box ]
edge [ xlabel = "Pull", minlen = 4 ];
{ rank = same; OFF -> HIGH -> LOW; }
LOW:s -> OFF:s;
}
which produces
so it looks good but with all the tweaking is difficult to expand.
I struggled to download m4 on my machine and so opted for using graphviz through the python API where you can define a style as a dictionary and apply to nodes / edges as desired.
import graphviz
dot = graphviz.Digraph(comment='Test File')
nodeAttr_statement = dot.node_attr = {"shape": 'box', "style": 'filled', "fillcolor":"red"}
nodeAttr_question = dot.node_attr = {"shape": 'diamond', "style": 'filled', "fillcolor":"blue"}
dot.edge_attr
edge_Attr_sample = dot.edge_attr = {"arrowhead":'vee',"color":"yellow"}
edge_Attr_sample2 = dot.edge_attr = {"arrowhead": 'diamond', "color": "green"}
dot.node("A", "A", nodeAttr_statement)
dot.node("B", "B", nodeAttr_question )
dot.edge("A", "B", _attributes=edge_Attr_sample)
dot.edge("B", "A", _attributes=edge_Attr_sample2)
dot.format = 'pdf'
dot.render('test', view=True)
Output
// Test File
digraph {
node [fillcolor=blue shape=diamond style=filled]
edge [arrowhead=diamond color=green]
A [label=A fillcolor=red shape=box style=filled]
B [label=B fillcolor=blue shape=diamond style=filled]
A -> B [arrowhead=vee color=yellow]
B -> A [arrowhead=diamond color=green]
}
Output image from python script
I'm trying to create a graphic network with dot. and Graphiz.
So far this is my code:
graph {
rankdir = LR;
splines=line;
subgraph cluster_1{
1; 2;
}
subgraph cluster_2{
b; c;
}
subgraph cluster_3{
color = white
10;11;
}
b -- {1 2 10 11}[color = blue];
c -- {1 2 10 11}[color = yellow];
1[label = "1", style = filled, fillcolor = grey91]
2[label = "2", style = filled, fillcolor = grey91]
b[label = "B", style = filled, fillcolor = blue]
c[label = "C", style = filled, fillcolor = yellow]
10[label = "10", style = filled, fillcolor = grey91]
11[label = "11", style = filled, fillcolor = grey91]
}
This is what I get:
This is what I would like to obtain:
How to put the subgraphs in the correct order?
Thank you everyone in advance for your help!
Kind regards!
Defining the edges in the order as desired helps. Your version puts 1 2 10 11 in the same rank, hence they are set one below the other.
graph
{
rankdir = LR;
splines = line;
node[ style = filled, fillcolor = grey91 ];
1 2 10 11;
b[ label = "B", fillcolor = blue ];
c[ label = "C", fillcolor = yellow ];
subgraph cluster_1
{
1; 2;
}
subgraph cluster_2
{
b; c;
}
subgraph cluster_3
{
color = white
10; 11;
}
edge[ color = blue ]
{ 1 2 } -- b -- { 10 11 };
edge[ color = yellow ]
{ 1 2 } -- c -- { 10 11 };
}
yields
This drawing shows a tree of parent-child relationships. It is directed, without cycles. A child can have multiple parents.
The corresponding array of arrays in Perl is:
(
[A C],
[B C],
[D F G],
[C E D],
[E J X I],
[I J]
)
The first element in each sub-array is the parent of the rest, and the number of sub-arrays is the number of nodes who have at least one child.
Problem
I want to assign a number to each node which tells which level it is on in the graph. The level should also tell whether two nodes are independent, by which I mean they are not in direct parent-child relation. The answer to this specific example should (among many other answers) be:
[A B C D E F G X I J]
[1 1 2 3 3 4 4 4 4 5]
I solution can be implemented in any language, but Perl is preferred.
Still, non of the suggested solutions seems to work for this array:
(
[ qw( Z A )],
[ qw( B D E ) ],
[ qw( A B C ) ],
[ qw( G A E )],
[ qw( L B E )]
)
as does
(
[ qw/ M A / ],
[ qw/ N A X / ],
[ qw/ A B C / ],
[ qw/ B D E / ],
[ qw/ C F G / ],
[ qw/ F G / ]
[ qw/ X C / ]
)
The Graph::Directed module will make it simpler to handle this kind of data.
Multiple source nodes makes it potentially more complicated (for instance if there was another edge [Y, X]) but as long as all the sources are at the first level it is workable.
Here is some code that produces the information you say you expect. It assumes all nodes below the top level are accessible from the first source node and measures their path length from there, ignoring the second source.
use strict;
use warnings;
use feature 'say';
use Graph::Directed;
my #data = (
[ qw/ A C / ],
[ qw/ B C / ],
[ qw/ D F G / ],
[ qw/ C E D / ],
[ qw/ E J X I / ],
[ qw/ I J / ],
);
my $graph = Graph->new(directed => 1);
for my $item (#data) {
my $parent = shift #$item;
$graph->add_edge($parent, $_) for #$item;
}
my ($source) = $graph->source_vertices;
for my $vertex (sort $graph->vertices) {
my $path;
if ($graph->is_source_vertex($vertex)) {
$path = 0;
}
else {
$path = $graph->path_length($source, $vertex);
}
printf "%s - %d\n", $vertex, $path+1;
}
output
A - 1
B - 1
C - 2
D - 3
E - 3
F - 4
G - 4
I - 4
J - 4
X - 4
[This calculates, for each node, the length of the shortest path from a root. But the OP want the length of the longest of the shortest path from each root.]
All you have to do is find the root nodes, then do a breadth-first traversal.
my %graph = map { my ($name, #children) = #$_; $name => \#children } (
[qw( A C )],
[qw( B C )],
[qw( D F G )],
[qw( C E D )],
[qw( E J X I )],
[qw( I J )]
);
my %non_roots = map { $_ => 1 } map #$_, values(%graph);
my #roots = grep !$non_roots{$_}, keys(%graph);
my %results;
my #todo = map [ $_ => 1 ], #roots;
while (#todo) {
my ($name, $depth) = #{ shift(#todo) };
next if $results{$name};
$results{$name} = $depth;
push #todo, map [ $_ => $depth+1 ], #{ $graph{$name} }
if $graph{$name};
}
my #names = sort { $results{$a} <=> $results{$b} || $a cmp $b } keys(%results);
my #depths = #results{#names};
print "#names\n#depths\n";
Finally, I think I have solved the problem of finding correct levels, using Borodin's and ikegami's solutions (thanks guys, highly appreiciate your efforts):
#!/usr/local/perl -w
use strict;
use warnings;
use Graph::Directed;
use List::Util qw( min max );
# my #data = (
# [ qw/ M A/ ],
# [ qw/ N A X/ ],
# [ qw/ A B C / ],
# [ qw/ B D E F/ ],
# [ qw/ C F G / ],
# [ qw/ F G / ],
# [ qw/ X C G/ ],
# [ qw/ L A B /],
# [ qw/ Q M D/]
# );
# my #data = (
# [ qw( Z A )],
# [ qw( B D E ) ],
# [ qw( A B C ) ],
# [ qw( G A E )],
# [ qw( L B E )]
# );
# my #data = (
# [ qw/ M A / ],
# [ qw/ N A X / ],
# [ qw/ A B C / ],
# [ qw/ B D E / ],
# [ qw/ C F G / ],
# [ qw/ F G / ],
# [ qw/ X C / ]
# );
my #data = (
[ qw/ A M B C/ ],
[ qw/ B D F C/ ],
[ qw/ D G/ ],
[ qw/ F G/ ],
[ qw/ C G/ ],
[ qw/ M G/ ],
);
sub createGraph{
my #data = #{$_[0]};
my $graph = Graph->new(directed => 1);
foreach (#data) {
my ($parent, #children) = #$_;
$graph->add_edge($parent, $_) for #children;
}
my #cycleFound = $graph->find_a_cycle;
print "$_\n" for (#cycleFound);
$graph->is_dag() or die("Graph has cycles - unable to sort\n");
$graph->is_weakly_connected() or die "Graph not weakly connected - unable to analyze\n";
return $graph;
}
sub getLevels{
my #data = #{$_[0]};
my $graph = createGraph \#data;
my #artifacts = $graph->topological_sort();
chomp #artifacts;
print "--------------------------\n";
print "Topologically sorted list: \n";
print "$_ " for #artifacts;
print "\n--------------------------\n";
print "Initial levels (longest path):\n";
my #sources = $graph->source_vertices;
my %max_levels = map { $_=>[]} #artifacts;
my #levels = ();
for my $vertex (#artifacts) {
my $path = 0;
foreach(#sources){
if(defined($graph->path_length($_, $vertex))){
if ($graph->path_length($_, $vertex) > $path){
$path = $graph->path_length($_, $vertex)
}
}
}
printf "%s - %d\n", $vertex, $path;
push #levels, $path;
push #{$max_levels{$vertex}}, $path;
}
print "--------------------------\n";
for (my $i = 0; $i < #levels; $i++){
my $parent_level = $levels[$i];
my $parent = $artifacts[$i];
for (my $j = $i+1; $j < #levels; $j++){
my $child = $artifacts[$j];
for (#data){
my ($p, #c) = #{$_};
if($parent eq $p){
my #matches = grep(/$child/, #c);
if(scalar(#matches) != 0){
$levels[$j] = 1 + $parent_level;
push #{$max_levels{$child}},$levels[$j];
$levels[$j] = max #{$max_levels{$child}};
}
}
}
}
}
print "Final levels:\n";
my %sorted = ();
for (my $i = 0; $i < #levels; $i++){
$sorted{$artifacts[$i]} = $levels[$i];
}
my #orderedList = sort { $sorted{$a} <=> $sorted{$b} } keys %sorted;
print "$sorted{$_} $_\n" for #orderedList;
print "--------------------------\n";
return \%max_levels;
}
getLevels \#data;
Output:
--------------------------
Topologically sorted list:
A M B D C F G
--------------------------
Initial levels (longest path):
A - 0
M - 1
B - 1
D - 2
C - 1
F - 2
G - 2
--------------------------
Final levels:
0 A
1 M
1 B
2 F
2 C
2 D
3 G
--------------------------