Qwen-Image-2512-vs-2.1-实测对比
掘金
同题对画:Qwen-Image-2512 与 Qwen-Image-2.1 实测对比 一、先厘清:它们不是"上一代 / 下一代" 很多人第一眼会把 2.1 当成 2512 的升级版。其实 2026 年
Балл: 57.36Уверенность: 54%
ПодробнееЗагружаем каталог…
НАВИГАТОР ПО ВОЗМОЖНОСТЯМ ИИ
Найдите свой ИИ-инструмент. Бесплатный доступ, пробные периоды и кредиты — в одном месте.
掘金
同题对画:Qwen-Image-2512 与 Qwen-Image-2.1 实测对比 一、先厘清:它们不是"上一代 / 下一代" 很多人第一眼会把 2.1 当成 2512 的升级版。其实 2026 年
Балл: 57.36Уверенность: 54%
Подробнее掘金
AI 写着写着跑偏了,怎么办?遇到自己不懂的生疏领域,AI 给出方案,是先花时间恶补知识还是闭眼听它的?调 Bug 时 AI 在同一个报错上反复碰壁“鬼打墙”,怎么解
Балл: 57.36Уверенность: 54%
Подробнее掘金
Claude Haiku 5.5 成本降约 75%。本文用零依赖 Python 路由测算器,把轻活路由给 Haiku、重活给 Sonnet,单日万次调用省约六成,并提醒新分词器隐性涨价。
Балл: 57.36Уверенность: 54%
Подробнееvelog
Call~Girls In Malviya Nagar ☎ 9643097474 Call Girls in Saket ¶¶Service, escorts Service in Home & Hotel in Delhi NCR 24 Hours Available Service Call Girls, Contact Aadi, +91-9643097474 (Any Time. Any Where) Call Girls in Delhi Charges, Shot 1500 Night 6000Call Girls In Delhi South Delhi Female Escort In Delhi Model College Girls In Delhi South Delhi Escorts Service Hauz Khas, Saket, Malviya Nagar, Lajpat Nagar, Munirka, Vasant Kunj, Vasant Vihar, Model Town, Nehru Place, Green Park, Safdarjung, Rk Puram, Khanpur, Rajiv Chowk, Greater Kailash, Kailash Colony, All,Model Escort Agency South Delhi WhatsApp +91- 9643097474 Delhi NCR Call Girls Contact Pooja, +91-9643097474 Call Girls in Delhi NCR in Delhi NCR Escorts in Delhi-NCR Escort Service In Delhi High Class Escort And Call Girls In Delhi Call Contact Aadi BHai, +91-9643097474 in Call South Delhi Malviya Nagar Delhi ,+91-9643097474 // We Provide You Hi Class Independent Call Girls Available In Connaught Place With Five Star Hotels. You Can Reach At Our Place in Delhi our place Which is very Clean Hygienic & 100% safe Accommodation.Get in touch with Me ((Pooja 9643097474-))”Get On the Line by Buzz Phone Call”((Aadi Bhai +91-9643097474 Available 24*7hr Top Grade Service/100% Cooperative All round Service.On the off chance that You Are Looking For Someone Who Can Give You Company In Weekend Of This Rainy Season Then You Can Choose Our Beautiful Connaught Place Central Delhi Escorts As Your Companion. These Girls Choose Escorts Profession Because They Want To Provide Some Relief That An Alone Person Face In Their Life.Sterile Full AC Neat and Clean Rooms Avail
Балл: 54.4Уверенность: 49%
Подробнееvelog
안녕하세요, 미니지식공간입니다. claude-haiku-5-5 가 2026년 10월 7일 공개되면서 Claude Haiku 5.5 가격이 프롬프트 길이에 따라 두 구간으로 쪼개졌다. 단가표만 보면 Haiku 4.5 대비 10분의 1이지만, 실제로 코드를 옮길 때 걸리는 것은 단가가 아니라 토크나이저 교체와 샘플링 파라미터 400 오류 쪽이다. TL;DR claude-haiku-5-5 , 2026-10-07 출시. 프롬프트 100K 토큰 이하 $0.10/$0.50, 초과 $0.50/$2.50 (100만 토큰당 입력/출력). 같은 텍스트가 Haiku 4.5보다 약 30% 많은 토큰 으로 계산된다(공식 변경 문서). 단가 90% 인하와 평균 75% 절감의 차이가 여기서 나온다. temperature · top_p · top_k 에 기본값 아닌 값을 넣으면 400 오류 . budget_tokens 방식 확장 사고도 제거됐다. 1. 모델 표면: 무엇이 늘고 무엇이 막혔나 공식 모델 개요 문서 기준으로 claude-haiku-5-5 의 스펙은 아래와 같다. Bedrock만 ID가 anthropic.claude-haiku-5-5 로 다르고, Claude API·Google Cloud·Microsoft Foundry는 동일한 문자열을 쓴다. 항목 Haiku 5.5 Haiku 4.5 컨텍스트 윈도 1M 토큰 200K 토큰 최대 출력 128K 토큰 64K 토큰 최대 출력(Batch, 베타) 300K 토큰 — 학습 데이터 기준 시점 2026-06 — 사고(thinking) Adaptive 수동(budget_tokens) 기본 노력 수준 medium 미지원 출처: https://platform.claude.com/docs/en/models/haiku-5-5/overview / https://platform.claude.com/docs/en/models/haiku-5-5/whats-new-haiku-5-5 Batch에서 출력 300K를 받으려면 베타 헤더 output-300k-2026-03-24 가 필요하다. 그리고 메시지 중간에 노력 수준을 바꾸는 per-message effort는 베타 헤더 mid-conversation-output-config-2026-07-01 를 쓰며, Haiku 5.5에서는 Claude API와 Google Cloud에서만 쓸 수 있고 Amazon Bedrock에서는 지원되지 않는다. 2. 100K 경계를 토큰으로 계산해보기 현재 Claude 라인업에서 프롬프트 길이로 단가가 갈리는 모델은 Haiku 5.5 하나다. 공식 요금 문서는 "Claude Haiku 5.5는 프롬프트 길이로 가격이 매겨지며, 10만 토큰을 넘는 프롬프트는 더 높은 가격을 낸다"고 적었고, 1M 컨텍스트를 표준 단가로 제공한다는 다른 모델들의 설명에서 Haiku 5.5를 예외로 빼놨다. 항목 (per 1M tokens) ≤100K >100K 배율 입력 $0.10 $0.50 5x 출력 $0.50 $2.50 5x 캐시 읽기 $0.01 $0.05 5x 캐시 쓰기 5m $0.125 $0.625 5x 캐시 쓰기 1h $0.20 $1.00 5x Batch 입력 $0.05 $0.25 5x Batch 출력 $0.25 $1.25 5x 출처: https://platform.claude.com/docs/en/about-claude/pricing 모든 항목이 정확히 5배다. 즉 "초과 구간은 입력 단가가 비싸다" 수준이 아니라, 캐시 읽기까지 포함해 요금 체계 전체가 한 번에 5배로 올라간다. 캐시 읽기가 $0.01에서 $0.05가 되는 구간이 특히 민감한데, 긴 시스템 프롬프트를 캐싱해 재사용하는 구조는 캐시 읽기 비중이 커서 경계를 넘는 순간의 체감 변화가 입력 단가보다 클 수 있다. Haiku 4.5와 직접 비교하면 입력 $1.00 → $0.10(-90%), 출력 $5.00 → $0.50(-90%), 캐시 읽기 $0.10 → $0.01(-90%)이다. 초과 구간과 비교하면 전부 -50%다. Anthropic은 Haiku 4.5 요청의 약 90%가 100K 이하였다고 밝히면서 평균 절감률을 약 75%로 제시했는데, 90%와 75%의 차이를 만드는 나머지 요인이 다음 절의 토크나이저다. 3. 토크나이저 30%: 가장 조용한 파괴적 변경 공식 변경 문서의 "Changed" 항목에 이런 내용이 있다. 같은 텍스트가 Haiku 4.5보다 약 30% 많은 토큰 으로 계산되며, 이는 Claude 4.7 이후 모델과 같은 새 토크나이저를 쓰기 때문이다. 발표 페이지 각주는 평균 75% 절감 수치에 이 증가분이 반영돼 있다고 설명했다. 이 변경이 조용한 이유는 오류를 내지 않기 때문이다. 코드는 그대로 돌고 응답도 정상인데, 같은 입력의 토큰 수가 늘어 두 가지가 동시에 틀어진다. 첫째, Haiku 4.5 시절에 측정한 "평균 8만 토큰" 같은 통계가 Haiku 5.5에서는 10만을 넘길 수 있어 경계를 넘는 요청 비율을 과소평가한다. 둘째, max_tokens 나 컨텍스트 여유분을 토큰 수로 하드코딩한 로직이 예상보다 빨리 한계에 닿는다. 실무 순서로는 단가 비교보다 토큰 재측정이 먼저다. 프로덕션 프롬프트 샘플을 Haiku 5.5로 한 번 호출해 usage 응답의 입력 토큰 수를 Haiku 4.5 값과 나란히 놓고, 분포 상위 10%가 100K 경계 어느 쪽에 있는지부터 확인하는 편이 안전하다. 4. 최소 호출과 effort 설정 아래는 Claude 공식 effort 문서의 "Set the effort level" 예제다. 문서 예제는 claude-opus-5-5 를 쓰고 있어 모델 문자열만 claude-haiku-5-5 로 바꿨고, 나머지 구조·파라미터명은 문서 원문 그대로다. import anthropic client = anthropic.Anthropic() response = client.messages.create( model="claude-haiku-5-5", max_tokens=4096, messages=[ { "role": "user", "content": "Analyze the trade-offs between microservices and monolithic architectures", } ], output_config={"effort": "medium"}, ) for block in response.content: if block.type == "text": print(block.text) 출처: https://platform.claude.com/docs/en/build-with-claude/effort 파라미터는 요청 최상위의 output_config.effort 이고, 베타 헤더 없이 지원 모델 전체에서 쓸 수 있다. Haiku 5.5는 다섯 단계를 모두 지원한다. 문서에 명시된 값과 제약만 옮기면 다음과 같다. # 공식 문서에 명시된 값만 전사 (문서에 없는 조합은 미기재) model claude-haiku-5-5 # Bedrock: anthropic.claude-haiku-5-5 output_config.effort low | medium | high | xhigh | max Haiku 5.5 기본값: medium (Opus 5.5도 medium, 그 외 모델은 high) "adaptive"는 effort 값이 아님 — thinking 모드이므로 전달 금지 thinking.type "disabled" 허용: effort high 이하 effort xhigh / max 에서 "disabled" → 400 오류 thinking.display 기본값에서 사고 텍스트 미출력 → "summarized" 설정 시 요약 출력 temperature 기본값 외 지정 → 400 오류 top_p 기본값 외 지정 → 400 오류 top_k 기본값 외 지정 → 400 오류 betas output-300k-2026-03-24 # Batch 출력 300K mid-conversation-output-config-2026-07-01 # 메시지 단위 effort (Bedrock 미지원) 출처: https://platform.claude.com/docs/en/build-with-claude/effort / https://platform.claude.com/docs/en/models/haiku-5-5/overview 문서가 권하는 출발점은 medium 이다. low 가 가장 싸고 빠른 단계이며, 지식 업무·긴 에이전트 작업·엄격한 지시 준수에는 high 를, xhigh 와 max 는 자체 평가에서 개선이 확인된 경우에만 쓰라고 적혀 있다. 5. 파괴적 변경 — 코드를 반드시 고쳐야 하는 것들 공식 변경 문서의 "Breaking changes" 항목을 그대로 정리했다. 전부 Haiku 4.5에서 올라올 때 적용된다. 기존 (Haiku 4.5) Haiku 5.5 대응 thinking.budget_tokens 로 수동 확장 사고 오류 적응형 사고로 전환, budget_tokens 제거 temperature / top_p / top_k 임의 지정 400 오류 세 파라미터 모두 전달하지 않음 어시스턴트 메시지 프리필 오류 messages 가 사용자 턴으로 끝나도록 수정 computer_20250124 컴퓨터 사용 도구 미지원 computer_toolset_20260801 로 교체(Claude API·Google Cloud) 이전 턴 수정 후 재호출 thinking 블록 무효화 대화를 append-only로 유지 출처: https://platform.claude.com/docs/en/models/haiku-5-5/whats-new-haiku-5-5 오류를 내지 않지만 동작이 달라지는 "Changed" 항목도 함께 봐야 한다. 응답이 thinking 블록으로 시작할 수 있어 content[0] 를 텍스트로 가정한 파싱이 깨지며, 문서는 블록을 위치가 아닌 type 으로 고르라고 적었다. 또 안전 분류기가 요청을 거절할 수 있어 stop_reason: "refusal" 처리 경로가 필요하고, 이 경우 서버 측 폴백은 제공되지 않는다고 명시럼 있다. thinking 블록은 이를 생성한 계정 또는 연결된 계정에서만 재생된다. 6. 벤치마크를 읽을 때 주의할 두 지점 Anthropic이 발표한 수치는 전부 자사 측정이며, VentureBeat와 MarkTechPost 모두 "벤더 보고 수치, 독립 검증 아님"이라고 명시했다. 주요 값은 OSWorld 2.1(오프라인) 72.4%(Haiku 4.5 15.7%, GPT-6 Luna 48.9%, Sonnet 5.5 83.9%), Terminal-Bench 4.0 39.2%(0.0% / 16.4% / 70.6%), FrontierCode 1.1 Main 46.4%(Luna 42.4%), GDPval-AA v2.1 1620(735 / 1437 / 1840), Humanity's Last Exam 도구 없음 45.9% · 도구 사용 57.4%다. 첫 번째 주의점은 노력 수준이 표에 붙어 있지 않다는 것 이다. VentureBeat는 Anthropic 차트상 Terminal-Bench 39.2%가 최대(max) 노력 수준 값이고 medium 에서는 약 20% 수준이라고 보도했는데, MarkTechPost는 같은 39.2%를 노력 수준 표기 없이 실었다. 기본값이 medium 인 모델에서 이 차이는 비용 추정을 두 배 이상 틀리게 만들 수 있다. 두 보도가 갈리므로 확인 필요로 둔다. 두 번째는 경쟁 모델과의 경계 위치가 다르다는 것 이다. GPT-6 Luna의 짧은 컨텍스트 단가는 $0.10/$0.50으로 Haiku 5.5의 ≤100K 구간과 같지만, Luna의 상위 구간은 272K 토큰부터 시작한다는 점이 두 매체에서 공통으로 언급됐다. 그러면 100K~272K 구간에서는 Luna 쪽이 유리해진다. 다만 Luna 상위 구간의 정확한 단가($0.20/$0.75)는 MarkTechPost 단일 출처라 확인 필요다. 7. 전환 체크리스트 프로덕션 프롬프트 샘플로 Haiku 5.5를 호출해 usage 입력 토큰을 재측정한다. Haiku 4.5 기준 토큰 통계를 그대로 쓰지 않는다. 측정된 토큰 분포에서 100K 경계를 넘는 요청 비율 을 구한다. 평균이 아니라 상위 10% 꼬리를 본다. temperature / top_p / top_k 를 코드베이스에서 전부 찾아 제거한다(400 오류). thinking.budget_tokens 를 제거하고 적응형 사고로 전환한다. thinking.type: "disabled" 를 쓰면 effort를 high 이하로 유지한다. 응답 파싱을 content[0] 위치 기반에서 block.type 기반으로 바꾸고, stop_reason: "refusal" 분기를 추가한다. 컴퓨터 사용 도구를 computer_toolset_20260801 로 교체한다. Bedrock에서는 per-message effort를 쓰지 않는다. effort 값을 medium 부터 스윕한다. Haiku 4.5에는 effort가 없었으므로 이원할 설정 자체가 없고, 벤치마크 수치는 노력 수준 표기가 없으니 자체 평가로 재측정한다. 자주 묻는 질문 Q. claude-haiku-5-5는 무료로 쓸 수 있나? API는 유료다. 다만 발표와 함께 구독에 월 API 크레딧이 추가됐다. Max 5x는 월 $100, Max 20x는 월 $200, Team은 사용자 전체가 공유하는 월 최대 $500이며 Claude Platform의 모든 모델에 쓸 수 있다. 발표 시점 안내는 "이번 주 중 순차 적용"이었다. Q. 기존 API 코드 수정이 필요한가? 필요하다. temperature · top_p · top_k 중 하나라도 기본값이 아니면 400 오류가 나고, budget_tokens 방식 확장 사고와 어시스턴트 프리필도 오류가 난다. 오류 없이 동작만 바뀌는 항목(응답 첫 블록이 thinking , 토큰 약 30% 증가)도 있어 파싱과 토큰 계산을 함께 확인해야 한다. Q. Sonnet 5.5를 쓰고 있으면 이번 발표와 무관한가? 무관하지 않다. 같은 날 Sonnet 5.5 캐시 읽기 단가가 100만 토큰당 $0.20에서 $0.10으로 내려갔고, Anthropic은 대부분의 에이전트 작업에서 Sonnet 5.5 비용이 약 20% 줄어든다고 설명했다. VentureBeat는 일부 Azure·Google Cloud 고객에게는 며칠에 걸쳐 적용된다고 전했다. 마무리 단가 90% 인하라는 헤드라인보다, 실제로 작업을 만드는 쪽은 토크나이저 30%와 샘플링 파라미터 400 오류입니다. 두 항목 모두 단가표에는 나오지 않고 공식 변경 문서에만 적혀 있어서, 전환 전에 그 문서를 한 번 통독하시는 편을 권합니다. 지난달 Sonnet 5.5의 파괴적 변경을 정리한 글( claude-sonnet-5-5 전환 가이드 )과 겹치는 항목이 있어 함께 보시면 작업량을 줄일 수 있습니다. 출처 Anthropic, "Introducing Claude Haiku 5.5" (2026-10-07) — https://www.anthropic.com/claude-haiku-5-5 Claude 공식 모델 문서 (Haiku 5.5 개요) — https://platform.claude.com/docs/en/models/haiku-5-5/overview Claude 공식 변경 문서 (What's new in Claude Haiku 5.5) — https://platform.claude.com/docs/en/models/haiku-5-5/whats-new-haiku-5-5 Claude 공식 effort 문서 — https://platform.claude.com/docs/en/build-with-claude/effort Claude 공식 요금 문서 — https://platform.claude.com/docs/en/about-claude/pricing VentureBeat (2026-10-07) — https://venturebeat.com/technology/anthropic-launches-claude-haiku-5-5-with-90-api-price-reduction-matching-gpt-6-luna MarkTechPost (2026-10-07) — https://www.marktechpost.com/2026/10/07/anthropic-releases-claude-haiku-5-5-a-small-model-with-1m-context-priced-at-0-10-per-million-input-tokens/ 본 글은 공개 자료를 바탕으로 정리했으며, 세부 내용·수치는 원 출처·공식 문서와 대조 확인을 권장합니다. 성능·속도·비용 수치는 Anthropic 자사 발표이며 독립 검증 결과가 아닙니다.
velog
想看日本AV、成人视频、a片?别再到处乱搜了! 本周热搜“日本AV”“成人视频”“a片”排名靠前的朋友注意!91porna.com 精选最新日本AV、 欧美成人视频、黄片、h动漫,一键免费高清观看,更新速度爆表! 直接进入看片: https://91porna.com 永久主域名,安全稳定,资源全网最全! 想看日本AV、成人视频、a片?别再到处乱搜了! 本周热搜“日本AV”“成人视频”“a片”排名靠前的朋友注意!91porna.com 精选最新日本AV、欧美成人视频、黄片、h动漫,一键免费高清观看,更新速度爆表! 直接进入看片: https://91porn.com 永久中转域名,安全稳定,资源全网最全!更新速度爆表
Балл: 54.4Уверенность: 49%
Подробнееvelog
1. 슈도코드 배열 array 를 순회하며 map에 숫자(key)별 등장 횟수(value) 를 저장한다. map을 순회하며 등장 횟수가 가장 큰 숫자 를 찾는다. 최빈값이 여러 개면 -1 을, 하나면 그 숫자를 반환한다. 2. 실제코드 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> #include <map> int solution(int array[], size_t array_len) { int answer = 0; map<int, int> countMap; for(int n : array) { countMap[n]++; } int maxCount = -1; int value = -1; bool isMultiValue = false; for(auto& iter : countMap) { if(iter.second > maxCount) { maxCount = iter.second; value = iter.first; isMultiValue = false; } else if(iter.second == maxCount) { isMultiValue = true; } } return isMultiValue ? -1 : value; } 전처리 지시문 #include <map> map 을 사용하기 위한 전처리 지시문이다. 숫자별 등장 횟수 세기 for (int n : array) { countMap[n]++; } array 의 원소를 하나씩 꺼내 n 에 담고, countMap[n] 의 값을 1 증가시킨다. countMap[n] 은 n을 key로 하는 value 에 접근한다. 처음 등장하는 key라면 value가 0으로 자동 생성 된 뒤 ++ 로 1이 된다. 이미 있는 key라면 기존 value에 1이 더해진다. 예시: {1, 2, 3, 3, 3, 4, 5} 꺼낸 값 (n) 동작 countMap 상태 1 key 1 생성 → 1 {1:1} 2 key 2 생성 → 1 {1:1, 2:1} 3 key 3 생성 → 1 {1:1, 2:1, 3:1} 3 key 3 증가 → 2 {1:1, 2:1, 3:2} 3 key 3 증가 → 3 {1:1, 2:1, 3:3} 4 key 4 생성 → 1 {1:1, 2:1, 3:3, 4:1} 5 key 5 생성 → 1 {1:1, 2:1, 3:3, 4:1, 5:1} 최종적으로 countMap 에는 숫자(key)와 그 숫자의 등장 횟수(value) 가 저장된다. 같은 key는 하나만 존재하고, 값이 다시 나올 때마다 value만 증가 한다는 점이 핵심이다. 최빈값 뽑기 int maxCount = -1; // 지금까지 본 가장 큰 등장 횟수 int value = -1; // 그 횟수를 가진 숫자 (최빈값 후보) bool isMultiValue = false; // 최빈값이 여러 개인지 여부 for (auto& iter : countMap) { if (iter.second > maxCount) { maxCount = iter.second; value = iter.first; isMultiValue = false; } else if (iter.second == maxCount) { isMultiValue = true; } } return isMultiValue ? -1 : value; countMap 을 처음부터 끝까지 돌면서 등장 횟수가 가장 큰 숫자 를 찾는다. iter.first : key (숫자) iter.second : value (등장 횟수) auto& : map의 원소를 복사하지 않고 참조로 가져온다. 조건별 동작 더 큰 횟수를 찾은 경우 ( iter.second > maxCount ) 새로운 최빈값이므로 maxCount 와 value 를 갱신한다. 이전에 동률이 있었더라도 더 큰 값이 나왔으니 isMultiValue 를 false 로 되돌린다. 같은 횟수를 찾은 경우 ( iter.second == maxCount ) 최빈값이 여러 개라는 뜻이므로 isMultiValue 를 true 로 바꾼다. maxCount 를 -1 로 시작하는 이유는 첫 번째 원소가 무조건 첫 조건에 걸려 최빈값 후보로 들어가게 하기 위해서다. 예시: {1, 2, 3, 3, 3, 4, 5} → countMap {1:1, 2:1, 3:3, 4:1, 5:1} iter 비교 maxCount value isMultiValue (1, 1) 1 > -1 → 갱신 1 1 false (2, 1) 1 == 1 → 동률 1 1 true (3, 3) 3 > 1 → 갱신 3 3 false (4, 1) 1 < 3 → 무시 3 3 false (5, 1) 1 < 3 → 무시 3 3 false 결과는 isMultiValue 가 false 이므로 3 을 반환한다. 최빈값이 여러 개인 경우: {1, 1, 2, 2} → countMap {1:2, 2:2} iter 비교 maxCount value isMultiValue (1, 2) 2 > -1 → 갱신 2 1 false (2, 2) 2 == 2 → 동률 2 1 true isMultiValue 가 true 이므로 -1 을 반환한다. 반환 return isMultiValue ? -1 : value; 삼항 연산자로, isMultiValue 가 true 면 -1 , 아니면 value 를 반환한다. 3. 중요 포인트 map으로 등장 횟수 세기 숫자별 개수를 셀 때는 map<int, int> 를 사용하면 편하다. (key: 숫자, value: 등장 횟수) countMap[n]++ 한 줄로 개수를 셀 수 있다. 처음 나온 key는 value가 0으로 자동 생성되기 때문에 따로 초기화할 필요가 없다. 반복문 한 번으로 최빈값 찾기 map을 순회하면서 지금까지의 최대 횟수( maxCount ) 와 비교해 최빈값을 갱신한다. 최빈값이 여러 개인지 확인하기 위해 isMultiValue 같은 bool 변수(플래그) 를 함께 사용한다. 같은 횟수가 나오면 true 더 큰 횟수가 나오면 다시 false 로 초기화 (이전 동률은 더 이상 최빈값이 아니므로) maxCount 를 -1 로 시작해야 첫 번째 원소가 반드시 최빈값 후보로 들어간다.
velog
If you’ve ever used Outlook, you have an idea what an OST file is. It’s an offline version of your mailbox that allows you to use your emails, contacts, and calendars without any internet access. But what do you do when you move to Microsoft 365 and want that OST data in your new cloud mailbox? Copying files manually will not work, as OST files are linked to an Exchange profile and you cannot open them in a different environment. That is when a professional OST to Office 365 conversion tool becomes essencial. In this article, we will understand why direct import of OST to Office 365 is not possible, and how can you effectively and efficiently import single or multiple OST files into Microsoft 365 without losing your crucial data. Why Can't You Import OST Files to Office 365 Directly? An OST file is not a regular data file. It is what Outlook creates and syncs with an Exchange or Microsoft 365 account, so that means it is tied to that one particular mail profile. If the original account is deleted, corrupted, or never accessible, that OST file is become orphaned, and unusable by Outlook. So when you are moving to Microsoft 365, you cannot just drop that OST file into the new mailbox. It needs to be converted into something that Microsoft 365 will be able to read and accept, and then it needs to be uploaded in the right manner. What You Need Before Starting the Migration A valid user account with administrative privilege in Microsoft 365 or Microsoft Exchange. Permission or Access to the OST file that need to be migrated. A trusted automated tool for converting OST files to Office 365. A good Internet connection for the process. Uploading data through a bad connection will only waste your time. How to Import a Single OST File to Microsoft 365 For users dealing with just one mailbox, the process is fairly straightforward when using the right software. Step 1: Download the Migration Tool Download and install reliable MailsDaddy OST to Office 365 Migration Tool. This utility is designed to open orphaned OST files, without the need for a live Outlook profile. Step 2: Start the tool and navigate to select the OST file that you want to migrate. The program will scan and load all the mailbox items such as emails, contacts, calendars, and attachments. Step 3: Most trusted solutions MailsDaddy being one of them allows you to preview folders and emails before migration. It is for data validation before go ahead. Continue with migrate button. Now select your options accordingly (i.e Choose the mailbox type: Primary, In-place Archive, Shared mailbox and Group mailbox based on your need, Date filter, Enable/Disable incremental migration etc. also select the path for log file) Step 4: Enter your Microsoft 365 credentials to log in and connect to your cloud mailbox. Step 5: Press on the Start Migration button. The tool logs in to your Microsoft 365 mailbox and imports the OST data to it, without modifying any formatting or folder structure. How to Import Multiple OST Files to Microsoft 365 For businesses handling employee mailbox migrations or IT admins managing bulk data transfer, migrating multiple OST files at once saves significant time and effort. The MailsDaddy OST to Office 365 Migration Tool comes with a bulk migration mode designed exactly for this situation. Open the tool and Click on Bulk-Migration option Step 1: Instead of adding OST files one by one, you can import a CSV file containing the list of OST files along with their destination mailbox mapping. This is especially useful for organizations migrating dozens or hundreds of employee mailboxes. Step 2: Assign each OST file to its correct Microsoft 365 mailbox to ensure data lands in the right place. Step 3: Start the process, and the tool will handle multiple OST files simultaneously, converting and uploading them without manual repetition. Step 4: After completion, check the migration summary report to confirm all mailboxes were transferred successfully. This tool also includes the normal Filtered Migration feature, so you can search for emails by "sender", "subject", "date", etc and only migrate those specific items that you want; Useful when you don't want to transfer a whole mailbox. Additionally, A Migration History tab will retain every migration job. Displaying total items, migrated items successfully, and the skipped or failed ones, with a one-click Retry alternative for anything not performed. Why Choose an Automated Migration Tool Over Manual Methods? Maintain the original formatting of emails, contacts, calendars, and attachments without losing any data. Even if the original Exchange account has been removed, it can still work with orphaned OST files. For IT teams, bulk migration is a major benefit because it saves a lot of time. The interface is straightforward and easy to use, requiring no technical knowledge. Maintains folder hierarchy by ensuring that data always lands in the appropriate mailbox. Manual methods are laborious, dangerous, and frequently result in corrupted or partial transfers. On the other hand, an automated approach is simple to use and has a good successful migration ratio with no data loss. Conclusion Converting OST to Microsoft 365 doesn't have to be complicated. Whether you’re an individual user upgrading a single mailbox or an IT admin migrating a whole company- wide migration, a reliable tool makes all the difference. MailsDaddy OST to Office 365 Migration Tool provides an easy, safe and efficient way to transfer single and multiple OST file to Office 365 without any kind of data loss. If your Microsoft 365 migration is coming up soon, this tool can save you both time and hassle.
velog
1. 오늘 학습 키워드 Docker Docker Image Docker Container Dockerfile Docker Network Bridge Network Docker Compose Port Mapping 컨테이너 간 통신 OpenFeign과 Docker 네트워크 2. 오늘 학습 한 내용을 나만의 언어로 정리하기 오늘은 Docker와 Docker Compose의 기본 개념을 학습했다. Docker에서 먼저 이해해야 할 것은 이미지와 컨테이너의 차이였다. 이미지는 애플리케이션을 실행하기 위한 환경과 파일들을 미리 담아둔 템플릿이고, 컨테이너는 그 이미지를 실제로 실행한 상태라고 이해했다. 예를 들어 다음 명령어는 현재 디렉토리의 Dockerfile을 기준으로 img-service-a 라는 이미지를 생성한다. docker build -t img-service-a . 반면, docker run ... 은 이미 만들어진 이미지를 이용해서 새로운 컨테이너를 생성하고 실행한다. 정리하면 다음과 같다. docker build → 이미지 생성 docker run → 이미지로 컨테이너 생성 및 실행 docker start → 이미 존재하는 컨테이너 다시 실행 Dockerfile Spring Boot 애플리케이션을 Docker 이미지로 만들기 위해 Dockerfile을 사용했다. FROM eclipse-temurin:17-jre VOLUME /tmp ARG JAR_FILE=build/libs/*.jar COPY ${JAR_FILE} app.jar ENTRYPOINT ["java", "-jar", "/app.jar"] 먼저 Gradle의 bootJar 를 통해 jar 파일을 만들고, Dockerfile에서는 그 jar 파일을 이미지 안에 복사한다. 이후 컨테이너를 실행하면 java -jar /app.jar 명령이 실행되면서 Spring Boot 애플리케이션이 시작된다. 전체 흐름은 아래처럼 이해했다. Spring Boot 코드 ↓ Gradle bootJar ↓ jar 파일 생성 ↓ docker build ↓ Docker Image 생성 ↓ docker run ↓ Container 실행 Docker Network 오늘 가장 헷갈렸던 부분 중 하나는 컨테이너 간 네트워크였다. 컨테이너는 각각 독립된 네트워크 공간을 가지고 있다. 따라서 service-a 컨테이너 안에서 localhost 를 사용하면 내 Windows PC나 service-b 가 아니라 service-a 컨테이너 자기 자신을 의미한다. 그래서 A 서비스가 B 서비스를 호출하려면 두 컨테이너가 같은 Docker 네트워크에 연결되어 있어야 한다. docker network create my-network 처럼 사용자 정의 bridge 네트워크를 만든 뒤 A와 B를 같은 네트워크에 연결하면 이름으로 서로 통신할 수 있다. 예를 들어 A가 B를 호출한다면 service-a ↓ http://service-b:8080 ↓ service-b 와 같은 형태가 된다. Docker가 내부 DNS를 통해 service-b 라는 이름을 실제 B 컨테이너의 IP로 찾아주기 때문에, 컨테이너의 IP가 바뀌더라도 직접 IP를 수정하지 않아도 된다. Port Mapping 실습에서는 아래와 같은 명령어도 사용했다. docker run -d --name service-b --network my-network -p 18081:8080 img-service-b 각 옵션의 의미는 다음과 같다. -d → 컨테이너를 백그라운드에서 실행 --name service-b → 컨테이너 이름을 service-b 로 지정 --network my-network → my-network 에 연결 -p 18081:8080 → 호스트의 18081 포트를 컨테이너의 8080 포트에 연결 img-service-b → 실행할 이미지 따라서 내 PC에서는 localhost:18081 로 접근하지만 실제 Spring Boot 애플리케이션은 컨테이너 내부의 8080 포트에서 실행되고 있다. Docker Compose 컨테이너가 하나라면 docker run 으로 직접 실행해도 되지만, 컨테이너가 여러 개가 되면 각각 포트, 네트워크, 환경변수를 관리하기가 점점 불편해진다. Docker Compose를 사용하면 여러 컨테이너의 설정을 하나의 파일에서 관리할 수 있다. 또한 Docker Compose를 실행하면 프로젝트 전용 사용자 정의 bridge 네트워크를 자동으로 만들어준다. 프로젝트명_default ├── service-a └── service-b 그래서 Compose로 실행한 컨테이너끼리는 별도로 IP를 지정하지 않아도 서비스 이름을 이용해서 통신할 수 있다. 예를 들어 A에서 B를 호출한다면 http://service-b:8080 처럼 사용할 수 있다. 그리고 docker compose down 을 실행하면 Compose가 만든 컨테이너와 네트워크도 함께 정리된다. 반면 docker network create my-network 처럼 내가 직접 만든 네트워크는 특정 Compose 프로젝트에 종속된 것이 아니기 때문에 컨테이너를 내리더라도 그대로 남아 있다. 4. 내일 학습 할 것은 무엇인지 오늘은 Docker와 Docker Compose를 이용해서 애플리케이션을 이미지로 만들고 여러 컨테이너를 실행하는 기본 흐름까지 학습했다. 다음 학습에서는 CI/CD 내용을 시작할 예정이다. 오늘 직접 해본 코드 → jar 빌드 → Docker Image 생성 → Container 실행 과정을 CI/CD에서는 어떻게 자동화하는지 집중해서 학습해 보고 싶다. 직접 명령어를 입력해서 빌드하고 이미지를 만드는 과정이 자동화되면 실제 배포 흐름이 어떻게 달라지는지 연결해서 이해해 볼 예정이다.
velog
2026년 10월 8일 휴일을 위해 달려가기 위해서 VOD 강의와 숫자 야구 게임 과제를 하는중이다. VOD 강의에서 본 것 속성 복제를 더 깊게 본다. RPC와 속성 복제 모두 “얼마나 자주 네트워크를 쓰는지”에 이 값이 걸린다. NetUpdateFrequency Actor 복제 빈도의 최대치 1초에 복제를 몇 번 시도 할지 기본값 100 → 이론상 서버는 0.01초마다 한 번 시도 최대치일 뿐, 그 간격을 보장하지 않는다. 실제 복제는 서버 Tick Rate와 서버 성능에 맞춰 줄어든다 서버가 100번을 못 돌리면, 그 성능만큼만 복제된다 그래픽을 그리지 않는 데디케이티드 서버가 같은 머신에서 더 자주 복제할 수 있다 기본값 움직이는 폰·컨트롤러는 자주, 킬 수·남은 시간처럼 천천히 바뀌는 상태는 드물게 보낸다. 부하를 줄이는 두 방법 서버 위치와 클라이언트 위치가 맞아야 할 때. 매 틱 위치 복제. 정확하지만 패킷이 많다. 처음 위치와 속도만 보내고, 클라이언트가 예측. 이번 실습은 이쪽. 빈도를 낮춘 뒤, 받은 값 사이를 보간 해서 뚝뚝 끊기지 않게 한다. DXBox 에서 하는 일: 생성자에서 NetUpdateFrequency 를 낮춘다 서버 위치·속도를 복제한다 클라이언트는 마지막 수신 위치에서 속도만큼 진행하며 사이를 보간한다 노션 코드 토글은 펼쳐지지 않아 DXBox.h / DXBox.cpp 원문은 여기 넣지 않았다. 앞 강의와 연결 100으로 둔다고 100번 도착하는 것이 아니다. 자주 안 바뀌는 Actor는 값을 내리고, 움직임은 예측·보간으로 메운다. 오늘 정리 100은 상한이다. 서버가 느리면 그보다 적다 Pawn 100, GameState 10, PlayerState 1이 기본 감각이다 위치를 매 틱 보내지 말고, 위치+속도로 클라이언트가 이어 가게 한다
Балл: 54.4Уверенность: 49%
Подробнееvelog
1부에서 키, 관계, 정규화, 인덱스, 트랜잭션 같은 개념을 정리했다. 이번 글에서는 그 개념을 스프링 부트와 JPA 코드로 옮긴다. 예시는 1부와 같다. 유저, 식사, 배변, 복용 기록이다. JPA는 자바 클래스와 DB 테이블을 연결해주는 기술이다. 클래스 하나가 테이블 하나, 객체 하나가 행 하나, 필드 하나가 컬럼 하나가 된다. 1. 엔티티 — 테이블을 클래스로 @Entity @Getter @Table(name = "users") @NoArgsConstructor(access = AccessLevel.PROTECTED) public class User extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, unique = true, length = 20) private String username; @Column(nullable = false) private String password; @Enumerated(EnumType.STRING) private IbdType ibdType; } 애너테이션 개념 하는 일 @Entity 테이블 이 클래스를 테이블로 관리한다 @Table(name) 테이블 이름 user 는 예약어라 users 로 지정한다 @Id PK 기본키 @GeneratedValue(IDENTITY) 대리키 DB가 번호를 자동으로 올린다 @Column(nullable = false) NOT NULL 빈 값 금지 @Column(unique = true) UNIQUE 중복 금지 @Enumerated(STRING) 데이터 타입 선택지를 문자열로 저장한다 @Enumerated 의 기본값은 순서 번호(ORDINAL)이기 때문에 반드시 String으로 바꿔야 한다. ORDINAL로 저장하면 선택지 중간에 값을 추가했을 때 기존 데이터의 의미가 밀린다. 2. 복합 유니크 1부에서 다루었던 "(유저, 음식 이름) 조합의 중복 금지" 이다. @Entity @Table(name = "food", uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "name"})) public class Food extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User user; @Column(nullable = false, length = 50) private String name; } columnNames 에는 DB 컬럼 이름 을 쓴다. 자바 필드 이름이 아니다. 필드는 user 지만 컬럼은 user_id 다. 카멜케이스 필드( providerUserId )도 DB에서는 provider_user_id 가 된다. 틀리면 서버가 켜질 때 컬럼을 못 찾는다. 3. 1:N — @ManyToOne "FK는 N쪽에 둔다." 배변 기록(N)이 유저(1)을 가리킨다. @Entity @Table(name = "bowel_record") public class BowelRecord extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User user; @Column(nullable = false) private LocalDateTime bowelTime; } @ManyToOne : 여러 배변 기록(Many)이 한 유저(One)에 속한다. @JoinColumn(name = "user_id") : FK 컬럼 이름이다. nullable = false : 주인 없는 기록은 없다. fetch = FetchType.LAZY : @ManyToOne에는 항상 LAZY를 붙인다. 기본값이 EAGER라서 직접 바꿔야 한다. 양방향은 필요할 때에만 유저 쪽에서도 기록 목록을 가질 수 있다. // User 안에 @OneToMany(mappedBy = "user") private List<BowelRecord> bowelRecords = new ArrayList<>(); 이걸 양방향 관계라고 한다. 하지만 처음엔 단방향(@ManyToOne)으로 시작 한다. 유저의 기록이 필요하면 레포지토리에서 user_id 로 조회하면 된다. 양방향은 양쪽을 맞춰줘야 하는 관리 부담이 생긴다. 양방향을 쓸 때 알아야 할 개념이 연관관계의 주인 이다. DB에는 FK가 배변 기록 테이블에만 있다. 그래서 FK를 실제로 바꾸는 것은 배변 기록 쪽( @ManyToOne )이다. mappedBy 는 "나는 주인이 아니고, 상대의 user 필드에 매핑된다"는 뜻이다. 유저의 리스트에만 기록을 추가하면 DB에는 반영되지 않는다. 4. N:M — 연결 엔티티로 직접 만든다. JPA에는 @ManyToMany 가 있다. 연결 테이블을 자동으로 만들어준다. 하지만 연결 테이블을 엔티티로 직접 만드는 것이 좋다. @ManyToMany 의 자동 연결 테이블에는 두 FK만 있다. 나중에 "먹은 양"같은 컬럼을 추가할 수 없다. 직접 만든 엔티티는 컬럼을 자유롭게 추가할 수 있다. @Entity @Table(name = "meal_record") public class MealRecord extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User user; @Column(nullable = false) private LocalDateTime eatenAt; } @Entity @Table(name = "meal_food", uniqueConstraints = @UniqueConstraint(columnNames = {"meal_id", "food_id"})) public class MealFood { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "meal_id", nullable = false) private MealRecord meal; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "food_id", nullable = false) private Food food; } MealFood가 FK 두 개를 다 가진다. MealRecord와 Food에는 상대를 가리키는 필드가 없다. (meal_id, food_id) 복합 유니크로 한 식사에 같은 음식이 두 번 연결되는 걸 막는다. 결과적으로 Food 1:N MealFood N:1 MealRecord다. 1부에서 언급한 "두 개의 1:N"이 이것이다. 저장 흐름 사용자가 ["떡볶이", "우유"] 를 입력했다고 하자. @Transactional public void createMeal(Long userId, MealCreateRequest request) { User user = userRepository.findById(userId) .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); MealRecord meal = mealRecordRepository.save( MealRecord.create(user, request.eatenAt())); request.foods().stream() .map(String::strip) // 앞뒤 공백 제거 .distinct() // 같은 음식 두 번 입력 방지 .forEach(name -> { Food food = foodRepository.findByUserIdAndName(userId, name) .orElseGet(() -> foodRepository.save(Food.create(user, name))); mealFoodRepository.save(MealFood.create(meal, food)); }); } 음식이 이미 있으면 그걸 쓰고, 없으면 만든다. 저장 전에 공백을 정리해서 "떡볶이" 와 "떡볶이 " 가 다른 음식이 되는 것을 방지한다. 이 전체가 한 트랜잭션이기 때문에 중간에 실패하면 식사도 저장되지 않는다. 5. 날짜와 생성 시각 private LocalDate eatDate; // 날짜만 (DATE) private LocalDateTime bowelTime; // 날짜 + 시각 (TIMESTAMP) 생성, 수정 시각은 모든 엔티티에 필요하기 때문에 공통 부모로 뺀다. @Getter @MappedSuperclass @EntityListeners(AuditingEntityListener.class) public class BaseTimeEntity { @CreatedDate private LocalDateTime createdAt; @LastModifiedDate private LocalDateTime updatedAt; } 메인 클래스에 @EnableJpaAuditing 이 있어야 작동한다. createdAt (기록한 시각)과 eatenAt (먹은 시각)은 다르다. 어제 먹은 것을 오늘 기록할 수 있기 때문이다. 분석은 eatenAt 기준으로 한다. 6. 레포지토리 — 조회 쿼리 메서드 이름으로 쿼리 만들기 public interface BowelRecordRepository extends JpaRepository<BowelRecord, Long> { List<BowelRecord> findByUserIdOrderByBowelTimeDesc(Long userId); long countByUserIdAndBowelTimeBetween(Long userId, LocalDateTime start, LocalDateTime end); } 이름 규칙 뜻 findBy... 조회 existsBy... 있는지만 확인 (boolean) countBy... 개수 UserId 필드 user 의 id . 연관 객체의 필드도 이어서 쓸 수 있다 Between 범위 OrderBy...Desc 정렬 StartingWith 앞부분이 일치 (LIKE '떡%') In 목록 안에 있는 것 전부 ( IN (1, 2, 3) ) countBy 가 1부에서 언급한 "저장하지 않고 계산"하는 부분이다. 오늘 배변 횟수를 컬럼에 저장하지 않고, 오늘 범위의 기록을 센다. 단건 조회는 Optional Optional<Food> findByUserIdAndName(Long userId, String name); 결과가 없을 수 있다. Optional로 받으면 호출하는 쪽이 "없으면 어떻게 할지"를 반드시 정하게 된다. 이름으로 안되면 @Query "내가 먹은 음식 중 '떡'으로 시작하는 것을, 많이 먹은 순으로"는 이름 규칙으로 만들기 어렵다. 이런 경우 JPQL을 직접 쓴다. JPQL은 테이블이 아니라 엔티티와 필드 이름을 쓰는 SQL이다. @Query(""" select f.name from Food f left join MealFood mf on mf.food = f where f.user.id = :userId and f.name like concat(:keyword, '%') group by f.id, f.name order by count(mf) desc """) List<String> searchByPrefix(@Param("userId") Long userId, @Param("keyword") String keyword, Pageable pageable); Pageable 로 개수를 제한한다. 호출할 때 PageRequest.of(0,10) 을 넘기면 상위 10개만 온다. 7. 지연 로딩과 N+1 문제 지연 로딩 (LAZY) 배변 기록을 조회하면 연결된 유저는 어떻게 될까. EAGER : 기록을 가져올 때 유저도 항상 같이 가져온다. Lazy : 기록만 가져온다. 유저는 record.getUser().getNickname() 처럼 실제로 쓰는 순간 가져온다. 필요 없는 데이터를 미리 가져오지 않도록 LAZY를 기본으로 쓴다. N+1 문제 LAZY에도 함정이 있다. 1번 식사의 음식 이름들을 보여준다고 하자. List<MealFood> mealFoods = mealFoodRepository.findByMealId(1L); // 쿼리 1번 for (MealFood mf : mealFoods) { mf.getFood().getName(); // 음식마다 쿼리가 한 번씩 더 } 연결 행 목록을 가져오는 쿼리가 1번 나간다. 그다음 음식 N개의 이름을 꺼낼 때마다 쿼리가 N번 더 나간다. 총 N+1이다. 식사 목록 화면이면 이게 식사 수만큼 곱해진다. 해결은 처음부터 같이 가져오는 것 이다. // 방법 1: fetch join @Query("select mf from MealFood mf join fetch mf.food where mf.meal.id = :mealId") List<MealFood> findWithFoodByMealId(@Param("mealId") Long mealId); // 방법 2: @EntityGraph @EntityGraph(attributePaths = "food") List<MealFood> findByMealId(Long mealId); 둘 다 음식까지 한 번의 쿼리로 가져온다. 개발 중에는 show-sql: true 로 실제 나가는 쿼리를 확인하는 습관을 들이는 것이 좋다. 같은 모양의 쿼리가 줄줄이 찍히면 N+1이다. 목록일 때 — IN 조회와 groupingBy 식사 하나는 위 방법으로 해결된다. 그런데 화면은 보통 여러 식사 를 한 번에 보여준다. 이렇게 짜기 쉽다. List<MealRecord> meals = mealRecordRepository.findMyMeals(userId, start, end); // 쿼리 1번 for (MealRecord meal : meals) { mealFoodRepository.findByMealId(meal.getId()); // 식사마다 1번씩 } @EntityGraph 를 붙여서 음식 이름의 N+1은 없앴다. 하지만 식사마다 MealFood 조회가 한 번씩 나간다. 식사가 30개면 쿼리가 31번이다. 반복문 안에 레포지토리 호출이 있으면 N+1이다. 해결은 반복문 밖에서 한 번에 가져오는 것이다. 순서는 다섯 단계다. public List<MealResponse> getMeals(Long userId, LocalDate from, LocalDate to) { LocalDateTime start = from.atStartOfDay(); LocalDateTime end = to.plusDays(1).atStartOfDay(); // 1 기간 안의 식사 조회 (쿼리 1번) List<MealRecord> meals = mealRecordRepository.findMyMeals(userId, start, end); if (meals.isEmpty()) { return List.of(); } // 2 식사 id만 뽑기 List<Long> mealIds = meals.stream() .map(MealRecord::getId) .toList(); // 3 그 식사들의 MealFood를 한 번에 (쿼리 1번) List<MealFood> mealFoods = mealFoodRepository.findAllByMealIdIn(mealIds); // 4 식사 id별로 음식 이름 묶기 Map<Long, List<String>> foodsByMeal = mealFoods.stream() .collect(Collectors.groupingBy( mf -> mf.getMeal().getId(), Collectors.mapping(mf -> mf.getFood().getName(), Collectors.toList()) )); // 5 식사마다 응답 만들기 (DB 조회 없음) return meals.stream() .map(meal -> MealResponse.of(meal, foodsByMeal.getOrDefault(meal.getId(), List.of()))) .toList(); } // MealFoodRepository @EntityGraph(attributePaths = "food") List<MealFood> findAllByMealIdIn(List<Long> mealIds); 식사가 몇 개든 쿼리는 2번이다. 3 IN 조회 메서드 이름 끝의 In 은 "이 목록 안에 있는 것 전부"디. SQL로는 WHERE meal_id IN (1, 2, 3) 이 된다. 식사마다 따로 묻는 대신 한 번에 묻는다. 결과는 MealFood가 한 줄로 쭉 나열된 목록이다. 이걸 식사별로 나눠야 한다. mealFoods = [ (식사1, 떡볶이), (식사1, 우유), (식사2, 김밥) ] Collectors.groupingBy(기준, 모으는 방법) 은 목록을 기준값별로 묶어 Map을 만든다. 기준 mf -> mf.getMeal().getId() : 식사 id가 같은 것끼리 한 그룹이 된다. 모으는 방법 mapping(mf -> mf.getFood().getName(), toList()) : 그룹 안의 MealFood를 음식 이름으로 바꿔 리스트로 모은다. foodsByMeal = { 1 → ["떡볶이", "우유"], 2 → ["김밥"] } 모으는 방법을 생략하면 {1 -> [MealFood, MealFood], ... } 처럼 객체가 그대로 담긴다. 이름만 필요하니 mapping 으로 바꾼다. mf.getMeal().getId() 는 LAZY여도 추가 쿼리가 나가지 않는다. 연관 객체의 id는 FK 값이라 이미 갖고 있기 때문이다. 8. 트랜잭션과 변경 감지 @Transactional @Service @Transactional(readOnly = true) public class MealService { @Transactional public void createMeal(...) { ... } public List<MealResponse> getMeals(...) { ... } } 클래스 기본은 읽기 전용이다. 실수로 데이터를 바꾸는 것을 막는다. 저장, 수정, 삭제 메서드만 @Transactional 을 따로 붙인다. 메서드 안에서 예외(RuntimeException)가 나면 전체가 롤백된다. 수정할 때 save()를 부르지 않는다. @Transactional public void updateBowel(Long id, BowelUpdateRequest request) { BowelRecord record = bowelRecordRepository.findById(id).orElseThrow(...); record.update(request.painLevel(), request.isBleed()); // save() 호출 없음 } 트랜잭션 안에서 조회한 엔티티는 JPA가 관리한다(영속 상태). 메서드가 끝날 때 JPA가 처음 조회했을 때와 지금을 비교하고, 바뀐게 있으면 UPDATE를 알아서 보낸다. 이걸 변경 감지(더티 체킹) 라고 한다. 읽기 전용 트랜잭션에서는 이 비교를 하지 않는다. 그래서 조회 메서드가 빨라진다. 9. 인덱스 @Entity @Table(name = "bowel_record", indexes = @Index(name = "idx_bowel_user_time", columnList = "user_id, bowel_time")) public class BowelRecord { ... } "유저별, 기간별 조회"를 자주 한다면 (user_id, bowel_time) 복
velog
오늘의 할일 코드카타 정리 세션 정리 코드카타 정리 1. 옹알이 (2) 문제 설명 머쓱이는 태어난 지 11개월 된 조카를 돌보고 있습니다. 조카는 아직 "aya", "ye", "woo", "ma" 네 가지 발음과 네 가지 발음을 조합해서 만들 수 있는 발음밖에 하지 못하고 연속해서 같은 발음을 하는 것을 어려워합니다. 문자열 배열 babbling이 매개변수로 주어질 때, 머쓱이의 조카가 발음할 수 있는 단어의 개수를 return하도록 solution 함수를 완성해주세요. #include <string> #include <vector> using namespace std; int solution(vector<string> babbling) { int answer = 0; for (int i = 0; i < babbling.size(); i++) { string word = babbling[i]; int pos = 0; // 현재 읽고 있는 글자의 위치 int prev_sound = 0; // 방금 낸 소리를 기억할 변수 (0: 없음, 1: aya, 2: ye, 3: woo, 4: ma) bool is_valid = true; // 이 단어를 발음할 수 있는지 체크 // 단어 끝까지 계속 글자를 확인 while (pos < word.length()) { // 현재 위치에서 3글자를 잘랐을 때 aya고 방금 낸 소리가 aya(1)가 아니면 if (word.substr(pos, 3) == "aya" && prev_sound != 1) { prev_sound = 1; // 방금 낸 소리를 "aya"로 업데이트 pos += 3; // 3글자를 읽었으니 위치를 3칸 앞으로 이동 } // 현재 위치에서 2글자를 잘랐을 때 ye고, 방금 낸 소리가 ye(2)가 아니면 else if (word.substr(pos, 2) == "ye" && prev_sound != 2) { prev_sound = 2; pos += 2; } // 현재 위치에서 3글자를 잘랐을 때 woo고 방금 낸 소리가 woo(3)가 아니면 else if (word.substr(pos, 3) == "woo" && prev_sound != 3) { prev_sound = 3; pos += 3; } // 현재 위치에서 2글자를 잘랐을 때 ma고 방금 낸 소리가 ma(4)가 아니면 else if (word.substr(pos, 2) == "ma" && prev_sound != 4) { prev_sound = 4; pos += 2; } // 네 가지 소리 중 아무것도 아니거나, 연속된 소리라서 발음할 수 없는 경우 else { is_valid = false; // 불합격 처리 break; } } // 단어를 끝까지 읽고 불합격 판정을 받지 않았다면 if (is_valid) { answer++; } } return answer; } AI가 작성한 코드 #include <string> #include <vector> using namespace std; int solution(vector<string> babbling) { int answer = 0; // 1. 하드코딩된 if-else를 없애고, 발음 사전을 정적 배열로 분리합니다. const string sounds[4] = {"aya", "ye", "woo", "ma"}; // 2. 값 복사를 방지하기 위해 const string& (상수 참조)로 단어를 읽어옵니다. for (const string& word : babbling) { int pos = 0; int prev_sound = -1; bool is_valid = true; while (pos < word.length()) { bool matched = false; // 3. 4개의 발음 사전을 돌며 현재 글자와 일치하는지 확인합니다. for (int i = 0; i < 4; i++) { // substr 대신 compare 함수를 사용하여 메모리 할당 없이 글자를 비교합니다. if (word.compare(pos, sounds[i].length(), sounds[i]) == 0) { if (prev_sound == i) { // 연속 발음 체크 is_valid = false; break; } prev_sound = i; pos += sounds[i].length(); matched = true; break; // 일치하는 발음을 찾았으니 사전 검색을 멈춥니다. } } // 4. 사전에 있는 4가지 발음 중 어떤 것과도 일치하지 않거나, 연속 발음으로 판정된 경우 if (!matched || !is_valid) { is_valid = false; break; } } // 끝까지 무사히 파싱(Parsing)을 마쳤다면 정답 처리합니다. if (is_valid) { answer++; } } return answer; } substr 소멸과 compare 도입 (Zero-Allocation): 이전 코드의 word.substr()은 호출될 때마다 힙(Heap) 메모리에 새로운 문자열 덩어리를 잘라서 계속 생성합니다. 만약 몬스터 AI나 전투 시스템이 매 프레임마다 이런 문자열 자르기를 수행한다면 심각한 메모리 파편화와 프레임 드랍이 발생합니다. 반면 word.compare(시작점, 길이, 비교할단어)는 원본 문자열을 건드리지 않고 '눈으로만' 빠르게 비교하고 넘어가므로 메모리 할당(동적 할당) 비용이 '0'입니다. const string& 참조자 사용: for (string word : babbling) 처럼 쓰면 배열 안의 단어를 매번 새로운 변수에 통째로 '복사'해서 가져옵니다. 뒤에 &(참조자)를 붙이면 복사하지 않고 원본 데이터의 주소만 슬쩍 들여다보기 때문에, 데이터가 클수록 속도 차이가 엄청나게 벌어집니다. 데이터와 로직의 분리 (Data-Driven Design): 이전에는 if (.. == "aya"), else if (.. == "ye") 처럼 로직 안에 데이터가 섞여 있어, 나중에 옹알이 종류가 10개로 늘어나면 코드 길이도 10배로 길어집니다. 이 코드는 sounds라는 배열로 데이터를 분리해 두었기 때문에, 옹알이 종류가 100개로 늘어나도 안쪽 for문의 조건만 i < 100으로 바뀔 뿐 전체 로직은 단 한 줄도 길어지지 않습니다. 2. 숫자 짝꿍 문제 설명 두 정수 X, Y의 임의의 자리에서 공통으로 나타나는 정수 k(0 ≤ k ≤ 9)들을 이용하여 만들 수 있는 가장 큰 정수를 두 수의 짝꿍이라 합니다(단, 공통으로 나타나는 정수 중 서로 짝지을 수 있는 숫자만 사용합니다). X, Y의 짝꿍이 존재하지 않으면, 짝꿍은 -1입니다. X, Y의 짝꿍이 0으로만 구성되어 있다면, 짝꿍은 0입니다. 예를 들어, X = 3403이고 Y = 13203이라면, X와 Y의 짝꿍은 X와 Y에서 공통으로 나타나는 3, 0, 3으로 만들 수 있는 가장 큰 정수인 330입니다. 다른 예시로 X = 5525이고 Y = 1255이면 X와 Y의 짝꿍은 X와 Y에서 공통으로 나타나는 2, 5, 5로 만들 수 있는 가장 큰 정수인 552입니다(X에는 5가 3개, Y에는 5가 2개 나타나므로 남는 5 한 개는 짝 지을 수 없습니다.) 두 정수 X, Y가 주어졌을 때, X, Y의 짝꿍을 return하는 solution 함수를 완성해주세요. #include <string> #include <vector> #include <algorithm> using namespace std; string solution(string X, string Y) { string answer = ""; // 0부터 9까지 숫자가 몇 번 나왔는지 countX, countY 2개(모두 0으로 초기화) int countX[10] = {0}; int countY[10] = {0}; // X 문자열을 돌면서 각 숫자가 몇 개 있는지 체크 for (int i = 0; i < X.length(); i++) { // '3'에서 '0'을 빼면 실제 숫자 3 (아스키코드 원리) int num = X[i] - '0'; countX[num]++; } // Y 문자열도 똑같이 체크 for (int i = 0; i < Y.length(); i++) { int num = Y[i] - '0'; countY[num]++; } // 가장 큰 수를 만들어야 하니까 제일 큰 숫자인 9부터 0까지 거꾸로 for (int i = 9; i >= 0; i--) { int common_count = min(countX[i], countY[i]); // 공통으로 가진 개수만큼 숫자를 이어 붙이기 for (int j = 0; j < common_count; j++) { answer += to_string(i); } } // 예외 처리: 짝꿍이 하나도 없어서 문자열 비면 "-1" if (answer == "") { return "-1"; } // 예외 처리: 짝꿍을 만들었는데 0으로만 시작한다면 그냥 "0"으로 바꾸기 if (answer[0] == '0') { return "0"; } return answer; } AI가 작성한 코드 #include <string> #include <vector> #include <algorithm> // min 함수 사용 using namespace std; string solution(string X, string Y) { int countX[10] = {0}; int countY[10] = {0}; // 1. 인덱스(i) 변수 없이 범위 기반 for문으로 문자열을 직접 순회합니다. for (char c : X) countX[c - '0']++; for (char c : Y) countY[c - '0']++; string answer = ""; // 2. 가장 큰 숫자(9)부터 0까지 확인하며 문자열을 조립합니다. for (int i = 9; i >= 0; i--) { int common_count = min(countX[i], countY[i]); if (common_count > 0) { // 3. 반복문 안의 += 연산과 to_string()을 완전히 제거하고, // append 함수를 통해 '필요한 개수만큼 단번에' 메모리에 복사해 넣습니다. answer.append(common_count, i + '0'); } } // 4. empty() 함수를 사용하여 빈 문자열인지 더 직관적으로 확인합니다. if (answer.empty()) return "-1"; if (answer[0] == '0') return "0"; return answer; } to_string과 += 연산의 소멸 (Zero Overhead) 이전 코드의 answer += to_string(i)는 겉보기엔 간단하지만, 내부적으로는 끔찍한 연산 폭탄입니다. 매번 숫자를 문자로 바꾸기 위해 새로운 임시 문자열(객체)을 생성하고, 그걸 다시 정답 문자열 뒤에 이어 붙이기 위해 힙(Heap) 메모리를 헐었다가 다시 짓는 과정을 300만 번 반복할 수도 있습니다. 반면 answer.append(개수, 문자)를 사용하면, 컴퓨터가 "아! 숫자 9를 10만 개 한 번에 붙이라는 거구나!" 하고 필요한 메모리 공간을 딱 한 번만 확보한 뒤 블록 단위로 초고속 복사를 수행합니다. char와 아스키코드를 이용한 직접 치환 (i + '0') to_string(3) 같은 무거운 변환 함수를 쓸 필요가 없습니다. 컴퓨터에게 3 + '0'(아스키코드 48)을 계산하라고 하면 즉시 아스키코드 51번, 즉 문자 '3'이 튀어나옵니다. 숫자를 문자로 바꾸는 가장 로우레벨(Low-level)이면서 가장 빠른 방법입니다. Unreal Engine 멀티플레이어 게임 개발 : 3회차 정리 챕터 2-1 : Remote Procedure Call 기초 RPC : 호출하는 PC와 실행하는 PC가 달라도 되게끔 해주는 통신 기법 -> 게임에 큰 영향을 끼치지 않는 일시적인 효과들에 사용(사운드, 파티클 등) 함수를 실행할 때 내부적으로 동작하는 방식에 따라 Call과 Invoke로 구분 1. Call 특징: 정적인(Static) 의미 결정 시점: 프로그램이 빌드되는 컴파일 타임에 어떤 함수가 실행될지, 호출하는 곳과 실행하는 곳이 이미 명확하게 정해져 있음. 작동 방식: 대상 함수를 직접적으로(Direct) 호출하고 실행 예시: 일반적인 전역 함수나 클래스의 멤버 함수 호출이 여기에 해당 2. Invoke 특징: 동적인(Dynamic) 의미 결정 시점: 프로그램이 실행 중인 런타임 상황에 따라 어떤 함수가 실행될지, 호출하는 곳과 실제로 실행되는 곳이 어디인지 결정 작동 방식: 대리자나 네트워크를 거쳐 간접적으로(Indirect) 함수를 호출하고 실행 예시: 함수 포인터, 동적 바인딩, 그리고 RPC가 대표적인 Invoke 방식 멀티플레이 프로그래밍에서 클라이언트가 서버의 함수를 실행하거나, 서버가 클라이언트의 함수를 실행하도록 명령할 때 "RPC를 Invoke 한다"고 표현 RPC는 코드를 작성하는 시점(컴파일 타임)에 바로 내 컴퓨터에서 실행될지 확정되는 것이 아니라, 게임이 돌아가는 도중(런타임)에 네트워크 상태와 NetRole, NetMode에 따라 실행될 타겟(서버 혹은 특정 클라이언트)이 동적으로 결정되어 간접적으로 실행되기 때문 Actor Ownership PlayerController가 Local PlayerController와 같다면, Client-Owned Actor 예: 내 플레이어 캐릭터 PlayerController가 Local PlayerController와 다르다면, Owned by different client 예: 내 화면에 보이는 친구의 플레이어 캐릭터 서버에서 스폰되었지만 SetOwner() 함수 호출이 없으면, Server-Owned Actor 예: 보물상자 액터 Unowned Actor == Server-Owned Actor 라고 생각해도 됨 NetMulticast, Server, Client 키워드 UFUNCTION() 매크로와 함께 사용하는 해당 원격 PC에서 RPC를 실행시켜 달라는 요청 실제로 실행될지는 표를 통해 판단 NetMulticast : 서버를 포함한 모든 클라이언트에서 해당 RPC 실행 요청 Server : 서버에서 해당 RPC 실행 요청 Client : 클라이언트에서 해당 RPC 실행 요청
Балл: 54.4Уверенность: 49%
Балл: 54.4Уверенность: 49%
Балл: 54.4Уверенность: 49%
Балл: 54.4Уверенность: 49%
Балл: 54.4Уверенность: 49%
Балл: 54.4Уверенность: 49%