반응형

🛠️ Flutter에서 디버그 모드일 때만 기능 실행하기

 

개발 중에만 실행하고 싶은 코드가 있을 때,

릴리즈 빌드에서는 제외되길 원하는 기능이 있을 때,

“디버그 모드에서만 실행되는 코드”**는 정말 유용한 패턴입니다.

 

Flutter는 이런 상황을 위해 간단한 방법을 제공합니다.

바로 kDebugMode를 사용하는 거예요.

 


 

 kDebugMode란?

 

Flutter에서 kDebugMode현재 앱이 디버그 빌드 상태인지 확인하는 전역 상수입니다.

import 'package:flutter/foundation.dart';

if (kDebugMode) {
  print('디버그 모드입니다!');
}

 

  • 디버그 빌드일 때만 true
  • 릴리즈 빌드나 프로파일 모드에서는 false
  • 코드 트리밍으로 인해 릴리즈 빌드에서는 이 조건문 자체가 제거됩니다

 


 

🎯 언제 사용할 수 있을까?

상황예시

개발자 전용 기능 실행 개발 중에만 보이는 버튼/토스트
로그 출력 디버그 중에만 print() 사용
테스트 데이터 주입 가짜 계정으로 자동 로그인
저장소 초기화 SharedPreferences 강제 리셋 등

 

 


 

🧪 실전 예시

 

 

📌 1. 디버그일 때만 실행 횟수 초기화

Future<void> resetLaunchCountIfDebug() async {
  final prefs = await SharedPreferences.getInstance();

  if (kDebugMode) {
    await prefs.setInt('launchCount', 0); // 디버깅 중엔 무조건 0부터 시작
  }

  int count = prefs.getInt('launchCount') ?? 0;
  await prefs.setInt('launchCount', count + 1);

  print('앱 실행 횟수: ${count + 1}');
}

 

 


 

📌 2. 디버그 모드에서만 개발자용 버튼 표시

if (kDebugMode)
  TextButton(
    onPressed: () => print('디버그용 기능 실행'),
    child: Text('개발자 전용 버튼'),
  ),

 

 


 

❗주의할 점

 

  • kDebugModepackage:flutter/foundation.dart에서 제공됩니다.
  • 릴리즈 빌드에서는 조건문 자체가 포함되지 않기 때문에 성능에도 영향 없음
  • kReleaseMode, kProfileMode도 함께 활용 가능해요.

 


 

✨ 마무리

 

Flutter 앱을 개발할 때, 디버깅 전용 기능을 안전하게 분리하고 싶다면

kDebugMode를 적극 활용해보세요!

 

  • 기능 테스트가 깔끔해지고
  • 실수로 릴리즈 빌드에 들어가는 걸 방지할 수 있어요.

 


🧡 이 글이 도움이 됐다면 공감 & 댓글 부탁드립니다 :)

Flutter 실전 팁, 상태관리, 배포 전략 등 계속 업데이트할게요!

반응형
반응형

 

Flutter로 만든 macOS 앱의 기본 아이콘은 Flutter 로고입니다.

이를 커스텀 아이콘으로 바꾸기 위해 가장 확실하게 적용된 방식만 정리합니다.

 

.icns 방식 없이, PNG 파일과 Contents.json 수정만으로 완전히 반영된 방법입니다.

 


 

아이콘 이미지 준비

 

아이콘 이미지는 다음과 같이 정사각형 PNG 파일로 준비합니다.

크기는 Apple에서 요구하는 해상도에 맞춰 리사이징합니다.

파일 이름크기(px)

icon_16x16.png 16×16
icon_16x16@2x.png 32×32
icon_32x32.png 32×32
icon_32x32@2x.png 64×64
icon_128x128.png 128×128
icon_128x128@2x.png 256×256
icon_256x256.png 256×256
icon_256x256@2x.png 512×512
icon_512x512.png 512×512
icon_512x512@2x.png 1024×1024

 

 


 

파일 위치

 

생성한 PNG 파일들을 모두 아래 경로로 복사합니다:

macos/Runner/Assets.xcassets/AppIcon.appiconset/

기존에 있던 Flutter 아이콘 파일들은 덮어쓰거나 삭제해도 됩니다.

 


 

Contents.json 수정

 

같은 폴더에 있는 Contents.json 파일을 아래와 같이 수정합니다:

{
  "images": [
    { "idiom": "mac", "size": "16x16", "scale": "1x", "filename": "icon_16x16.png" },
    { "idiom": "mac", "size": "16x16", "scale": "2x", "filename": "icon_16x16@2x.png" },
    { "idiom": "mac", "size": "32x32", "scale": "1x", "filename": "icon_32x32.png" },
    { "idiom": "mac", "size": "32x32", "scale": "2x", "filename": "icon_32x32@2x.png" },
    { "idiom": "mac", "size": "128x128", "scale": "1x", "filename": "icon_128x128.png" },
    { "idiom": "mac", "size": "128x128", "scale": "2x", "filename": "icon_128x128@2x.png" },
    { "idiom": "mac", "size": "256x256", "scale": "1x", "filename": "icon_256x256.png" },
    { "idiom": "mac", "size": "256x256", "scale": "2x", "filename": "icon_256x256@2x.png" },
    { "idiom": "mac", "size": "512x512", "scale": "1x", "filename": "icon_512x512.png" },
    { "idiom": "mac", "size": "512x512", "scale": "2x", "filename": "icon_512x512@2x.png" }
  ],
  "info": {
    "version": 1,
    "author": "xcode"
  }
}

 

 


 

빌드 및 확인

 

설정이 완료되면 앱을 다시 빌드합니다:

flutter clean
flutter build macos

빌드가 완료된 .app 파일을 실행하거나 Finder, Dock, Spotlight에서 확인했을 때

아이콘이 제대로 보이면 성공입니다.

 


 

마무리

 

Flutter macOS 앱에 아이콘을 적용하는 가장 간단하고 확실한 방법은

.icns 없이 PNG와 Contents.json을 정확히 구성하는 것입니다.

 

이 방식은 Xcode 프로젝트를 수정할 필요도 없고, Flutter 기본 구조 내에서 완전히 동작합니다.

아이콘 적용이 막히셨다면 이 방식으로 시도해보시길 추천드립니다.

반응형
반응형

Flutter로 만든 macOS 앱을 .dmg 형식으로 만들어 사용자가 쉽게 설치할 수 있도록 배포하려면,

create-dmg라는 유틸리티를 사용하면 깔끔한 드래그 앤 드롭 설치 UI를 만들 수 있습니다.

이 글은 그 전체 과정을 정리한 실습 가이드입니다.

 


 

앱 빌드 완료 후 준비

 

먼저 Flutter 앱을 macOS 용으로 빌드합니다:

flutter build macos

빌드가 끝나면 .app 파일이 아래 경로에 생깁니다:

build/macos/Build/Products/Release/YourAppName.app

이제 이 .app.dmg로 포장해 보겠습니다.

 


 

1. 설치에 필요한 폴더 구성

 

.dmg 안에는 앱 외에 /Applications로 연결되는 링크가 필요합니다.

mkdir -p dist
cp -R build/macos/Build/Products/Release/YourAppName.app dist/
ln -s /Applications dist/Applications
dist/ 폴더 안에는 두 개가 있어야 합니다:

 

  • YourAppName.app
  • Applications (심볼릭 링크)

 


 

2. create-dmg 설치 (처음 한 번만)

brew install create-dmg

설치가 완료되면, 어디서든 create-dmg 명령을 사용할 수 있습니다.

 


 

3.  .dmg 파일 생성

create-dmg \
  --volname "YourAppName" \
  --window-size 500 300 \
  --icon-size 100 \
  --icon "YourAppName.app" 125 150 \
  --hide-extension "YourAppName.app" \
  --app-drop-link 375 150 \
  YourAppName.dmg \
  dist/
✅ 가장 중요한 포인트:

 

  • 출력 파일 (YourAppName.dmg) 은 마지막에서 두 번째
  • 소스 폴더 (dist/) 는 가장 마지막 인자

 

예시 결과:

SimpleFitLog.dmg
├── YourAppName.app
├── Applications (→ /Applications)

.dmg를 열면 macOS에서 친숙한 드래그 앤 드롭 설치 UI가 뜹니다.

 


 

4. 자주 발생하는 오류 및 해결

🔸 cd: --: invalid option

 

--output 옵션을 쓰면 안 됩니다.

.dmg 파일 이름은 항상 마지막 두 번째 인자로 넣어야 합니다.

 

잘못된 예:

create-dmg ... --output YourAppName.dmg dist/

올바른 예:

create-dmg ... YourAppName.dmg dist/

 

 


 

5. 추가 팁 (선택 사항)

배경 이미지 넣기

--background "background.png"

 

  • PNG만 지원
  • 이미지 크기는 창 크기보다 크거나 같아야 함

 

아이콘 위치 조정

 

아이콘 좌표는 픽셀 단위로 조정 가능:

--icon "YourAppName.app" 125 150
--app-drop-link 375 150

 

 


 

마무리

 

이제 완성된 .dmg 파일은 다른 사용자에게 배포하거나 웹사이트에 업로드해 사용할 수 있습니다.

앱 실행 시 Gatekeeper 경고가 뜰 경우, 터미널에서 아래 명령어로 실행 제한을 해제할 수 있습니다:

sudo xattr -rd com.apple.quarantine /Applications/YourAppName.app

이 과정을 통해 보다 전문적이고 신뢰감 있는 macOS 앱 배포가 가능해집니다.

 

반응형
반응형

🚀 VSCode에서 Vim 사용 시 키 연속 입력이 안 되는 문제 해결 (macOS 기준)

h, j, k, l 키를 누르면서 방향 이동하는 게 Vim의 핵심인데, VSCode에서 Vim 확장 플러그인을 사용할 때 이 키들이 연속으로 입력되지 않는 문제가 발생할 수 있습니다.

이 글에서는 그 원인과 해결 방법을 자세히 정리해봅니다.


🔍 문제 현상

  • h, j, k, l 키를 길게 눌러도 한 글자만 입력됨
  • Vim 모드에서 커서가 연속으로 움직이지 않음
  • 일반적인 키보드 타이핑은 이상 없음

🧠 원인: macOS의 Press and Hold 기능

macOS는 기본적으로 키를 길게 누르면 "특수 문자 선택 팝업"이 뜨도록 설정돼 있습니다. 이 기능이 Vim에서는 방해가 되죠.
VSCode에서도 이 기능이 활성화되어 있다면, 키를 꾹 눌러도 연속 입력이 되지 않습니다.


✅ 해결 방법

1. VSCode에 한해서 Press and Hold 끄기

터미널을 열고 아래 명령어를 입력하세요:

defaults write com.microsoft.VSCode ApplePressAndHoldEnabled -bool false

그리고 VSCode를 완전히 종료한 후 다시 실행합니다. - 창닫기만 할 시 적용 안됨.
이제 h, j, k, l 키를 꾹 눌렀을 때 연속 입력이 정상 동작합니다!


2. 시스템 전체에 적용하고 싶다면 (선택)

다른 앱에서도 연속 입력이 안 되는 경우, 아래 명령어로 전역 설정을 바꿀 수 있어요:

defaults write -g ApplePressAndHoldEnabled -bool false

※ 주의: 이 설정은 시스템 전체에 영향을 미치므로, 일부 앱에서는 부작용이 생길 수도 있습니다.


3. 키 반복 속도 빠르게 조절하기 (추가 팁)

더 쾌적한 Vim 사용을 위해 키보드 입력 속도도 아래처럼 조절해보세요.

  • 시스템 설정 > 키보드에서
    • 키 반복 속도: 빠르게
    • 반복 시작 지연 시간: 짧게

이렇게 하면 j, k로 커서 이동할 때 더 빠르고 자연스럽습니다.


✨ 마무리

VSCode에서 Vim을 제대로 사용하려면 연속 키 입력 설정은 필수입니다.
이 문제는 macOS에서 자주 발생하는 이슈지만, 간단한 설정으로 쉽게 해결할 수 있어요.

혹시 위 방법으로도 해결되지 않거나 QMK 키보드 설정과 관련된 문제가 있다면 댓글로 남겨주세요! 😄

반응형
반응형

앱에서 고유 ID(Unique ID)를 생성해야 할 때, 가장 널리 쓰이는 방식이 바로 UUID (Universally Unique Identifier)입니다.
Flutter에서는 uuid 패키지를 통해 간편하게 다양한 방식의 UUID를 생성할 수 있습니다.

이 글에서는:

  • uuid 패키지 설치 방법
  • UUID 버전(v1, v4, v5)의 차이점
  • 실전 예제와 사용 팁

까지 하나씩 정리해보겠습니다.


📦 1. uuid 패키지 설치하기

flutter pub add uuid

혹은 pubspec.yaml에 직접 추가:

dependencies:
  uuid: ^4.2.1  # 최신 버전 확인

그리고 import:

import 'package:uuid/uuid.dart';

🧬 2. UUID 버전별 차이점 정리

버전 방식 설명 특징

v1 Time-based 시간 + 기기(MAC 주소) 기반 순차적 생성 가능, MAC 유출 위험
v2 DCE Security v1 + 사용자/그룹 ID 거의 사용되지 않음
v3 Name-based (MD5) 문자열 + 네임스페이스 → MD5 해시 입력이 같으면 UUID도 같음
v4 Random 무작위(random) 값 기반 일반적으로 가장 많이 사용됨
v5 Name-based (SHA-1) v3와 동일하나 SHA-1 해시 사용 더 강력한 보안 해시

⚙️ 3. UUID 생성 예제 (Dart 코드)

final uuid = Uuid();

// ✅ v1 – 시간 + MAC 주소 기반
String idV1 = uuid.v1(); // 예: 'f64c8b26-3e46-11ec-8d3d-0242ac130003'

// ✅ v4 – 랜덤 기반 (가장 많이 사용)
String idV4 = uuid.v4(); // 예: 'c3c3bc94-b070-4787-9f7d-b2592b4d51ef'

// ✅ v5 – 고정된 문자열로 UUID 생성 (SHA-1)
String idV5 = uuid.v5(Uuid.NAMESPACE_URL, 'https://example.com');

📌 v3도 동일 방식으로 가능:

String idV3 = uuid.v3(Uuid.NAMESPACE_URL, 'https://example.com'); // MD5 기반

✅ 실전에서는 언제 어떤 버전?

상황 추천 버전 이유

단순한 고유 ID가 필요할 때 v4 간단하고 충돌 확률 매우 낮음
시간 순서대로 정렬이 필요할 때 v1 생성 시간 정보 포함됨
입력값이 항상 같을 때 동일한 ID가 필요할 때 v5 (또는 v3) 해시 기반 고정 UUID 생성

❗️주의할 점 (v1의 MAC 주소 노출)

  • v1 UUID는 내부적으로 기기의 MAC 주소를 사용합니다.
  • 이 정보는 유출되면 기기 식별 가능성이 있으므로 보안이 중요한 앱에서는 피하는 것이 좋습니다.
  • 대부분의 경우 v4를 사용하는 것이 안전하고 충분합니다.

✨ 실전 사용 예: 모델 ID 생성

class Task {
  final String id;
  final String title;

  Task({required this.title}) : id = const Uuid().v4();
}

→ 매 Task 인스턴스를 생성할 때 자동으로 고유한 UUID를 부여합니다.


🧩 요약

  • uuid 패키지를 설치하면 다양한 방식으로 고유 식별자를 생성할 수 있음
  • 대부분의 경우 v4 (랜덤 기반)가 안정적이고 보편적
  • v5는 동일 입력 → 동일 UUID가 필요할 때 매우 유용
반응형
반응형

부분합이 특정 값을 넘는 가장 짧은 연속된 부분 배열의 길이를 구하는 문제입니다.
단순히 합만 구하는 것이 아니라, 길이까지 고려해야 하기에 효율적인 알고리즘이 필요합니다.
이 글에서는 Brute-force 접근 → 슬라이딩 윈도우 최적화 과정을 거쳐,
Dart로 완성도 높은 코드를 작성하고 이해하는 과정을 함께합니다.


📘 문제 설명

양의 정수 배열 nums와 목표값 target이 주어질 때,
합이 target 이상이 되는 연속된 부분 배열(subarray) 중에서
가장 짧은 길이를 반환하라.
없으면 0을 반환한다.


❌ Brute-force 접근 (비효율)

int minLength = nums.length + 1;

for (int i = 0; i < nums.length; i++) {
  int sum = 0;
  for (int j = i; j < nums.length; j++) {
    sum += nums[j];
    if (sum >= target) {
      minLength = min(minLength, j - i + 1);
      break;
    }
  }
}
  • 시간복잡도: O(n²) → 큰 입력에서는 시간 초과 가능성 있음
  • 매 i마다 전체 탐색 → 비효율적

✅ 최적화: 슬라이딩 윈도우

슬라이딩 윈도우는 합이 기준 이상일 때 길이를 최소화하는 문제에 딱 적합합니다.


💻 Dart 코드

class Solution {
  int minSubArrayLen(int target, List<int> nums) {
    int n = nums.length;
    int left = 0;
    int sum = 0;
    int minLength = n + 1;

    for (int right = 0; right < n; right++) {
      sum += nums[right];

      while (sum >= target) {
        minLength = minLength < (right - left + 1) ? minLength : (right - left + 1);
        sum -= nums[left];
        left++;
      }
    }

    return minLength == n + 1 ? 0 : minLength;
  }
}

 


🧠 코드 풀이

구간 설명

sum += nums[right] 윈도우 오른쪽 확장하며 합 누적
while (sum >= target) 조건 만족 시 길이 갱신 시도
minLength = ... 현재 길이가 기존 최소보다 짧으면 갱신
sum -= nums[left]; left++ 왼쪽을 줄이며 더 짧은 조합 시도
종료 후 리턴 조건 만족한 적 없으면 0, 있으면 최소 길이

🌊 슬라이딩 윈도우 흐름 예시

입력: target = 7, nums = [2,3,1,2,4,3]

step left right sum 조건 만족 윈도우 길이 minLength
1 0 0 2 -
2 0 1 5 -
3 0 2 6 -
4 0 3 8 4 4
5 1 3 6 - 4
6 1 4 10 4 4 → 3
7 2 4 7 3 3 → 2
8 3 4 4 - 2
9 3 5 7 3 유지
10 4 5 3 - 2

⏱ 시간 & 공간 복잡도

항목 복잡도 설명

시간 O(n) 각 포인터가 최대 한 번씩만 이동
공간 O(1) 추가 공간 없음

✨ 실전 팁 요약

  • “조건 만족하는 최소 길이” 문제는 무조건 슬라이딩 윈도우 먼저 떠올릴 것
  • 오른쪽으로 확장하면서 합을 만들고, 왼쪽을 줄여가며 최소 길이 갱신
  • 투 포인터 + 조건 기반 줄이기 전략은 여러 문제에 응용 가능

LeetCode, Dart 알고리즘, 슬라이딩 윈도우, 투 포인터, 부분합 문제

반응형
반응형

물통에 가장 많은 물을 담는 방법은?
LeetCode의 11번 문제 "Container With Most Water"는 그리디한 사고와 투 포인터 알고리즘을 적절히 활용할 수 있는 대표 문제입니다.

이 글에서는 Dart로 문제를 풀이하며,
왜 투 포인터가 최적인지,
어떻게 중복 계산 없이 최대값을 구하는지
명확히 설명합니다.


📘 문제 요약

  • height[i]는 위치 i에서 세운 수직선의 높이입니다.
  • 두 선을 골라, 그 사이를 x축으로 막아 생기는 컨테이너의 물 저장 용량을 구해야 합니다.
  • 조건: 컨테이너는 기울일 수 없습니다.

🚀 핵심 아이디어: 투 포인터 (Two Pointers)

❌ 브루트 포스 접근 (비효율)

int max = 0;
for (int i = 0; i < height.length; i++) {
  for (int j = i + 1; j < height.length; j++) {
    int area = (j - i) * min(height[i], height[j]);
    max = max > area ? max : area;
  }
}
  • 시간 복잡도: O(n²)
  • → 모든 경우를 비교 = 너무 느림!

✅ 최적화: 투 포인터 전략

import 'dart:math';

class Solution {
  int maxArea(List<int> height) {
    int left = 0, right = height.length - 1, answer = 0;

    while (left < right) {
      int width = right - left;
      int water = width * min(height[left], height[right]);
      answer = max(answer, water);

      if (height[left] < height[right]) {
        left++;  // 더 낮은 쪽을 안쪽으로 이동
      } else {
        right--;
      }
    }

    return answer;
  }
}

💡 왜 투 포인터인가?

  • 물의 양 = 너비 × 높이
  • 너비는 왼쪽/오른쪽 포인터 거리
  • 높이는 둘 중 더 낮은 벽이 기준
  • 더 높은 벽은 바꾸지 않고, 낮은 벽을 바꿔야 더 나은 결과 가능

→ 즉, 낮은 쪽을 안쪽으로 이동시키며 최댓값 갱신


🧪 예제 설명

입력: [1,8,6,2,5,4,8,3,7]
→ 최댓값은 49: height[1]=8, height[8]=7, 거리 7 → 7 × 7 = 49


⏱ 복잡도 분석

구분 복잡도

시간 O(n) — 포인터 한 번씩만 움직임
공간 O(1) — 추가 배열 없이 변수만 사용

✨ 실전 팁

  • 이 문제는 투 포인터 개념을 익히기에 아주 좋아요.
  • 리스트의 양 끝에서 출발하여, 내려오면서 갱신하는 방식은 다양한 문제에 응용 가능
  • 슬라이딩 윈도우 / 그리디 문제에서도 자주 등장하는 패턴입니다.

🏁 마무리

"Container With Most Water"는 단순해보이지만,
투 포인터의 본질적인 사고법을 배울 수 있는 문제입니다.
이 문제 하나만 확실히 이해해도, 이후 LeetCode 문제에서 유사 패턴을 빠르게 인식하게 됩니다.

반응형
반응형

📝 요약 설명 

Flutter로 macOS 앱을 개발할 때 기본 창 크기를 설정하고, 앱을 "항상 위에 고정(Always on Top)"으로 띄우는 방법을 소개합니다. 에디터와 앱을 나란히 띄우기 위한 실사용 팁!


🖥️ 문제 상황

Flutter로 macOS 앱을 개발할 때, 앱을 항상 에디터 옆에 띄워놓고 싶었어요.
하지만 앱을 실행하면 매번 이상한 위치에 뜨거나 다른 창 뒤로 가버리는 게 너무 불편했습니다.

그래서 macOS 앱을 항상 위에 고정시키고, 기본 창 크기도 지정하는 방법을 정리해봤습니다.


🔧 해결 방법 요약

  • MainFlutterWindow.swift에서 macOS 앱의 창 속성을 직접 설정할 수 있습니다.
  • 여기서 창 크기, 창 위치, 항상 위에 고정(floating)을 커스터마이징하면 됩니다.

📁 수정할 파일

macos/Runner/MainFlutterWindow.swift


✅ 최종 코드 예시

import Cocoa
import FlutterMacOS

class MainFlutterWindow: NSWindow {
  override func awakeFromNib() {
    let flutterViewController = FlutterViewController()

    // 원하는 창 크기
    let windowWidth: CGFloat = 800
    let windowHeight: CGFloat = 600

    // 화면 중앙에 창 위치 설정
    if let screenFrame = NSScreen.main?.frame {
      let originX = (screenFrame.width - windowWidth) / 2
      let originY = (screenFrame.height - windowHeight) / 2
      let newFrame = NSRect(x: originX, y: originY, width: windowWidth, height: windowHeight)
      self.setFrame(newFrame, display: true)
    }

    // 항상 위에 설정
    self.level = .floating

    self.contentViewController = flutterViewController

    RegisterGeneratedPlugins(registry: flutterViewController)

    super.awakeFromNib()
  }
}

📌 추가 팁

  • self.level = .floating → 이걸 설정하면 앱 창이 항상 다른 창 위에 떠 있어요.
    특히 코드 에디터 + 앱 창 나란히 테스트할 때 엄청 편합니다.
  • self.setFrame(...) → 앱의 기본 위치와 크기를 지정할 수 있습니다.
반응형

✨ 마무리

에디터와 앱을 동시에 띄워놓고 작업할 때, 이 설정 하나로 스트레스가 확 줄었어요.
macOS 앱에서 UI 디버깅 자주 하시는 분들께 꼭 추천드립니다.


필요하면 이미지나 gif 첨부용 예시도 만들어줄 수 있어.
티스토리에 바로 붙여넣을 HTML 포맷도 원해?

반응형
반응형

LeetCode 167: Two Sum II - Input Array Is Sorted

📌 문제 설명

주어진 정렬된 배열에서 두 숫자의 합주어진 target과 일치하는 인덱스를 반환하는 문제입니다.
배열의 인덱스는 1부터 시작하고, 합이 일치하는 두 숫자의 인덱스를 1-based로 반환해야 합니다.


✅ 풀이 아이디어: Two Pointers 알고리즘

이 문제는 Two Pointers 기법을 사용하여 해결할 수 있습니다.

  1. 배열이 정렬되어 있음을 이용해 left 포인터는 배열의 앞에서 시작하고, right 포인터는 배열의 끝에서 시작합니다.
  2. 두 포인터의 합이 target보다 작으면 left를 증가시키고, 합이 크면 right를 감소시킵니다.
  3. 두 포인터가 가리키는 값이 합이 target과 같다면 그 인덱스를 반환합니다.
  4. 이러한 방식으로 한 번 순회하여 문제를 해결할 수 있습니다. 이때 **시간 복잡도는 O(n)**입니다.

✅ 구현 코드 (TypeScript)

export function twoSum(numbers: number[], target: number): number[] {
  let left = 0;
  let right = numbers.length - 1;

  while (left < right) {
    const sum = numbers[left] + numbers[right];

    if (sum === target) {
      return [left + 1, right + 1]; // 1-based index
    }

    if (sum < target) {
      left++; // 합이 target보다 작으면 left 포인터를 증가
    } else {
      right--; // 합이 target보다 크면 right 포인터를 감소
    }
  }

  return [];
}

✅ 테스트 코드 (Jest)

describe("Medium 167: Two Sum II", () => {
  it("should return [1, 2] for [2, 7, 11, 15] and target 9", () => {
    expect(twoSum([2, 7, 11, 15], 9)).toEqual([1, 2]);
  });

  it("should return [1, 2] for [-1, 0] and target -1", () => {
    expect(twoSum([-1, 0], -1)).toEqual([1, 2]);
  });

  it("should return [1, 3] for [2, 3, 4] and target 6", () => {
    expect(twoSum([2, 3, 4], 6)).toEqual([1, 3]);
  });
});

✅ 시간 복잡도

  • 시간 복잡도: O(n)
    • 배열을 한 번만 순회하므로, 시간 복잡도는 배열의 길이에 비례하여 선형입니다.
  • 공간 복잡도: O(1)
    • 추가적인 공간을 사용하지 않으므로 공간 복잡도는 상수입니다.

✅ 마무리

이 문제를 풀면서 중간 난이도 문제도 이제는 한 번에 풀 수 있게 된 것 같다는 자신감이 생겼습니다.
Two Pointers 알고리즘을 통해 정렬된 배열에서 빠르게 답을 찾는 기법을 잘 활용한 문제였습니다. 이제는 이런 유형의 문제를 더 잘 풀 수 있을 것 같아요.

반응형
반응형

🔍 문제 상황

React에서 컴포넌트 리스트를 .map()으로 렌더링할 때, 다음과 같은 에러가 자주 발생합니다:

Warning: Each child in a list should have a unique 'key' prop.

그래서 보통은 아래처럼 key를 추가하죠.

items.map((item) => (
  <MyComponent key={item.id} item={item} />
))

하지만! 정상적으로 key를 넣었음에도 불구하고 위와 같은 경고가 계속 발생하는 경우가 있습니다.


❓ 대체 왜?

이 문제의 진짜 원인은… 바로 Fragment (<>...</>) 때문입니다.


💥 문제 코드 예시

items.map((item) => (
  <>
    <MyComponent key={item.id} item={item} />
  </>
))

위 코드는 MyComponent에 key를 넣었지만,
사실상 무시되고 있습니다.

왜냐하면, React는 .map()으로 직접 리턴하는 최상위 노드에 key가 있는지를 보기 때문입니다.

즉, 위의 코드는:

  • Fragment가 최상위인데 key 없음 → ❌
  • 내부 MyComponent에 있는 key → 무시됨 → ❌ 경고 발생

✅ 해결 방법

방법 1: Fragment 없애기 (권장)

items.map((item) => (
  <MyComponent key={item.id} item={item} />
))

방법 2: Fragment에 key 넣기 (가능하지만 비권장)

items.map((item) => (
  <React.Fragment key={item.id}>
    <MyComponent item={item} />
  </React.Fragment>
))

💡 실전 예: React Native ScrollView 안에서 발생한 사례

<ScrollView>
  {items.map((item) => (
    <>
      <TimelineItem key={item.id} item={item} />
    </>
  ))}
</ScrollView>

이 구조에서 Fragment가 key를 무시하면서 경고가 발생했습니다.
→ Fragment를 제거하거나, key를 Fragment로 올리면 해결됩니다!


🧠 정리

구조 key 인식 여부 추천 여부

<Component key={...} /> ✅ 강력 추천
<><Component key={...} /></> ❌ 경고 발생
<Fragment key={...}><Component /></Fragment> ⚠️ 가능하지만 가독성 저하 우려

✅ 마무리

React에서 key 경고가 사라지지 않을 때는
"내가 넣은 key가 최상위 엘리먼트에 있는가?" 를 꼭 확인하세요!

특히, 아무 생각 없이 쓴 <> ... </> 가 문제의 원인일 수 있습니다 😉

 

 

반응형

+ Recent posts