Devin.KR

실패 입력 줄이기

80분 안팎

학습 목표

중복 할 일 목록에서 실패를 유지하는 최소 입력을 Python으로 추출합니다.

개념

최소화는 실패 조건을 보존합니다

긴 목록을 AI에게 보내기 전에 실패를 설명하는 작은 입력을 찾습니다. 목록에서 아무 항목이나 지우는 작업이 아니라 같은 판정 함수로 실패가 남는지 반복 확인하는 작업입니다. 이번 Python 연습의 대상은 앱 자체가 아니라 자료 정리용 fixture입니다. 처음 등장한 제목 순서를 보존하는 중복 제거가 요구인데 결함 함수는 sorted(set(items))를 반환합니다. 값의 개수만 비교하면 이 결함을 놓칩니다. 순서가 있는 목록끼리 비교하는 predicate를 먼저 고정합니다.

프로젝트와 연습 모델의 차이

Java 앱은 같은 제목이라도 서로 다른 ID의 할 일을 보존합니다. 이 연습은 실패 보고에서 제목을 요약하는 별도 도구이며 중복 제목을 처음 한 번만 남깁니다. Python 모델의 set을 Java 저장소에 적용하면 정상 할 일을 삭제하게 됩니다. 두 계약을 구별하려고 연습 입력에는 제목 배열만 쓰고 앱 미션에는 ID 배열을 씁니다. 중복 제거와 순서 보존을 함께 다루는 작은 모델로 최소화 절차를 익힌 뒤, 실제 앱에서는 생성 호출 순서와 ID 관계를 보존합니다.

정상 oracle을 만듭니다

oracle은 요구에 따른 답을 계산하는 참조 함수입니다. 빈 결과 배열을 준비하고 입력을 왼쪽부터 읽습니다. 아직 결과에 없는 문자열이면 append합니다. 이 방법은 처음 등장한 순서를 유지합니다. 결함 fixture의 sorted(set(items))와 구현 방법을 공유하지 않도록 직접 작성합니다. 둘의 결과가 다르면 fails가 True입니다. 문자열 배열만 제공하며 null·숫자·잘못된 JSON은 이번 계약 밖입니다. 다른 타입을 허용하려면 최소화 전에 새 입력 규칙과 오류 기대를 정의해야 합니다.

실패가 없는 입력의 출력

빈 배열, 한 문자열, 이미 사전순인 고유 제목 배열에서는 두 함수가 같은 결과를 반환할 수 있습니다. 이런 입력은 최소 반례가 없다는 뜻으로 null을 출력합니다. 빈 배열 출력과 null 출력은 다릅니다. 빈 배열은 후보 데이터이며 null은 원래 입력이 실패하지 않는다는 상태입니다. starter는 아직 모든 입력에 null을 내므로 정상 사례 일부만 통과합니다. 채점에는 순서가 뒤집히는 사례와 실패가 없는 경계 사례가 함께 있어야 작성한 함수의 판별 능력을 확인할 수 있습니다.

한 항목 삭제 후보를 만듭니다

현재 배열의 인덱스 i를 정하고 items[:i]와 items[i+1:]를 이어 붙입니다. 원본 배열을 직접 지우면 다른 후보와 인덱스가 어긋날 수 있으므로 새 배열로 만듭니다. 후보가 여전히 fails이면 현재 배열을 후보로 교체합니다. 후보가 통과하면 그 항목을 다시 유지하고 다음 인덱스로 갑니다. 입력 순서는 바꾸지 않습니다. 정렬해서 줄인 입력을 만들면 결함의 핵심인 순서 차이를 없앨 수 있으므로 최소화의 조작은 삭제로 제한합니다.

삭제 후 처음부터 다시 봅니다

실패하는 후보를 채택하면 인덱스를 0으로 되돌립니다. 배열 길이가 줄었으므로 이전 검사 위치를 그대로 이어가면 일부 항목을 건너뛸 수 있습니다. 한 바퀴 돌면서 어느 삭제도 실패를 유지하지 못하면 반복을 종료합니다. 삭제를 채택할 때마다 길이가 줄기 때문에 이 알고리즘은 유한하게 끝납니다. 종료 조건을 명시하지 않고 while True만 쓰면 실패 없는 입력에서 멈추지 않거나 같은 후보를 반복할 수 있습니다. 정상 입력 여부는 반복을 시작하기 전에 확인합니다.

최소라는 단어를 정확히 씁니다

여기서 얻는 결과는 한 항목을 더 삭제하면 실패가 사라지는 1-minimal 입력입니다. 모든 가능한 부분수열을 탐색한 전역 최소라는 주장과 다릅니다. 다른 결함 predicate에서는 삭제 순서에 따라 다른 답이 나올 수 있습니다. 채점의 결정성을 위해 왼쪽부터 삭제하고 채택할 때 처음부터 시작하는 규칙을 고정합니다. 학습자는 더 짧아 보이는 다른 답이 있다는 이유로 출력을 임의로 바꾸지 않고 지정한 절차를 설명합니다. 특정 예제만 하드코딩하는 방식은 새 입력의 실패를 보존하지 못합니다.

중복을 지우는 이유도 확인합니다

Z, Z, A가 들어오면 oracle은 Z, A이고 fixture는 A, Z라 실패합니다. 첫 Z를 삭제해도 Z, A가 남아 실패하므로 삭제를 채택할 수 있습니다. 그러나 최종 Z, A에서 어느 하나를 삭제하면 한 제목만 남아 두 함수가 일치합니다. 이 관찰은 순서 차이에 서로 다른 두 문자열이 필요하다는 사실을 보여 줍니다. 중복이 원인이었다고 단정하기보다 중복 없이도 재현된다고 기록합니다. 불필요한 항목을 줄였다는 사실과 원인을 확인했다는 사실은 별개입니다.

오류 출력을 읽는 방법

채점에서 expected가 문자열 배열인데 actual이 null이면 실패 판정 함수부터 확인합니다. actual이 정렬된 배열이면 최소화 전에 입력을 정렬했는지 봅니다. IndexError는 순서 결함이 아니라 후보 생성이나 반복 인덱스 오류입니다. JSONDecodeError는 표준 입력을 읽는 단계의 문제로 최소화에 도달하지 못한 상태입니다. 디버깅 출력은 결과와 섞지 말고 로컬에서 따로 관찰합니다. 최종 제출은 json.dumps로 배열 또는 null 한 개만 출력하여 형식을 유지합니다.

축소한 입력도 다시 재현합니다

최종 배열을 원래 fails에 한 번 더 넣어 True인지 확인합니다. 이어 각 인덱스를 하나씩 삭제해 모든 후보가 False인지 확인하면 1-minimal 조건을 검증할 수 있습니다. 결과 길이가 작다는 느낌만으로 판단하지 않습니다. 원본과 최소 입력을 함께 기록해야 다른 담당자가 어떤 조건을 제거했는지 알 수 있습니다. 이 검사는 제목 문자열의 내용을 바꾸지 않으며 초기 입력의 부분수열만 만듭니다. 개인정보를 바꾸는 작업은 별도로 합성 입력을 만든 후 같은 실패가 남는지 확인합니다.

같은 제목 배열을 앱에 적용할 때

축소 모델에서 Z, A를 얻었으면 Java 서비스의 빈 상태에서 같은 순서로 두 번 생성합니다. 중복 제거 helper를 앱에 추가하지 않습니다. Java 조회 ID [2, 1]이 관찰되면 Python의 축소 결과가 이 사례를 설명하는 데 도움이 된 것입니다. 두 언어 출력이 자동으로 같은 원인을 증명하는 것은 아닙니다. 제목순 정렬이라는 가설은 다음 레슨에서 서비스와 저장소 경계의 관찰로 확인합니다. 최소 입력은 조사 비용을 줄이는 자료이며 수정 코드 자체가 아닙니다.

검증 범위를 보고합니다

브라우저 실습은 지정된 문자열 배열에서 oracle과 fixture가 다른지 확인하고 삭제 순서에 따른 결과를 반환합니다. Unicode 문자열은 Python의 정렬 규칙으로 비교하며 업무용 언어별 사전 정렬 규칙을 새로 정의하지 않습니다. 실행 시간 성능이나 아주 큰 입력은 이번 채점 범위 밖입니다. 반복문과 슬라이스의 자세한 기초는 더 읽기의 컬렉션 장으로 연결합니다. 제출할 때는 predicate·삭제 규칙·종료 조건을 자신의 말로 설명하고 작은 입력을 전체 앱의 완료 증거로 과장하지 않습니다.

따라하기

구별 입력을 확인합니다

아래 코드를 그대로 실행해 관찰 값을 비교합니다.

items = ["Z", "Z", "A"]
expected = list(dict.fromkeys(items))
actual = sorted(set(items))
print(expected)
print(actual)
print(expected != actual)

실행 결과

['Z', 'A']
['A', 'Z']
True

한 항목을 삭제합니다

아래 코드를 그대로 실행해 관찰 값을 비교합니다.

items = ["Z", "Z", "A"]
for i in range(len(items)):
    print(i, items[:i] + items[i+1:])

실행 결과

0 ['Z', 'A']
1 ['Z', 'A']
2 ['Z', 'Z']

최종 입력의 삭제 경계를 봅니다

아래 코드를 그대로 실행해 관찰 값을 비교합니다.

items = ["Z", "A"]
for i in range(len(items)):
    candidate = items[:i] + items[i+1:]
    print(candidate, list(dict.fromkeys(candidate)) != sorted(set(candidate)))

실행 결과

['A'] False
['Z'] False

확인 문제

실습

입력은 문자열 JSON 배열입니다. 처음 등장 순서를 보존하는 중복 제거 oracle과 sorted(set(items)) 결함 fixture를 비교합니다. 실패가 없으면 null, 있으면 왼쪽부터 한 항목씩 삭제하고 실패가 남는 후보를 채택할 때 처음부터 재검사한 1-minimal 배열을 출력합니다. 앱의 중복 할 일 보존 규칙과 구분합니다.

모범 답안
import json, sys

def oracle(items):
    out = []
    for item in items:
        if item not in out:
            out.append(item)
    return out

def fails(items):
    return oracle(items) != sorted(set(items))

def minimize(items):
    if not fails(items):
        return None
    items = list(items)
    i = 0
    while i < len(items):
        candidate = items[:i] + items[i+1:]
        if fails(candidate):
            items = candidate
            i = 0
        else:
            i += 1
    return items

print(json.dumps(minimize(json.load(sys.stdin)), ensure_ascii=False))

더 읽기

면접 질문

  • 생성 코드의 오류를 AI에 다시 전달하는 방법을 설명해 주시면 됩니다.