트랙
/
Julia
Julia
/
연습 문제
/
부티크 제안
부티크 제안

부티크 제안

학습 연습 문제

소개

Python을 써 본 사람이라면 리스트 컴프리헨션도 거의 틀림없이 써 봤을 거예요. 리스트 컴프리헨션은 초기 버전부터 Python 문법의 중심에 있었어요.

이렇게 편리한 기능은 조금씩 다른 언어로도 퍼져 나가는데, Julia도 그중 하나예요.

Julia 프로그래머에게 컴프리헨션은 필수라기보다 선택 사항이에요. 보통은 브로드캐스팅, 고차 함수 같은 대안이 있거든요.

그래도 컴프리헨션은 배열을 만들 때 간단하고 읽기 쉬우면서 성능도 좋은 방법이 되는 경우가 많아요. 결국 쓰느냐 마느냐는 개인의 취향, 그리고 Python과 함수형 언어를 각각 어떻게 느끼는지에 달려 있어요.

문법은 대부분 Python을 그대로 가져왔지만, 고차원 배열을 위한 확장이 있어요.

변수 하나

선택적인 if 절까지 포함해서, 아주 Python스러운 형태예요.

julia> [x^2 for x in 1:3]
3-element Vector{Int64}:
 1
 4
 9
julia> [x^2 for x in 1:3 if isodd(x^2)]
2-element Vector{Int64}:
 1
 9

출력 형태는 컴프리헨션의 세부 내용에 따라 달라져요. 단순한 경우에는 보통 입력 컬렉션과 같은 형태지만, if 절이 붙으면 Vector로 평탄화돼요.

julia> m
2×3 Matrix{Int64}:
 1  2  3
 4  5  6
julia> [x^2 for x in m]
2×3 Matrix{Int64}:
  1   4   9
 16  25  36
julia> [x^2 for x in m if isodd(x^2)] 
3-element Vector{Int64}:
  1
 25
  9

변수 여러 개, Vector 출력

Python처럼 서로 다른 변수를 사용하는 for 절을 여러 개 둘 수 있고, 입력 컬렉션은 서로 같아도 달라도 돼요.

julia> [x * y for x in 1:3 for y in 4:6]
9-element Vector{Int64}:
  4
  5
  6
  8
 10
 12
 12
 15
 18
julia> [x * y for x in 1:3 for y in 4:6 if isodd(x * y)]
2-element Vector{Int64}:
  5
 15

이건 중첩 루프와 같아요. 행렬을 입력으로 줘도 출력은 1차원이에요.

julia> m
2×3 Matrix{Int64}:
 1  2  3
 4  5  6
julia> [x*y for x in m for y in m]
36-element Vector{Int64}:
  1
  4
  2
  5
(truncated output...)

변수 여러 개, 다차원 출력

앞 절에서는 공백으로만 구분된 for 절이 여러 개였어요.

이번 절에서는 for가 하나뿐이고, 변수들은 쉼표로 구분해요.

julia> [(x, y) for x in 1:3, y in 4:6]
3×3 Matrix{Tuple{Int64, Int64}}:
 (1, 4)  (1, 5)  (1, 6)
 (2, 4)  (2, 5)  (2, 6)
 (3, 4)  (3, 5)  (3, 6)

컴프리헨션의 변수 하나하나가 출력에 새로운 차원을 만들어요. 각 변수의 가능한 조합마다 항목이 하나씩 생기는 거예요.

위 예시에서 x는 행을 따라 아래로 커지고, y는 열을 따라 오른쪽으로 커져요. 더 높은 차원도 만들 수 있지만, 출력을 읽기 어려워질 수 있다는 점은 늘 주의해야 해요.

제너레이터 표현식

지금까지는 컴프리헨션을 대괄호 [ ... ] 안에 넣어 배열을 출력하는 경우를 다뤘어요.

대괄호를 소괄호 ( ... )로 바꾸면 다른 일이 벌어져요. 바로 generator expression이 만들어지는데, 이건 필요할 때 다음 값을 내놓을 수 있는 지연 평가 이터레이터예요.

julia> g = ((x, y) for x in 1:3, y in 4:6)
Base.Generator{Base.Iterators.ProductIterator{Tuple{UnitRange{Int64}, UnitRange{Int64}}}, var"#9#10"}(var"#9#10"(), Base.Iterators.ProductIterator{Tuple{UnitRange{Int64}, UnitRange{Int64}}}((1:3, 4:6)))
# Indexing fails with a generator, the entries don't exist yet
julia> g[1, 2]
ERROR: MethodError: no method matching getindex(::Base.Generator{Base.Iterators.ProductIterator{Tuple{UnitRange{…}, UnitRange{…}}}, var"#9#10"}, ::Int64, ::Int64)
# conversion to array
julia> collect(g)
3×3 Matrix{Tuple{Int64, Int64}}:
 (1, 4)  (1, 5)  (1, 6)
 (2, 4)  (2, 5)  (2, 6)
 (3, 4)  (3, 5)  (3, 6)
# generators are mainly designed for iteration
julia> [i * j for (i, j) in g]
3×3 Matrix{Int64}:
  4   5   6
  8  10  12
 12  15  18

컴프리헨션의 결과를 곧바로 이어서 처리할 때 제너레이터를 쓰면 메모리를 아낄 수 있어요. 큰 중간 배열을 저장할 필요가 없거든요.

제너레이터를 함수 인자로 쓸 때는 괄호를 따로 붙이지 않아도 돼요.

# inefficient
julia> v = [x^2 for x in 1:1e6];
julia> sum(v)
3.333338333335e17
# better - use a generator
julia> sum(x^2 for x in 1:1e6)
3.3333383333312755e17

제너레이터 문법은 감싸는 괄호만 빼면 컴프리헨션과 똑같아요.

제너레이터는 딕셔너리를 만들 때도 편리해요.

julia> Dict(x => x^2 for x in 1:5)
Dict{Int64, Int64} with 5 entries:
  5 => 25
  4 => 16
  2 => 4
  3 => 9
  1 => 1

지침

온라인 패션 부티크 매장에서 일하고 있어요. 사용자에게 코디를 추천하는 웹사이트 기능 아이디어를 떠올려요. 관심 있는 것은 가능한 코디와 그 가격, 그리고 서로 충돌하는 선택지를 없애는 거예요.

Note

다음 문제들을 푸는 방법은 여러 가지일 수 있지만, 모두 컴프리헨션으로 풀 수 있어요.

1. 의류 품목 만들기

clothingitem()을 구현해서 두 개의 Tuple을 받도록 해요. 하나는 categories이고 다른 하나는 각 항목에 대응하는 qualities예요. 이 함수는 categories를 키로, qualities를 값으로 하는 Dict를 반환해야 해요.

julia> categories = ("item_name", "price", "color", "base_color");

julia> qualities = ("Descriptive Name", 99.00, "Ochre Red", "red");

julia> clothingitem(categories, qualities)
Dict{String, Any} with 4 entries:
  "price"      => 99.0
  "item_name"  => "Descriptive Name"
  "base_color" => "red"
  "color"      => "Ochre Red"

함수는 어떤 개수의 categories든 처리할 수 있어야 해요.

2. 조합 제안하기

get_combinations()를 구현해서 상의 Tuple과 하의 Tuple을 받도록 해요. 이 함수는 두 튜플의 cartesian product를 2차원 배열(즉, Matrix)로 반환해야 해요. 각 항목은 Tuple 형태의 (top, bottom) 조합이에요. 상의는 행을 따라 바뀌고, 하의는 열을 따라 바뀌어요.

julia> tops = (
        Dict("item_name" => "Dress shirt"),
        Dict("item_name" => "Casual shirt")
        );

julia> bottoms = (
        Dict("item_name" => "Jeans"),
        Dict("item_name" => "Dress trousers")
       );

julia> get_combinations(tops, bottoms)
2×2 Matrix{Tuple{Dict{String, String}, Dict{String, String}}}:
 (Dict("item_name"=>"Dress shirt"), Dict("item_name"=>"Jeans"))   (Dict("item_name"=>"Dress shirt"), Dict("item_name"=>"Dress trousers"))
 (Dict("item_name"=>"Casual shirt"), Dict("item_name"=>"Jeans"))  (Dict("item_name"=>"Casual shirt"), Dict("item_name"=>"Dress trousers"))

3. 코디 가격 합산하기

각 옷에는 price 필드가 있어서, 코디의 전체 가격을 비교할 수 있으면 도움이 돼요. get_prices()를 구현해요. 이 함수는 의류 조합 배열을 받아요. 이 함수는 같은 모양의 배열을 반환하는데, 각 위치에 해당 코디의 가격이 들어 있어요.

julia> tops = (
        Dict("item_name" => "Dress shirt", "base_color" => "blue", "price" => 35),
        Dict("item_name" => "Casual shirt", "base_color" => "black", "price" => 20)
       );

julia> bottoms = (
        Dict("item_name" => "Jeans", "base_color" => "blue", "price" => 30),
        Dict("item_name" => "Dress trousers", "base_color" => "black", "price" => 75)
       );

julia> combomatrix = get_combinations(tops, bottoms);

julia> get_prices(combomatrix)
2×2 Matrix{Int}::
 65  110
 50   95

julia> filteredmatrix = filter_clashing(combomatrix)
1-element Vector{Tuple{Dict{String, Any}, Dict{String, Any}}}:
 (Dict("price" => 20, "item_name" => "Casual shirt", "base_color" => "black"), Dict("price" => 30, "item_name" => "Jeans", "base_color" => "blue"))

julia> get_prices(filteredmatrix)
1-element Vector{Int}:
 50

4. 충돌하는 코디 걸러내기

각 옷에는 base_color 필드가 있어요. 이 필드를 사용해서 상의와 하의의 base_color가 같은 모든 조합을 제거해요. filter_clashing()을 구현해서 의류 조합 배열을 받도록 해요. 조건에 맞는 조합들의 1차원 배열(즉, Vector)을 반환해요.

julia> tops = (
        Dict("item_name" => "Dress shirt", "base_color" => "blue", "price" => 35),
        Dict("item_name" => "Casual shirt", "base_color" => "black", "price" => 20)
       );

julia> bottoms = (
        Dict("item_name" => "Jeans", "base_color" => "blue", "price" => 30),
        Dict("item_name" => "Dress trousers", "base_color" => "black", "price" => 75)
       );

julia> combos = get_combinations(tops, bottoms);

julia> filter_clashing(combos)
1-element Vector{Tuple{Dict{String, Any}, Dict{String, Any}}}:
 (Dict("price" => 20, "item_name" => "Casual shirt", "base_color" => "black"), Dict("price" => 30, "item_name" => "Jeans", "base_color" => "blue"))
GitHub에서 편집 링크가 새 창이나 탭에서 열려요
Julia Exercism

부티크 제안 문제를 시작해 볼 준비가 됐나요?

Exercism에 가입하고 Julia 트랙을 개념 35개연습 문제 128개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.