Tracks
/
Julia
Julia
/
Übungen
/
Exercism-Matrix
Exercism-Matrix

Exercism-Matrix

Lernübung

Einführung

Weit zurück im Konzept Vectors haben wir festgehalten, dass „Arrays beliebige Größen haben können (nur begrenzt durch die Speicherbeschränkungen deiner Hardware) und beliebig viele Dimensionen haben dürfen.“

Seitdem haben wir Arrays mit mehr als einer Dimension weitgehend ignoriert, um die Sache einfach zu halten. Diese Entscheidung leuchtet dir eher ein, wenn du versuchst, die Julia-Referenzdokumente in ihrer vollen Komplexität zu lesen.

Höherdimensionale Arrays sind jedoch sehr, sehr wichtig im wissenschaftlichen Rechnen, also müssen wir sie verstehen.

Nomenklatur: Nach jahrhundertelangem mathematischem Vorbild nennen wir 1-D-Arrays Vectors und 2-D-Arrays Matrices.

Die Beispiele in diesem Dokument sind größtenteils Matrizen. Die Arbeit mit 3 oder mehr Dimensionen ist syntaktisch nahezu identisch, aber die Ausgabe ist (auf einem 2-D-Bildschirm) schwer und verwirrend zu lesen.

Arrays erstellen

Wir haben viele Vektoren erstellt, indem wir eine kommagetrennte Liste in eckige Klammern gesetzt haben. Semikolons können ebenfalls als Trennzeichen verwendet werden.

julia> v = [1, 2, 3]
3-element Vector{Int64}:
 1
 2
 3

julia> w = [1; 2; 3]
3-element Vector{Int64}:
 1
 2
 3

julia> v == w
true

Wenn wir ein Leerzeichen (oder einen Tabulator) als Trennzeichen verwenden, ist das Ergebnis anders.

julia> u = [1 2 3]
1×3 Matrix{Int64}:
 1  2  3

Julia definiert das nun als 1×3-Matrix (in anderen Zusammenhängen würden wir sie row vector nennen).

Im Allgemeinen verbinden Leerzeichen die Dinge horizontal, Semikolons (oder Zeilenumbrüche) verbinden sie vertikal.

Der Bezug auf „Dinge“ ist absichtlich vage, denn Julia versucht, mit dem zu arbeiten, was du ihm gibst.

julia> [v 2v]
3×2 Matrix{Int64}:
 1  2
 2  4
 3  6

julia> [v; 2v]
6-element Vector{Int64}:
 1
 2
 3
 2
 4
 6

Es gibt die Funktionen hcat()und vcat(), die dasselbe tun und deutlicher machen, dass es sich um horizontale und vertikale Verkettungen handelt. Die höherdimensionale Verallgemeinerung ist die Funktion cat().

jjulia> hcat(v, 2v)
3×2 Matrix{Int64}:
 1  2
 2  4
 3  6

julia> vcat(v, 2v)
6-element Vector{Int64}:
 1
 2
 3
 2
 4
 6

Explizite Matrizen gibt man bequem zeilenweise ein, weil das zur menschlichen Intuition passt (für Kulturen mit horizontalem Text ist es leichter zu lesen):

julia> m = [1 2 3; 4 5 6]
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

Beachte jedoch, dass Julia (wie Fortran, R und Matlab, aber anders als C/C++ oder NumPy) N-dimensionale Arrays spaltenweise speichert. Das kann einen enormen Unterschied bei der Performance machen, wenn du über die Elemente schleifst. Hilf deinem CPU-Cache, dir zu helfen!

# put these integers in 2 rows and 3 columns

julia> reshape(collect(1:6), 2, 3)
2×3 Matrix{Int64}:
 1  3  5
 2  4  6

Das obige Beispiel nimmt die Ganzzahlen 1 bis 6 und füllt eine 2×3-Matrix spaltenweise damit.

Es gibt verschiedene Hilfsfunktionen, um gängige Array-Typen zu erstellen (gleichmäßig oder zufällig).

julia> zeros(2, 3)  # see also ones()
2×3 Matrix{Float64}:
 0.0  0.0  0.0
 0.0  0.0  0.0

julia> falses(2, 2)  # booleans, see also trues()
2×2 BitMatrix:
 0  0
 0  0

julia> rand(Float32, 2, 3)  # random numbers in the interval [0, 1)
2×3 Matrix{Float32}:
 0.768823  0.169633  0.632565
 0.388451  0.109176  0.850381

Indexierung

Für ein 2-D-Array verwenden wir im Allgemeinen zwei Indizes in der Reihenfolge [row, col].

julia> m
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

julia> m[1, 2] # row 1, col 2
2

# Stay within bounds! There is no row 3.
julia> m[3, 1]
ERROR: BoundsError: attempt to access 2×3 Matrix{Int64} at index [3, 1]

julia> m[3]
2

Das letzte Beispiel ist vielleicht überraschend: Ein einzelner Index ist kein Fehler und gibt ein einzelnes Element zurück.

Die Erklärung führt zurück zum Kommentar über die spaltenweise Speicherung: Julia geht Spalte 1 hinunter, dann Spalte 2, bis es das 3. Element im Speicher findet.

Sei vorsichtig: Das hat einige Verwendungen beim Schreiben von Allzweckbibliotheken, ist aber eher verwirrend!

Ein ähnliches Problem tritt auf, wenn du die Größe eines Arrays abfragst. length() gibt die Gesamtzahl der Elemente an, size() gibt ein Tupel aus ndims() Elementen mit der Länge jeder Dimension an.

julia> m
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

julia> length(m)
6

julia> size(m)  # 2 rows, 3 cols
(2, 3)

julia> ndims(m)  # how many dimensions? Like `m |> size |> length`
2

Indexieren mit Bereichen und Arrays

Wir können eine Teilmatrix leicht kopieren.

Im folgenden Beispiel zwingt die Funktion reshape() ein Array in die angegebenen Dimensionen, indem sie es spaltenweise füllt, dann schneiden wir einen Ausschnitt heraus.

julia> m12 = reshape(collect(1:12), 4, 3)
4×3 Matrix{Int64}:
 1  5   9
 2  6  10
 3  7  11
 4  8  12

julia> m12[2:4, 1:2]
3×2 Matrix{Int64}:
 2  6
 3  7
 4  8

# some rows, all columns
julia> m12[2:4, :]
3×3 Matrix{Int64}:
 2  6  10
 3  7  11
 4  8  12

Ein : allein bedeutet „alles in dieser Dimension kopieren“.

Verwende Vektoren für nicht aufeinanderfolgende Zeilen/Spalten:

julia> m12[[1, 3], :]  # rows 1 and 3
2×3 Matrix{Int64}:
 1  5   9
 3  7  11

Funktionen auf ein Array anwenden

Wir haben zuvor Aggregationsfunktionen wie sum() und maximum() auf 1-D-Sammlungen angewandt gesehen, wo sie auf allen Elementen arbeiten und ein skalares Ergebnis zurückgeben.

Das funktioniert auch in höheren Dimensionen. Manchmal möchten wir die Funktion jedoch nur auf eine Dimension anwenden, zum Beispiel indem wir nach unten oder quer summieren, um ein Array mit einer singleton dimension der Größe 1 zurückzugeben.

Dafür gibt es ein optionales Schlüsselwortargument dims.

julia> m
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

julia> sum(m)  # sum everything
21

julia> sum(m; dims=1)  # sum down
1×3 Matrix{Int64}:
 5  7  9

julia> sum(m; dims=2)  # sum across
2×1 Matrix{Int64}:
  6
 15

# other aggregation functions are similar
julia> maximum(m; dims=2)
2×1 Matrix{Int64}:
 3
 6

Der Wert von dims ist die Dimension, die singulär wird, also dims=1 => 1×3 und dims=2 => 2×1 in den obigen Beispielen.

Erweiternd können höherdimensionale Arrays mehrere Dimensionen reduzieren, wenn dims auf ein Array oder einen Bereich gesetzt wird (auch hier erfordert das Verständnis der Ausgabe vielleicht etwas Nachdenken!).

Dimensionsbewusste Funktionen schreiben

Das Schlüsselwortargument dims ist in eingebauten Funktionen wie sum() üblich, aber wie schreiben wir etwas Entsprechendes in unserem eigenen Code?

Eine gute Antwort ist die Verwendung von Funktionen höherer Ordnung wie reduce(), und das wird in einem späteren Konzept ausführlicher behandelt.

Alternativ bietet Julia mehrere Funktionen, mit denen wir N-dimensionale Arrays behandeln können, als wären sie verschachtelte Vektoren von Vektoren.

Für Matrizen sind eachrow() und eachcol() praktisch, aber die allgemeinere Funktion ist eachslice(), die beliebige Dimensionen verarbeiten kann.

# m is as in the previous examples

julia> eachrow(m)
2-element RowSlices{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}:
 [1, 2, 3]
 [4, 5, 6]

julia> eachcol(m)
3-element ColumnSlices{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}, true}}:
 [1, 4]
 [2, 5]
 [3, 6]

Der Typ ist etwas beunruhigend, aber nur, weil dies eine View in das ursprüngliche Array ist, die es uns erlaubt, damit zu arbeiten, ohne zu kopieren. Julia-Arrays können potenziell mehrere Terabyte groß sein, also ist das Kopieren möglicherweise ein Performance-Albtraum!

Eine solche View kann zum Schleifen, für Broadcasting oder für all die anderen Operationen verwendet werden, die wir bisher im Lehrplan gesehen haben.

Außerdem können Comprehensions mit Array-Eingaben mächtig und vielseitig sein. Einfache Fälle wurden im Konzept Loops erwähnt, aber es wird eine ausführlichere Diskussion in einem späteren Konzept geben.

Broadcasting in mehreren Dimensionen

Den 1-D-Fall haben wir im Konzept Vector Operations besprochen.

julia> v = [1.2, 1.5, 1.7]
3-element Vector{Float64}:
 1.2
 1.5
 1.7

julia> v .- 0.5
3-element Vector{Float64}:
 0.7
 1.0
 1.2

Der punktierte Operator .- behandelt das Singleton 0.5 als äquivalent zu [0.5, 0.5, 0.5], indem er es per broadcasting an die Dimensionen angleicht, und führt dann die Subtraktion elementweise durch.

Die Erweiterung auf höhere Dimensionen ist wirklich nur mehr vom Gleichen.

Wenn wir zum Beispiel die Multiplikation eines 2x1-Zeilenvektors [1.0 1.5 2.0] mit der 2x3-Matrix [1 2 3; 4 5 6] broadcasten, ist das äquivalent dazu, [1.0 1.5 2.0] zu [1.0 1.5 2.0; 1.0 1.5 2.0] zu broadcasten und dann die Multiplikation elementweise durchzuführen.

julia> m
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

julia> m .* [1.0 1.5 2.0]  # broadcast a 1x3 row vector down
2×3 Matrix{Float64}:
 1.0  3.0   6.0
 4.0  7.5  12.0

julia> m .* [1.0 1.5 2.0; 1.0 1.5 2.0]  # 2x3 elementwise multiplication
2×3 Matrix{Float64}:
 1.0  3.0   6.0
 4.0  7.5  12.0

julia> m ./ [1, 2]  # broadcast a 2x1 column vector across
2×3 Matrix{Float64}:
 1.0  2.0  3.0
 2.0  2.5  3.0

julia> m ./ [1 1 1; 2 2 2]  # 2x3 elementwise division
2×3 Matrix{Float64}:
 1.0  2.0  3.0
 2.0  2.5  3.0

Hinweis: Für das Broadcasting müssen die nicht-singulären Dimensionen in ihrer Größe übereinstimmen (z. B. 2x3 Matrix .* 2x1 Matrix).

Außerdem kann eine Funktion genau so, wie es mit einem Vector gemacht wird, auf jedes Element einer Matrix gebroadcastet werden.

julia> m
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

julia> (x -> x^2).(m)  # broadcast a function to all elements
2×3 Matrix{Int64}:
  1   4   9
 16  25  36

Zuerst magst du das Gefühl haben, dass der Spielraum für Verwirrung bei Programmierern exponentiell mit der Anzahl der Dimensionen wächst, aber Übung hilft sehr. Außerdem lässt sich Vertrautheit mit NumPy-Arrays trotz der anderen Syntax recht gut auf Julia übertragen.

Anleitung

Eine Punktmatrix ist ein zweidimensionales Bild, das aus Punkten und Leerraum besteht. Die Punkte bilden das gewünschte Bild auf dem Hintergrund des Leerraums.

Ein Punktmatrixbild kann im Speicher als zweidimensionale Matrix gespeichert und bearbeitet werden. Im Folgenden wird der „Leerraum“ in den Matrizen durch 0 dargestellt und die „Punkte“ sind ungleich null.

1. Definiere die Matrix des Exercism-Logos

Die Matrix sieht wie folgt aus, wobei 0 für Leerraum und 1 für einen Punkt steht:

[
    0 0 1 1 0 0 0 0 0 0 0 0 0 0 1 1 0 0;
    0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0;
    0 1 0 0 0 1 0 0 0 0 0 0 1 0 0 0 1 0;
    0 1 0 0 1 0 1 0 0 0 0 1 0 1 0 0 1 0;
    0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0;
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1;
    0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0;
    0 1 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1 0;
    0 1 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 0;
    0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0;
    0 0 1 1 0 0 0 0 0 0 0 0 0 0 1 1 0 0;
]

Wenn du sie „renderst“, sieht sie so aus:

  XX          XX  
 X              X 
 X   X      X   X 
 X  X X    X X  X 
 X              X 
X                X
 X    X    X    X 
 X     X  X     X 
 X      XX      X 
 X              X 
  XX          XX

2. Definiere Funktionen, die das Logo traurig aussehen lassen

Definiere die Funktionen frown!() und frown(), die die Matrix des Exercism-Logos entgegennehmen. Gib eine Matrix zurück, bei der der lächelnde Mund in einen traurigen Mund geändert wurde.

Die resultierende Matrix würde so gerendert werden:

  XX          XX
 X              X
 X   X      X   X
 X  X X    X X  X
 X              X
X                X
 X      XX      X
 X     X  X     X
 X    X    X    X
 X              X
  XX          XX

3. Setze eine Stickerwand zusammen

Definiere die Funktion stickerwall(), die die Exercism-Matrix als Eingabe entgegennimmt. Gib eine Matrix der Punktmatrix zurück, die wie folgt gerendert wird:

  XX          XX    XX          XX
 X              X  X              X
 X   X      X   X  X   X      X   X
 X  X X    X X  X  X  X X    X X  X
 X              X  X              X
X                XX                X
 X    X    X    X  X      XX      X
 X     X  X     X  X     X  X     X
 X      XX      X  X    X    X    X
 X              X  X              X
  XX          XX    XX          XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  XX          XX    XX          XX
 X              X  X              X
 X   X      X   X  X   X      X   X
 X  X X    X X  X  X  X X    X X  X
 X              X  X              X
X                XX                X
 X      XX      X  X    X    X    X
 X     X  X     X  X     X  X     X
 X    X    X    X  X      XX      X
 X              X  X              X
  XX          XX    XX          XX

4. Ändere Punkte in die Pixelanzahl pro Spalte um

Wir sind nicht darauf beschränkt, nur 1 als Punkt zu verwenden, also könnten wir bei Bedarf andere nützliche Informationen kodieren. Definiere die Funktion colpixelcount(), die jede Punktmatrix als Eingabe entgegennimmt, deren Punkte 1 sind. Gib eine Punktmatrix derselben Größe zurück, wobei die Punkte in jeder Spalte der Anzahl der Punkte in dieser Spalte entsprechen.

Wenn die Matrix des Exercism-Logos als Eingabe dient, ist die Ausgabe eine Matrix wie folgt:

[
    0 0 2 2 0 0 0 0 0 0 0 0 0 0 2 2 0 0;
    0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0;
    0 8 0 0 0 1 0 0 0 0 0 0 1 0 0 0 8 0;
    0 8 0 0 1 0 2 0 0 0 0 2 0 1 0 0 8 0;
    0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0;
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1;
    0 8 0 0 0 0 2 0 0 0 0 2 0 0 0 0 8 0;
    0 8 0 0 0 0 0 1 0 0 1 0 0 0 0 0 8 0;
    0 8 0 0 0 0 0 0 1 1 0 0 0 0 0 0 8 0;
    0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0;
    0 0 2 2 0 0 0 0 0 0 0 0 0 0 2 2 0 0;
]

5. Rendere eine Punktmatrix

Statt nur darüber zu reden, wie wir unsere Kreationen rendern, möchten wir das auch tatsächlich tun können, um sie uns einfach ansehen zu können. Definiere die Funktion render(), die eine Punktmatrix als Eingabe entgegennimmt. Gib einen String zurück, bei dem Punkte als 'X', die 0en als ' ' dargestellt werden und Zeilenumbrüche die einzelnen Zeilen verbinden.

julia> render(E)
"  XX          XX  \n X              X \n X   X      X   X \n X  X X    X X  X \n X              X \nX                X\n X    X    X    X \n X     X  X     X \n X      XX      X \n X              X \n  XX          XX  "

Wenn er ausgegeben wird, sollte er die Exercism-Matrix wie erwartet rendern.

julia> print(render(E))
  XX          XX
 X              X
 X   X      X   X
 X  X X    X X  X
 X              X
X                X
 X    X    X    X
 X     X  X     X
 X      XX      X
 X              X
  XX          XX

Das sollte auch mit „Punkten“ funktionieren, die sich von 1 unterscheiden:

julia> print(render(colpixelcount(E)))
  XX          XX
 X              X
 X   X      X   X
 X  X X    X X  X
 X              X
X                X
 X    X    X    X
 X     X  X     X
 X      XX      X
 X              X
  XX          XX
Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab
Julia Exercism

Bereit, mit Exercism-Matrix zu starten?

Melde dich bei Exercism an, um Julia mit 35 Konzepte128 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.