<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jmj732</title>
        <link>https://velog.io/</link>
        <description>Backend Developer</description>
        <lastBuildDate>Sat, 07 Mar 2026 15:08:19 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jmj732</title>
            <url>https://velog.velcdn.com/images/bssm_woals/profile/4d921aaa-6f57-4abd-9e75-2034a5534865/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jmj732. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/bssm_woals" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[코골이 ANC: FFT 분석으로 본 코골이의 주파수 구조]]></title>
            <link>https://velog.io/@bssm_woals/%EC%BD%94%EA%B3%A8%EC%9D%B4-ANC-FFT-%EB%B6%84%EC%84%9D%EC%9C%BC%EB%A1%9C-%EB%B3%B8-%EC%BD%94%EA%B3%A8%EC%9D%B4%EC%9D%98-%EC%A3%BC%ED%8C%8C%EC%88%98-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@bssm_woals/%EC%BD%94%EA%B3%A8%EC%9D%B4-ANC-FFT-%EB%B6%84%EC%84%9D%EC%9C%BC%EB%A1%9C-%EB%B3%B8-%EC%BD%94%EA%B3%A8%EC%9D%B4%EC%9D%98-%EC%A3%BC%ED%8C%8C%EC%88%98-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Sat, 07 Mar 2026 15:08:19 GMT</pubDate>
            <description><![CDATA[<p>코골이를 줄이기 위한 <strong>헤드보드 기반 ANC(Active Noise Cancellation)</strong> 시스템을 구상하면서, 가장 먼저 한 일은 코골이 소리를 <strong>주파수 관점에서 분석하는 것</strong>이었다.</p>
<p>이어폰 ANC는 귀 근처에서 직접 소음을 상쇄하지만, <strong>헤드보드 ANC는 공간에서 전달되는 소리를 제어해야 한다.</strong><br>따라서 단순히 “코골이는 시끄럽다”는 수준이 아니라 다음 질문에 답할 필요가 있었다.</p>
<ul>
<li>어떤 <strong>주파수 대역에 에너지가 집중되는가</strong></li>
<li>실제 코골이 샘플들은 <strong>얼마나 다양한 구조를 가지는가</strong></li>
<li>ANC가 <strong>실제로 겨냥해야 할 주파수 대역은 어디인가</strong></li>
</ul>
<p>이 글은 <code>snoring_analysis</code> 노트북과 분석 결과 CSV를 기반으로 정리한 <strong>코골이 오디오 신호 분석 기록</strong>이다.</p>
<hr>
<h1 id="1-프로젝트-배경과-목표">1. 프로젝트 배경과 목표</h1>
<p>이번 분석의 목표는 다음과 같다.</p>
<blockquote>
<p><strong>코골이의 주파수 구조를 분석하고 이를 ANC 설계의 입력 정보로 연결하는 것</strong></p>
</blockquote>
<p>특히 다음 세 가지 질문에 집중했다.</p>
<ol>
<li>코골이는 실제로 <strong>저주파 중심 소리인가</strong></li>
<li>모든 코골이가 <strong>비슷한 주파수 구조를 가지는가</strong></li>
<li><strong>ANC 타깃 주파수 대역</strong>을 현실적으로 어디로 설정해야 하는가</li>
</ol>
<p>이를 위해 다음과 같은 분석 파이프라인을 구성했다.</p>
<h3 id="분석-파이프라인">분석 파이프라인</h3>
<ul>
<li>파형 시각화</li>
<li>FFT 분석</li>
<li>스펙트로그램 분석</li>
<li>80~300Hz 대역통과 필터 적용</li>
<li>파일 단위 정량 지표 계산</li>
<li>Snore Index 기반 유형 분류</li>
<li>세그먼트 추출 후 분포 및 상관 분석</li>
</ul>
<hr>
<h1 id="2-데이터셋과-전처리">2. 데이터셋과 전처리</h1>
<p>이번 분석에서는 <strong>29개의 코골이 오디오 파일</strong>을 사용했다.</p>
<p>비침묵 구간을 기준으로 코골이 세그먼트를 추출한 결과:</p>
<ul>
<li><strong>총 세그먼트 수:</strong> 1,444개  </li>
<li><strong>파일당 평균 세그먼트:</strong> 약 49.79개</li>
</ul>
<h3 id="세그먼트-수가-많은-파일">세그먼트 수가 많은 파일</h3>
<ul>
<li><code>freesound_community-snoring-50041.mp3</code> : 601개  </li>
<li><code>freesound_community-snoring-6773.mp3</code> : 282개  </li>
<li><code>freesound_community-soft-female-snoring-17325.mp3</code> : 101개  </li>
</ul>
<h3 id="세그먼트-수가-적은-파일">세그먼트 수가 적은 파일</h3>
<ul>
<li><code>freesound_community-snoring-long-78149.mp3</code> : 1개  </li>
<li><code>freesound_community-snoring-42710.mp3</code> : 3개  </li>
<li><code>freesound_community-snoring-67961.mp3</code> : 3개  </li>
</ul>
<p>이 분포만 보더라도 <strong>코골이 데이터는 매우 비균일한 특성</strong>을 가진다.</p>
<p>같은 “snoring” 라벨이라도 다음과 같은 형태가 섞여 있다.</p>
<ul>
<li>길게 이어지는 <strong>단일 이벤트형 코골이</strong></li>
<li>짧은 <strong>burst 반복형 코골이</strong></li>
<li><strong>호흡음 또는 환경음이 섞인 샘플</strong></li>
</ul>
<p>이러한 다양성은 이후 분석에서 등장하는 <strong>Snore Index 편차</strong>와도 연결된다.</p>
<hr>
<h1 id="3-파일-단위-분석">3. 파일 단위 분석</h1>
<h2 id="3-1-파형-waveform">3-1. 파형 (Waveform)</h2>
<p><img src="https://velog.velcdn.com/images/bssm_woals/post/7eeb022b-2485-4720-8c21-41acd848f944/image.png" alt="">
시간 영역 파형을 보면 코골이는 보통 다음 패턴을 가진다.</p>
<ol>
<li>호흡과 함께 에너지가 증가</li>
<li>짧은 burst 발생</li>
<li>다시 에너지 감소</li>
</ol>
<p>즉 코골이는 <strong>연속적인 noise라기보다 이벤트성 저주파 burst의 반복</strong>에 가깝다.</p>
<p>예시 파일
<code>mollyroselee-a-person-snoring-468533.mp3</code></p>
<table>
<thead>
<tr>
<th>항목</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>샘플레이트</td>
<td>48,000 Hz</td>
</tr>
<tr>
<td>길이</td>
<td>8.04초</td>
</tr>
<tr>
<td>샘플 수</td>
<td>385,920</td>
</tr>
<tr>
<td>RMS 에너지</td>
<td>0.1167</td>
</tr>
</tbody></table>
<p>이 결과는 코골이가 단순한 작은 잡음이 아니라 <strong>상당한 에너지를 가진 신호</strong>임을 보여준다.</p>
<hr>
<h2 id="3-2-fft-분석">3-2. FFT 분석</h2>
<p>시간 영역 분석만으로는 소리의 크기만 확인할 수 있다.<br>ANC 설계에서는 <strong>에너지가 집중된 주파수 대역</strong>이 더 중요하다.</p>
<p>그래서 FFT를 적용했다.</p>
<h3 id="fft-결과-예시-파일">FFT 결과 (예시 파일)</h3>
<p><img src="https://velog.velcdn.com/images/bssm_woals/post/3d7a3bab-7044-4a05-b448-166e553b31a3/image.png" alt=""></p>
<table>
<thead>
<tr>
<th>항목</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>전체 파워</td>
<td>1,013,758,848</td>
</tr>
<tr>
<td>80~300Hz 파워</td>
<td>709,436,352</td>
</tr>
<tr>
<td>파워 비율</td>
<td>0.6998</td>
</tr>
<tr>
<td>지배 주파수</td>
<td>136.07 Hz</td>
</tr>
</tbody></table>
<p>이 샘플의 경우 <strong>전체 에너지의 약 70%가 80~300Hz 대역</strong>에 집중되어 있었으며<br>가장 강한 피크는 <strong>약 136Hz</strong> 부근에 존재했다.</p>
<p>즉 이 코골이는 <strong>전형적인 저주파 dominant 코골이</strong>라고 볼 수 있다.</p>
<hr>
<h2 id="3-3-스펙트로그램">3-3. 스펙트로그램</h2>
<p>FFT는 전체 주파수 분포를 보여주지만,<br>스펙트로그램은 <strong>시간에 따른 주파수 변화</strong>를 보여준다.</p>
<p><img src="https://velog.velcdn.com/images/bssm_woals/post/95eb0c86-13fb-4c05-a530-4447a2dce1f3/image.png" alt=""></p>
<h3 id="스펙트로그램-요약">스펙트로그램 요약</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>프레임 수</td>
<td>754</td>
</tr>
<tr>
<td>주파수 bin 수</td>
<td>1025</td>
</tr>
<tr>
<td>dominant frequency 평균</td>
<td>433.90 Hz</td>
</tr>
<tr>
<td>표준편차</td>
<td>407.18 Hz</td>
</tr>
<tr>
<td>전체 평균 dB</td>
<td>-24.01 dB</td>
</tr>
<tr>
<td>80~300Hz 평균 dB</td>
<td>2.57 dB</td>
</tr>
</tbody></table>
<p>여기서 중요한 점은 다음이다.</p>
<p>FFT 기준으로 보면 코골이는 저주파 중심이지만,<br>시간 단위로 보면 <strong>더 높은 주파수 성분도 함께 나타난다.</strong></p>
<p>즉 코골이는 단일 톤이 아니라 다음이 결합된 <strong>복합 음향 신호</strong>에 가깝다.</p>
<ul>
<li>기도 저주파 진동</li>
<li>호흡 난류</li>
<li>비강/구강 공명</li>
<li>녹음 환경에서 발생한 broadband 성분</li>
</ul>
<hr>
<h2 id="3-4-80300hz-대역통과-필터">3-4. 80~300Hz 대역통과 필터</h2>
<p>코골이 연구에서 자주 등장하는 가설은 다음과 같다.</p>
<blockquote>
<p>코골이의 주요 에너지는 <strong>80~300Hz 저주파 대역</strong>에 집중된다.</p>
</blockquote>
<p>이를 검증하기 위해 <strong>80~300Hz band-pass filter</strong>를 적용했다.</p>
<h3 id="필터-적용-결과">필터 적용 결과</h3>
<p><img src="https://velog.velcdn.com/images/bssm_woals/post/b9289770-66ed-48ea-903b-eadb4f3e9404/image.png" alt=""></p>
<table>
<thead>
<tr>
<th>항목</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>원본 에너지</td>
<td>5253.48</td>
</tr>
<tr>
<td>필터 후 에너지</td>
<td>3562.27</td>
</tr>
<tr>
<td>에너지 비율</td>
<td>0.6781</td>
</tr>
<tr>
<td>원본 RMS</td>
<td>0.1167</td>
</tr>
<tr>
<td>필터 후 RMS</td>
<td>0.0961</td>
</tr>
</tbody></table>
<p>RMS는 약간 감소했지만, <strong>대부분의 에너지가 여전히 80~300Hz에 남아 있었다.</strong></p>
<p>이는 이 샘플에서 <strong>저주파 대역이 코골이 핵심 성분</strong>임을 의미한다.</p>
<hr>
<h1 id="4-정량-지표와-snore-index">4. 정량 지표와 Snore Index</h1>
<p>파일 단위 분석에서는 다음 지표들을 계산했다.</p>
<ul>
<li>spectral centroid</li>
<li>spectral bandwidth</li>
<li>spectral rolloff</li>
<li>RMS energy</li>
<li>dominant frequency</li>
<li><strong>snore_index</strong></li>
</ul>
<h3 id="snore-index-정의">Snore Index 정의</h3>
<p>Snore Index = (80~300Hz 에너지) / (20Hz 이상 전체 에너지)</p>
<p>즉 <strong>전체 코골이 에너지 중 저주파 대역이 차지하는 비율</strong>을 의미한다.</p>
<h3 id="snore-index-분포">Snore Index 분포</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>평균</td>
<td>0.3437</td>
</tr>
<tr>
<td>표준편차</td>
<td>0.2629</td>
</tr>
<tr>
<td>최소</td>
<td>0.0107</td>
</tr>
<tr>
<td>중앙값</td>
<td>0.2592</td>
</tr>
<tr>
<td>최대</td>
<td>0.8896</td>
</tr>
</tbody></table>
<h3 id="해석">해석</h3>
<h4 id="1-모든-코골이가-저주파-dominant는-아니다">1. 모든 코골이가 저주파 dominant는 아니다</h4>
<p>평균이 0.34라는 것은 <strong>저주파만으로 코골이를 설명할 수 없다는 의미</strong>다.</p>
<p>실제 코골이는</p>
<blockquote>
<p><strong>저주파 진동 + 광대역 성분</strong></p>
</blockquote>
<p>의 결합으로 보는 것이 더 적절하다.</p>
<h4 id="2-편차가-매우-크다">2. 편차가 매우 크다</h4>
<p>최소값 0.01, 최대값 0.89라는 결과는<br><strong>코골이 스펙트럼 구조가 샘플마다 크게 다르다</strong>는 것을 의미한다.</p>
<h4 id="3-anc에-적합한-코골이도-존재한다">3. ANC에 적합한 코골이도 존재한다</h4>
<p>3사분위수 값이 약 <strong>0.59</strong>라는 것은<br>상위 25% 코골이는 <strong>저주파 중심 구조</strong>를 가진다는 의미다.</p>
<p>즉 일부 코골이는 <strong>ANC로 효과적으로 제어될 가능성이 높다.</strong></p>
<hr>
<h1 id="5-세그먼트-단위-분석">5. 세그먼트 단위 분석</h1>
<p>파일 평균만 보면 burst 특성이 희석된다.<br>그래서 비침묵 구간을 기준으로 <strong>세그먼트 단위 분석</strong>을 진행했다.</p>
<p>핵심 분석 대상은 다음 두 가지였다.</p>
<ul>
<li>dominant frequency 분포</li>
<li>Snore Index 분포</li>
</ul>
<hr>
<h2 id="5-1-dominant-frequency-분포">5-1. dominant frequency 분포</h2>
<p>세그먼트 단위 dominant frequency는 대부분</p>
<blockquote>
<p><strong>약 30~150Hz 구간</strong></p>
</blockquote>
<p>에 집중되어 있었다.</p>
<p>이는 기존 연구에서 말하는 <strong>80~300Hz</strong>보다 낮아 보일 수 있지만, 실제로는 모순이 아니다.</p>
<p>코골이는 보통 다음 구조를 가진다.</p>
<ul>
<li>fundamental: 50~100Hz</li>
<li>고조파: 2배, 3배 주파수</li>
</ul>
<p>따라서</p>
<ul>
<li><strong>dominant frequency:</strong> 30~150Hz</li>
<li><strong>에너지 중심 대역:</strong> 80~300Hz</li>
</ul>
<p>이라는 결과는 충분히 일관된 해석이다.</p>
<hr>
<h2 id="5-2-세그먼트-분석의-의미">5-2. 세그먼트 분석의 의미</h2>
<p>세그먼트 단위 Snore Index를 보면 다음 특징이 나타난다.</p>
<ul>
<li>낮은 값의 세그먼트</li>
<li>매우 높은 값(0.6~0.9)의 세그먼트</li>
</ul>
<p>즉 <strong>순수 코골이 burst와 혼합 신호</strong>가 명확히 구분된다.</p>
<p>이 결과는 ANC 설계에서 중요한 의미를 가진다.</p>
<blockquote>
<p>ANC를 항상 작동시키기보다<br><strong>저주파 dominant burst에만 강하게 반응하는 구조</strong>가 더 효율적일 수 있다.</p>
</blockquote>
<hr>
<h1 id="6-anc-설계로-이어지는-인사이트">6. ANC 설계로 이어지는 인사이트</h1>
<p>이번 분석에서 얻은 주요 인사이트는 세 가지다.</p>
<h3 id="6-1-타깃-대역-설정">6-1. 타깃 대역 설정</h3>
<ul>
<li>주 제어 대역: <strong>80~300Hz</strong></li>
<li>보조 관측 대역: <strong>30~80Hz</strong></li>
</ul>
<p>세그먼트 분석 결과를 고려하면 <strong>더 낮은 주파수도 함께 관측하는 것이 현실적</strong>이다.</p>
<hr>
<h3 id="6-2-코골이-유형별-anc-전략-필요">6-2. 코골이 유형별 ANC 전략 필요</h3>
<p>Snore Index 편차가 매우 크기 때문에<br><strong>모든 코골이에 동일한 ANC 전략을 적용하는 것은 비효율적</strong>이다.</p>
<ul>
<li>저주파 dominant 코골이 → ANC에 매우 적합</li>
<li>광대역 코골이 → ANC만으로는 제한적</li>
</ul>
<hr>
<h3 id="6-3-공간-anc-접근의-필요성">6-3. 공간 ANC 접근의 필요성</h3>
<p>코골이는 머리 근처에서 발생해 <strong>방 전체로 퍼지는 저주파 소리</strong>다.</p>
<table>
<thead>
<tr>
<th>주파수</th>
<th>파장</th>
</tr>
</thead>
<tbody><tr>
<td>100Hz</td>
<td>약 3.4m</td>
</tr>
<tr>
<td>150Hz</td>
<td>약 2.3m</td>
</tr>
</tbody></table>
<p>따라서 문제는 <strong>귀 근처 제어가 아니라 공간 음향 제어에 가깝다.</strong></p>
<p>이 점에서 <strong>헤드보드 ANC 또는 multi-channel ANC</strong>가 자연스러운 접근이 된다.</p>
<hr>
<h1 id="7-한계와-다음-단계">7. 한계와 다음 단계</h1>
<h2 id="한계">한계</h2>
<h3 id="공개-오디오-데이터셋-특성">공개 오디오 데이터셋 특성</h3>
<p>녹음 환경이 서로 다르다.</p>
<ul>
<li>마이크 거리</li>
<li>배경 소음</li>
<li>mp3 압축</li>
<li>방 반사</li>
</ul>
<h3 id="세그먼트-추출-방식">세그먼트 추출 방식</h3>
<p><code>librosa.effects.split</code>은 <strong>비침묵 탐지</strong>일 뿐<br>코골이 자체를 분류한 것은 아니다.</p>
<h3 id="anc-성능-검증-미포함">ANC 성능 검증 미포함</h3>
<p>이번 글은 <strong>분석 단계</strong>이며<br>실제 ANC 성능 검증은 별도 실험이 필요하다.</p>
<hr>
<h1 id="마무리">마무리</h1>
<p>이번 분석에서 확인한 핵심은 다음과 같다.</p>
<blockquote>
<p>코골이는 단순한 “시끄러운 소리”가 아니라<br><strong>저주파 진동과 광대역 성분이 결합된 복합 음향 현상</strong>이다.</p>
</blockquote>
<p>그리고 그 안에서 <strong>ANC에 매우 적합한 저주파 dominant 코골이도 분명 존재한다.</strong></p>
<p>즉 <strong>헤드보드 ANC는 막연한 아이디어가 아니라<br>실제 스펙트럼 데이터를 기반으로 설계 가능한 문제</strong>라는 점이 이번 분석의 가장 큰 수확이었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[헤드보드 ANC를 기획하며 정리한 Active Noise Control 핵심 개념]]></title>
            <link>https://velog.io/@bssm_woals/%ED%97%A4%EB%93%9C%EB%B3%B4%EB%93%9C-ANC</link>
            <guid>https://velog.io/@bssm_woals/%ED%97%A4%EB%93%9C%EB%B3%B4%EB%93%9C-ANC</guid>
            <pubDate>Thu, 05 Mar 2026 04:45:29 GMT</pubDate>
            <description><![CDATA[<p>(Active Noise Control 개념부터 알고리즘까지)</p>
<p>코골이를 줄이기 위해 착용형 기기가 아니라 침대 머리맡에서 소음을 줄이는 시스템을 만들고 있다.</p>
<p>이어폰처럼 귀에 착용하는 방식이 아니라
침대 헤드보드에서 코골이 소음을 줄이는 ANC 시스템이다.</p>
<p>이 글에서는 개발하면서 정리한 <strong>Active Noise Control (ANC)</strong>의 개념을 설명한다.</p>
<p>설명 순서는 다음과 같다.</p>
<blockquote>
<p>Active Noise Control이란 무엇인가
ANC의 물리적 원리
ANC 시스템 구조
ANC 알고리즘 (LMS, FxLMS)
실제 제품(이어폰)의 ANC 구조
최신 ANC 기술
헤드보드 ANC에서 중요한 포인트</p>
</blockquote>
<h2 id="active-noise-control이란">Active Noise Control이란</h2>
<p><strong>Active Noise Control (ANC)</strong>은
소음을 막는 것이 아니라 상쇄시키는 기술이다.</p>
<p>일반적인 방음은</p>
<pre><code>소음 → 차단</code></pre><p>방식이지만 ANC는</p>
<pre><code>소음 + 반대 위상의 소리 = 소리 감소</code></pre><p>원리를 이용한다.</p>
<p>이를 <strong>Destructive Interference (상쇄 간섭)</strong>라고 한다.</p>
<p>간단히 표현하면</p>
<pre><code>noise + anti_noise = 0</code></pre><p>이 되는 방향으로 소리를 만든다.</p>
<h2 id="anc의-물리적-원리">ANC의 물리적 원리</h2>
<p>소리는 <strong>공기 압력의 진동(파동)</strong>이다.</p>
<p>파동은 보통 다음과 같이 표현된다.</p>
<pre><code>x(t) = A * sin(ωt)</code></pre><p>각 기호의 의미</p>
<table>
<thead>
<tr>
<th>기호</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>x(t)</td>
<td>시간에 따라 변하는 소리</td>
</tr>
<tr>
<td>A</td>
<td>진폭(소리의 크기)</td>
</tr>
<tr>
<td>ω</td>
<td>각주파수</td>
</tr>
<tr>
<td>t</td>
<td>시간</td>
</tr>
</tbody></table>
<p>반대 위상의 소리는</p>
<pre><code>-A * sin(ωt)</code></pre><p>두 소리를 합치면</p>
<pre><code>A*sin(ωt) + (-A*sin(ωt)) = 0</code></pre><p>즉 소리가 상쇄된다.</p>
<p>하지만 현실에서는 다음 문제가 있다.</p>
<p>지연 (delay)
반사 (reflection)
공간 전달 (acoustic transfer)</p>
<p>그래서 단순히 반대 파형을 만드는 것만으로는 충분하지 않다.</p>
<h2 id="anc-시스템-구조">ANC 시스템 구조</h2>
<p>ANC 시스템은 보통 다음 4가지 구성으로 이루어진다.</p>
<table>
<thead>
<tr>
<th>구성</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>Reference microphone</td>
<td>소음 측정</td>
</tr>
<tr>
<td>Controller (DSP)</td>
<td>anti-noise 계산</td>
</tr>
<tr>
<td>Speaker</td>
<td>anti-noise 출력</td>
</tr>
<tr>
<td>Error microphone</td>
<td>실제 소리 측정</td>
</tr>
</tbody></table>
<p>구조는 다음과 같다.</p>
<pre><code>Noise source
      ↓
Reference Mic
      ↓
Controller (DSP)
      ↓
Speaker
      ↓
Error Mic</code></pre><p>시스템의 목표는 남은 소리를 최소화하는 것이다.</p>
<pre><code>e(n) = d(n) + y(n)</code></pre><p>각 변수의 의미</p>
<table>
<thead>
<tr>
<th>변수</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>d(n)</td>
<td>원래 소음 (primary noise)</td>
</tr>
<tr>
<td>y(n)</td>
<td>anti-noise</td>
</tr>
<tr>
<td>e(n)</td>
<td>남은 소리 (residual noise)</td>
</tr>
</tbody></table>
<p>목표</p>
<pre><code>e(n) → 0</code></pre><h2 id="anc에서-중요한-두-경로">ANC에서 중요한 두 경로</h2>
<p>ANC에서는 두 가지 전달 경로가 존재한다.</p>
<hr>
<p>Primary Path - 원래 소음이 전달되는 경로</p>
<pre><code>Noise source → 공기 → 귀</code></pre><p>수식</p>
<pre><code>d(n) = P(z) * x(n)</code></pre><table>
<thead>
<tr>
<th>기호</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>P(z)</td>
<td>소음 전달 경로 (Primary path)</td>
</tr>
<tr>
<td>x(n)</td>
<td>소음 신호</td>
</tr>
<tr>
<td>d(n)</td>
<td>귀에 도달한 소리</td>
</tr>
<tr>
<td>---</td>
<td></td>
</tr>
<tr>
<td>Secondary Path - 스피커에서 나온 소리가 전달되는 경로</td>
<td></td>
</tr>
<tr>
<td>```</td>
<td></td>
</tr>
<tr>
<td>Speaker → 공기 → 귀</td>
<td></td>
</tr>
<tr>
<td>```</td>
<td></td>
</tr>
<tr>
<td>수식</td>
<td></td>
</tr>
<tr>
<td>```</td>
<td></td>
</tr>
<tr>
<td>y(n) = S(z) * u(n)</td>
<td></td>
</tr>
<tr>
<td>```</td>
<td></td>
</tr>
<tr>
<td>기호</td>
<td>의미</td>
</tr>
<tr>
<td>---</td>
<td>---</td>
</tr>
<tr>
<td>S(z)</td>
<td>스피커 전달 경로 (Secondary path)</td>
</tr>
<tr>
<td>u(n)</td>
<td>스피커 신호</td>
</tr>
<tr>
<td>y(n)</td>
<td>실제 전달된 소리</td>
</tr>
</tbody></table>
<p>이 Secondary Path 때문에 ANC가 어려워진다.</p>
<p>왜냐하면</p>
<p>거리
공기
반사</p>
<p>때문에 위상 변화와 지연이 생기기 때문이다.</p>
<h2 id="anc-알고리즘">ANC 알고리즘</h2>
<p>ANC의 핵심은 <strong>Adaptive Filter (적응형 필터)</strong>이다.</p>
<p>LMS (Least Mean Square)</p>
<p>가장 기본적인 적응 알고리즘</p>
<pre><code>w(n+1) = w(n) + μ * e(n) * x(n)</code></pre><table>
<thead>
<tr>
<th>기호</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>w(n)</td>
<td>필터 계수</td>
</tr>
<tr>
<td>μ</td>
<td>학습률 (step size)</td>
</tr>
<tr>
<td>e(n)</td>
<td>error signal</td>
</tr>
<tr>
<td>x(n)</td>
<td>입력 신호</td>
</tr>
</tbody></table>
<p>즉, error가 줄어드는 방향으로 필터 계수를 계속 업데이트한다.</p>
<p>FxLMS (Filtered-x LMS)</p>
<p>ANC에서 가장 많이 사용되는 알고리즘이다.</p>
<p>핵심 아이디어</p>
<p>Secondary Path를 고려해서 입력을 먼저 필터링한다.</p>
<pre><code>
x&#39;(n) = S(z) * x(n)</code></pre><p>업데이트</p>
<pre><code>w(n+1) = w(n) + μ * e(n) * x&#39;(n)</code></pre><p>즉, FxLMS = LMS + 음향 경로 보정
대부분의 ANC 연구와 제품은 FxLMS 기반이다.</p>
<h2 id="feedforward-vs-feedback-anc">Feedforward vs Feedback ANC</h2>
<p>ANC 구조는 크게 두 가지다.</p>
<p><strong>Feedforward ANC</strong> : 소음을 미리 측정한다.</p>
<pre><code>Noise → Reference mic → Controller → Speaker</code></pre><p>장점 - 저주파 소음 제거에 강함
단점 - reference mic 필요</p>
<hr>
<p><strong>Feedback ANC</strong></p>
<p>error mic만 사용한다.</p>
<pre><code>Speaker → Error mic → Controller</code></pre><p>장점 - 구조 단순
단점 - 안정성 문제 가능</p>
<hr>
<p><strong>Hybrid ANC</strong>
실제 제품은 대부분 Feedforward + Feedback 구조를 사용한다.</p>
<p>실제 이어폰 ANC 구조</p>
<p>예를 들어 AirPods 같은 제품은 다음 구조를 사용한다.</p>
<p>구성</p>
<ul>
<li>외부 마이크</li>
<li>내부 마이크</li>
<li>DSP</li>
<li>스피커</li>
</ul>
<h4 id="신호-흐름">신호 흐름</h4>
<pre><code>외부 소음
   ↓
외부 마이크
   ↓
DSP
   ↓
스피커
   ↓
귀
   ↓
내부 마이크</code></pre><p>DSP는</p>
<p>소음 분석
anti-noise 생성
error correction</p>
<p>을 초당 수만 번 반복한다.</p>
<h2 id="최신-anc-기술">최신 ANC 기술</h2>
<p>최근 ANC 시스템은 단순 FxLMS만 사용하지 않는다.</p>
<p><strong>Multi-band ANC</strong></p>
<p>주파수 대역별로 ANC 수행</p>
<pre><code>Low band ANC
Mid band ANC
High band ANC</code></pre><p><strong>Directional Filtering</strong></p>
<p>여러 마이크로 소리 방향을 계산</p>
<pre><code>Mic array → sound direction</code></pre><p>이를 Beamforming이라고 한다.</p>
<p><strong>ML Noise Classifier</strong></p>
<p>AI가 소음 종류를 분류한다.</p>
<p>자동차
사람 목소리
엔진</p>
<p>그리고 ANC 강도를 조절한다.</p>
<h2 id="anc의-한계">ANC의 한계</h2>
<p>ANC는 모든 소음을 제거하지 못한다.</p>
<p>특히 고주파 소음은 어렵다.</p>
<p>파장 공식</p>
<pre><code>λ = c / f</code></pre><table>
<thead>
<tr>
<th>주파수</th>
<th>파장</th>
</tr>
</thead>
<tbody><tr>
<td>100 Hz</td>
<td>3.4 m</td>
</tr>
<tr>
<td>1000 Hz</td>
<td>34 cm</td>
</tr>
</tbody></table>
<p>고주파는</p>
<p>파장이 짧고</p>
<p>위상 오차에 민감하다.</p>
<p>그래서 ANC는 보통</p>
<pre><code>20Hz ~ 500Hz</code></pre><p>영역에서 가장 효과적이다.</p>
<h2 id="헤드보드-anc에서-중요한-포인트">헤드보드 ANC에서 중요한 포인트</h2>
<p>헤드보드 ANC는 이어폰 ANC와 다른 문제가 있다.</p>
<p><strong>공간이 크다</strong></p>
<p>이어폰</p>
<pre><code>speaker → ear</code></pre><p>헤드보드</p>
<pre><code>speaker → 공기 → 머리 위치</code></pre><p><strong>Multi-channel ANC 필요</strong>
여러 스피커</p>
<pre><code>Spk1 Spk2 Spk3</code></pre><p>여러 마이크</p>
<pre><code>Mic1 Mic2 Mic3</code></pre><p>즉</p>
<p>Multi-channel ANC</p>
<p>Secondary Path 변화</p>
<p>사람이 움직이면</p>
<pre><code>S(z) 변화</code></pre><p>그래서 adaptive control이 중요하다.</p>
<h2 id="정리">정리</h2>
<p>ANC 시스템의 핵심은 다음 네 가지다.</p>
<p>파동 상쇄 (Destructive interference)
적응 필터 (LMS / FxLMS)
음향 전달 경로 모델링
실시간 DSP 제어</p>
<p>실제 제품에서는</p>
<pre><code>Adaptive filter
+ Multi-band processing
+ Beamforming
+ ML noise detection</code></pre><p>같은 기술이 함께 사용된다.</p>
<h2 id="마무리">마무리</h2>
<p>다음 글에서는 실제로 만들고 있는</p>
<p>헤드보드 ANC 시스템 아키텍처
코골이 주파수 분석
코골이 특화 ANC 알고리즘</p>
<p>을 정리해볼 예정이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[??? : ㅋㅋ SQL 왜 배우냐? ORM 쓰면 되는데]]></title>
            <link>https://velog.io/@bssm_woals/%E3%85%8B%E3%85%8B-SQL-%EC%99%9C-%EB%B0%B0%EC%9A%B0%EB%83%90-ORM-%EC%93%B0%EB%A9%B4-%EB%90%98%EB%8A%94%EB%8D%B0-9f5px814</link>
            <guid>https://velog.io/@bssm_woals/%E3%85%8B%E3%85%8B-SQL-%EC%99%9C-%EB%B0%B0%EC%9A%B0%EB%83%90-ORM-%EC%93%B0%EB%A9%B4-%EB%90%98%EB%8A%94%EB%8D%B0-9f5px814</guid>
            <pubDate>Mon, 19 May 2025 15:25:03 GMT</pubDate>
            <description><![CDATA[<h2 id="배경">배경</h2>
<p>&quot;??? : ㅋㅋ SQL 왜 배우냐? ORM 쓰면 되는데&quot; 이렇게 말하는 거 들어보시지 않으셨나요? 저희들이 농담삼아 많이 하는 말인데요. 이러면서 누구에게 가르쳐 줄 때는 SQL을 반드시 배우라고 조언을 해줍니다. 그럼 왜 그런지 알아보도록 하겠습니다.</p>
<h2 id="sql과-orm">SQL과 ORM</h2>
<h3 id="sql이란">SQL이란?</h3>
<blockquote>
<p>Structured Query Language의 약자로,  <strong>관계형 데이터베이스 관리 시스템(RDBMS)</strong>의 데이터를 관리하기 위해 설계한 특수 목적의 <strong>프로그래밍 언어</strong>입니다.</p>
</blockquote>
<h3 id="orm이란">ORM이란?</h3>
<blockquote>
<p>Object Relational Mapping의 약자로, 객체와 데이터베이스를 연결해 주는 도구입니다. 프로그래밍 언어의 객체와 관계형 데이터베이스 사이의 중계자 역할을 합니다.</p>
</blockquote>
<h3 id="orm의-장점은">ORM의 장점은?</h3>
<ol>
<li>객체 지향적인 코드로 인해 더 직관적이고 비즈니스 로직에 더 집중할 수 있게 도와준다.</li>
<li>SQL Query가 아닌 메서드로 데이터를 조작할 수 있어 개발자가 객체 모델로 프로그래밍하는 데 집중할 수 있도록 도와준다.</li>
</ol>
<h3 id="orm의-단점과-한계점은">ORM의 단점과 한계점은?</h3>
<ol>
<li><p>성능 문제 - 비효율적인 쿼리를 날릴 수 있습니다. N + 1 문제와 같이 연관 데이터를 조회할 때, 불필요하게 많은 쿼리가 실행되어 성능 저하를 일으킬 수 있습니다.</p>
</li>
<li><p>런타임 오류 가능성 - ORM은 내부적으로 SQL을 자동 생성하므로, 직접적인 SQL 작성이 줄어드는 대신 컴파일 시점에 쿼리 오류를 확인하기 어렵습니다.</p>
</li>
<li><p>ORM 프레임워크 자체의 복잡성 - ORM은 초보자에게는 SQL 쓰지 않아 쉽게 느껴질 수 있지만, 고급 기능을 제대로 이해하지 못하면 버그나 성능 저하를 발생시킵니다.</p>
</li>
</ol>
<h3 id="orm만-사용하면-어떻게-될까">ORM만 사용하면 어떻게 될까?</h3>
<p>편리해 보이지만, 복잡한 통계 쿼리, 성능이 중요한 대용량 조회 등에서는 오히려 비효율적인 방식이 될 수 있습니다.</p>
<h3 id="sql만-사용하면-어떻게-될까">SQL만 사용하면 어떻게 될까?</h3>
<p>쿼리 작성시에 오류를 즉각 알 수 있고, 성능을 최적화 할 수 있습니다. 하지만 기본적인 CURD 작업에도 일일이 쿼리를 작성해야 한다는 단점이 생산성을 저하시키고 코드 중복을 초래할 수 있습니다.</p>
<h2 id="sql과-orm을-균형있게-활용하자">SQL과 ORM을 균형있게 활용하자</h2>
<p>ORM이 이러한 문제를 가졌기에 ORM과 SQL을 같이 사용하며 성능 문제, 런타임 오류 가능성과 같은 문제점을 해결할 수 있다.</p>
<h3 id="균형있게-사용하기-위한-방법">균형있게 사용하기 위한 방법</h3>
<ol>
<li>복잡한 쿼리나 성능이 중요한 부분은 SQL로 처리합니다.</li>
<li>간단한 CURD, 혹은 성능이 중요하지 않은 부분은 ORM으로 처리해도 됩니다.</li>
<li>ORM과 SQL을 같이 사용합니다. 보통 ORM은 직접 쿼리를 날리는 기능도 메서드로 구현되어 있어 직접 SQL을 실행할 수 있습니다.</li>
</ol>
<h3 id="글을-쓴-나의-생각">글을 쓴 나의 생각</h3>
<p>저는 Spring의 ORM인 JPA를 사용해 본 경험이 있습니다.
초기에는 어떻게 메서드를 정의해야 할지 몰라 CustomRepository를 직접 만들어 쓰기도 했고, JPQL 메서드 이름을 잘못 작성해서 원하는 쿼리가 생성되지 않아 한참을 헤맨 적도 있습니다.
특히 update를 할 때, JPA가 해당 레코드를 삭제하고 새로 생성해버리는 바람에 오류가 났고, 그 원인을 찾느라 이틀 이상 고민했던 기억이 납니다.
이처럼 JPA는 분명 강력한 도구지만, 내부 동작을 정확히 이해하지 않으면 예상치 못한 버그로 이어질 수 있습니다.
결국 저는 SQL을 어느 정도 알고 있었기에 이 문제를 해결할 수 있었고,
그래서 지금도 복잡한 상황에서는 SQL을 더 선호합니다.</p>
<h4 id="참고자료">참고자료</h4>
<p><a href="https://ko.wikipedia.org/wiki/SQL">https://ko.wikipedia.org/wiki/SQL</a>
<a href="https://gmlwjd9405.github.io/2019/02/01/orm.html">https://gmlwjd9405.github.io/2019/02/01/orm.html</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이거 안 지키면 너도 🍝스파게티 코드 제조자?? (feat. SOLID)]]></title>
            <link>https://velog.io/@bssm_woals/SOLID%EB%A5%BC-%EC%9E%90%EB%B0%94%EB%9D%BC-4oe5iwal</link>
            <guid>https://velog.io/@bssm_woals/SOLID%EB%A5%BC-%EC%9E%90%EB%B0%94%EB%9D%BC-4oe5iwal</guid>
            <pubDate>Sun, 20 Apr 2025 13:14:25 GMT</pubDate>
            <description><![CDATA[<h2 id="⛹️발단">⛹️발단</h2>
<p>개발을 하다 보면 이런 말을 종종 듣을 수 있습니다.
<strong>&quot;유지보수가 어렵다&quot;</strong>, <strong>&quot;코드가 너무 얽혀 있다&quot;</strong>, <strong>&quot;기능 하나 수정했는데 다른 데서 에러가 난다&quot;</strong>는 문제를 자주 마주하게 됩니다. 
이럴 때 등장하는 것이 바로 <strong>SOLID 원칙</strong>입니다.</p>
<p><strong>SOLID</strong>는 객체지향 설계의 5가지 원칙을 뜻하며, 코드를 유지보수하기 쉽고 확장 가능하며 유연하게 만드는 데 도움을 줍니다.</p>
<hr>
<h1 id="🏆solid">🏆SOLID</h1>
<p><strong>SOLID</strong>는 5가지 원칙의 앞 글자를 딴 것입니다.</p>
<p><strong>S</strong> : <strong>단일 책임 원칙</strong> (Single Responsibility Principle)
<strong>O</strong> :    <strong>개방/폐쇄 원칙</strong> (Open/Closed Principle)
<strong>L</strong> : <strong>리스코프 치환 원칙</strong> (Liskov Substitution Principle)
<strong>I</strong> : <strong>인터페이스 분리 원칙</strong> (Interface Segregation Principle)
<strong>D</strong> : <strong>의존 역전 원칙</strong> (Dependency Inversion Principle)</p>
<hr>
<h2 id="s--단일-책임-원칙">S:  단일 책임 원칙</h2>
<pre><code>&quot;한 클래스는 하나의 책임만 가져야 한다.&quot;</code></pre><ul>
<li>즉, 클래스는 하나의 기능 또는 역할만 가져야 하며, 하나의 변경 이유만 있어야 합니다.</li>
</ul>
<h3 id="❌-위반-예시">❌ 위반 예시</h3>
<pre><code class="language-java">@Service
public class UserService {

    private static final Logger logger = LoggerFactory.getLogger(UserService.class);

    public static UserService createUserService() {
        logger.info(&quot;UserService 인스턴스 생성됨&quot;);
        return new UserService();
    }

    public void registerUser(String username) {
        logger.info(&quot;유저 등록 시도: {}&quot;, username);
        // 사용자 저장 로직 (예: DB 저장)
        System.out.println(username + &quot; 등록 완료&quot;);
    }

    public void sendWelcomeEmail(String username) {
        logger.info(&quot;환영 이메일 전송: {}&quot;, username);
        // 이메일 전송 로직
        System.out.println(&quot;환영 이메일 전송 완료: &quot; + username);
    }
}</code></pre>
<h4 id="문제점">문제점</h4>
<p>UserService가 너무 많은 책임을 가지고 있습니다</p>
<ol>
<li>인스턴스 생성 로직 (팩토리 메서드)</li>
<li>유저 등록 (비즈니스 로직)</li>
<li>이메일 전송 (외부 시스템 연동)</li>
<li>로깅 (로깅도 일종의 부가 책임)</li>
</ol>
<h3 id="✅개선한-예시">✅개선한 예시</h3>
<pre><code class="language-java">// 도메인 객체
class User {
    private String username;

    public User(String username) {
        this.username = username;
    }

    public String getUsername() {
        return username;
    }
}

// 이메일 전송 책임
@Service
class EmailService {
    private final Logger logger = LoggerFactory.getLogger(EmailService.class);

    public void sendWelcomeEmail(User user) {
        logger.info(&quot;환영 이메일 전송: {}&quot;, user.getUsername());
        // 실제 이메일 전송 코드
        System.out.println(&quot;환영 이메일 전송 완료: &quot; + user.getUsername());
    }
}

// 유저 등록 로직
@Service
class UserService {
    private final EmailService emailService;
    private final Logger logger = LoggerFactory.getLogger(UserService.class);

    public UserService(EmailService emailService) {
        this.emailService = emailService;
    }

    public void registerUser(User user) {
        logger.info(&quot;유저 등록 시도: {}&quot;, user.getUsername());
        // DB 저장 로직
        System.out.println(user.getUsername() + &quot; 등록 완료&quot;);

        emailService.sendWelcomeEmail(user);
    }
}

// 팩토리 메서드 책임 분리
@Component
class UserServiceFactory {
    private final EmailService emailService;

    public UserServiceFactory(EmailService emailService) {
        this.emailService = emailService;
    }

    public UserService create() {
        Logger logger = LoggerFactory.getLogger(UserServiceFactory.class);
        logger.info(&quot;UserService 인스턴스 생성됨&quot;);
        return new UserService(emailService);
    }
}</code></pre>
<h4 id="요약">요약</h4>
<p>User: 도메인 모델
EmailService: 알림 전송 책임
UserService: 유저 등록 비즈니스 로직
UserServiceFactory: 팩토리 역할만 담당
Logger는 각 클래스 안에서 자기 책임 범위 내에서만 사용</p>
<hr>
<h3 id="o--개방폐쇄-원칙">O:  개방/폐쇄 원칙</h3>
<pre><code>“소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.”</code></pre><ul>
<li>기존 코드를 수정하지 않고 기능을 확장할 수 있어야 합니다.</li>
</ul>
<h3 id="❌-위반-예시-1">❌ 위반 예시</h3>
<pre><code class="language-java">@Service
public class PaymentService {
    public void pay(String paymentType, int amount) {
        if (paymentType.equals(&quot;card&quot;)) {
            System.out.println(&quot;카드로 &quot; + amount + &quot;원 결제합니다.&quot;);
        } else if (paymentType.equals(&quot;kakao&quot;)) {
            System.out.println(&quot;카카오페이로 &quot; + amount + &quot;원 결제합니다.&quot;);
        } else if (paymentType.equals(&quot;naver&quot;)) {
            System.out.println(&quot;네이버페이로 &quot; + amount + &quot;원 결제합니다.&quot;);
        } else {
            System.out.println(&quot;지원하지 않는 결제 수단입니다.&quot;);
        }
    }
}
</code></pre>
<h4 id="문제점-1">문제점</h4>
<ol>
<li>결제 수단 결정 로직 (if-else 조건 분기) </li>
<li>결제 실행 로직 (각 결제 방식의 세부 동작 수행)</li>
<li>결제 수단 추가/확장 처리 (OCP 위반의 핵심)</li>
</ol>
<h3 id="✅개선한-예시-1">✅개선한 예시</h3>
<pre><code class="language-java">// 결제 전략 인터페이스
public interface PaymentStrategy {
    boolean supports(String paymentType);  // 어떤 타입인지 판별
    void pay(int amount);                  // 결제 실행
}

// 카드 결제 구현체
public class CardPayment implements PaymentStrategy {
    @Override
    public boolean supports(String paymentType) {
        return paymentType.equals(&quot;card&quot;);
    }

    @Override
    public void pay(int amount) {
        System.out.println(&quot;💳 카드로 &quot; + amount + &quot;원 결제합니다.&quot;);
    }
}

// 카카오페이 결제 구현체
public class KakaoPayment implements PaymentStrategy {
    @Override
    public boolean supports(String paymentType) {
        return paymentType.equals(&quot;kakao&quot;);
    }

    @Override
    public void pay(int amount) {
        System.out.println(&quot;📱 카카오페이로 &quot; + amount + &quot;원 결제합니다.&quot;);
    }
}

// 결제 서비스
public class PaymentService {
    private final Map&lt;String, PaymentStrategy&gt; strategyMap;

    public PaymentService(List&lt;PaymentStrategy&gt; strategies) {
        this.strategyMap = new HashMap&lt;&gt;();
        for (PaymentStrategy strategy : strategies) {
            strategyMap.put(strategy.getType(), strategy);
        }
    }

    public void pay(String paymentType, int amount) {
        PaymentStrategy strategy = strategyMap.get(paymentType);
        if (strategy != null) {
            strategy.pay(amount);
        } else {
            System.out.println(&quot;🚫 지원하지 않는 결제 수단입니다: &quot; + paymentType);
        }
    }
}
</code></pre>
<h4 id="요약-1">요약</h4>
<ol>
<li>개방/폐쇄 원칙에 따라 기존 코드를 변경하지 않고 확장할 수 있어야 합니다</li>
<li>조건문 기반으로 결제 수단을 처리하는 대신 전략 패턴을 사용하여 결제 방식의 유연한 확장을 가능하게 했습니다</li>
</ol>
<hr>
<h2 id="l--리스코프-치환-원칙">L:  리스코프 치환 원칙</h2>
<pre><code>“프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.”</code></pre><ul>
<li>상속받은 클래스(자식 클래스)는 부모 클래스의 행위를 일관성 있게 유지해야 합니다.</li>
</ul>
<h3 id="❌-위반-예시-2">❌ 위반 예시</h3>
<pre><code class="language-java">// 부모 클래스: 직사각형
public class Rectangle {
    protected int width;
    protected int height;

    public void setWidth(int width) {
        this.width = width;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getArea() {
        return width * height;
    }
}

// 자식 클래스: 정사각형
public class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        this.width = width;
        this.height = width; // 정사각형은 너비와 높이가 항상 같아야 하니까
    }

    @Override
    public void setHeight(int height) {
        this.width = height; // 정사각형은 높이도 같게 맞춤
        this.height = height;
    }
}</code></pre>
<h4 id="문제점-2">문제점</h4>
<ol>
<li>Rectangle을 상속받은 Square는 setWidth()와 setHeight()가 서로 영향을 줘 Rectangle을 훼손시킵니다</li>
<li>getArea()에서 Rectangle은 width와 height가 서로 독립되어 있다고 생각했지만, Square에서는 그렇게 되지 않고 있습니다</li>
</ol>
<h3 id="✅개선한-예시-2">✅개선한 예시</h3>
<pre><code class="language-java">public interface Shape {
    int getArea();
}

// 직사각형
public class Rectangle implements Shape {
    private int width;
    private int height;

    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public int getArea() {
        return width * height;
    }
}

// 정사각형
public class Square implements Shape {
    private int side;

    public Square(int side) {
        this.side = side;
    }

    @Override
    public int getArea() {
        return side * side;
    }
}</code></pre>
<h4 id="요약-2">요약</h4>
<p>Rectangle과 Square의 상속관계에서 Shape과 Rectangle, Square과 Shape의 구현체로 변경해 서로의 특징을 보장할 수 있습니다.</p>
<hr>
<h2 id="i--인터페이스-분리-원칙">I:  인터페이스 분리 원칙</h2>
<pre><code>“특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.”</code></pre><h3 id="❌-위반-예시-3">❌ 위반 예시</h3>
<pre><code class="language-java">public interface Machine {
    void print(Document doc);
    void scan(Document doc);
    void fax(Document doc);
}

// 단순 프린터
public class SimplePrinter implements Machine {
    public void print(Document doc) {
        System.out.println(&quot;프린트 완료&quot;);
    }

    public void scan(Document doc) {
        throw new UnsupportedOperationException(&quot;스캔 기능 없음&quot;);
    }

    public void fax(Document doc) {
        throw new UnsupportedOperationException(&quot;팩스 기능 없음&quot;);
    }
}
</code></pre>
<h4 id="문제점-3">문제점</h4>
<p>SimplePrinter는 프린트 기능만 필요한데도 스캔, 팩스 메서드를 구현해야 합니다 (불필요한 의존성 발생)
변경이 있을 경우, 쓰지 않는 메서드 때문에 부작용이 생길 수도 있습니다</p>
<h3 id="✅개선한-예시-3">✅개선한 예시</h3>
<pre><code class="language-java">public interface Printer {
    void print(Document doc);
}

public interface Scanner {
    void scan(Document doc);
}

public interface Fax {
    void fax(Document doc);
}

public class SimplePrinter implements Printer {
    public void print(Document doc) {
        System.out.println(&quot;프린트 완료&quot;);
    }
}

public class MultiFunctionPrinter implements Printer, Scanner, Fax {
    public void print(Document doc) { /* 구현 */ }
    public void scan(Document doc) { /* 구현 */ }
    public void fax(Document doc) { /* 구현 */ }
}
</code></pre>
<h4 id="요약-3">요약</h4>
<p>불필요한 메서드 강제 구현을 피하기 위해 인터페이스를 기능별로 분리</p>
<hr>
<h2 id="d--의존-역전-원칙">D:  의존 역전 원칙</h2>
<pre><code>프로그래머는 “추상화에 의존해야지, 구체화에 의존하면 안된다.”</code></pre><ul>
<li><p>고수준 모듈은 저수준 모듈의 구현에 의존해서는 안 됩니다. 대신 저수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야 합니다.</p>
<h3 id="❌-위반-예시-4">❌ 위반 예시</h3>
<pre><code class="language-java">public class Kid {
private Robot toy;

public Kid(Robot toy) {
    this.toy = toy;
}

public void play() {
    toy.activate();
}
}
</code></pre>
</li>
</ul>
<p>public class Robot {
    public void activate() {
        System.out.println(&quot;🤖 로봇이 움직입니다!&quot;);
    }
}</p>
<pre><code>#### 문제점
Kid가 Lego를 장난감으로 삼는다면 Kid코드를 직접 고쳐야하는 문제가 발생합니다. 구체화에 의존하고 있음 → DIP 위반

### ✅개선한 예시
```java
// 추상화
public interface Toy {
    void play();
}

// 구체화 1
public class Robot implements Toy {
    public void play() {
        System.out.println(&quot;🤖 로봇이 움직입니다!&quot;);
    }
}

// 구체화 2
public class Lego implements Toy {
    public void play() {
        System.out.println(&quot;🧱 레고로 탑을 쌓습니다!&quot;);
    }
}

// 고수준 모듈
public class Kid {
    private Toy toy;

    public Kid(Toy toy) {
        this.toy = toy;
    }

    public void play() {
        toy.play();
    }
}
</code></pre><h4 id="요약-4">요약</h4>
<p>Kid는 Toy라는 추상화에만 의존하므로, 어떤 장난감을 쓰든 Kid 코드를 수정할 필요 없게 됩니다
즉, 새로운 장난감이 생겨도 쉽게 확장 가능하게 되었습니다</p>
<hr>
<h2 id="✨-solid-원칙을-적용하면-어떤-이점이-있을까">✨ SOLID 원칙을 적용하면 어떤 이점이 있을까?</h2>
<p>1.유지보수가 쉬워집니다
하나 고치면 연쇄적으로 에러가 나는 현상 줄어듭니다.</p>
<p>2.새로운 기능 추가가 편해집니다
기존 코드 건드릴 필요 없이 확장할 수 있습니다.</p>
<p>3.협업이 쉬워집니다
각 클래스/모듈의 역할이 분명해서 다른 사람이 코드를 봐도 이해하기 쉬워집니다.</p>
<p>4.테스트 코드 작성이 쉬워집니다
의존성 분리 덕분에 단위 테스트나 Mocking이 쉬워집니다.</p>
<h2 id="🏌️마무리하며">🏌️마무리하며</h2>
<p>SOLID 원칙은 무조건 지켜야 하는 법칙은 아니지만, 객체지향 설계를 더 튼튼하고 유연하게 만들 수 있는 가이드라인입니다.
처음엔 어렵게 느껴질 수도 있지만, 점점 프로젝트를 진행하면서 &quot;아, 그래서 이게 필요했구나!&quot; 하고 체감하게 됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발을 자바라]]></title>
            <link>https://velog.io/@bssm_woals/%EC%9B%B9-%EA%B0%9C%EB%B0%9C%EC%9D%84-%EC%9E%90%EB%B0%94%EB%9D%BC</link>
            <guid>https://velog.io/@bssm_woals/%EC%9B%B9-%EA%B0%9C%EB%B0%9C%EC%9D%84-%EC%9E%90%EB%B0%94%EB%9D%BC</guid>
            <pubDate>Sun, 13 Apr 2025 23:11:54 GMT</pubDate>
            <description><![CDATA[<h2 id="implicit과-explicit">Implicit과 Explicit</h2>
<h3 id="implicit--묵시적인">Implicit : 묵시적인</h3>
<p>Implicit하게 코드를 짜는 것은 생략해서 간결하게 작성하는 것이다.
예시)</p>
<pre><code class="language-java">// Implicit
name = name;

// Implicit
int b = 1;
double a = b; // 자동 형변환 (double)이 생략됨</code></pre>
<h3 id="explicit--명시적인">Explicit : 명시적인</h3>
<p>Explicit하게 코드를 짜는 것은 모든 과정을 명확하게 드러내며 작성하는 것이다.
예시)</p>
<pre><code class="language-java">// Explicit
this.name = name;

// Explicit
int b = 1;
double a = (double) b;</code></pre>
<h4 id="implicit한-코드의-장점">Implicit한 코드의 장점</h4>
<ul>
<li>짧고 간단합니다.</li>
<li>개발하기 편합니다.</li>
</ul>
<h4 id="implicit한-코드의-단점">Implicit한 코드의 단점</h4>
<ul>
<li>협업 시 내 코드가 다른 사람이 봤을 때 이해하기 어려움</li>
</ul>
<h4 id="explicit한-코드의-장점">Explicit한 코드의 장점</h4>
<ul>
<li>코드가 수행하는 게 무엇인지 명확히 드러남.</li>
<li>코드를 봤을 때 이해하기 쉬움.</li>
</ul>
<h4 id="explicit한-코드의-단점">Explicit한 코드의 단점</h4>
<ul>
<li>다 명시적으로 써 놓아서 코드가 한도끝도 없이 길어질 수 있음.</li>
</ul>
<h4 id="요약">요약</h4>
<p>&quot;Implicit은 나에게는 편하지만, 다른 사람에겐 불친절할 수 있음&quot;
&quot;Explicit은 길더라도, 의도를 명확히 드러내므로 협업에 적합&quot;</p>
<h3 id="언제-implicit-언제-explicit을-써야할까">언제 Implicit 언제 Explicit을 써야할까?</h3>
<p>개인 프로젝트나 빠른 개발: Implicit도 OK<del>저축은행</del>
협업, 오픈소스, 유지보수 고려 시: Explicit 추천</p>
<h3 id="마무리">마무리</h3>
<p>Implicit과 Explicit은 상황에 따라 적절히 선택해야 한다.
하지만 협업을 하거나, 명확한 코드가 필요한 경우에는 Explicit이 더 안전한 선택이다.</p>
<h2 id="웹서버">웹서버</h2>
<p>웹서버는 정적 파일(HTML, CSS)을 제공하는 서버로 데이터를 처리하지 않는 서버입니다.
주로 사용되는 것은 Apache, Nginx 등이 있습니다.</p>
<h2 id="was">WAS</h2>
<p>WAS는 웹 페이지도 만들어주기는 하지만 API 서버 역할도 수행합니다.</p>
<ul>
<li>API 서버: 클라이언트에게 데이터(JSON) 를 반환</li>
<li>대표 예: Spring MVC에서 @RestController</li>
</ul>
<h2 id="rest-api">Rest API</h2>
<p>API 서버 내부에 존재하면서 REST 방식으로 자원을 주고받는데 이 때 REST 방식이란 자원과 동작을 분리하여 HTTP 메서드(GET, POST 등)로 동작을 표현하는 방식이다.</p>
<pre><code>예시)
동작    메서드    예시 경로
조회    GET           /사실
생성    POST       /밥
수정    PUT           /유후/1
삭제    DELETE      /잠만/1
-&gt; GET /사실 → 사용자 목록 조회
POST /밥 → 새로운 사용자 추가
PUT /유후/1 → 1번 사용자 정보 수정
DELETE /잠만/1 → 1번 사용자 삭제</code></pre><h2 id="restful-api">Restful API</h2>
<p>Restful API는 REST의 원칙을 더 철저히 지킨 API이다.
URL, 자원 표현 방식, 응답까지 REST스럽게 구성해야 한다.</p>
<ul>
<li>URL에는 동사가 들어가지 않고, 명사만 들어간다. 보통 복수형으로 사용.</li>
<li>상황에 맞는 HTTP 상태 코드를 반환</li>
<li>JSON 형식으로 반환</li>
<li>자원의 관계를 URL로 표현해 URL이 예측 가능하게 한다.</li>
</ul>
<p>넌 전혀 Restful하지 않아의 예)</p>
<pre><code>GET /getUser
POST /createUser </code></pre><p>Restful하게 고친 예)</p>
<pre><code>Get /users
Post /users</code></pre><p>REST API: REST 원칙을 조금 지키는 API
Restful API: REST 원칙을 철저히 지킨 API
→ 즉, Restful API는 &#39;넌 정말 Restful해. 넌 최고로 Restful해&#39;이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[네트워크를 자바라]]></title>
            <link>https://velog.io/@bssm_woals/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%8A%B8%EB%A5%BC-%EC%9E%90%EB%B0%94%EB%9D%BC</link>
            <guid>https://velog.io/@bssm_woals/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%8A%B8%EB%A5%BC-%EC%9E%90%EB%B0%94%EB%9D%BC</guid>
            <pubDate>Tue, 08 Apr 2025 00:40:34 GMT</pubDate>
            <description><![CDATA[<p>네트워크는 <strong>데이터를 주고받는 규약 등을 포함한 통신 과정 전체</strong>를 의미합니다.</p>
<h3 id="공인망과-사설망">공인망과 사설망</h3>
<p>데이터를 주고 받으려면 서로의 위치를 알아야 통신이 가능하지 않을까요?</p>
<p>이때 서로의 위치를 알려주는 게 IP인데요. IP에는 <strong>공인 IP</strong>와 <strong>사설 IP</strong>가 있습니다.
<strong>공인 IP</strong>는 전세계에서 유일한 IP입니다. 공인 IP는 전세계를 기준으로 어디 있는지 찾을 수 있는 고유 번호인셈이죠.
하지만 우리는 인구 80억 시대를 살아가고 있기 때문에 현재 IPv4(최대 IP 갯수 42억개)로는 할당하기 부족해서 나온게 IPv6와 사설망입니다. IPv6는 32개의 비트를 사용하는 IPv4와 달리 128개의 비트를 사용해 사실상 무한개로 볼 수 있습니다. 하지만 IPv6는 전세계적으로 보급되지 않아 호환성이 부족하기에 <strong>사설망</strong>을 사용합니다. 
앞서 공인 IP에 대해 이야기해 보았는데요 공인망에서는 공인 IP가 쓰이듯이 사설망에서 쓰이는 IP를 <strong>사설 IP</strong>라고 합니다. 
사설 IP가 어디 쓰이는 건진 알겠는데 사설망이랑 공인망은 뭐야? 라고 생각하실 것 같아 설명드리자면 공인망은 인터넷 상에서 직접 접근 가능한 망, 사설망은 외부(인터넷)에서는 직접 접근할 수 없는 망이라고 생각하시면 됩니다.
사설망은 보통 회사, 학교, 가정용 공유기에서 사용됩니다.
외부에선 접근이 안되고 내부에서만 접근이 가능하기에 보안에도 용이합니다.</p>
<h3 id="사설-ip주소-대역cidr">사설 IP주소 대역(CIDR)</h3>
<p>아래 이미지는 사설망의 IP대역입니다.  <img src="https://velog.velcdn.com/images/bssm_woals/post/bce8bdc3-002a-4e6f-a102-8130fd7a834b/image.png" alt=""></p>
<table>
<thead>
<tr>
<th align="left"><center>클래스</center></th>
<th align="center">사설 IP 대역</th>
<th align="center">CIDR 표기</th>
<th align="center">네트워크 수</th>
<th align="right"><center>호스트 수 </center></th>
</tr>
</thead>
<tbody><tr>
<td align="left">A</td>
<td align="center">10.0.0.0 ~ 10.255.255.255</td>
<td align="center">10.0.0.0/8</td>
<td align="center">1</td>
<td align="right">약 16,777,216</td>
</tr>
<tr>
<td align="left">B</td>
<td align="center">172.16.0.0 ~ 172.31.255.255</td>
<td align="center">172.16.0.0/12</td>
<td align="center">16</td>
<td align="right">약 1,048,576</td>
</tr>
<tr>
<td align="left">C</td>
<td align="center">192.168.0.0 ~ 192.168.255.255</td>
<td align="center">192.168.0.0/16</td>
<td align="center">256</td>
<td align="right">약 65,536</td>
</tr>
</tbody></table>
<h4 id="cidr-표기법은">CIDR 표기법은?</h4>
<p>CIDR(Classless Inter-Domain Routing) 표기법의 /숫자는 IP 주소에서 네트워크를 식별하는 비트 수를 나타냅니다.
10.0.0.0/8은 앞의 8비트(10)가 네트워크 주소라는 의미로, 남은 24비트를 호스트 주소로 사용할 수 있습니다.</p>
<h3 id="네트워크-인터페이스와-포트">네트워크 인터페이스와 포트</h3>
<p>네트워크 인터페이스는 네트워크 연결을 위한 컴퓨터나 장치의 출입구 역할을 하는 장치 입니다. IP는 여기에 할당되게 됩니다.
포트는 하나의 네트워크 인터페이스에 여러 애플리케이션이 돌아갈 때 어떤 애플리케이션에 접속할지를 구분하는 번호입니다.
컴퓨터에 랜선을 꼽으면 인터페이스에 IP가 할당되고 그 컴퓨터 속에 돌아가는 애플리케이션에 접근하려면 IP:포트번호로 접근할 수 있게 되는겁니다. 하지만 IP가 사설IP면 외부에서 접근할 수 없겠죠? ㅎ_ㅎ</p>
<h3 id="nat와-포트포워딩">NAT와 포트포워딩</h3>
<p><img src="https://velog.velcdn.com/images/bssm_woals/post/6bfd9703-d14b-4d5f-b0e6-9bd5b3d7e603/image.png" alt=""></p>
<h4 id="outbound-inbound">OutBound, InBound?</h4>
<p>OutBound는 내 서버에서 다른 서버에 요청을 보내는 것이고
InBound는 내 서버로 들어오는 요청을 받을 때이다.</p>
<p>OutBound 후에 오는 결과를 내 서버로 받아야 하기에 사설망을 사용중인 내 서버를 공인망과 연결해주는 역할을 하는 녀석이 필요한데 그 역할을 해주는게 바로 NAT 테이블이다. 하지만 이 NAT 테이블은 OutBound가 되야 기록되기에 그냥 InBound가 올때는 내 서버가 어디있는지 모른다. 그래서 나온게 포트포워딩이다.
<img src="https://velog.velcdn.com/images/bssm_woals/post/fbce6fa9-8cc5-4126-a895-975f47295107/image.png" alt="">
포트포워딩은 테이블에 공인포트번호를 지정해서 한자리를 먹어놓는다.(공인포트번호는 중복x)
이렇게 포트포워딩을 해놓으면 외부에서 얼마든지 접근할 수 있게 된다. 만약 같은 사설망을 사용하는게 아니라면 다른 사설망에 있는 서버에 접근하려면 이렇게 포트포워딩을 해줘야겠죠?</p>
<h3 id="dns">DNS</h3>
<p>DNS는 Domain name server의 약자로 Domain name과 Ip를 매핑해 가지고 있는 녀석이다. 
예시를 들어보면 naver로 접속하고 싶으면 url에 naver.com을 치면 DNS서버로 가서 DNS서버가 naver.com과 매핑되어있는 Ip를 반환해주기에 그냥 naver.com만 쳐도 naver에 들어갈 수 있는 것이다.</p>
<h3 id="도커-컨테이너-가상화">도커 컨테이너 가상화</h3>
<p>호스트 OS 위에서 격리된 컨테이너을 만들어 실행하는 기술이에요.
VM은 OS까지 가상화시키지만 docker는 프로세스만 격리해서 빠릅니다.</p>
<h4 id="가상-인터페이스">가상 인터페이스</h4>
<p>컨테이너가 인터넷 쓸 수 있도록 만들어주는 가짜 랜선
브릿지 네트워크를 docker0라하고 새로 생긴 컨테이너를 Veth1이라 하면 docker0와 연결되서 컨테이너끼리 통신, 외부와의 통신이 가능해지게 됩니다.</p>
<h3 id="도커-네트워크-명령어">도커 네트워크 명령어</h3>
<pre><code>docker network ls    # 네트워크 목록 확인
docker network inspect #    네트워크 상세 정보
docker network create    # 사용자 정의 네트워크 생성
docker network connect    # 컨테이너 네트워크 연결
docker network disconnect #    컨테이너 네트워크 연결 해제
docker network rm    # 네트워크 삭제
docker run --network    # 컨테이너 생성 시 네트워크 지정</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[빠른 CPU를 위한 설계 방법]]></title>
            <link>https://velog.io/@bssm_woals/%EB%B9%A0%EB%A5%B8-CPU%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%84%A4%EA%B3%84-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@bssm_woals/%EB%B9%A0%EB%A5%B8-CPU%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%84%A4%EA%B3%84-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Wed, 04 Dec 2024 10:05:12 GMT</pubDate>
            <description><![CDATA[<p>컴퓨터 부품들은 클럭 신호에 맞춰 움직이기에</p>
<h3 id="span-style--colorred클럭span"><span style = "color:red;">클럭</span></h3>
<p>첫 번째 방법은 <strong>클럭 신호를 빠르게 해주는 방법</strong>이 있습니다.
<strong>클럭 속도</strong>는 헤르츠(Hz) 단위로 측정합니다. Hz는 1초에 클럭이 몇 번 반복되는지 나타냅니다. 1초에 100번 반복되면 100Hz로 표현합니다.</p>
<p>그럼 클럭 속도만 많이 올리면 될까요?
*<em>무작정 속도만 올리게 된다면 발열 문제가 생깁니다. *</em>
클럭 속도를 올리는 건 빠른 CPU를 만들 순 있겠지만 이것만으로 CPU성능을 올리기엔 한계가 있습니다.</p>
<h3 id="span-style--colorred코어와-멀티코어span"><span style = "color:red;">코어와 멀티코어</span></h3>
<p>클럭 속도를 높이는 것 이외의 CPU성능을 올릴 수 있는 방법도 있습니다.
바로 <strong>CPU의 코어수와 스레드 수를 늘리는 방법</strong>이죠.</p>
<p>많은 전공 서적에서는 &quot;명령어를 실행하는 부품&quot;으로 하나만 존재했습니다. 
하지만 오늘날의 CPU는 많은 발전을 거듭했고, <strong>이젠 CPU내부에 &quot;명령어를 실행하는 부품&quot;을 얼마든지 만들 수 있게 되었습니다.</strong></p>
<p><strong>우리가 지금까지 CPU의 정의로 알고 있던 &quot;명령어를 실행하는 부품&quot;은 <span style ="color : red;">코어</span>라고 불립니다.</strong>
<strong>오늘날의 CPU는</strong> 단순히 &quot;명령어를 실행하는 부품&quot;에서 <strong>&quot;명령어를 실행하는 부품을 여러 개 포함하는 부품&quot;</strong>으로 확대되었습니다.
이런 CPU를 <span style = "color:red;"><strong>멀티코어 CPU</strong></span> 또는 <span style = "color:red;"><strong>멀티코어 프로세서</strong></span>라고 합니다.</p>
<p>CPU의 종류는 <strong>CPU안에 코어가 몇 개가 들어 있는지</strong>에 따라 아래와 같이 나뉩니다.<img src="https://velog.velcdn.com/images/bssm_woals/post/30346da2-24aa-48ee-bb24-570aed3911dc/image.png" alt="">
그럼 코어 수를 100개로 늘리면 100배 빨라질까요? 
<strong>안타깝게도 연산 속도와 코어 수는 비례하여 증가하지 않습니다.</strong> 학교에서 4인 1조를 한다고 생각해 볼까요? 모두 똑같이 참여해 한 사람이 생성해 낼 수 있는 생산성의 4배에 가까운 결과를 내는 경우도 있으나, 그렇지 않은 경우가 더 많습니다. </p>
<p><strong>중요한 것은 코어마다 처리할 명령어를 얼마나 적절히 분배하느냐이고 그에 따라 연산 속도는 크게 달라집니다.</strong></p>
<h3 id="span-style--colorred스레드span"><span style = "color:red;">스레드</span></h3>
<p>스레드의 사전적 의미는 &#39;실행 흐름의 단위&#39;입니다. 하지만 CPU의 스레드와 프로그래밍에서의 스레드는 용도가 다르기에 더욱 엄밀히 이해해야 합니다.</p>
<p>스레드는 CPU에서 쓰이는 <span style = "color:red;"><strong>하드웨어적 스레드</strong></span>, 
프로그래밍에서 쓰이는 <span style = "color:red;"><strong>소프트웨어적 스레드</strong></span>가 있습니다.</p>
<h4 id="span-style--colorred하드웨어적-스레드span"><span style = "color:red;">하드웨어적 스레드</span></h4>
<p> <strong>하나의 코어가 동시에 처리하는 명령어 단위</strong>를  의미합니다. 
 지금까지 배운 것은 1코어 1스레드였습니다. 즉 명령어를 실행하는 부품이 하나 있고, 한 번에 하나씩 명령어를 실행하는 CPU였죠.
 반면 여러 스레드를 지원하는 CPU는 <strong>하나의 코어로도 여러 개의 명령어를 동시에 실행</strong>할 수 있습니다. 
 예를 들어 2코어 4스레드 CPU는 명령어를 실행하는 부품이 두 개고, 한번에 4개의 명령어를 처리할 수 있는 CPU를 의미합니다. 이처럼 코어 하나로 여러 명령어를 동시에 처리하는 CPU를 <span style = "color:red;"><strong>멀티스레드 프로세서</strong></span> 또는 <span style = "color:red;"><strong>멀티스레드 CPU</strong></span>라고 합니다.</p>
<h4 id="span-style--colorred소프트웨어적-스레드span"><span style = "color:red;">소프트웨어적 스레드</span></h4>
<p> <strong>하나의 프로그램에서 독립적으로 실행되는 단위</strong>를 의미합니다.
 하나의 프로그램이 실행되는 과정에서 한 부분만 실행될 수 있지만, 프로그램의 여러 부분이 동시에 실행될 수도 있습니다.</p>
<p> 만약 워드 프로세서 프로그램을 개발한다고 하고, 아래의 기능을 동시에 수행하고 싶어 한다고 생각해 봅시다.</p>
<ul>
<li>사용자로부터 입력받은 내용을 화면에 띄워주는 기능</li>
<li>사용자가 입력한 내용의 맞춤법이 맞는지 검사하는 기능</li>
<li>사용자가 입력한 내용을 수시로 저장하는 기능</li>
</ul>
<p>이 기능들을 작동시키는 코드를 각각의 스레드로 만들면 동시에 실행할 수 있습니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[해싱(Hashing) 이란?]]></title>
            <link>https://velog.io/@bssm_woals/%ED%95%B4%EC%8B%B1Hashing-%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@bssm_woals/%ED%95%B4%EC%8B%B1Hashing-%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Wed, 20 Nov 2024 06:46:34 GMT</pubDate>
            <description><![CDATA[<h3 id="해싱hashing">해싱(Hashing)</h3>
<blockquote>
<p>입력값에 수학적 알고리즘(해시 함수)을 적용하여 고정된 크기의 문자열을 출력하는 과정</p>
</blockquote>
<p>키에 대한 연산에 의해 직접 접근이 가능한 구조를 <strong>해시 테이블</strong>이라 부르며, <strong>해시 테이블을 이용한 탐색을 해싱이라 한다</strong>.
<img src="https://velog.velcdn.com/images/bssm_woals/post/37e82c4d-e96d-49ec-8a17-7cde8eb28340/image.png" alt=""></p>
<h3 id="충돌-collision">충돌 (Collision)</h3>
<blockquote>
<p>서로 다른 키를 갖는 항목들이 같은 해시주소를 가지는 현상이다.</p>
</blockquote>
<h3 id="오버플로우-overflow">오버플로우 (overflow)</h3>
<blockquote>
<p> 해시 주소에 더이상 빈 버킷이 남아 있지 않으면 발생한다. 
오버플로우는 충돌이 발생하고, 오버플로우가 발생하면 해시테이블에 항목을 더 이상 저장하는 것이 불가능해진다.</p>
</blockquote>
<p><strong>개방주소법(open addressing)</strong>
: 현재 사용되고 있지 않은 공간을 찾아 저장하는 방법. 빈 공간을 효율적으로 찾는 것이 중요하다. 충돌이 일어났을 때 대처하는 방법에 따라 <strong>선형 조사법, 이차 조사법, 이중 해싱법, 체인법</strong> 등이 존재한다. <br></p>
<ol>
<li><strong>선형 조사법(linear probing)</strong></li>
</ol>
<p>선형조사법 조사되는 위치:
h(k), (h(k)+1) % 해시테이블 크기,(h(k)+2) % 해시테이블 크기.     (h(k)+3) % 해시테이블 크기 (h(k)+4) % 해시테이블 크기
문제점 : clustering 문제(비선형법) -&gt; 한 번 충돌이 시작 시, 그 위치에 집중되는 현상</p>
<p><img src="https://velog.velcdn.com/images/bssm_woals/post/a71a9479-1c3e-4c8e-b3e2-f1f1fe975f3d/image.png" alt=""></p>
<p>2.** 이차 조사법 (quadratic probing)**
  <div>
   이차조사법 조사되는 위치 <br></p>
<ul>
<li><p>h(k), (h(k)+1) % 해시테이블 크기,(h(k)+4) % 해시테이블 크기. (h(k)+9) % 해시테이블 크기, (h(k)+16) % 해시테이블 크기 <br> <br>
문제점 : 2차 클러스터링 문제 -&gt; 집중되어지는 slot을 중심으로 클러스터링이 발생될 확률이 여전히 높다. 또한 오버플로우 처리과정에서 불필요한 data의 탐색을 하기에 탐색효율성이 떨어진다. 이중해싱법/ 체인법을 사용하여 해결할 수 있다.</p>
</div>
</li>
<li><p><em>이차조사법에서 2를 삽입하고자 할 때*</em>
<img src="https://velog.velcdn.com/images/bssm_woals/post/cb38c575-5a5e-43b4-90a9-50048e573545/image.png" alt=""></p>
</li>
</ul>
<ol start="3">
<li>** 이중 해싱법 (double hashing) **<br></li>
</ol>
<div>
          이중해싱법 조사되는 위치<br>
            h(k), (h(k)+1*h'(k))%해시테이블 크기(h(k)+2*h'(k))%해시테이블 크기.(h(k)+3*h'(k))%해시테이블 크기, (h(k)+4*h'(k))%해시테이블 크기
      <br>참고 [h와 h'는 다른 해싱함수]
</div>

<p>** 이중해싱법에서 2를 삽입하고자 할 때 **
<img src="https://velog.velcdn.com/images/bssm_woals/post/4ac92c08-8750-4f84-a687-062292b54fb8/image.png" alt="">
4. <strong>체인법 (chaining)</strong></p>
<p>배열의 각 버킷(해시 테이블)에 저장하는 것은 인덱스를 해시값으로 하는 연결 리스트의 앞쪽 노드(head node)를 참조하는 것이다. 
만약 해시 주소가 같은 키만을 하나의 리스트로 묶어둔다면 불필요한 비교는 하지 않아도 될 것이다. 충돌을 해결하는 두 번째 방법은 해시 테이블의 구조를 변경하여 각 버킷이 하나 이상의 값을 저장할 수 있도록 하는 것이다.
<img src="https://velog.velcdn.com/images/bssm_woals/post/4f495e87-d11a-4afd-80bb-134c7224d5b2/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[합병 정렬(Merge Sort)]]></title>
            <link>https://velog.io/@bssm_woals/%ED%95%A9%EB%B3%91-%EC%A0%95%EB%A0%ACMerge-Sort</link>
            <guid>https://velog.io/@bssm_woals/%ED%95%A9%EB%B3%91-%EC%A0%95%EB%A0%ACMerge-Sort</guid>
            <pubDate>Wed, 13 Nov 2024 03:20:14 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>합병 정렬 - 주어진 배열의 원소가 하나가 될 때까지 배열을 2개로 분할 후 합치면서 정렬하는 알고리즘.</p>
</blockquote>
<h4 id="합병정렬의-단점">합병정렬의 단점</h4>
<ul>
<li>시간복잡도가 O(n logn)이라서 n이 커져도 성능이 보장된다.</li>
</ul>
<h4 id="합병정렬의-단점-1">합병정렬의 단점</h4>
<ul>
<li>임시 하위 배열에 저장하기에 추가공간이 필요해 메모리가 제한된 환경에서는 적합하지 않다.</li>
</ul>
<h3 id="예시">예시 <img src="https://velog.velcdn.com/images/bssm_woals/post/cc3f1012-18c6-4d93-84b7-08fbec411753/image.png" alt=""></h3>
<h3 id="c언어-구현">C언어 구현</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;
int sorted[100],count;

void merge(int list[], int left,int mid, int right)
{
    int i,j,k,l;
    i=left;
    j=mid+1;
    k=left;
    while(i&lt;=mid &amp;&amp; j&lt;=right)
    {
        if(list[i]&lt;=list[j])
        {
            sorted[k++]=list[i++];
        }
        else
        {
            sorted[k++]=list[j++];
        }
    }
    if(i&gt;mid)
    {
        for(l=j; l&lt;=right; l++)
        {
            sorted[k++]=list[l];
        }
    }
    else
    {
        for(l=i; l&lt;=mid; l++)
        {
            sorted[k++]=list[l];
        }
    }
    for(l=left; l&lt;=right; l++)
    {
        list[l]=sorted[l];
    }
}

void mergesort(int list[], int left,int right)
{
    int mid;
    if(left&lt;right)
    {
        mid=(left+right)/2;
        mergesort(list,left,mid);
        mergesort(list,mid+1,right);
        merge(list,left,mid,right);
    }
}

int main()
{
    int list[4]={27,12,20,25};
    mergesort(list,0,3);
    for(int i=0; i&lt;4; i++)
    {
        printf(&quot;%d &quot;,list[i]);
    }
    return 0;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Quicksort]]></title>
            <link>https://velog.io/@bssm_woals/Quicksort</link>
            <guid>https://velog.io/@bssm_woals/Quicksort</guid>
            <pubDate>Wed, 06 Nov 2024 03:14:51 GMT</pubDate>
            <description><![CDATA[<h2 id="quicksort란">Quicksort란?</h2>
<blockquote>
<p>평균적으로 가장 빠른 정렬
O(n logn)의 시간복잡도를 가진다</p>
</blockquote>
<h3 id="정렬-방법">정렬 방법</h3>
<ol>
<li>리스트 안에 한 요소를 피벗으로 설정한다.</li>
<li>피벗보다 작은 요소를 피벗의 왼쪽, 피벗보다 큰 요소는 피벗의 오른쪽으로 옮긴다.</li>
<li>정렬되어 있는 피벗이 아닌 리스트들을 다시 이러한 방법으로 정렬한다.</li>
</ol>
<h3 id="구현-코드">구현 코드</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#define swap(x,y,t) ((t)=(x), (x)=(y), (y)=(t))
int partition(int list[], int left,int right)
{
    int pivot,temp,low,high;
    low = left;
    high= right+1;
    pivot=list[left];
    do
    {
        do
        {
            low++;
        }while(list[low]&lt;pivot &amp;&amp; low&lt;=right);
        do
        {
            high--;
        }while(list[high]&gt;pivot);

        if(low&lt;high)
        {
            swap(list[low],list[high],temp);
        }
    }while(low&lt;high);
    swap(list[left],list[high],temp);
    return high;
}

void quicksort(int list[], int left,int right)
{
    if(left&lt;right)
    {
        int q=partition(list, left, right);
        quicksort(list,left,q-1);
        quicksort(list,q+1,right);
    }
}


int main()
{
    int list[6]={10,2,20,7,50,1};
    quicksort(list,0,5);
    for(int i=0; i&lt;6; i++)
    {
        printf(&quot;%d &quot;,list[i]);
    }
    return 0;
}</code></pre>
<h3 id="코드-설명">코드 설명</h3>
<pre><code>변수 설명 - pivot : 정렬 기준, low : 피벗 보다 큰 값을 가르킬 인덱스, high : 피벗보다 작은 값을 가르킬 인덱스</code></pre><blockquote>
<p>함수 partition
[반복]
피벗을 맨앞 수로 잡고 list[low]의 값이 피벗보다 클 때까지 증가시키고
high값을 list[high]값이 피벗보다 작을 때까지 감소시킨다.
swap을 써서 list[low]값과 list[high]값을 바꾼다.
[반복]
마지막으로 피벗을 list[high]값과 바꿔주면 피벗을 기준으로 왼쪽에는 작은 값 오른쪽에는 큰 값이 들어가고 피벗의 인덱스를 반환한다.</p>
</blockquote>
<blockquote>
<p>함수 quicksort
[반복]
partition 함수에서 받아온 피벗의 인덱스를 받고
피벗의 인덱스를 기준으로 왼쪽 리스트와 오른쪽 리스트를 다시 정렬한다.
[반복]</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[순차탐색과 이진탐색]]></title>
            <link>https://velog.io/@bssm_woals/%EC%88%9C%EC%B0%A8%ED%83%90%EC%83%89%EA%B3%BC-%EC%9D%B4%EC%A7%84%ED%83%90%EC%83%89</link>
            <guid>https://velog.io/@bssm_woals/%EC%88%9C%EC%B0%A8%ED%83%90%EC%83%89%EA%B3%BC-%EC%9D%B4%EC%A7%84%ED%83%90%EC%83%89</guid>
            <pubDate>Mon, 07 Oct 2024 03:31:48 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><font color="red">순차탐색</font> - 탐색해야할 자료들을 처음부터 마지막까지 순차적으로 비교하는 탐색 방식.
<font color="blue">순차탐색의 장점</font>  - 자료들이 정렬되어있지 않아도 탐색이 가능하다.
<font color="Blue">순차탐색의 단점</font>  - 리스트가 길어지면 비효율적이다.</p>
</blockquote>
<h3 id="순차탐색-구현">순차탐색 구현</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;
int i, key, count, n, result;
int arr[5] = {9, 5, 8, 3, 7};

int search() 
{
    for (i = 0; i &lt; n; i++) 
    {
        count++;
        if (arr[i] == key)
        {
            return count;
        }
    }
    return -1;
}

int main() 
{
    n = sizeof(arr) / sizeof(int);
    printf(&quot;탐색할 값은? &quot;);
    scanf(&quot;%d&quot;, &amp;key);
    result = search();
    if (result == -1) 
        printf(&quot;탐색 실패입니다.&quot;);
    else 
        printf(&quot;탐색 성공이며 탐색 횟수는 %d회입니다.&quot;, result);

    return 0;
}
</code></pre>
<blockquote>
<p><font color="red">이진탐색</font> - 자료들을 반씩 나누어서 탐색하는 방식.
<font color="Blue">이진탐색의 장점</font>  - 탐색범위를 절반씩 줄여가기 때문에 시간복잡도가 <font color="red">O(logN)</font>이다.
<font color="Blue">이진탐색의 단점</font>  - 자료들이 <font color="red">반드시</font> 정렬되어 있어야 탐색이 가능하다.</p>
</blockquote>
<h3 id="이진탐색-구현">이진탐색 구현</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;
int i, key, count, n, result;
int arr[6] = {1, 3, 5, 7, 9, 11};

int search(int start, int end)
{
    int middle;
    if (start &lt;= end)
    {
        count++;
        middle = (start + end) / 2;
        if (key == arr[middle]) return count;
        else if (key &lt; arr[middle]) return search(start, middle - 1);
        else if (key &gt; arr[middle]) return search(middle + 1, end);
    }
    return -1;
}

int main()
{
    n = sizeof(arr) / sizeof(int);
    printf(&quot;탐색할 값은? &quot;);
    scanf(&quot;%d&quot;, &amp;key);
    result = search(0, n - 1);
    if (result == -1) 
        printf(&quot;탐색 실패입니다.&quot;);
    else 
        printf(&quot;탐색 성공이며 탐색 횟수는 %d회입니다.&quot;, result);

    return 0;
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Union-Find 알고리즘]]></title>
            <link>https://velog.io/@bssm_woals/Union-Find-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
            <guid>https://velog.io/@bssm_woals/Union-Find-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</guid>
            <pubDate>Wed, 25 Sep 2024 03:16:10 GMT</pubDate>
            <description><![CDATA[<h2 id="union-find-알고리즘">Union-Find 알고리즘</h2>
<blockquote>
<p>_ - 여러 개의 노드가 존재할 때 두 개의 노드를 선택해서 현재 이 노드가 서로 같은 그래프에 속하는지 판별하는 알고리즘_    </p>
</blockquote>
<h2 id="union-find-동작-과정">Union-Find 동작 과정</h2>
<p> 처리할 연산 : Union(1,2), Union(2,3),Union(5,6)</p>
<ol>
<li>n개의 부모노드를 초기화 해준다
<img src="https://velog.velcdn.com/images%2Fzer0silver%2Fpost%2Ffcc25efd-007f-4eb4-a207-c45f26ae8cbf%2Fimage.png" alt=""><ol start="2">
<li>Union(1,2) : 노드1의 부모와 노드2의 부모를 찾고 둘의 부모가 다르면 부모를 같에 만들어준다</li>
</ol>
<ul>
<li>노드2번의 부모에 노드1의 부모 1을 넣어준다</li>
</ul>
3.Union(2,3)<ul>
<li>노드3번의 부모에 노드2의 부모 1을 넣어준다</li>
</ul>
4.Union(5,6)<ul>
<li>노드6번의 부모에 노드5의 부모 5를 넣어준다
<code>결과 : 노드1,2,3은 같은 집합, 노드5,6이 같은 집합, 노드4는 단독적인 집합에 존재하게된다.</code><h2 id="union-find-구현-방법">Union-Find 구현 방법</h2>
</li>
</ul>
</li>
</ol>
<ul>
<li>Set (초기화) : n개의 부모노드가 각각의 집합에 포함되게 초기화</li>
<li>Union (합치기) : 두 원소의 각각의 집합을 같은 집합으로 합친다 </li>
<li>Find (찾기) : 원소 a가 들어오면 그 원소가 어느 집합에 포함되어 있는지 찾아서 반환한다</li>
</ul>
<p>[구현]</p>
<pre><code class="language-c"> #include &lt;stdio.h&gt;
int parent[1001];
int Find(int v)
{
    if(parent[v] == v) return v; // 노드의 부모가 v값과 같으면 부모가 같기에 부모를 리턴

    return parent[v] = Find(parent[v]); //노드의 부모가 같을 때까지 반복
}
void Union(int x, int y) // 연결시키기 or 연결됬는지 확인
{
    x = Find(x);
    y = Find(y);

    if(x != y) // x,y가 같다면 부모가 같기 때문에 다르면 저장해줌
        parent[x] = y; 
}
void Set(int n){ // parent 배열 초기화
    for(int i = 1; i &lt;= n; i++){
        parent[i] = i;
    }
}
int main(void) {
    int n ,m, uf1,uf2;
    int f1,f2;
    scanf(&quot;%d %d&quot;,&amp;n,&amp;m);
        Set(n);
    for(int i = 1; i &lt;= m; i++){
        scanf(&quot;%d %d&quot;,&amp;uf1,&amp;uf2);
                Union(uf1,uf2);
    }
    scanf(&quot;%d %d&quot;,&amp;f1,&amp;f2);
    printf(&quot;%s&quot;,(Find(f1) == Find(f2)) ? &quot;YES&quot;:&quot;NO&quot; ); // 부모가 같다면 YES 다르면 NO를 출력
    return 0;
}
</code></pre>
]]></description>
        </item>
    </channel>
</rss>