오늘은 React(Vite)와 TypeScript를 활용하여 시리얼 통신을 구현하는 방법에 대해 알아보도록 하겠습니다. 최근 웹 개발에서 시리얼 포트를 다루는 경우가 많아지고 있는데요, Web Serial API를 통해 이를 손쉽게 처리할 수 있습니다. 그럼 시작해볼까요?😊

Serialport?

SerialPort는 시리얼 통신(Serial Communication)을 통해 데이터를 주고받기 위한 소프트웨어 인터페이스로, 주로 하드웨어 장치(센서, 마이크로컨트롤러, IoT 장치 등)와 컴퓨터 간의 데이터를 교환할 때 사용됩니다. 특히 임베디드 시스템, 하드웨어 제어 및 다양한 장치들과의 통신에 자주 활용됩니다. 이 과정에서 직렬 통신 포트(시리얼 포트)를 사용하여 바이트 단위의 데이터를 순차적으로 전송합니다.

시리얼 통신(Serial Communication)?

시리얼 통신(Serial Communication)이란 데이터를 한 번에 한 비트씩 순차적으로 전송하는 방식입니다. 이는 병렬 통신과 대비되며, 전송선이 적고 전송 거리가 길어도 안정적인 전송이 가능하다는 장점이 있습니다.
시리얼 통신은 RS-232, USB, SPI, I2C와 같은 여러 프로토콜을 통해 구현되며, 다양한 장치들과 통신할 수 있습니다.

Web Serial API

Web Serial API는 브라우저가 직렬 장치에 접근할 수 있도록 해주는 기능입니다. 이 API를 사용하면 웹 애플리케이션에서 직접 하드웨어 장치와 소통할 수 있게 됩니다.
(Web Serial API는 보안상의 이유로 HTTPS에서만 사용할 수 있습니다.)

환경 설정하기

개발관련 세팅이 되어있다는 가정하에 진행하도록 하겠습니다. (Node.js, npm, ...)
먼저, 프로젝트를 생성하고 필요한 패키지를 설치해야 합니다.

npm create vite@latest serial-communication --template react-ts

✔ Select a framework: › React
✔ Select a variant: › TypeScript

cd serial-communication
npm install
npm run dev


위와 같이 명령어를 작성하면 vite로 구성된 기본 화면이 출력됩니다. 이후 시리얼 통신을 위해서 최소한의 파일만 남겨두고 삭제하도록 하겠습니다.

 

UI 구성하기

시리얼 통신에는 포트 연결, 포트 열기, 포트에 데이터 쓰기, 포트에서 데이터 읽기 등과 같은 기능을 사용해야 합니다. 필요한 버튼 및 연결된 것을 알 수 있게 UI를 구성하겠습니다.

// App.tsx
import "./App.css";

function App() {
  return (
    <section>
      <h1>serial-communication</h1>
      <p>Selected Port: </p>
      <div className="wrapper">
        <button>Port select</button>
        <button>Send Data</button>
      </div>
    </section>
  );
}
export default App;

Serial Communication 구현하기

이제 본격적으로 시리얼 통신을 구현해보겠습니다! 아래 코드를 참고하세요:D

사용 가능한 포트 확인

시리얼 통신을 하기위해 사용 가능한 포트를 확인하여 연결하는 과정의 코드를 작성하겠습니다.
먼저 재사용성 있게 컴포넌트를 구성하기 위해 custom hook을 생성했습니다. 이때, Web Serial API를 사용하기 위해서는 기본적으로 비동기 통신이 되어야하는 것을 주의해서 작성해주시면 됩니다.

navigator.serial.requestPort(): 연결 가능한 포트 목록을 확인할 수 있도록 해주는 메서드입니다.

port.open({ option }): 직렬 포트에 맞는 option을 설정해서 시리얼 통신을 할 수 있도록 포트를 웹브라우저와 연결하는 메서드입니다.

// hooks/useSerialCommunication.tsx
import { useState } from "react";

function useSerialCommunication() {
  const [port, setPort] = useState();

  const initialize = async () => {
    try {
      // @ts-expect-error: navigator.serial is not yet supported in TypeScript
      const port = await navigator.serial.requestPort();
      setPort(port);
      await port.open({ baudRate: 9600 });
    } catch (err) {
      console.error("포트 연결 실패:", err);
    }
  };

  return { port, initialize };
}

export default useSerialCommunication;
// App.tsx
import "./App.css";
import useSerialCommunication from "./hooks/useSerialCommunication";

function App() {
  const { port, initialize } = useSerialCommunication();
  return (
    <section>
      <h1>serial-communication</h1>
      <p>Selected Port: {port ? "connect" : ""}</p>
      <div className="wrapper">
        <button onClick={initialize}>Port select</button>
        <button>Send Data</button>
      </div>
    </section>
  );
}

export default App;

 

포트에 데이터 쓰기

sendData 함수는 React 애플리케이션에서 Web Serial API를 사용하여 시리얼 포트로 데이터를 전송하는 로직을 처리합니다. 이 함수는 사용자가 입력한 문자열 데이터를 시리얼 포트에 인코딩하여 전송하고, 오류 발생 시 적절히 로그를 출력합니다.

 

  • 포트 선택 확인
    port 객체가 없는 경우 에러를 던져 사용자가 시리얼 포트 연결을 먼저 설정하도록 요구합니다.
  • Writable 스트림 가져오기
    port.writable.getWriter()를 호출해 데이터를 전송할 수 있는 Writable 스트림을 생성합니다. 스트림은 단일 작업을 수행하기 위해 잠금(lock) 상태가 되며, 작업이 완료된 후 releaseLock()으로 잠금을 해제해야 합니다.
  • 데이터 인코딩
    사용자가 전송하고자 하는 데이터(messageToSend)는 문자열이므로, 시리얼 포트가 이해할 수 있는 바이너리 형식으로 변환해야 합니다. 이를 위해 Web API의 TextEncoder를 사용하여 문자열을 Uint8Array로 인코딩합니다.
  • 데이터 전송
    writer.write(data)를 호출하여 인코딩된 데이터를 시리얼 포트로 전송합니다.
  • 스트림 해제
    releaseLock()을 호출하여 스트림 잠금을 해제합니다. 이렇게 해야 다른 쓰기 작업이나 리소스 처리가 가능합니다.
  • 에러 처리
    데이터 전송 중 문제가 발생하면 catch 블록에서 에러를 캡처하고, 오류 메시지를 출력합니다.
  • 디버깅 로그 출력
    console.log를 사용해 전송된 데이터(messageToSend)와 변환된 바이너리 데이터(data)를 확인할 수 있도록 로그를 출력합니다.

 

const sendData = async (messageToSend: string) => {
  if (!port) {
    throw new Error("Port is not selected");
  }

  try {
    // 시리얼 포트의 writable 스트림 가져오기
    // @ts-expect-error: navigator.serial은 TypeScript에서 아직 지원되지 않음
    const writer = port.writable.getWriter();

    // TextEncoder를 사용해 문자열 데이터를 Uint8Array 형식으로 인코딩
    const data = new TextEncoder().encode(messageToSend);

    // 데이터를 시리얼 포트로 쓰기
    await writer.write(data);

    // writer 객체의 락 해제 (다른 쓰기 작업 가능하도록)
    writer.releaseLock();

    console.log("Data sent:", messageToSend, data);
  } catch (error) {
    // 데이터 쓰기 중 오류 발생 시 로그 출력
    console.error("Error writing to serial port:", error);
  }
};

요청한 데이터 값

포트에서 데이터 읽기

initialize 함수는 Web Serial API를 사용해 시리얼 포트를 초기화하고, 포트로부터 데이터를 읽는 로직을 구현합니다. 데이터는 TextDecoder를 사용해 문자열로 변환되며, React의 상태를 활용해 UI에서 확인할 수 있도록 처리됩니다.

 

  • 포트 요청 및 초기화
    • navigator.serial.requestPort()를 통해 사용자가 연결하려는 시리얼 포트를 선택합니다.
    • 선택된 포트는 port.open({ baudRate: 9600 })를 호출해 특정 전송 속도로 초기화합니다.
  • Readable 스트림 가져오기
    • port.readable을 통해 포트의 Readable 스트림을 가져옵니다.
    • getReader()를 호출해 데이터를 읽기 위한 Reader 객체를 생성합니다.
  • 데이터 읽기
    • reader.read()를 통해 데이터를 읽습니다.
    • 반환값은 { value, done } 형식이며:
      • value: Uint8Array 형식의 읽은 데이터
      • done: 읽기가 종료되었는지 여부
    • value가 존재하면 TextDecoder를 사용해 Uint8Array 데이터를 문자열로 변환하고, 이전 데이터에 추가하여 상태로 저장합니다.
  • Reader 잠금 해제
    • Reader는 단일 작업을 수행하기 위해 잠금(lock) 상태가 되므로, 작업이 끝나면 reader.releaseLock()을 호출해 잠금을 해제합니다.
  • 에러 처리
    • 포트 요청 실패, 포트 해제, 권한 부족 등의 에러를 try...catch 블록에서 처리하여 디버깅 로그를 출력합니다.

 

const initialize = async () => {
  try {
    // 시리얼 포트 요청 및 초기화
    // @ts-expect-error: navigator.serial은 TypeScript에서 아직 지원되지 않음
    const port = await navigator.serial.requestPort();
    setPort(port); // 선택한 포트를 상태로 저장
    await port.open({ baudRate: 9600 }); // 포트 열기

    // 데이터 읽기 설정
    const reader = port.readable?.getReader(); // 포트의 Readable 스트림 가져오기
    if (reader) {
      while (true) {
        const { value, done } = await reader.read(); // 데이터 읽기
        if (done) break; // 읽기가 종료되면 반복 종료
        if (value) {
          // Uint8Array 데이터를 문자열로 변환하고 상태 업데이트
          setReceivedData((prev) => prev + new TextDecoder().decode(value));
        }
      }
      reader.releaseLock(); // Reader 잠금 해제
    }
  } catch (err) {
    console.error("포트 연결 실패:", err); // 에러 처리
  }
};

Reference

- Web Serial API 공식문서
https://stackoverflow.com/questions/68806607/using-web-serial-with-reactjs

 

 

https://www.acmicpc.net/problem/30970

 

30970번: 선택의 기로

첫 번째 줄에는 첫 번째 방법을 선택했을 때의 첫 번째로 고른 촉석루 미니어처의 품질과 가격, 두 번째로 고른 촉석루 미니어처의 품질과 가격을 공백으로 구분하여 순서대로 출력한다. 두 번

www.acmicpc.net

문제

[사진] 촉석루

품질이냐 가격이냐, 그것이 문제로다..

진주 나들이를 온 보선이는 기념품으로 촉석루 미니어처를 사기로 했다. 촉석루는 진주성에 있는 누각이며 경상남도 유형문화재 중 하나로, 진주성의 남쪽 지휘대로 사용됨과 동시에 논개가 촉석루 앞 의암에서 순국한 것으로 알려져 유명한 곳이다.

촉석루 미니어처를 사기 위해 기념품 가게에 들른 보선이는 놀라움을 금치 못했다. 왜냐하면, 가게에는 각양각색의 촉석루 미니어처가 진열되어 있었기 때문이다. 그리고 모든 촉석루 미니어처는 장인이 한 땀 한 땀 심혈을 기울여서 만들어서 그런지 품질과 가격이 천차만별이었고, 품질과 가격이 전부 동일한 두 촉석루 미니어처는 없었다. 각양각색의 촉석루 미니어처를 본 보선이는 물욕이 폭발할 뻔했지만 가까스로 마음을 진정시키고, 촉석루 미니어처를 두 개만 사기로 했다. 보선이는 두 가지 방법으로 촉석루 미니어처를 골라보기로 했는데, 이는 다음과 같다.

  • 진열되어 있는 촉석루 미니어처 중에서 품질이 가장 높은 촉석루 미니어처를 골라 가져온다. 만약 그런 미니어처가 여러 개라면 가격이 가장 낮은 것을 골라 가져온다. 이 과정을 두 번 반복하는 것이 첫 번째 방법이다.
  • 진열되어 있는 촉석루 미니어처 중에서 가격이 가장 낮은 촉석루 미니어처를 골라 가져온다. 만약 그런 미니어처가 여러 개라면 품질이 가장 높은 것을 골라 가져온다. 이 과정을 두 번 반복하는 것이 두 번째 방법이다.

보선이가 각 방법에 따라 촉석루 미니어처들을 고르게 될 때 어떤 촉석루 미니어처들을 고르게 되는지 알아보자.

입력

첫 번째 줄에는 기념품 가게에 진열되어 있는 촉석루 미니어처의 개수 이 주어진다. (2 ≤ N ≤ 100,000)

두 번째 줄부터 개의 줄에 걸쳐 가게에 진열되어 있는 번째 촉석루 미니어처 종류의 품질 , 가격 가 공백으로 구분되어 주어진다. (1≤Q_i,P_i≤10000)입력으로 주어지는 모든 수는 정수이다.

 

출력

첫 번째 줄에는 첫 번째 방법을 선택했을 때의 첫 번째로 고른 촉석루 미니어처의 품질과 가격, 두 번째로 고른 촉석루 미니어처의 품질과 가격을 공백으로 구분하여 순서대로 출력한다.

두 번째 줄에는 두 번째 방법을 선택했을 때의 첫 번째로 고른 촉석루 미니어처의 품질과 가격, 두 번째로 고른 촉석루 미니어처의 품질과 가격을 공백으로 구분하여 순서대로 출력한다.

첫 번째 방법의 결과가 두 번째 방법의 결과에 영향을 미치지 않는다.

예제 입력 1 복사

4
3 1
2 2
2 3
1 1

예제 출력 1 복사

3 1 2 2
3 1 1 1

문제풀이(1)

1. 촉석루 미니어처 중 품질이 가장 높은 미니어처를 가져온다.
2. 여러개일 경우 가격이 가장 낮은 것을 가져온다.
3. 1-2를 1번더 반복한다.
4. 촉석루 미니어처 중 가격이 가장 낮은 미니어처를 가져온다.
5. 여러개일 경우 품질이 가장 높은 것을 가져온다.
6. 4-5를 1번더 반복한다.

1-3번을 반복했을 때의 결과를 추출하고, 4-6번을 반복한 결과를 추출한다.
첫번째 output으로 1-3번을 반복했을 경우의 상위 2순위를 출력하고,
두번째 output으로 4-6번을 반복했을 경우의 상위 2순위를 출력한다.
정렬 문제기때문에 sort에 조건을 정확하게 달아주는 것이 중요하다.

const filePath = process.platform === "linux" ? "/dev/stdin" : "./input.txt";
const [N, ...input] = require("fs").readFileSync(filePath).toString().trim().split("\n");

const miniatures = input.map((el) => el.split(" ").map(Number));

// 품질을 기준으로 내림차순, 가격 오름차순 정렬
const sortByQualityThenPrice = (a, b) => {
  if (a[0] !== b[0]) return b[0] - a[0];
  return a[1] - b[1];
};

// 가격을 기준으로 오름차순, 품질 내림차순 정렬
const sortByPriceThenQuality = (a, b) => {
  if (a[1] !== b[1]) return a[1] - b[1];
  return b[0] - a[0];
};

// 결과 출력 함수
const printTopTwo = (arr) => {
  console.log(
    arr
      .slice(0, 2)
      .map((item) => item.join(" "))
      .join(" ")
  );
};

// 첫 번째 정렬 및 출력
miniatures.sort(sortByQualityThenPrice);
printTopTwo(miniatures);

// 두 번째 정렬 및 출력
miniatures.sort(sortByPriceThenQuality);
printTopTwo(miniatures);

https://www.acmicpc.net/problem/31474

 

31474번: 양갈래 짝 맞추기

첫째 줄에 양갈래 손님의 수 $N$이 주어진다. ($2 \leq N \leq 28$, $N$은 짝수)

www.acmicpc.net

문제

시현이 카페에 양갈래 손님들이 들어가려고 한다.

양갈래 손님들은 양갈래를 너무 좋아하는 나머지 두 명씩 모여 있어 총 인원은 짝수이다.

시현이 카페에는 두 명씩 앉을 수 있는 테이블이 충분히 많이 있고, 양갈래 손님들은 한 테이블에 두 명씩 앉기로 하였다.

그런 양갈래 손님들을 맞이하게 된 시현이는 갑자기 궁금증이 생겼다.

손님들이 앉는 경우의 수를 세는 조건이 다음과 같을 때, 양갈래 손님들이 모두 앉을 수 있는 방법의 수는 몇 가지가 있을까?

  1. 테이블의 좌석을 구분하지 않는다.
    • 한 테이블에서 손님 A와 손님 B가 앉는 경우, 손님 B와 손님 A가 앉는 경우는 같은 경우이다.
  2. 각각의 테이블 또한 구분하지 않는다.
    • 1번 테이블에 손님 A, B가 앉고, 2번 테이블에 손님 C, D가 앉는 것과, 1번 테이블에 손님 C, D가 앉고, 2번 테이블에 손님 A, B가 앉는 것은 서로 같은 경우이다.

입력

첫째 줄에 양갈래 손님의 수 이 주어진다. (2≤�≤28, 은 짝수)

출력

첫째 줄에 양갈래 손님들이 앉을 수 있는 경우의 수를 출력한다.

연산 중 32비트 정수형 타입의 표현 범위를 초과할 수 있으므로 오버플로우에 주의한다.

예제 입력 1 복사

2

예제 출력 1 복사

1

예제 입력 2 복사

4

예제 출력 2 복사

3

 

문제풀이(1)

const filePath = process.platform === "linux" ? "/dev/stdin" : "./input.txt";
const N = Number(require("fs").readFileSync(filePath).toString().trim());

let result = 1;

for (let i = N; i > 0; i -= 2) {
  result *= (i * (i - 1)) / 2;
  result /= i / 2;
}

console.log(result);

React 19

React 공식 블로그에 24.02.15에 업데이트 진행사항이 올라왔습니다.

주요 내용은 크게 React Compiler, Actions, New Features in React Canary입니다.

React Compiler

공식블로그에 따르면 React를 위한 컴파일러가 개발중이며 오픈 소스 릴리즈를 준비하고 있다고 한다.

React의 상태를 관리할 때 의도와는 달리 많은 리랜더링이 발생하는 경우가 있는데 이것을 해결하기 위해서는 useMemo, useCallback등과 같은 메모이제이션 방식을 통해 리랜더링을 줄이는 방법을 사용했는데 이를 자동화으로 최적화할 수 있도록 되어있다고 한다.

Actions

아래 코드에서 보이는 것과 같이 form에 action이 추가 되었습니다. 

action은 동기, 비동기식으로 모두 동작하며, 데이터를 제출할 때 useFormStatus, useFormState를 사용하여 데이터의 응답 및 에러처리와 같은 작업을 처리하기 쉽도록 도와준다고 합니다.
또, 한가지 추가되는 훅으로는 useOptimistic가 있습니다. useOptimistic은 말그대로 낙관적인 상태를 가져올 때 사용할 수 있다고 되어있습니다. 실제로 완료되는데 시간이 걸리더라도 사용자에게 결과를 즉시 보여줄 수 있도록 도와준다고 합니다.

import { useFormStatus } from "react-dom";
import { submitForm } from "./actions.js";

function Submit() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? "Submitting..." : "Submit"}
    </button>
  );
}

function Form({ action }) {
  return (
    <form action={action}>
      <Submit />
    </form>
  );
}

export default function App() {
  return <Form action={submitForm} />;
}

New Features in React Canary

Directives

"use client"와 "use server"를 사용하여 Next.js와 같이 서버, 클라이언트 어느 부분에서 동작하는지에 대한 지시자를 설정할 수 있습니다.

Document Metadata

 <title>, <meta>, and metadata <link>와 같은 태그를 컴포넌트에 추가할 수 있습니다. 이전에는 React Helmet을 사용하던 작업을 지원합니다.

Asset Loading

Suspense를 <style>, <link>, <script> 태그의 요소들이 표시될 준비가 되어 있는지 판단할 수 있도록 하였으며, 리소스가 언제 로드되고 초기화될지에 대한 제어를 할 수 있도록 preload와 preinit와 같은 새로운 리소스 로딩 API를 추가하였습니다.

Actions

이전에 작성한 Actions 파트의 부분과 같습니다. <form>태그에 action을 추가하고, useFormStatus를 사용하여 상태를 관리하며, useFormState로 결과를 처리합니다. 또한, useOptimistic으로 UI를 낙관적으로 업데이트할 수 있습니다.

 

Conclusion

한동안 취업준비를 하면서 이전에 프론트엔드 개발 공부를 하느라 기술 업데이트 관련 내용을 찾아보는 것을 못했던 것 같았다. 그래서 react 공식 문서와 블로그를 보니 24.2.15 날짜로 새로운 글이 올라온 것을 보았고 유튜브에는 이미 내용들이 정리되어 많이 올라와 있었다. 추후 react 19로 버전업을 하는 경우에 어떤점을 학습해야하는지 어떤점이 좋아지는지에 대해 알고 있다면 프로젝트에 적용하는데 많으 도움이 될 것 같다.

위의 내용들을 봤을 때는 Next.js에서 사용하던 것들을 React에서도 사용할 수 있도록 많은 기능이 추가되는 것 같다. Next.js의 기능 뿐만 아니라 React Helmet, TanstackQuery등과 같은 일부 라이브러리의 기능을 사용할 수 있다면 라이브러리에 대한 의존성은 낮출 수 있을 거란 생각이 들었다. 하지만, 언제나 개발시장은 변화하고 있기 때문에 변화에 맞춰나가는 것이 가장 중요하다고 생각한다.

현재 거의 모든 웹 개발 시장이 React로 되어 있는데 이도 사실은 React에 많은 의존을 하고 있다는게 느껴지니 바닐라JS도 다시 학습해야겠다는 생각이 들었다.

Reference

- React 공식 블로그: https://react.dev/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024

https://www.acmicpc.net/problem/11899

 

11899번: 괄호 끼워넣기

첫 번째 줄에 S를 올바른 괄호열으로 만들기 위해 앞과 뒤에 붙여야 할 괄호의 최소 개수를 출력합니다. 불가능한 경우는 주어지지 않습니다.

www.acmicpc.net

문제

심심한 승현이는 너무 심심한 나머지 올바른 괄호열을 가지고 놀고 있었습니다.

(()(()))()()

그러다가 어쩌다 보니 괄호열을 부러뜨렸습니다.

(()    ((    )))()    ()

크게 낙담한 승현이는 노력해 보았지만, 대부분이 부러져 버려 단 한 부분만 재사용할 수 있다는 것을 깨닫게 되었습니다.

)))()

승현이는 이 괄호열을 가지고 놀려고 했으나 올바른 괄호열이 아니기 때문에 행복하지 않았습니다. 이를 보던 지학이는 승현이에게 “그러면 앞과 뒤에 적절하게 괄호를 붙이면 올바른 괄호열이 되지 않을까?”라고 했고, 승현이는 조금 생각한 뒤 그렇게 하기로 했습니다. 예를 들어, 위의 올바르지 않은 괄호열의 경우 앞에 여는 괄호 3개를 붙이면 올바른 괄호열이 됩니다.

((()))()

그러나 괄호열을 사서 붙이는 데에는 돈이 들고 많이 붙일수록 놀기가 불편해지기 때문에, 승현이는 가능한 한 괄호열을 적게 추가하려고 합니다.

승현이가 망가뜨리고 사용 가능한 올바르지 않은 괄호열이 주어질 때, 올바른 괄호열으로 만들기 위해 앞과 뒤에 붙여야 할 괄호의 최소 개수를 구하는 프로그램을 작성하세요.

입력

첫 번째 줄에 올바르지 않은 괄호열 S가 주어집니다. S의 길이는 1 이상 50 이하입니다.

출력

첫 번째 줄에 S를 올바른 괄호열으로 만들기 위해 앞과 뒤에 붙여야 할 괄호의 최소 개수를 출력합니다. 불가능한 경우는 주어지지 않습니다.

예제 입력 1 복사

)))()

예제 출력 1 복사

3

예제 입력 2 복사

)(()

예제 출력 2 복사

2

문제풀이(1)

처음생각은 단순하게 괄호의 갯수만 파악하여 다른것을 하면되지 않을까란 생각을 했지만
예제 2번을 보고 괄호의 갯수로를 파악할 수 없다는 것을 알게 되었다.
괄호가 맞는지 맞지 않는지 여부를 파악하는 것이 먼저라고 생각했다.

입력받은 괄호를 순회하며 여는 괄호가 나올 경우 leftBracket에 괄호를 추가해주고
닫는 괄호가 나올 경우 leftBracket이 빈 배열이 아니라면 짝이 맞다는 것이기 때문에 pop을 해준다.
만약, leftBracket이 빈 배열이라면 rigthBracket에 괄호를 추가한다.

const filePath = process.platform === "linux" ? "/dev/stdin" : "./input.txt";
const arr = require("fs").readFileSync(filePath).toString().trim().split("");

const leftBracket = [];
const rightBracket = [];

for (let bracket of arr) {
  if (bracket === "(") {
    leftBracket.push(bracket);
  } else if (bracket === ")") {
    if (leftBracket.length > 0) {
      leftBracket.pop();
    } else {
      rightBracket.push(bracket);
    }
  }
}

console.log(leftBracket.length + rightBracket.length);

https://www.acmicpc.net/problem/17254

 

17254번: 키보드 이벤트

첫째 줄에 연결된 키보드의 개수 N과, 키보드를 누르게 될 횟수 M이 주어진다. (1 ≤ N, M ≤ 1,000) 다음 M개의 줄에 정수 a, b와 문자 c가 주어진다. 이는 a번 키보드로, b초에 문자 c가 적힌 키를

www.acmicpc.net

문제

키보드에서 어떤 키를 누르면 어떤 키가 눌러졌는지 컴퓨터에 전송되고, 컴퓨터에서는 키가 눌러진 순서대로 화면에 출력된다.

승지는 팀 과제로 보고서 100장을 작성해야 하는 데, 마감 시간이 얼마 남지 않아서 팀원에게 도움을 요청하였다. 하지만 컴퓨터가 하나뿐이라 보고서를 동시에 작성할 수 없었다.

주변을 둘러보니 쓰지 않는 키보드 여러 개가 바닥에서 굴러다니는 것을 보았다. 급한 대로 컴퓨터에 키보드 여러 개를 연결해서 타자 속도를 올리기로 했다.

문제는 원하는 문장을 작성하기 위해서는 서로 호흡을 잘 맞춰야 하는데, 키보드의 동작 원리를 아직 알지 못했던 승지는 어떤 결과가 출력될지 알 수 없었다.

승지는 팀원 N명에게 N개의 키보드를 나눠주고 전부 컴퓨터에 연결했다. 각자 어느 시간에 어떤 키를 누를지를 미리 정한다면, 어떤 결과가 화면에 출력될 것인지 알아내고자 한다.

키보드의 번호는 컴퓨터에 연결된 순서대로 1번 키보드, 2번 키보드, .... N번 키보드이다. 동시에 여러 키보드에서 키를 누른다면 번호가 작은 키보드에서 누른 키가 먼저 출력되지만, 하나의 키보드에서 여러 개의 키를 동시에 누를 수는 없다. 즉, 하나의 키보드에서 각 키를 누른 시간은 모두 다르다.

입력

첫째 줄에 연결된 키보드의 개수 N과, 키보드를 누르게 될 횟수 M이 주어진다. (1 ≤ N, M ≤ 1,000)

다음 M개의 줄에 정수 a, b와 문자 c가 주어진다. 이는 a번 키보드로, b초에 문자 c가 적힌 키를 누를 것이라는 의미이다. (1 ≤ a ≤ N, 0 ≤ b ≤ 1,000,000)

키보드에는 알파벳 대문자와 숫자키만 존재한다.

출력

모든 입력이 끝난 후에 화면에 출력될 결과를 출력한다.

예제 입력 1 복사

3 5
1 0 A
2 1 P
1 2 L
2 4 E
3 0 P

예제 출력 1 복사

APPLE

예제 입력 2 복사

2 7
1 6 B
1 1 A
1 0 L
1 3 D
2 8 G
2 6 U
2 3 Y

예제 출력 2 복사

LADYBUG

 

문제풀이(1)

a키보드, b초, c알파벳이다. 이때, 같은 시간에 입력했다면 키보드의 번호가 낮은 알파벳을 먼저 출력한다.
sort에 조건을 추가하여 시간을 오름차순으로 정렬하고 같을 경우 키보드를 오름차순으로 정렬한 뒤 알파벳을 출력한다.

const filePath = process.platform === "linux" ? "/dev/stdin" : "./input.txt";
const input = require("fs").readFileSync(filePath).toString().trim().split("\n");

input.shift();

const keyboards = input.map((el) => el.split(" ")).sort((a, b) => +a[1] - +b[1] || +a[0] - +b[0]);

console.log(keyboards.map((el) => el[2]).join(""));

+ Recent posts