트랙
/
Julia
Julia
/
연습 문제
/
메카 먹방 관리
메카 먹방 관리

메카 먹방 관리

학습 연습 문제

소개

Pairs와 Dicts 개념에서는 Julia의 딕셔너리 타입 기초를 다뤘어요.

Dict는 중요하고 자주 쓰이는 컬렉션 타입이라, 이를 다루는 함수도 아주 많아요.

항목 가져오기

항목을 가져오는 가장 간단한 방법은 키를 사용하는 거예요(많은 언어에서 그렇듯이). 그런데 없는 키를 요청하면 KeyError가 발생해요.

julia> d = Dict(i => ('a':'c')[i] for i in 1:3)
Dict{Int64, Char} with 3 entries:
  2 => 'b'
  3 => 'c'
  1 => 'a'
julia> d[2]
'b': ASCII/Unicode U+0062 (category Ll: Letter, lowercase)
# missing key
julia> d[4]
ERROR: KeyError: key 4 not found

값을 가져오기 전에 haskey(d, 4) 같은 걸로 키가 있는지 먼저 확인할 수도 있지만, 없는 키에 대해 그냥 기본값을 가져오는 게 더 유용할 때도 있어요.

이럴 때 쓰는 게 get() 함수와, Dict 자체를 바꾸는 변형인 get!()예요. 둘은 "없는" 키를 Dict에 추가하는지 아닌지가 달라요.

julia> get(d, 4, '!')
'!': ASCII/Unicode U+0021 (category Po: Punctuation, other)
# get() leaves Dict unchanged
julia> d
Dict{Int64, Char} with 3 entries:
  2 => 'b'
  3 => 'c'
  1 => 'a'
julia> get!(d, 4, '!')
'!': ASCII/Unicode U+0021 (category Po: Punctuation, other)
# get!() adds new key to Dict
julia> d
Dict{Int64, Char} with 4 entries:
  4 => '!'
  2 => 'b'
  3 => 'c'
  1 => 'a'

앞선 개념에서 delete!()가 키로 항목을 제거한다는 걸 봤어요. 여기에 값을 가져오는 것까지 함께 하고 싶다면 pop!() 함수를 쓰면 돼요.

julia> d
Dict{Int64, Char} with 4 entries:
  4 => '!'
  2 => 'b'
  3 => 'c'
  1 => 'a'
# popped value is returned
julia> pop!(d, 3)
'c': ASCII/Unicode U+0063 (category Ll: Letter, lowercase)
# Dict is shrunk
julia> d
Dict{Int64, Char} with 3 entries:
  4 => '!'
  2 => 'b'
  1 => 'a'

반복

Dict를 반복하면 기본적으로 key => value 쌍을 내놓아요. 각 구성 요소는 익숙한 .first와 .second라는 이름으로 접근할 수 있어요.

# d is from the previous example
julia> [(kv.first, kv.second) for kv in d]
3-element Vector{Tuple{Int64, Char}}:
 (4, '!')
 (2, 'b')
 (1, 'a')

키만, 또는 값만 사용하고 싶을 때도 많은데, 이건 아주 간단해요.

julia> [k for k in keys(d)]
3-element Vector{Int64}:
 4
 2
 1
julia> [v for v in values(d)]
3-element Vector{Char}:
 '!': ASCII/Unicode U+0021 (category Po: Punctuation, other)
 'b': ASCII/Unicode U+0062 (category Ll: Letter, lowercase)
 'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)

병합

두 Dict를 합치는 건 유용할 수 있지만, 여러 입력에 같은 키가 들어 있을 때 그 키를 어떻게 처리할지 분명히 해야 해요.

merge()와 merge!() 함수는 주어진 Dict를 몇 개든 왼쪽에서 오른쪽으로 합쳐요.

이미 있는 키의 값은 그냥 덮어써져서, 가장 오른쪽에 있는 Dict가 '이겨요'.

julia> d3 = Dict("color" => "red", "shape" => "circle")
Dict{String, String} with 2 entries:
  "shape" => "circle"
  "color" => "red"
julia> d4 = Dict("color" => "blue", "coords" => (3, 2))
Dict{String, Any} with 2 entries:
  "color"  => "blue"
  "coords" => (3, 2)
# color from d3 is lost
julia> merge(d3, d4)
Dict{String, Any} with 3 entries:
  "shape"  => "circle"
  "color"  => "blue"
  "coords" => (3, 2)

mergewith()와 mergewith!() 함수는 첫 번째 인자로 함수를 넘겨서, 겹치는 키를 어떻게 합칠지 정할 수 있게 해줘요. 덧셈이 간단한 예예요.

julia> d5 = Dict("color" => "red", "area" => 3)
Dict{String, Any} with 2 entries:
  "area"  => 3
  "color" => "red"
julia> d6 = Dict("shape" => "circle", "area" => 5)
Dict{String, Any} with 2 entries:
  "shape" => "circle"
  "area"  => 5
# area values are added, not overwritten
julia> mergewith(+, d5, d6)
Dict{String, Any} with 3 entries:
  "shape" => "circle"
  "area"  => 8
  "color" => "red"

참고로, mergewith 함수들은 고차 함수가 실제로 쓰이는 좋은 예예요.

지침

식료품 쇼핑 자동화 회사인 Mecha Munch™가 주문 앱 작업을 맡길 사람으로 방금 여러분을 채용했어요. 여러분의 팀은 장바구니의 기본적인 활동을 모두 관리하는 MVP(최소 기능 제품)를 만드는 임무를 맡았어요. 사용자가 식료품 주문을 추가하고, 삭제하고, 정렬할 수 있게 해주는 제품이죠. 다행히도 결제와 계산 기능은 다른 팀이 담당하고 있어요!

1. 사용자의 장바구니에 품목 추가하기

MVP는 사용자가 장바구니에 품목을 추가할 수 있어야 해요. 한 번에 하나일 수도, 여러 개일 수도 있어요. MVP이므로 품목의 수량은 _반복_으로 나타내요. 사용자가 'Oranges'를 2개 추가하려면 입력 이터러블에 'Oranges'가 두 번 나타나요. 사용자의 장바구니에 이미 있는 품목이라면 장바구니 수량을 1만큼 늘려요. 장바구니에 새로운 품목이라면 수량 1로 추가해요.

장바구니 딕셔너리와 추가할 품목의 이터러블을 인자로 받는 함수 add_items!(<cart>, <items>)를 만들어요. 이 함수는 사용자의 갱신된 장바구니 딕셔너리를 반환해야 해요.

julia> additems!(Dict("Banana" => 3, "Apple" => 2, "Orange" => 1),
                 ("Apple", "Apple", "Orange", "Apple", "Banana"))
Dict{String, Int64} with 3 entries:
  "Apple"  => 5
  "Orange" => 2
  "Banana" => 4

julia> additems!(Dict("Banana" => 3, "Apple" => 2, "Orange" => 1),
                 ["Banana", "Orange", "Blueberries", "Banana"])
Dict{String, Int64} with 4 entries:
  "Blueberries" => 1
  "Apple"       => 2
  "Orange"      => 2
  "Banana"      => 5

2. 레시피 "아이디어" 섹션 업데이트하기

앱에는 다양한 요리의 완성된 레시피로 채워진 "아이디어" 섹션이 있어요. 사용자는 이 레시피 중 하나를 골라 모든 재료를 장바구니에 자동으로 추가할 수 있어요. 콘텐츠 팀이 재료와 수량을 자꾸 바꾸고 있어서, 프로젝트 매니저는 이런 "아이디어" 레시피를 편집하는 방법을 만들어 달라고 요청했어요.

"아이디어" 딕셔너리와 레시피 업데이트 딕셔너리를 인자로 받는 함수 update_recipes!(<ideas>, <updates>)를 만들어요. 이 함수는 갱신된 "아이디어" 딕셔너리를 반환해야 해요.

julia>update_recipes!(
        Dict("Banana Bread"  => Dict("Banana" => 1, "Apple" => 1, "Walnuts" => 1, "Flour" => 1, "Eggs" => 2, "Butter" => 1),
             "Raspberry Pie" => Dict("Raspberry" => 1, "Orange" => 1, "Pie Crust" => 1, "Cream Custard" => 1)),
        Dict("Banana Bread" => Dict("Banana" => 4,  "Walnuts" => 2, "Flour" => 1, "Butter" => 1, "Milk" => 2, "Eggs" => 3))
    )
Dict{String, Dict{String, Int64}} with 2 entries:
  "Raspberry Pie" => Dict("Cream Custard"=>1, "Raspberry"=>1, "Orange"=>1, "Pie Crust"=>1)
  "Banana Bread"  => Dict("Eggs"=>3, "Flour"=>1, "Milk"=>2, "Butter"=>1, "Walnuts"=>2, "Banana"=>4)

julia> update_recipes!(
    Dict("Banana Bread"    => Dict("Banana" => 1, "Apple" => 1, "Walnuts" => 1, "Flour" => 1, "Eggs" => 2, "Butter" => 1),
         "Raspberry Pie"   => Dict("Raspberry" => 1, "Orange" => 1, "Pie Crust" => 1, "Cream Custard" => 1),
         "Pasta Primavera" => Dict("Eggs" => 1, "Carrots" => 1, "Spinach" => 2, "Tomatoes" => 3, "Parmesan" => 2, "Milk" => 1, "Onion" => 1)),
    Dict("Raspberry Pie"    => Dict("Raspberry" => 3, "Orange" => 1, "Pie Crust" => 1, "Cream Custard" => 1, "Whipped Cream" => 2),
        "Pasta Primavera"   => Dict("Eggs" => 1, "Mixed Veggies" => 2, "Parmesan" => 2, "Milk" => 1, "Spinach" => 1, "Bread Crumbs" => 1),
        "Blueberry Crumble" => Dict("Blueberries" => 2, "Whipped Creme" => 2, "Granola Topping" => 2, "Yogurt" => 3))
    )
Dict{String, Dict{String, Int64}} with 4 entries:
  "Raspberry Pie"     => Dict("Raspberry"=>3, "Orange"=>1, "Pie Crust"=>1, "Cream Custard"=>1, "Whipped Cream"=>2)
  "Pasta Primavera"   => Dict("Eggs"=>1, "Mixed Veggies"=>2, "Parmesan"=>2, "Milk"=>1, "Spinach"=>1, "Bread Crumbs"=>1)
  "Blueberry Crumble" => Dict("Blueberries"=>2, "Whipped Creme"=>2, "Granola Topping"=>2, "Yogurt"=>3)
  "Banana Bread"      => Dict("Banana"=>1, "Apple"=>1, "Walnuts"=>1, "Flour"=>1, "Eggs"=>2, "Butter"=>1)

3. 사용자 장바구니를 매장으로 보내 주문 처리하기

앱은 특정 사용자의 장바구니를 매장으로 보내 주문을 처리해야 해요. 그런데 매장 직원은 고유한 진열대 코드를 통해 품목이 어디에 있는지 알아야 해요. "처리용 장바구니"는 직원들이 더 효율적으로 일할 수 있도록 품목의 진열대 코드 순서대로 정렬되어야 해요.

사용자 장바구니와 매장 진열대 번호를 담은 딕셔너리를 인자로 받는 함수 send_to_store(<cart>, <aislecodes>)를 만들어요. 이 함수는 고객이 주문하는 각 품목마다 aislecode => quantity 쌍을 담은 결합된 "처리용 장바구니"를 반환해야 해요. 품목은 진열대 코드 순서대로 나타나야 해요.

julia> cart = Dict("Orange" => 1, "Milk" => 2, "Banana" => 3, "Apple" => 2, "Chocolate" => 2);

julia> aislecodes = Dict("Orange" => 2.4, "Milk" => 3.3, "Banana" => 2.6, "Apple" => 2.2, "Chocolate" => 5.7);

julia> send_to_store(cart, aislecodes)
5-element Vector{Pair{Float64, Int64}}:
 2.2 => 2
 2.4 => 1
 2.6 => 3
 3.3 => 2
 5.7 => 2

4. 사용자가 주문한 내용을 반영하도록 매장 재고 업데이트하기

앱이 고객 주문을 끝없이 받기만 할 수는 없어요. 결국 매장은 여러 제품이 떨어지게 돼요. 그래서 앱 MVP는 사용자가 주문을 매장으로 보낼 때마다 매장 재고를 업데이트해야 해요. 그렇지 않으면 고객이 실제로는 없는 제품을 주문하게 될 거예요.

cart와 inventory를 인자로 받는 함수 update_store_inventory!(<inventory>, <cart>)를 만들어요. 이 함수는 "cart"에서 "주문된" 수량만큼 매장 재고 수량을 줄이고, 재고가 없는(즉 0이 된) 품목들의 딕셔너리를 반환해야 해요.

julia> inventory = Dict("Banana" => 15, "Apple" => 12, "Orange" => 1, "Milk" => 4);

julia> cart = Dict("Orange" => 1, "Milk" => 2, "Banana" => 3, "Apple" => 2);

julia> update_store_inventory!(inventory, cart)
Dict{String, Int64} with 1 entry:
  "Orange" => 0

julia> inventory
Dict{String, Int64} with 4 entries:
  "Milk"   => 2
  "Apple"  => 10
  "Orange" => 0
  "Banana" => 12

5. 품절 품목 재주문하기

재고를 업데이트하는 것에 더해, 앱은 재고를 다시 채우는 일도 도울 수 있어요. MVP는 고객 주문 후 품절된 품목들의 소규모 주문을 준비할 수 있어야 해요. 이렇게 하면 대규모 주문을 시작하기가 더 쉬워져요. 게다가 가끔은 재고 목록에 없는 기간 한정 프로모션 품목도 있어요. 이런 품목이 프로모션 기간이 끝나기 전에 다 팔렸다면 인기가 있다는 뜻이므로 재고 목록에 추가할 수 있어요.

outofstock과 stock을 인자로 받는 reorder!(<outofstock>, <stock>)를 만들어요. stock 딕셔너리에는 표준 재고 품목과 그 재주문 수량이 들어 있어요. outofstock 딕셔너리는 update_store_inventory!의 결과, 즉 수량이 0인 품목들의 딕셔너리예요. outofstock의 수량은 stock에 있는 값으로 업데이트되어 반환되어야 해요. 또한 outofstock에 있지만 stock에는 없는 품목은 기본 수량 100으로 stock에 추가되어야 해요.

julia> inventory = Dict("Banana" => 15, "Apple" => 12, "Orange" => 1, "Milk" => 4, "Chocolate" => 2);

julia> cart = Dict("Orange" => 1, "Milk" => 2, "Banana" => 3, "Apple" => 2, "Chocolate" => 2);

julia> stock = Dict("Banana" => 150, "Apple" => 120, "Orange" => 120, "Milk" => 45);

julia> outofstock = update_store_inventory!(inventory, cart)
Dict{String, Int64} with 1 entry:
  "Orange"    => 0
  "Chocolate" => 0

julia> reorder!(outofstock, stock)
Dict{String, Int64} with 1 entry:
  "Orange"    => 120
  "Chocolate" => 100

julia> stock 
Dict{String, Int64} with 5 entries:
  "Milk"      => 45
  "Apple"     => 120
  "Chocolate" => 100
  "Orange"    => 120
  "Banana"    => 150
GitHub에서 편집 링크가 새 창이나 탭에서 열려요
Julia Exercism

메카 먹방 관리 문제를 시작해 볼 준비가 됐나요?

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