Trilhas
/
Julia
Julia
/
Exercícios
/
Matriz do Exercism
Matriz do Exercism

Matriz do Exercism

Exercício de aprendizagem

Introdução

Lá atrás, no conceito Vectors, notamos que "arrays podem ter tamanho arbitrário (sujeitos apenas às restrições de memória do seu hardware) e podem ter um número arbitrário de dimensões."

Desde então, temos praticamente ignorado arrays com mais de uma dimensão, só para manter as coisas simples. Essa decisão vai fazer mais sentido se você tentar ler os documentos de referência do Julia, com toda a complexidade deles.

No entanto, arrays de dimensões superiores são muito, muito importantes em computação científica, então precisamos entendê-los.

Nomenclatura: Seguindo séculos de precedente matemático, nos referimos a arrays 1-D como Vectors e a arrays 2-D como Matrices.

Os exemplos neste documento serão, na maioria, matrizes. Trabalhar com 3 ou mais dimensões é sintaticamente quase idêntico, mas a saída é difícil e confusa de ler (numa tela 2-D).

Construindo arrays

Já criamos muitos vetores colocando uma lista separada por vírgulas entre colchetes. Também podemos usar ponto e vírgula como separador.

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 usarmos espaço (ou tabulação) como separador, o resultado é diferente.

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

O Julia agora define isso como uma Matrix 1×3 (em outros contextos, chamaríamos isso de row vector).

Em geral, espaços unem coisas na horizontal e ponto e vírgula (ou quebras de linha) unem coisas na vertical.

A referência a "coisas" é deliberadamente vaga, já que o Julia vai tentar trabalhar com o que você der a ele.

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

Existem as funções hcat() e vcat() que fazem a mesma coisa, deixando mais explícito que se trata de concatenações horizontais e verticais. A generalização para dimensões superiores é a função 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

Digitar matrizes explícitas é convenientemente feito na ordem por linhas, porque isso combina com a intuição humana (mais fácil de olhar, para culturas com texto horizontal):

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

No entanto, fique atento: o Julia (assim como Fortran, R e Matlab, mas ao contrário de C/C++ ou NumPy) armazena arrays de N dimensões na ordem por colunas, e isso pode fazer uma diferença enorme de desempenho se você iterar sobre os elementos. Ajude o cache da CPU a te ajudar!

# 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

O exemplo acima pega os inteiros de 1 a 6 e preenche uma Matrix 2×3 com eles, coluna por coluna.

Existem várias funções utilitárias para construir tipos comuns de array (uniformes ou aleatórios).

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

Indexação

Para um array 2-D, geralmente usamos dois índices na ordem [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

O último exemplo talvez surpreenda: um índice único não é um erro e retorna um único elemento.

A explicação remonta ao comentário sobre a ordem por colunas: o Julia desce a coluna 1, depois a coluna 2, até encontrar o 3º elemento na memória.

Cuidado: isso tem algumas utilidades ao escrever bibliotecas de propósito geral, mas é mais provável que confunda!

Uma questão parecida surge quando consultamos o tamanho de um array. length() dá o número total de elementos, size() dá uma tupla de ndims() elementos com o comprimento de cada dimensão.

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

Indexação com intervalos e arrays

Podemos copiar facilmente uma submatriz.

No exemplo abaixo, a função reshape() converte um array para as dimensões especificadas preenchendo coluna por coluna, e então fazemos um recorte dele.

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

Um : sozinho significa "copiar tudo nesta dimensão".

Use vetores para linhas/colunas não consecutivas:

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

Aplicando funções a um array

Já vimos funções de agregação como sum() e maximum() aplicadas a coleções 1-D, em que elas operam sobre todos os elementos e retornam um resultado escalar.

Isso também funciona em dimensões superiores. No entanto, podemos querer aplicar a função a apenas uma dimensão, por exemplo somando para baixo ou para o lado, para retornar um array com uma singleton dimension de tamanho 1.

Para isso, existe um argumento nomeado opcional 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

O valor de dims é a dimensão que se torna singleton, então dims=1 => 1×3 e dims=2 => 2×1 nos exemplos acima.

Por extensão, arrays de dimensões superiores podem reduzir várias dimensões, se dims for definido como um array ou um intervalo (de novo, entender a saída pode exigir alguma reflexão!).

Escrevendo funções que levam em conta as dimensões

O argumento nomeado dims é comum em funções internas como sum(), mas como escrevemos algo equivalente no nosso próprio código?

Uma boa resposta é usar funções de ordem superior como reduce(), e isso será abordado com algum detalhe em um conceito mais adiante.

Como alternativa, o Julia oferece várias funções que nos permitem tratar arrays de N dimensões como se fossem vetores aninhados de vetores.

Para matrizes, eachrow() e eachcol() são convenientes, mas a função mais geral é eachslice(), que dá conta de dimensões arbitrárias.

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

O tipo é um pouco assustador, mas só porque isso é uma view para o array original, permitindo trabalhar nele sem copiar. Arrays do Julia podem ter terabytes de tamanho, então copiar pode ser um pesadelo de desempenho!

Uma view como essa pode ser usada para fazer laços, broadcasting ou todas as outras operações que vimos no syllabus até agora.

Além disso, as compreensões podem ser poderosas e versáteis quando a entrada é um array. Casos simples foram mencionados no conceito Loops, mas haverá uma discussão mais ampla em um conceito mais adiante.

Broadcasting em múltiplas dimensões

Discutimos o caso 1-D no conceito 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

O operador com ponto .- trata o singleton 0.5 como equivalente a [0.5, 0.5, 0.5], fazendo broadcasting dele para casar as dimensões, e depois faz a subtração elemento a elemento.

Estender isso para dimensões superiores é, na verdade, só mais do mesmo.

Por exemplo, ao fazer broadcasting da multiplicação de um row vector 2x1 [1.0 1.5 2.0] com a matriz 2x3 [1 2 3; 4 5 6], isso equivale a fazer broadcasting de [1.0 1.5 2.0] para [1.0 1.5 2.0; 1.0 1.5 2.0], com a multiplicação feita elemento a elemento em seguida.

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: São as dimensões não singleton que precisam ter o mesmo tamanho (por exemplo, 2x3 Matrix .* 2x1 Matrix) para o broadcasting.

Além disso, uma função pode ser aplicada via broadcasting a cada elemento de uma Matrix exatamente como se faz com um 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

Você pode achar de início que o potencial de confusão do programador cresce exponencialmente com o número de dimensões, mas a prática ajuda muito. Além disso, a familiaridade com arrays do NumPy se traduz muito bem para o Julia, apesar da sintaxe diferente.

Instruções

Uma matriz de pontos é uma imagem bidimensional formada por pontos e espaços em branco. Os pontos formam a imagem desejada sobre o fundo de espaços em branco.

Uma imagem de matriz de pontos pode ser armazenada e manipulada na memória como uma Matrix bidimensional. No que segue, os "espaços em branco" nas matrizes serão representados por 0 e os "pontos" serão diferentes de zero.

1. Defina a Matrix do logo do Exercism

A Matrix se parece com o seguinte, com 0 como espaço em branco e 1 como ponto:

[
    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 você a "renderizar", ela se parece com isto:

  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. Defina funções que fazem o logo franzir a boca

Defina as funções frown!() e frown() que recebem a Matrix do logo do Exercism. Retorne uma Matrix com a boca sorridente alterada para uma boca franzida.

A Matrix resultante seria renderizada assim:

  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. Monte uma parede de adesivos

Defina a função stickerwall(), que recebe a matriz do Exercism como entrada. Retorne uma Matrix da matriz de pontos que é renderizada assim:

  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. Transforme pontos em contagens de pixels por coluna

Não precisamos usar apenas 1 como ponto, então podemos codificar outras informações úteis, se quisermos. Defina a função colpixelcount(), que recebe qualquer matriz de pontos com pontos 1 como entrada. Retorne uma matriz de pontos do mesmo tamanho, em que os pontos de cada coluna sejam a quantidade de pontos naquela coluna.

Com a Matrix do logo do Exercism como entrada, a saída é uma Matrix como 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. Renderize uma matriz de pontos

Em vez de apenas falar sobre renderizar nossas criações, gostaríamos de poder fazer isso de verdade para visualizar com facilidade. Defina a função render(), que recebe uma matriz de pontos como entrada. Retorne uma string com os pontos renderizados como 'X', os 0s como ' ' e quebras de linha conectando cada linha.

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  "

Quando impressa, ela deve renderizar a Matrix do Exercism como esperado.

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

Isso também deve funcionar com "pontos" diferentes de 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
Editar via GitHub O link abre em uma nova janela ou aba
Julia Exercism

Tudo pronto para começar Matriz do Exercism?

Crie sua conta no Exercism para aprender e dominar Julia com 35 conceitos128 exercícios e mentoria humana de verdade, tudo de graça.