소개
소포 접수대는 가격을 구할 수 있지만 비교 메서드는 아직 최저가 규칙을 반환하지 않습니다. 규칙 객체를 비교하고, 최저가 동률을 모두 유지하고, 여러 소포의 완전한 견적 파일을 저장하도록 만듭니다.
이 실습은 독립적으로 시작하며 앞선 guided 실습의 두 완성된 규칙 구현이 준비되어 있습니다. 그 실습에서 배운 인터페이스, 조건 충족 여부, 정수 구간 요금 지식이 필요합니다. 다른 가격 알고리즘을 발명하는 대신 기존 규칙의 새 매개변수 인스턴스를 추가합니다. 모든 요금은 합성 데이터입니다. 시간은 작성자의 추정치입니다.
인터페이스를 통해 비교하기
이 단계에서는 비교기가 처음 보는 구현을 포함하여 제공된 모든 ShippingRule에 동작하는 비교 메서드를 구현합니다.
WebIDE 탭을 열고 메뉴에서 Terminal → New Terminal을 선택하여 통합 터미널을 엽니다. 아래 명령은 이 WebIDE 터미널에서 실행하세요. 명령은 같은 셸에서 실행되며, cd는 그 셸의 작업 디렉터리를 바꿉니다. 터미널을 새로 열었다면 cd 명령을 다시 실행하세요. JDK와 소스 코드 틀은 준비되어 있으므로 빌드 도구를 설치할 필요가 없습니다.
cd /home/labex/project/parcels
./quote.sh quote 1500
Town과 Steps는 모두 600을 표시합니다. 이런 정상 동률 때문에 가장 저렴한 규칙 하나만 유지하면 유효한 선택지를 잃습니다. 전체 공개 계약과 입력 조건은 다음과 같습니다.
공개 계약은 ShippingRule.id(), ShippingRule.quote(Parcel), 그리고 Quote.eligible()와 Quote.cents()입니다. RuleFactory.flat(String, int, long)과 RuleFactory.stepped(String, int, int, long)은 규칙 객체를 만들고, QuoteEngine.bestRules(Parcel, List<ShippingRule>)는 최저가인 모든 규칙 ID를 반환합니다. 이 시그니처를 통해 같은 호출 코드가 여러 구현을 사용할 수 있습니다. 구현 클래스 구성을 바꾸고, 이를 생성하도록 RuleFactory를 수정해도 됩니다. 공개 메서드와 제공된 명령 및 출력 형식은 유지하세요. 팩터리 내부 클래스 이름과 사용하는 편집기는 채점하지 않습니다.
지원하는 소포 무게는 1부터 1,000,000그램까지의 정수입니다. 규칙의 최대 무게와 구간 크기도 1부터 1,000,000까지의 정수이며, 고정 요금이나 구간당 요금은 0부터 1,000,000센트까지의 정수입니다. 조건에 맞는 0센트 견적도 유효합니다. 이는 Quote.unavailable()과 다릅니다. 후자는 eligible()이 false이며 내부에 저장한 금액은 비교에 사용하면 안 됩니다. 계산한 센트는 long에 저장하므로 큰 유효 곱도 32비트 int를 넘쳐 흐르게 하지 않습니다. 모든 요금, 인물, 소포 이름은 합성 연습 데이터이며 실제 운송사의 실시간 요금이 아닙니다.
비교기는 List<ShippingRule>을 받습니다. 각 요소의 구현이 달라도 향상된 for 반복문으로 모든 객체를 방문할 수 있습니다. 규칙 이름을 검사하거나 instanceof로 업무 로직을 재현하지 말고 quote(parcel)을 호출하세요.
빈 최저가 목록과 가장 큰 long 값인 Long.MAX_VALUE를 비교용 센티널로 사용합니다. 조건에 맞지 않는 견적은 continue로 건너뜁니다. 더 낮은 가격이 나오면 이전 최솟값을 교체하므로 clear()로 예전 최저가 규칙을 지웁니다. 같은 가격이면 그 ID를 추가합니다. 이는 별개의 if 두 개입니다. 방금 새 최솟값이 된 규칙도 추가해야 하기 때문입니다. 조건에 맞는 것이 없으면 목록은 빈 상태로 남습니다.
다음 명령은 Java 소스 파일 하나를 교체합니다. cat은 두 JAVA 줄 사이의 텍스트를 > 뒤 경로에 기록합니다. 시작 구분자의 따옴표는 셸이 Java 코드의 표현식을 치환하지 않게 합니다. 마지막 JAVA도 단독 줄로 포함하여 전체 블록을 붙여 넣으세요. 같은 파일을 WebIDE에서 편집하고 저장한 뒤 WebIDE 터미널로 돌아와도 됩니다. 불완전하게 붙여 넣어 계속 입력하라는 프롬프트가 나타나면 Ctrl+C를 누르고 전체 블록을 다시 붙여 넣으세요.
cat > src/QuoteEngine.java <<'JAVA'
import java.util.ArrayList;
import java.util.List;
public final class QuoteEngine {
public static List<String> bestRules(Parcel parcel, List<ShippingRule> rules) {
List<String> winners = new ArrayList<>();
long lowest = Long.MAX_VALUE;
for (ShippingRule rule : rules) {
Quote quote = rule.quote(parcel);
if (!quote.eligible()) continue;
if (quote.cents() < lowest) {
lowest = quote.cents();
winners.clear();
}
if (quote.cents() == lowest) winners.add(rule.id());
}
return winners;
}
}
JAVA
./quote.sh batch data/parcels.tsv data/rates.tsv
batch는 소포 TSV 경로와 요금 TSV 경로를 받습니다. 소포 행은 ID, 표시 이름, 정수 그램 수입니다. 출력은 각 소포를 모든 견적 및 BEST 행과 묶습니다. UNAVAILABLE은 특정 규칙이 운송할 수 없다는 뜻이며, BEST NONE은 어느 규칙도 운송할 수 없다는 뜻입니다. 조건에 맞는 견적이 없는 유효한 배치도 상태 0으로 정상 종료합니다.
P02의 BEST에는 Town과 Steps가 모두 있어야 합니다. P03은 1그램 더 무거워 Town만 최저가입니다. P01과 P02는 모두 Gift라는 이름이지만 서로 다른 ID가 견적을 구분합니다. P04에 적용 가능한 규칙은 없습니다. 이용 불가 결과도 포함하여 모든 소포와 규칙을 유지하세요.
규칙을 추가 등록하고 완전한 견적 저장하기
이 단계에서는 비교기를 바꾸지 않고 새 고정 요금 인스턴스를 등록한 뒤 접수대가 나중에 읽을 결과를 저장합니다.
WebIDE 터미널의 /home/labex/project/parcels에서 계속 작업합니다. 요금 읽기 코드는 RuleFactory로 요청한 구현을 만듭니다. 따라서 flat 행을 더하면 독립된 매개변수의 다른 객체가 생성됩니다. 원본 data/ 파일은 바꾸지 마세요. cp는 작업 복사본을 만들고, printf는 텍스트를 씁니다. \t는 탭, \n은 행 끝을 나타냅니다. >>는 파일을 교체하지 않고 끝에 추가합니다.
cp data/rates.tsv work/rates.tsv
printf 'flat\tRelay\t6000\t600\n' >> work/rates.tsv
./quote.sh batch data/parcels.tsv work/rates.tsv
Relay는 6,000그램을 포함한 범위에서 600센트로 적용 가능합니다. 이제 P02에는 세 개의 최저가 동률 규칙이 있고, 6,500그램인 P04에는 여전히 견적이 없습니다. 등록된 규칙 모음을 바꾸었지만 원래 비교 코드는 그대로 동작했습니다.
출력 자체가 실제 견적 산출물입니다. >는 표준 출력을 파일에 저장하며 같은 이름의 기존 파일을 교체합니다. Java가 입력을 검증하기 전에 셸이 출력 파일을 열기 때문에 유효한 배치만 저장하세요.
./quote.sh batch data/parcels.tsv work/rates.tsv > work/quotes.tsv
cat work/quotes.tsv
각 ID에는 PARCEL 행 하나, 등록된 규칙별 QUOTE 행 하나, 조건에 맞는 모든 최저가 ID를 쉼표로 구분한 BEST 행 또는 NONE이 필요합니다. 금액은 계속 정수 센트입니다. 규칙과 최저가 ID의 순서는 등록 목록을 따라도 됩니다. 중요한 것은 구성원과 관계입니다.
잘못된 무게는 유효하지만 견적이 없는 소포와 다릅니다. 제공된 오류 데이터에는 0그램인 BAD가 있습니다. 파서는 견적을 출력하기 전에 전체 배치를 검증합니다. 잘못된 배치는 상태 2로 종료하고 표준 오류에 설명을 쓰며 견적 행을 만들지 않습니다. 이 의도한 실패를 정상 파일로 리다이렉트하지 말고 실행하세요.
./quote.sh batch data/invalid-parcels.tsv work/rates.tsv
INVALID_WEIGHT BAD가 예상됩니다. 유효한 소포 파일로 돌아가 복구하세요.
./quote.sh batch data/parcels.tsv work/rates.tsv
원래 소포 및 요금 기록과 정상적인 work/quotes.tsv는 온전히 유지됩니다. 컴파일 오류는 지정된 파일을 수정하고 저장한 뒤 재실행하며, 경로 오류는 cd /home/labex/project/parcels를 다시 실행한 뒤 재시도합니다. 실행 스크립트는 매번 저장한 소스를 다시 빌드하고 앞선 실습처럼 UTF-8을 명시합니다.
검사는 수정하지 않은 복사본을 임시 데이터에서 컴파일하며 다른 유효한 매개변수와 인터페이스 구현도 시험합니다. 작업 중인 결과의 빈 부분을 채우거나 작업 공간을 다시 쓰지 않습니다. 이는 학습 환경의 기능 평가이지 관리자의 파일 위조를 막는 기능은 아닙니다.
요약
운송사별 분기 없이 인터페이스 객체를 비교하고, 최저가 동률을 모두 남기고, 유효하지만 견적이 없는 상태를 명시했습니다. 설정한 규칙을 추가해도 같은 흐름을 재사용했습니다. 저장 파일은 모든 소포의 ID와 전체 선택지를 유지합니다.



