본문 바로가기
AI/AI 활용 기초

명단 비교 HTML, 엑셀 눈대중 대조를 누락자 목록으로 바꾸기

by 고돌한 AI 2026. 9. 20.
반응형
두 명단을 붙여넣어 누락자를 찾는 HTML 비교 도구 대표 이미지

명단 비교 HTML, 엑셀 눈대중 대조를 누락자 목록으로 바꾸기

두 명단을 한 줄에 한 명씩 붙여넣고 버튼을 누르면, 한쪽에만 있는 이름을 나눠 보여주는 HTML 도구를 만들 수 있습니다. 설치나 서버 없이 파일 하나만 저장하면 되며, 핵심은 이름을 정리한 뒤 JavaScript의 Map으로 포함 여부를 비교하는 것입니다. 띄어쓰기와 오타는 자동으로 같은 사람으로 판단하지 않는다는 비교 규칙만 먼저 정해두면 결과를 잘못 읽는 일을 줄일 수 있습니다.

30초 요약

  • 메모장에 명단비교.html 파일 하나를 만듭니다.
  • AI에게 첫 프롬프트를 주고 완성 코드를 통째로 받습니다.
  • 두 칸에 명단을 붙여넣으면 양쪽 누락자를 따로 표시합니다.
  • 빈 줄과 이름 양끝 공백은 지우고, 영문 대소문자는 구분하지 않습니다.
  • 실명 명단은 외부 전송·자동 저장 기능 없이 필요한 순간에만 비교합니다.

이걸 만들면 무엇이 달라질까요?

신청자 명단과 출석 명단을 번갈아 보며 형광펜을 긋는 대신, 두 목록을 각각 붙여넣고 기준 명단에만 있는 사람을 바로 확인하게 됩니다. 반대 방향 결과도 함께 보여주므로, 출석표에만 추가된 이름도 놓치지 않습니다.

예시 데이터 · 정적 완성 화면

두 명단 비교 결과

기준 명단에만 있음 · 1명

박서준

확인 명단에만 있음 · 0명

없음

입력 예: 기준 4명 · 확인 3명

이 화면은 글 안에서 구조를 보여주는 정적 데모라 버튼이나 입력칸이 작동하지 않습니다. 아래 HTML 파일을 저장해 브라우저에서 열면 실제 입력과 비교 버튼을 사용할 수 있습니다.

예시 화면으로 표시된 두 명단 비교 HTML 완성 화면
업무 전 업무 후
두 문서 사이를 오가며 이름을 눈으로 찾음 두 칸에 붙여넣고 한쪽에만 있는 이름을 확인
중복 이름과 빈 줄을 직접 정리 같은 값의 중복과 빈 줄을 코드가 정리
누락 방향을 한쪽만 확인하기 쉬움 양쪽 차이를 별도 결과로 표시

AI에게 첫 프롬프트를 어떻게 써야 할까요?

바이브코딩은 원하는 결과와 규칙을 말로 설명하고 AI와 수정하며 작은 프로그램을 완성하는 방식입니다. “명단 비교기 만들어줘”에서 멈추지 말고, 입력 형식과 비교 기준, 개인정보 조건까지 한 번에 적어야 결과가 덜 흔들립니다.

아래 문장을 AI 채팅에 그대로 붙여넣으세요. 코드는 하나의 HTML 파일로 달라고 명시하는 게 포인트입니다.

설치 없이 브라우저에서 여는 단일 HTML 명단 비교 도구를 만들어줘.

요구사항:
1. 왼쪽은 기준 명단, 오른쪽은 확인 명단 textarea로 만든다.
2. 이름은 한 줄에 한 명씩 붙여넣는다.
3. 비교 버튼을 누르면 '기준 명단에만 있는 이름'과 '확인 명단에만 있는 이름'을 따로 보여준다.
4. 빈 줄과 이름 양끝 공백은 제거하고 영문 대소문자는 구분하지 않는다.
5. 같은 이름이 반복되면 한 명으로 처리한다.
6. 결과 인원수, 결과 복사 버튼, 입력 초기화 버튼을 넣는다.
7. 외부 라이브러리, 서버 전송, localStorage를 사용하지 않는다.
8. 결과 출력에는 innerHTML 대신 textContent를 사용한다.
9. 모바일에서도 두 입력칸이 세로로 자연스럽게 쌓이게 한다.
10. HTML, CSS, JavaScript를 한 파일 안에 넣고 코드 전체를 한 번에 보여준다.

AI가 설명과 코드를 섞어 답했다면 HTML 코드 블록의 <!doctype html>부터 마지막 </html>까지만 복사합니다. 중간 일부만 복사하면 화면이나 버튼이 빠질 수 있습니다.

완성 코드는 어디에 붙여넣나요?

메모장이나 텍스트 편집기를 열고 아래 코드를 전부 붙여넣습니다. 파일 이름을 명단비교.html로 저장한 뒤 파일을 더블클릭하세요. 브라우저에서 “두 명단 비교” 제목과 입력칸 두 개가 보이면 첫 단계는 성공입니다.

<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>두 명단 비교</title>
  <style>
    * { box-sizing: border-box; }
    body { margin: 0; font-family: system-ui, sans-serif; background: #f1f5f9; color: #0f172a; }
    main { width: min(920px, calc(100% - 32px)); margin: 40px auto; }
    h1 { margin-bottom: 8px; }
    .guide { color: #475569; }
    .inputs, .results { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
    .card { padding: 18px; border: 1px solid #cbd5e1; border-radius: 14px; background: white; }
    label, h2 { display: block; margin: 0 0 10px; font-size: 17px; font-weight: 750; }
    textarea { width: 100%; min-height: 220px; padding: 12px; border: 1px solid #94a3b8; border-radius: 10px; resize: vertical; font: inherit; }
    .actions { display: flex; flex-wrap: wrap; gap: 10px; margin: 18px 0; }
    button { padding: 11px 16px; border: 0; border-radius: 10px; background: #0f766e; color: white; font-weight: 700; cursor: pointer; }
    button.secondary { background: #475569; }
    ul { min-height: 48px; margin: 0; padding-left: 22px; }
    .count { color: #0f766e; font-weight: 800; }
    .notice { padding: 12px; border-radius: 10px; background: #ecfeff; color: #155e75; }
    @media (max-width: 680px) { .inputs, .results { grid-template-columns: 1fr; } main { margin: 24px auto; } }
  </style>
</head>
<body>
  <main>
    <h1>두 명단 비교</h1>
    <p class="guide">한 줄에 한 명씩 붙여넣으세요. 빈 줄과 양끝 공백은 제외합니다.</p>

    <section class="inputs">
      <div class="card">
        <label for="baseList">기준 명단</label>
        <textarea id="baseList" placeholder="김민지&#10;이도윤&#10;박서준"></textarea>
      </div>
      <div class="card">
        <label for="checkList">확인 명단</label>
        <textarea id="checkList" placeholder="이도윤&#10;김민지"></textarea>
      </div>
    </section>

    <div class="actions">
      <button id="compareButton" type="button">명단 비교하기</button>
      <button id="copyButton" class="secondary" type="button">결과 복사</button>
      <button id="resetButton" class="secondary" type="button">입력 초기화</button>
    </div>

    <p id="status" class="notice" aria-live="polite">두 명단을 입력해 주세요.</p>

    <section class="results">
      <div class="card">
        <h2>기준 명단에만 있음 · <span id="onlyBaseCount" class="count">0</span>명</h2>
        <ul id="onlyBase"></ul>
      </div>
      <div class="card">
        <h2>확인 명단에만 있음 · <span id="onlyCheckCount" class="count">0</span>명</h2>
        <ul id="onlyCheck"></ul>
      </div>
    </section>
  </main>

  <script>
    const baseInput = document.querySelector('#baseList');
    const checkInput = document.querySelector('#checkList');
    const status = document.querySelector('#status');

    function parseList(text) {
      const originalByKey = new Map();
      text.split(/\r?\n/).forEach(line => {
        const name = line.trim();
        if (!name) return;
        const key = name.toLocaleLowerCase('ko-KR');
        if (!originalByKey.has(key)) originalByKey.set(key, name);
      });
      return originalByKey;
    }

    function difference(left, right) {
      return [...left.entries()]
        .filter(([key]) => !right.has(key))
        .map(([, original]) => original);
    }

    function renderList(elementId, names) {
      const list = document.querySelector(elementId);
      list.textContent = '';
      const values = names.length ? names : ['없음'];
      values.forEach(name => {
        const item = document.createElement('li');
        item.textContent = name;
        list.appendChild(item);
      });
    }

    function compare() {
      const base = parseList(baseInput.value);
      const check = parseList(checkInput.value);
      if (!base.size || !check.size) {
        status.textContent = '두 명단을 모두 입력한 뒤 다시 눌러 주세요.';
        return;
      }

      const onlyBase = difference(base, check);
      const onlyCheck = difference(check, base);
      renderList('#onlyBase', onlyBase);
      renderList('#onlyCheck', onlyCheck);
      document.querySelector('#onlyBaseCount').textContent = onlyBase.length;
      document.querySelector('#onlyCheckCount').textContent = onlyCheck.length;
      status.textContent = `비교 완료: 기준 ${base.size}명, 확인 ${check.size}명`;
    }

    document.querySelector('#compareButton').addEventListener('click', compare);

    document.querySelector('#copyButton').addEventListener('click', async () => {
      const text = [
        '[기준 명단에만 있음]',
        document.querySelector('#onlyBase').innerText || '없음',
        '',
        '[확인 명단에만 있음]',
        document.querySelector('#onlyCheck').innerText || '없음'
      ].join('\n');
      try {
        await navigator.clipboard.writeText(text);
        status.textContent = '결과를 클립보드에 복사했습니다.';
      } catch {
        status.textContent = '복사가 막혔습니다. 결과를 드래그해 직접 복사해 주세요.';
      }
    });

    document.querySelector('#resetButton').addEventListener('click', () => {
      baseInput.value = '';
      checkInput.value = '';
      renderList('#onlyBase', []);
      renderList('#onlyCheck', []);
      document.querySelector('#onlyBaseCount').textContent = '0';
      document.querySelector('#onlyCheckCount').textContent = '0';
      status.textContent = '입력을 지웠습니다.';
      baseInput.focus();
    });
  </script>
</body>
</html>

확장자가 .txt가 아니라 .html인지 확인하세요. Windows 메모장에서는 저장 창의 파일 형식을 ‘모든 파일’로 고른 뒤 UTF-8로 저장하면 한글이 깨질 가능성을 줄일 수 있습니다.

누락자는 어떤 원리로 찾나요?

코드는 줄바꿈을 기준으로 이름을 나눈 뒤 trim()으로 양끝 공백을 정리합니다. 빈 줄을 버리고 소문자 비교용 키를 만든 다음, Map에 처음 나온 표기만 남겨 중복 이름을 한 번만 계산합니다.

그다음 한 명단의 키가 다른 명단에 있는지 Map.has()로 확인합니다. 출석부를 투명 필름 두 장에 적어 겹친 뒤, 한쪽 필름에만 남은 이름을 찾는 셈입니다.

작은 예제로 보면 흐름이 선명합니다.

기준 입력: 김민지, 이도윤, 박서준, 최유진
확인 입력: 이도윤, 김민지, 최유진
예상 출력: 기준 명단에만 있음 → 박서준
기준 명단과 확인 명단을 양방향 결과로 비교하는 입력 과정

여기서 김민지김민지는 양끝 공백을 지운 뒤 같은 값이 됩니다. 반면 김민지김 민지는 다른 문자열이므로 사람이 원본을 확인해야 합니다.

원하는 결과가 안 나오면 무엇을 고쳐 달라고 하나요?

한 번에 기능을 계속 얹기보다 보이는 문제 하나씩 수정 프롬프트로 전달하세요. AI에게 현재 코드 전체와 함께 아래 문장 중 필요한 것만 보내면 됩니다.

현재 코드를 유지하면서 비교 규칙만 수정해줘.
쉼표로 붙여넣은 이름도 줄바꿈과 같은 구분자로 처리해줘.
수정한 이유를 짧게 설명하고 완성된 HTML 전체를 다시 보여줘.

사번과 이름이 A102 김민지처럼 함께 있다면 이름만 떼어 비교하지 않는 편이 안전합니다. 오히려 사번 전체를 비교 기준으로 삼아 동명이인을 구분하도록 요청하세요.

각 줄이 '사번,이름' 형식이야.
사번을 비교 기준으로 사용하고 결과에는 사번과 이름을 함께 표시해줘.
같은 사번에 이름이 다르면 별도의 '확인 필요' 목록에 보여줘.
외부 전송과 자동 저장 기능은 추가하지 마.

오류 메시지는 다음 행동으로 바꾸면 됩니다

화면이 코드 문자로만 보이면 파일 이름 끝이 .html.txt인지 먼저 확인합니다. 비교 버튼이 반응하지 않으면 브라우저 개발자 도구보다 먼저 AI에게 브라우저 콘솔 오류 문구를 그대로 전달하고, 전체 HTML을 다시 달라고 하세요.

복사 버튼에서 권한 오류가 나더라도 비교 기능 자체는 사용할 수 있습니다. 브라우저나 파일 실행 환경에 따라 클립보드 접근이 막힐 수 있으므로, 이때는 결과를 마우스로 선택해 복사하면 됩니다.

보이는 문제 먼저 할 일
한글이 깨짐 파일 인코딩을 UTF-8로 다시 저장
버튼이 눌려도 변화 없음 코드가 </html>까지 복사됐는지 확인
같은 사람이 누락자로 나옴 이름 내부 띄어쓰기·오타를 원본에서 확인
복사가 거부됨 결과를 드래그해 직접 복사

비교 결과 0명은 입력이 정확하다는 보증이 아닙니다. 두 명단에 같은 오타가 있거나 서로 다른 사람이 같은 이름을 쓰는 상황까지 이 도구가 판별하지는 못합니다.

개인정보가 든 명단은 어떻게 다뤄야 할까요?

이 예제에는 서버 전송, 외부 라이브러리, localStorage 저장을 넣지 않았습니다. 제시한 코드 그대로라면 입력값은 현재 브라우저 탭의 메모리에서 비교되지만, 코드를 수정할 때 전송 기능이 추가되지 않았는지 다시 살펴야 합니다.

실명과 연락처를 한꺼번에 붙여넣기보다 비교에 필요한 식별자만 사용하세요. 공유 PC라면 작업 후 초기화 버튼을 누르고 탭을 닫는 것이 좋습니다. 민감한 명단은 회사의 개인정보 처리 규칙이 우선입니다.

자동 저장을 붙이고 싶더라도 로컬 HTML 파일에서 localStorage 동작은 브라우저마다 달라질 수 있습니다. 편의보다 데이터 최소화를 먼저 택하면, 파일 하나짜리 도구의 장점도 더 분명해집니다.

언제 이 도구가 잘 맞고, 언제 다른 방법이 나을까요?

교육 신청자와 출석자, 행사 등록자와 체크인 명단처럼 행 단위 식별자가 분명한 두 목록을 잠깐 대조할 때 잘 맞습니다. 별도 프로그램 설치 권한이 없고 결과를 파일로 누적 관리할 필요가 없는 경우에도 가볍습니다.

열이 여러 개인 엑셀 파일, 수천 건을 반복 처리하는 업무, 철자 유사도까지 판단해야 하는 데이터라면 스프레드시트의 조회 함수나 데이터 정제 도구가 낫습니다. 이 HTML은 사람을 추정하는 시스템이 아니라, 정해진 문자열 규칙으로 차이를 보여주는 작은 업무 도구입니다.

작업 환경을 함께 정리한다면

코드와 두 명단을 한 화면에서 번갈아 보기 답답하다면 휴대용 모니터가 보조 화면 역할을 합니다. USB-C 영상 출력 지원 여부는 노트북 사양과 상품 상세 페이지에서 먼저 확인해야 하며, 화면 하나로도 불편하지 않다면 따로 살 필요는 없습니다.

추천 상품 이미지
본문 기반 추천 상품빅트랙 2.1K 휴대용 모니터, 35.56cm, 140PM02Q검색 상위 노출과 본문 관련성 기준쿠팡에서 상품 보기 →

노트북 키보드로 긴 명단과 수정 프롬프트를 반복 입력하기 불편한 독자라면 멀티페어링 키보드가 입력 환경을 보완합니다. 이미 편한 키보드가 있거나 이동성이 더 중요하다면 이 선택도 건너뛰어도 됩니다.

추천 상품 이미지
본문 기반 추천 상품아이리버 Silent 듀얼모드 멀티페어링 블루투스 무소음…검색 상위 노출과 본문 관련성 기준쿠팡에서 상품 보기 →

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

출처

  • MDN, Set: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
  • MDN, String.prototype.trim(): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim
  • MDN, Window.localStorage: https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage
  • MDN, Blob: https://developer.mozilla.org/en-US/docs/Web/API/Blob

핵심 정리: 다섯 가지 질문과 답

Q. 설치가 필요한가요?

아니요. 코드를 명단비교.html로 저장하고 브라우저에서 열면 됩니다.

Q. 누락자는 어느 방향으로 나오나요?

기준 명단에만 있는 이름과 확인 명단에만 있는 이름을 따로 보여줍니다.

Q. 중복 이름과 빈 줄은 어떻게 처리하나요?

빈 줄은 제외하고 같은 비교 키가 반복되면 한 명으로 계산합니다.

Q. 띄어쓰기와 오타도 자동으로 맞춰주나요?

아니요. 양끝 공백만 제거하며 이름 내부 띄어쓰기와 철자 차이는 사람이 확인해야 합니다.

Q. 실명 명단을 넣어도 되나요?

필요한 식별자만 사용하고 조직의 개인정보 처리 규칙을 먼저 따르세요. 예제에는 외부 전송과 자동 저장 기능이 없습니다.

반응형

댓글