상품 목록과 주문 목록을 각각 레코드(record)의 목록으로 만들고, 두 목록을 상품 번호로 맞춰(join) 상품 종류별 판매를 셈하라.
반복문 대신 질의식 처리(C# LINQ, Java 스트림)로 풀어 보자.
첫 줄에 상품 수 p와 주문 수 m이 주어진다.
다음 p줄에 상품이 "상품번호 종류 가격"으로, 그다음 m줄에 주문이 "상품번호 수량"으로 하나씩 주어진다.
주문의 상품번호가 상품 목록에 없으면 그 주문은 셈에서 뺀다.
첫 줄에 이렇게 뺀 주문의 수를 출력하라.
이어서 상품 목록에 나온 종류마다 "종류 판매수량 매출"을 한 줄씩 출력하라. 판매수량은 그 종류 상품의 주문 수량의 합이고, 매출은 주문마다 가격×수량을 더한 값이다.
주문이 하나도 없는 종류도 "종류 0 0"으로 출력한다.
종류는 매출이 큰 것부터, 매출이 같으면 종류 이름의 사전 순(문자 코드 순이라 숫자가 영어 소문자보다 앞이고, ab는 abc보다 앞)으로 출력한다.
입력
1 ≤ p ≤ 1,000, 1 ≤ m ≤ 5,000. 상품번호와 종류는 영어 소문자·숫자 1 ~ 10글자이고, 상품번호는 서로 다르다. 1 ≤ 가격 ≤ 1,000,000, 1 ≤ 수량 ≤ 1,000(정수)
실행하면 이 줄들이 차례로 input()·표준 입력으로 들어가요(실행 중에 묻지 않아요).
$ 준비됐어요
코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.
다른 언어로는?언어별 풀이 보기 · 2개 언어 풀이
이 문제는 질의식 처리가 언어에 들어 있는 C#(LINQ)과 Java(스트림)만 받아요. 두 목록을 맞추는 방법이 서로 달라요. C#에는 조인이 있어요: orders.Join(products, o => o.ProductId, p => p.Id, ...)는 짝이 있는 것만 남기는 안쪽 조인이고, products.GroupJoin(orders, ...)는 상품마다 주문 묶음을 붙여서 주문이 없는 상품도 빈 묶음으로 남겨요(질의 문법의 join ... into가 이것). Java 스트림에는 조인이 없어서, 한쪽을 Collectors.toMap(Product::id, Function.identity())로 Map으로 만들어 두고 다른 쪽을 돌며 맞춰 봐요. 함정 넷: ① 안쪽 조인만 하면 주문이 없는 종류가 통째로 사라져요. C#은 GroupJoin처럼 상품 쪽에서 출발하고, Java는 groupingBy한 Map이 아니라 상품에 나온 종류 목록(distinct())에서 출발해 getOrDefault(c, List.of())로 붙여요. ② 없는 상품번호: Java byId.get(id)는 null을 돌려줘서 바로 .price()를 부르면 NullPointerException, C# Dictionary의 dict[id]는 KeyNotFoundException을 던져요. Join은 짝이 없는 주문을 조용히 빼니, 뺀 수는 orders.Count - matched.Count()로 셀 수 있어요. ③ 매출은 100만 원 × 1,000개 × 5,000건이면 5조라서 long이어야 해요. Java mapToInt(...).sum()은 조용히 넘치고, C# LINQ Sum은 int에서 넘치면 OverflowException을 던져요. ④ 주문이 없는 종류는 모두 매출 0으로 같아서 이름 순 정렬이 꼭 필요해요. OrderByDescending·sorted는 안정 정렬이라 ThenBy·thenComparing을 빠뜨리면 상품 목록에 나온 차례가 그대로 남아요.
JavaMain.java · 72 줄
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.Comparator;import java.util.List;import java.util.Map;import java.util.function.Function;import java.util.stream.Collectors;record Product(String id, String category, long price) {}record Order(String productId, int qty) {}record Line(String category, int qty, long amount) {}record CategoryStat(String category, int units, long revenue) {}public class Main { public static void main(String[] args) throws IOException { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String[] first = in.readLine().trim().split(" "); int p = Integer.parseInt(first[0]), m = Integer.parseInt(first[1]); List<Product> products = new ArrayList<>(); for (int i = 0; i < p; i++) { String[] s = in.readLine().trim().split(" "); products.add(new Product(s[0], s[1], Long.parseLong(s[2]))); } List<Order> orders = new ArrayList<>(); for (int i = 0; i < m; i++) { String[] s = in.readLine().trim().split(" "); orders.add(new Order(s[0], Integer.parseInt(s[1]))); } // 스트림에는 join이 없다: 한쪽을 id → 상품 Map으로 만들어 두고 맞춰 본다 Map<String, Product> byId = products.stream() .collect(Collectors.toMap(Product::id, Function.identity())); // 안쪽 조인: 짝이 되는 상품이 있는 주문만 List<Line> lines = orders.stream() .filter(o -> byId.containsKey(o.productId())) // 없으면 get이 null을 준다 .map(o -> { Product pr = byId.get(o.productId()); return new Line(pr.category(), o.qty(), pr.price() * o.qty()); }) .toList(); int unknown = orders.size() - lines.size(); // 종류별로 묶기. 주문이 없는 종류는 이 Map에 없다 Map<String, List<Line>> byCategory = lines.stream() .collect(Collectors.groupingBy(Line::category)); // 바깥 조인: 상품에 나온 모든 종류에서 출발해 묶음을 붙인다(없으면 빈 목록) List<CategoryStat> stats = products.stream() .map(Product::category) .distinct() .map(c -> { List<Line> ls = byCategory.getOrDefault(c, List.of()); return new CategoryStat(c, ls.stream().mapToInt(Line::qty).sum(), ls.stream().mapToLong(Line::amount).sum()); }) .sorted(Comparator.comparingLong(CategoryStat::revenue).reversed() .thenComparing(CategoryStat::category)) .toList(); StringBuilder out = new StringBuilder().append(unknown).append('\n'); for (CategoryStat s : stats) out.append(s.category()).append(' ').append(s.units()).append(' ').append(s.revenue()).append('\n'); System.out.print(out); }}
C#Main.cs · 46 줄
using System;using System.Collections.Generic;using System.Linq;using System.Text;record Product(string Id, string Category, long Price);record Order(string ProductId, int Qty);class Program{ static void Main() { string[] first = Console.ReadLine()!.Trim().Split(' '); int p = int.Parse(first[0]), m = int.Parse(first[1]); var products = new List<Product>(); for (int i = 0; i < p; i++) { string[] s = Console.ReadLine()!.Trim().Split(' '); products.Add(new Product(s[0], s[1], long.Parse(s[2]))); } var orders = new List<Order>(); for (int i = 0; i < m; i++) { string[] s = Console.ReadLine()!.Trim().Split(' '); orders.Add(new Order(s[0], int.Parse(s[1]))); } // 안쪽 조인(Join): 짝이 되는 상품이 없는 주문은 빠진다 var matched = orders.Join(products, o => o.ProductId, pr => pr.Id, (o, pr) => new { pr.Category, o.Qty, Amount = pr.Price * o.Qty }); int unknown = orders.Count - matched.Count(); // 묶음 조인(GroupJoin): 상품마다 그 상품의 주문 묶음. 주문이 없는 상품도 빈 묶음으로 남는다 var stats = products .GroupJoin(orders, pr => pr.Id, o => o.ProductId, (pr, os) => new { pr.Category, Units = os.Sum(o => o.Qty), Revenue = os.Sum(o => pr.Price * o.Qty) }) .GroupBy(x => x.Category) .Select(g => new { Category = g.Key, Units = g.Sum(x => x.Units), Revenue = g.Sum(x => x.Revenue) }) .OrderByDescending(s => s.Revenue) .ThenBy(s => s.Category, StringComparer.Ordinal); var sb = new StringBuilder().Append(unknown).Append('\n'); foreach (var s in stats) sb.Append($"{s.Category} {s.Units} {s.Revenue}\n"); Console.Write(sb); }}