Track
/
Julia
Julia
/
Esercizi
/
Matrice di Exercism
Matrice di Exercism

Matrice di Exercism

Esercizio di apprendimento

Introduzione

Molto tempo fa, nel concetto Vectors, abbiamo notato che «gli array possono avere dimensioni arbitrarie (soggette solo ai vincoli di memoria del tuo hardware) e possono avere un numero arbitrario di dimensioni».

Da allora abbiamo in gran parte ignorato gli array con più di una dimensione, solo per mantenere le cose semplici. Questa scelta avrà più senso se provi a leggere i documenti di riferimento di Julia, in tutta la loro complessità.

Tuttavia, gli array con più dimensioni sono molto, molto importanti nel calcolo scientifico, quindi dobbiamo capirli.

Nomenclatura: Seguendo secoli di precedenti matematici, chiamiamo gli array 1-D Vectors e gli array 2-D Matrices.

Gli esempi in questo documento saranno per lo più matrici. Lavorare con 3 o più dimensioni è sintatticamente quasi identico, ma l'output è difficile e confuso da leggere (su uno schermo 2-D).

Costruire gli array

Abbiamo creato moltissimi vettori mettendo un elenco separato da virgole tra parentesi quadre. Anche i punti e virgola possono essere usati come separatore.

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

Se usiamo lo spazio (o la tabulazione) come separatore, il risultato è diverso.

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

Ora Julia lo definisce come una Matrix 1×3 (in altri contesti la chiameremmo row vector).

In generale, gli spazi uniscono le cose in orizzontale, i punti e virgola (o le nuove righe) le uniscono in verticale.

Il riferimento a «cose» è volutamente vago, perché Julia cercherà di lavorare con qualunque cosa tu le dia.

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

Esistono le funzioni hcat() e vcat() che fanno la stessa cosa, rendendo più esplicito che si tratta di concatenazioni orizzontali e verticali. La generalizzazione a dimensioni superiori è la funzione 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

Digitare matrici esplicite è comodo farlo in ordine row-major, perché si adatta all'intuizione umana (più facile da guardare, per le culture con testo orizzontale):

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

Tuttavia, tieni presente che Julia (come Fortran, R e Matlab, ma a differenza di C/C++ o NumPy) memorizza gli array a N dimensioni in ordine column-major, e questo può fare un'enorme differenza in termini di prestazioni se iteri sugli elementi. Aiuta la cache della tua CPU ad aiutarti!

# 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

L'esempio qui sopra prende i numeri interi da 1 a 6 e riempie una Matrix 2×3 colonna per colonna.

Esistono diverse funzioni di utilità per costruire tipi comuni di array (uniformi o casuali).

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

Indicizzazione

Per un array 2-D, di solito usiamo due indici nell'ordine [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

L'ultimo esempio è forse sorprendente: un indice singolo non è un errore, e restituisce un singolo elemento.

La spiegazione risale al commento sull'ordine column-major: Julia scende lungo la col 1, poi la col 2, finché non trova il 3° elemento in memoria.

Fai attenzione: questo ha alcuni utilizzi quando scrivi librerie di uso generale, ma è più probabile che confonda!

Un problema simile si presenta quando si interroga la dimensione di un array. length() dà il numero totale di elementi, size() dà una tupla di ndims() elementi con la lunghezza di ciascuna dimensione.

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

Indicizzazione con intervalli e array

Possiamo copiare facilmente una sottomatrice.

Nell'esempio qui sotto, la funzione reshape() forza un array alle dimensioni specificate riempiendolo colonna per colonna, poi ne estraiamo una porzione.

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

Un : da solo significa «copia tutto in questa dimensione».

Usa i vettori per righe o colonne non consecutive:

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

Applicare funzioni a un array

Abbiamo già visto funzioni di aggregazione come sum() e maximum() applicate a collezioni 1-D, dove operano su tutti gli elementi e restituiscono un risultato scalare.

Questo funziona anche in dimensioni superiori. Tuttavia, potremmo voler applicare la funzione a una sola dimensione, ad esempio sommando verso il basso o in orizzontale per restituire un array con una singleton dimension di dimensione 1.

Per questo esiste un argomento opzionale per parola chiave 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

Il valore di dims è la dimensione che diventa singleton, quindi negli esempi precedenti dims=1 => 1×3 e dims=2 => 2×1.

Per estensione, gli array con più dimensioni possono ridurre più dimensioni, se dims è impostato a un array o a un intervallo (di nuovo, capire l'output potrebbe richiedere un po' di riflessione!).

Scrivere funzioni che tengono conto delle dimensioni

L'argomento per parola chiave dims è comune nelle funzioni predefinite come sum(), ma come scriviamo qualcosa di equivalente nel nostro codice?

Una buona risposta è usare funzioni di ordine superiore come reduce(), e questo sarà trattato in dettaglio in un concetto successivo.

In alternativa, Julia fornisce diverse funzioni che ci permettono di trattare gli array a N dimensioni come se fossero vettori annidati di vettori.

Per le matrici, eachrow() e eachcol() sono comode, ma la funzione più generale è eachslice(), che può gestire dimensioni arbitrarie.

# 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]

Il tipo è un po' allarmante, ma solo perché questa è una vista dell'array originale, che ci permette di lavorarci senza copiarlo. Gli array di Julia possono potenzialmente raggiungere i terabyte, quindi copiarli può essere un incubo per le prestazioni!

Una vista come questa può essere usata per iterare, per il broadcasting o per tutte le altre operazioni che abbiamo visto nel percorso finora.

Inoltre, le comprehension possono essere potenti e versatili con input ad array. Casi semplici sono stati menzionati nel concetto Loops, ma ci sarà una discussione più ampia in un concetto successivo.

Il broadcasting su più dimensioni

Abbiamo discusso il caso 1-D nel concetto Vector Operations.

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

L'operatore puntato .- tratta il singleton 0.5 come equivalente a [0.5, 0.5, 0.5] facendone il broadcasting per adattarlo alle dimensioni, poi esegue la sottrazione elemento per elemento.

Estendere questo a dimensioni superiori è davvero solo la stessa cosa ripetuta.

Ad esempio, quando si fa il broadcasting della moltiplicazione di un vettore riga 2x1 [1.0 1.5 2.0] con la matrice 2x3 [1 2 3; 4 5 6], è equivalente a fare il broadcasting di [1.0 1.5 2.0] a [1.0 1.5 2.0; 1.0 1.5 2.0] e poi eseguire la moltiplicazione elemento per elemento.

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

Nota: Per il broadcasting sono le dimensioni non singleton a dover avere la stessa dimensione (ad esempio 2x3 Matrix .* 2x1 Matrix).

Inoltre, una funzione può essere trasmessa a ciascun elemento di una Matrix esattamente come si fa con un Vector.

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

All'inizio potresti pensare che il margine di confusione per chi programma cresca esponenzialmente con il numero di dimensioni, ma la pratica aiuta molto. Inoltre, la familiarità con gli array di NumPy si trasferisce abbastanza bene a Julia, nonostante la sintassi diversa.

Istruzioni

Una matrice di punti è un'immagine bidimensionale composta da punti e spazi bianchi. I punti formano l'immagine desiderata sullo sfondo degli spazi bianchi.

Un'immagine a matrice di punti può essere memorizzata e manipolata in memoria come una Matrix bidimensionale. Nel seguito, gli «spazi bianchi» nelle matrici saranno indicati con 0 e i «punti» saranno diversi da zero.

1. Definire la Matrix del logo di Exercism

La Matrix appare così, con 0 per uno spazio bianco e 1 per un punto:

[
    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;
]

Se lo «renderizzi», appare così:

  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

Definisci le funzioni frown!() e frown() che prendono in input la Matrix del logo di Exercism. Restituisci una Matrix con la bocca sorridente trasformata in una bocca imbronciata.

La Matrix risultante si renderizzerebbe così:

  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. Comporre un muro di adesivi

Definisci la funzione stickerwall(), che prende in input la matrice di Exercism. Restituisci una Matrix della matrice di punti che si renderizza così:

  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. Trasformare i punti in conteggi di pixel per colonna

Non siamo limitati a usare solo 1 come punto, quindi potremmo codificare altre informazioni utili, se lo desideriamo. Definisci la funzione colpixelcount() che prende in input qualsiasi matrice di punti con punti pari a 1. Restituisci una matrice di punti della stessa dimensione, in cui i punti di ogni colonna sono il numero di punti presenti in quella colonna.

Prendendo come input la Matrix del logo di Exercism, l'output è una Matrix come segue:

[
    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. Renderizzare una matrice di punti

Anziché limitarci a parlare di renderizzare le nostre creazioni, vorremmo poterlo fare davvero, per osservarle facilmente. Definisci la funzione render() che prende in input una matrice di punti. Restituisci una stringa con i punti renderizzati come 'X', gli 0 come ' ' e le righe collegate da caratteri di nuova riga.

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  "

Una volta stampato, dovrebbe renderizzare la Matrix di Exercism come previsto.

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

Dovrebbe funzionare anche con «punti» diversi da 1:

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
Modifica tramite GitHub Il link si apre in una nuova finestra o scheda
Julia Exercism

Vuoi iniziare Matrice di Exercism?

Iscriviti a Exercism per imparare e padroneggiare Julia con 35 concetti128 esercizi e il mentoring di persone reali, tutto gratis.