<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>딩코링코</title>
    <link>https://dingco-ringco.tistory.com/</link>
    <description>백엔드 개발자 준비중</description>
    <language>ko</language>
    <pubDate>Mon, 6 Jul 2026 07:04:32 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>bogyeom</managingEditor>
    <image>
      <title>딩코링코</title>
      <url>https://tistory1.daumcdn.net/tistory/5194988/attach/67b67a62dfd544c79f4b3e11d1416e5d</url>
      <link>https://dingco-ringco.tistory.com</link>
    </image>
    <item>
      <title>사용자 취향 반영을 위한 음악 추천 알고리즘 개선 과정</title>
      <link>https://dingco-ringco.tistory.com/12</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;사용자 맞춤형 음악 추천 기능을 구현하기 위해서는, &lt;/span&gt;&lt;b&gt;사용자의 취향을 정확하게 반영할 수 있는 알고리즘 설계가 핵심&lt;/b&gt;&lt;span&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MiTi 프로젝트에서는 이를 위해 곡의 특성을 기반으로 한 벡터 유사도 계산 방식을 도입하였고, &lt;span&gt;&lt;b&gt;추천 정확도를 높이기 위한 리팩터링&lt;/b&gt;&lt;/span&gt;을 단계적으로 진행하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;기존 추천 알고리즘 설계 방식&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계한 알고리즘: 개인 맞춤형 음악 추천, 장르별 음악 추천, 유사 음악 추천&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1742833879939&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# user_records에 포함된 album_id에 대해 평균 특성 벡터를 계산하는 함수
def calculate_average_features(user_records, album_dict):
    feature_sums = np.zeros(len(next(iter(album_dict.values()))))  # 첫 번째 앨범의 특성 벡터 길이만큼 0으로 초기화
    count = 0

    for user_id, album_id in user_records:
        feature_sums += album_dict[album_id]
        count += 1

    if count == 0:
        return feature_sums
    else:
        return feature_sums / count  # 평균 특성 벡터 계산

# main 함수
def main():
    # MySQL 데이터베이스에 연결
    connection = pymysql.connect(
        host='mitidb.cvm64ss6y2xv.ap-northeast-2.rds.amazonaws.com',       
        user='minseo',  
        password='Alstj!!809', 
        database='mitiDB'
    )

    try:
        records, albums = fetch_records_and_albums(connection)

        # album_id를 키로 하고, 여러 특성을 값으로 하는 딕셔너리 생성
        album_dict = {album[0]: np.array(album[1:]) for album in albums}

        # user_record에 있는 모든 user_id에 대해 평균 특성 벡터 계산
        average_features = calculate_average_features(records, album_dict)

        similarities = []
        for album_id, features in album_dict.items():
            # 유클리드 거리 계산
            euclidean_distance = euclidean(average_features, features)
            similarities.append((album_id, euclidean_distance))

        # 유사도 기준으로 상위 20개의 앨범 선택
        top_20_albums = sorted(similarities, key=lambda x: x[1])[:20]

        # 결과를 customized_rec 테이블에 삽입
        with connection.cursor() as cursor:
            for user_id, _ in records:  # 모든 user_id에 대해 반복
                for album_id, distance in top_20_albums:
                    try:
                        cursor.execute(
                            &quot;INSERT INTO customized_rec (user_id, album_id) VALUES (%s, %s)&quot;,
                            (user_id, album_id)
                        )
                    except pymysql.IntegrityError:
                        # 중복 키 에러가 발생하면 해당 항목을 삽입하지 않음
                        continue
            connection.commit()  # 변경사항을 커밋하여 데이터베이스에 반영&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Spotify에서 재생 가능한 곡의 오디오 특성(audio features)&lt;/b&gt;을 수집한 후,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사용자의 재생 기록에 포함된 곡들의 평균 특성 벡터를 계산하고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 벡터와 가장 유사한 곡들을 추천하는 방식으로 알고리즘을 설계했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;  예: danceability, energy, acousticness 등의 특성을 평균내어 사용자 취향을 표현하는 벡터를 구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❗ &lt;span&gt;&lt;b&gt;발생한 문제&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능 구현은 가능했지만, 실제 추천 결과를 보았을 때 &lt;span&gt;&lt;b&gt;사용자의 취향과 일치하지 않는 곡이 포함되는 문제가 발생&lt;/b&gt;&lt;/span&gt;했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 원인을 분석해보니 다음과 같은 한계점이 있었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;곡 수가 적은 재생목록은 평균값이 왜곡될 가능성이 높음&lt;/li&gt;
&lt;li&gt;단순히 유클리드 거리로 유사도를 계산할 경우, 절대값 차이에 지나치게 민감하게 반응함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면, 운동 장르 플레이리스트에는 운동을 할 때 활력을 돋게 하기 위해 밝고 강한 음악을 추천하도록 설계하였지만, 차분하고 감미로운 음악이 추천되기도 하는 상황이 발생했습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;span&gt;&lt;b&gt;개선 방식&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;1. &lt;/b&gt;&lt;/span&gt;&lt;b&gt;유사도 계산 방식 변경 (유클리드 &amp;rarr; 코사인 유사도)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 곡의 특성을 평균 낸 벡터는 스케일의 영향을 많이 받지 않기 때문에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;절댓값 차이를 계산하는 유클리드 거리보다는 &lt;span&gt;&lt;b&gt;벡터 간 방향을 비교하는 코사인 유사도&lt;/b&gt;&lt;/span&gt;가 더 적합하다고 판단했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;실제로 유사도 계산에 있어 &lt;/span&gt;&lt;b&gt;곡의 개수가 달라도 비슷한 경향성을 가진 벡터들끼리는 높은 유사도로 판단&lt;/b&gt;&lt;span&gt;할 수 있어,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추천 정확도를 높이는 데 도움이 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 유사한 특성 값을 그룹으로 분류&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;&amp;lt; 음악 별 특성 값 정리 &amp;gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;&lt;span&gt;music_speechiness (곡의 말하기 부분의 비율): 가사가 없는 edm, 어두운 분위기의 랩 등 변수가 있는 요소이기 때문에 비교군에서 제외&lt;/span&gt;&lt;/s&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;값이 클수록 밝은 곡인 특성&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;music_danceability (곡의 춤출 수 있는 정도)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;music_energy (곡의 에너지 수준)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;music_loudness (곡의 음량 수준)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;music_valence (곡의 긍정적/부정적 감정 정도)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;music_tempo (곡의 템포)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;값이 작을수록 밝은 곡인 특성&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;music_acousticness (곡의 어쿠스틱 특성) &lt;span style=&quot;color: #9d9d9d;&quot;&gt;-&amp;gt; 주로 어쿠스틱 악기의 사용이 많은 음악이 차분한 경우가 많음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;music_liveness (곡의 라이브 느낌 정도) &lt;span style=&quot;color: #9d9d9d;&quot;&gt;-&amp;gt; 값이 클수록 청중의 소리나 라이브 환경의 특성이 포함되기 때문에 상대적으로 깔끔하지 않고 차분하다고 판단함&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1742835482587&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 각 그룹의 특성 인덱스 정의 (0부터 시작)
    features_group1_indices = [1, 2, 4, 5, 6]  # danceability, energy, tempo, loudness, valence
    features_group2_indices = [0, 3]  # acousticness, liveness

    try:
        # user_record와 album 테이블에서 데이터 가져오기
        records, albums = fetch_records_and_albums(connection)
        # album_id를 정수로 변환하여 딕셔너리 생성
        album_dict = {album[0]: np.array(album[1:], dtype=float) for album in albums}  # 실수로 변환

        # user_id별로 추천 결과 계산
        user_ids = set(user_id for user_id, _ in records)
        user_recommendations = {}

        for user_id in user_ids:
            # 해당 user_id의 레코드만 필터링
            user_records = [(uid, aid) for uid, aid in records if uid == user_id]

            # 각 그룹에 대해 평균 특성 벡터 계산
            average_features_group1 = calculate_average_features(user_records, album_dict, features_group1_indices)
            average_features_group2 = calculate_average_features(user_records, album_dict, features_group2_indices)

            # 각 앨범 특성 벡터와 사용자 평균 특성 벡터 간 코사인 유사도 계산 후 리스트에 저장
            similarities_group1 = []
            similarities_group2 = []

            for album_id, features in album_dict.items():
                try:
                    feature_group1 = features[features_group1_indices]
                    feature_group2 = features[features_group2_indices]

                    if np.all(feature_group1 == 0) or np.all(average_features_group1 == 0):
                        similarity_group1 = 0
                    else:
                        similarity_group1 = 1 - cosine(average_features_group1, feature_group1)

                    if np.all(feature_group2 == 0) or np.all(average_features_group2 == 0):
                        similarity_group2 = 0
                    else:
                        similarity_group2 = 1 - cosine(average_features_group2, feature_group2)

                    similarities_group1.append((album_id, similarity_group1))
                    similarities_group2.append((album_id, similarity_group2))
                
                except Exception as e:
                    print(f&quot;Error processing album_id {album_id}: {e}&quot;)

            # 각 그룹별로 유사도 내림차순으로 정렬
            sorted_group1 = sorted(similarities_group1, key=lambda x: x[1], reverse=True)
            sorted_group2 = sorted(similarities_group2, key=lambda x: x[1], reverse=True)

            # 중복된 앨범만 선택하여 상위 20개 앨범 리스트에 추가
            top_albums = []
            seen_album_ids = set()

            for album_id, similarity in sorted_group1 + sorted_group2:
                if album_id in seen_album_ids:
                    continue
                top_albums.append((album_id, similarity))
                seen_album_ids.add(album_id)
                if len(top_albums) == 20:
                    break
            
            user_recommendations[user_id] = top_albums&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;값이 클수록/ 작을수록 밝은 요소끼리 그룹으로 묶어 따로 유사도를 계산한 후,&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;유사도를 내림차순으로 정렬하고, 최대한 유사도가 강한 음악을 선별하기 위해 두 그룹에 모두 담겨있는 앨범만을 저장했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ 추가로 중복 방지를 위한 코드를 삽입해 한 번 더 중복 저장을 예방하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1742835704164&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;except pymysql.IntegrityError:
	continue&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 장르별 음악의 적합성 향상&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운동 (Workout)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;높은 music_danceability: 춤추기 좋은 리듬&lt;/li&gt;
&lt;li&gt;높은 music_energy: 활기찬 음악&lt;/li&gt;
&lt;li&gt;높은 music_tempo: 빠른 템포&lt;/li&gt;
&lt;li&gt;높은 music_loudness: 높은 볼륨, 강렬한 효과음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;휴식 (Relaxation)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;높은 music_acousticness: 차분하고 조용한 분위기&lt;/li&gt;
&lt;li&gt;낮은 music_energy: 에너지 부족한 분위기&lt;/li&gt;
&lt;li&gt;낮은 music_tempo: 느린 템포, 편안한 리듬&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;집중 (Focus)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;낮은 music_loudness: 낮은 볼륨, 부드러운 사운드&lt;/li&gt;
&lt;li&gt;낮은 music_tempo: 느린 템포, 차분한 리듬&lt;/li&gt;
&lt;li&gt;높은 music_valence: 긍정적이고 희망적인 감정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파티 (Party)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;높은 music_danceability: 춤추기 좋은 리듬&lt;/li&gt;
&lt;li&gt;높은 music_energy: 활기찬 음악&lt;/li&gt;
&lt;li&gt;높은 music_tempo: 빠른 템포&lt;/li&gt;
&lt;li&gt;높은 music_valence: 긍정적이고 밝은 감정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트레스 해소 (Stress Relief)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;높은 music_acousticness: 자연스러운 소리, 어쿠스틱 악기 사용&lt;/li&gt;
&lt;li&gt;높은 music_liveness: 라이브 느낌, 현장감 있는 사운드&lt;/li&gt;
&lt;li&gt;낮은 music_tempo: 느린 템포, 차분한 리듬&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여행 (Travel)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;높은 music_energy: 에너지 넘치는 음악&lt;/li&gt;
&lt;li&gt;높은 music_tempo: 빠른 템포, 활동적인 리듬&lt;/li&gt;
&lt;li&gt;높은 music_valence: 긍정적이고 열정적인 감정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사랑 (Romance)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;높은 music_acousticness: 어쿠스틱 악기와 조용한 분위기&lt;/li&gt;
&lt;li&gt;높은 music_valence: 로맨틱하고 감정적인 음악&lt;/li&gt;
&lt;li&gt;낮은 music_tempo: 느린 템포, 섬세한 리듬&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수면 (Sleep)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;높은 music_acousticness: 차분하고 조용한 분위기&lt;/li&gt;
&lt;li&gt;낮은 music_energy: 에너지 부족한 분위기&lt;/li&gt;
&lt;li&gt;낮은 music_tempo: 느린 템포, 부드러운 리듬&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래식 (Classical)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;높은 music_acousticness: 어쿠스틱 악기와 조용한 분위기&lt;/li&gt;
&lt;li&gt;낮은 music_tempo: 느린 템포, 고요한 리듬&lt;/li&gt;
&lt;li&gt;높은 music_valence: 우아하고 감동적인 감정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우울 (Sadness)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;낮은 music_valence: 부정적이고 슬픈 감정&lt;/li&gt;
&lt;li&gt;낮은 music_energy: 에너지 부족한 분위기&lt;/li&gt;
&lt;li&gt;낮은 music_tempo: 느린 템포, 침울한 리듬&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 장르를 10개로 지정해 강조돼야 하는 특성을 3개씩 뽑아 동일한 방법으로 비슷한 특성끼리 그룹으로 묶어 계산을 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;span&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;위에서 예를 들었던 운동장르의 코드 개선 전, 후를 비교해 보면 빠르고 밝고 활기찬 느낌에 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;훨씬 더 적합한 음악이 추천되는 것을 한눈에 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ 마무리하며&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평균 특성 벡터 기반의 추천 구조를 설계하면서 &lt;span&gt;&lt;b&gt;데이터를 해석하고, 적절한 기술을 선택하는 판단력의 중요성&lt;/b&gt;&lt;/span&gt;을 체감했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로도 사용자의 경험을 중심에 두고, 끊임없이 개선하고 실험하며 더 나은 추천 시스템을 설계해 나가고 싶습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://github.com/bogyeom0922/MiTi/tree/main/algorithm&quot;&gt;전체 알고리즘 코드 보러 가기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;  &lt;a href=&quot;https://dingco-ringco.tistory.com/11&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;spotify 크롤링 개선 과정 보러 가기&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>SPRINGBOOT/음악 스트리밍 서비스</category>
      <author>bogyeom</author>
      <guid isPermaLink="true">https://dingco-ringco.tistory.com/12</guid>
      <comments>https://dingco-ringco.tistory.com/12#entry12comment</comments>
      <pubDate>Tue, 25 Mar 2025 02:09:18 +0900</pubDate>
    </item>
    <item>
      <title>Spotify 음악 크롤링 중 마주한 문제들과 이를 해결한 과정</title>
      <link>https://dingco-ringco.tistory.com/11</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  프로젝트 배경&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음악 스트리밍 프로젝트 &amp;lsquo;MiTi&amp;rsquo;에서는 사용자의 취향에 맞는 음악을 추천하기 위해 &lt;span&gt;&lt;b&gt;Spotify API를 이용한 음악 데이터 크롤링&lt;/b&gt;&lt;/span&gt;이 필요했습니다. 크롤링 과정에서 다양한 문제들이 발생했고, 이를 해결하며 기능의 완성도를 높일 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;110&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lX466/btsMVROw1uk/w72gMWeyzeCM04sD5yjQD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lX466/btsMVROw1uk/w72gMWeyzeCM04sD5yjQD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lX466/btsMVROw1uk/w72gMWeyzeCM04sD5yjQD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlX466%2FbtsMVROw1uk%2Fw72gMWeyzeCM04sD5yjQD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;458&quot; height=&quot;110&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;110&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spotify는 다른 플랫폼과는 달리 저작권이나 보안 문제를 책임지지 않아도 되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 맞춤형 추천 기능이 주 기능인 저희 웹서비스에서는 추천 알고리즘을 중요하게 생각했기 때문에, 음악의 특성(danceability, energy 등)을 자세히 가져올 수 있어 해당 api를 선택하기로 결정했습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;pre id=&quot;code_1742543143260&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;client_credentials_manager = SpotifyClientCredentials(client_id=client_id, client_secret=client_secret)

sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)

csv_file_path = &quot;spotify_tracks.csv&quot;

chart_tracks = sp.playlist_tracks('37i9dQZEVXbMDoHDwVN2tF') #트랙 인기차트 id

with open(csv_file_path, 'w', newline='', encoding='utf-8') as file:
    writer = csv.writer(file)
    # 헤더
    writer.writerow([
        &quot;id&quot;, &quot;music_name&quot;, &quot;music_id&quot;, &quot;music_popularity&quot;, &quot;album_image&quot;, &quot;album_detail&quot;,
        &quot;music_artist_name&quot;, &quot;music_artist_id&quot;, &quot;music_artist_popular&quot;, &quot;music_genre&quot;, 
        &quot;music_artist_genres&quot;, &quot;music_artist_followers&quot;, &quot;music_analysis_url&quot;, 
        &quot;music_key&quot;, &quot;music_duration_ms&quot;, &quot;music_instrumentalness&quot;, &quot;music_acousticness&quot;, 
        &quot;music_danceability&quot;, &quot;music_energy&quot;, &quot;music_liveness&quot;, &quot;music_loudness&quot;, 
        &quot;music_mode&quot;, &quot;music_speechiness&quot;, &quot;music_tempo&quot;, &quot;music_time_signature&quot;, 
        &quot;music_valence&quot;, &quot;music_track_href&quot;, &quot;music_type&quot;, &quot;music_uri&quot;
    ])
    
    # 트랙마다 음악 정보 작성
    track_id = 1

    for track in chart_tracks['items']:
        track_info = track['track']
        album_id = track_info['album']['id']
        album_tracks = sp.album_tracks(album_id)['items']

        for album_track in album_tracks:
            # 개별 트랙의 상세 정보
            track_details = sp.track(album_track['id'])

            # 오디오 특징
            audio_features = sp.audio_features(album_track['id'])[0]
            
            # 아티스트 정보
            artist_names = []
            artist_ids = []
            artist_popularities = []
            artist_genres = []
            artist_followers = []

            for artist in album_track['artists']:
                artist_id = artist['id']
                artist_info = sp.artist(artist_id)
                artist_names.append(artist_info['name'])
                artist_ids.append(artist_info['id'])
                artist_popularities.append(artist_info['popularity'])
                artist_genres.append(', '.join(artist_info['genres']))
                artist_followers.append(artist_info['followers']['total'])

            # 행 작성
            writer.writerow([
                track_id, album_track['name'], album_track['id'], 
                track_details['popularity'],  # 개별 트랙의 인기도 가져오기
                track_info['album']['images'][0]['url'], track_info['album']['name'],
                ', '.join(artist_names), ', '.join(artist_ids), ', '.join(map(str, artist_popularities)),
                ', '.join(artist_names), ', '.join(artist_genres), ', '.join(map(str, artist_followers)),
                audio_features['analysis_url'], audio_features['key'], 
                audio_features['duration_ms'], audio_features['instrumentalness'],
                audio_features['acousticness'], audio_features['danceability'], 
                audio_features['energy'], audio_features['liveness'], 
                audio_features['loudness'], audio_features['mode'], 
                audio_features['speechiness'], audio_features['tempo'], 
                audio_features['time_signature'], audio_features['valence'], 
                audio_features['track_href'], audio_features['type'], album_track['uri']
            ])
            track_id += 1  # ID 값 증가&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 초기에 작성한 코드로, 인기차트의 id를 직접 삽입해 해당 차트에 담겨있는 음악들의 정보를 가져왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 간단한 방법이기 때문에 오류 발생률이 적고, 빠르게 정보 수집이 가능하다는 장점이 있었지만, 여러 가지 문제점도 많이 발생했습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;❗ 크롤링 중 마주한 문제점과 해결 방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 수동으로 음악 ID를 하나씩 검색해야 해 시간이 과도하게 소요됨&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 수동으로 ID를 가져오다 보니 크롤링 범위가 제한되고, 자동화가 어렵고 비효율적이었음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;해결 방법&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp;Spotify의 카테고리 정보를 활용해 자동으로 플레이리스트를 탐색하는 방식으로 전환&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;사용 코드&lt;/span&gt;&amp;nbsp;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1742830738368&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Spotify에서 제공하는 카테고리 가져오기
categories = sp.categories(limit=50)  # 최대 50개의 카테고리 가져오기
for category in categories['categories']['items']:
	category_id = category['id']
    print(f&quot;Fetching playlists from category: {category['name']}&quot;)

    # 카테고리별 플레이리스트 가져오기 (최대 20개)
    playlists = sp.category_playlists(category_id=category_id, limit=20)  # 각 카테고리당 20개의 플레이리스트
    for playlist in playlists['playlists']['items']:
    	playlist_id = playlist['id']
        playlist_name = playlist['name']
        print(f&quot;Fetching tracks from playlist: {playlist_name}&quot;)

        # 플레이리스트에서 트랙 가져오기
        offset = 0
        limit = 50  # 트랙을 50개씩 가져오기
        total_tracks = sp.playlist_tracks(playlist_id, limit=1)['total']  # 전체 트랙 수 확인&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 서로 다른 플레이리스트 간에 음악이 중복 저장됨&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 동일한 음악이 여러 플레이리스트에 포함되어 중복 데이터가 누적됨&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;해결 방법&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt; 음악 제목 기준으로 중복 여부를 검사한 후 저장&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;사용 코드&lt;/span&gt;&amp;nbsp;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1742832366346&quot; class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;if music_name in recorded_music_names:
	continue&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 특정 음악은 국내(KR)에서 재생이 불가능함&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 재생이 불가능한 음악까지 수집되어 실제 사용자 재생 시 오류 발생 우려&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;해결 방법&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt; available_markets&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;필드에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;KR이 포함되어 있는 음악만 수집&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;사용 코드&lt;/span&gt;&amp;nbsp;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1742832374873&quot; class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;if track_info.get('is_playable', True) and 'KR' in track_info.get('available_markets', []):&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 과도한 API 요청으로 Spotify API 호출 제한이 발생함&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 크롤링 범위가 넓어지며 일정 시간 후 API 호출 제한에 도달&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;해결 방법&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt; 카테고리 수, 플레이리스트 수, 트랙 수를 제한하고 요청 간 딜레이를 적용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;사용 코드&lt;/span&gt;&amp;nbsp;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1742832376788&quot; class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;time.sleep(REQUEST_DELAY)&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ 마무리하며&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 크롤링 작업을 통해 API 사용 시의 유의점과 효율적인 데이터 수집 방식에 대해 배울 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 단순히 데이터를 가져오는 것뿐 아니라, &lt;span&gt;&lt;b&gt;서비스 환경에서 실제로 사용할 수 있는 데이터를 선별하고 처리하는 능력&lt;/b&gt;&lt;/span&gt;이 중요하다는 점을 실감했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;a href=&quot;https://github.com/bogyeom0922/MiTi/tree/main/crawling&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;전체 코드 보러 가기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  크롤링한 데이터를 이용한&amp;nbsp;&lt;a title=&quot;크롤링한 데이터를 이용한 추천 알고리즘 설계 과정&quot; href=&quot;https://dingco-ringco.tistory.com/12&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;추천 알고리즘 설계 및 리팩터링 과정 보러 가기&lt;/a&gt;&lt;/p&gt;</description>
      <category>SPRINGBOOT/음악 스트리밍 서비스</category>
      <author>bogyeom</author>
      <guid isPermaLink="true">https://dingco-ringco.tistory.com/11</guid>
      <comments>https://dingco-ringco.tistory.com/11#entry11comment</comments>
      <pubDate>Fri, 21 Mar 2025 17:06:43 +0900</pubDate>
    </item>
    <item>
      <title>[우테코 프리코스 BE] 3주차 회고록</title>
      <link>https://dingco-ringco.tistory.com/10</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1599&quot; data-origin-height=&quot;1201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ttjDf/btsMwdztTuY/qwufN0qp58hFX8bad7TJ20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ttjDf/btsMwdztTuY/qwufN0qp58hFX8bad7TJ20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ttjDf/btsMwdztTuY/qwufN0qp58hFX8bad7TJ20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FttjDf%2FbtsMwdztTuY%2FqwufN0qp58hFX8bad7TJ20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1599&quot; height=&quot;1201&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1599&quot; data-origin-height=&quot;1201&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이번&amp;nbsp;주차에는&amp;nbsp;예외&amp;nbsp;테스트&amp;nbsp;2개&amp;nbsp;중&amp;nbsp;1개를&amp;nbsp;실패해서&amp;nbsp;정말&amp;nbsp;아쉽게&amp;nbsp;마무리하게됐다.&amp;nbsp;이번&amp;nbsp;주&amp;nbsp;문제점을&amp;nbsp;파악하고&amp;nbsp;다음주엔&amp;nbsp;부족한&amp;nbsp;점을&amp;nbsp;모두&amp;nbsp;개선해보려고&amp;nbsp;한다&amp;nbsp;!&lt;br /&gt;&lt;br /&gt;[&lt;a href=&quot;https://github.com/woowacourse-precourse/java-lotto-7/pull/542](https://github.com/woowacourse-precourse/java-lotto-7/pull/542)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/woowacourse-precourse/java-lotto-7/pull/542](https://github.com/woowacourse-precourse/java-lotto-7/pull/542)&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;hr&amp;gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;##&amp;nbsp;3주차&amp;nbsp;[로또]&lt;br /&gt;&lt;br /&gt;###&amp;nbsp; &amp;nbsp;프로젝트&amp;nbsp;소개&amp;nbsp;&amp;nbsp;&lt;br /&gt;이&amp;nbsp;프로젝트는&amp;nbsp;**로또**를&amp;nbsp;구현합니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;사용자가&amp;nbsp;입력한&amp;nbsp;**당첨&amp;nbsp;번호**와&amp;nbsp;시스템이&amp;nbsp;생성한&amp;nbsp;**당첨&amp;nbsp;번호**를&amp;nbsp;비교하며&amp;nbsp;로또&amp;nbsp;게임을&amp;nbsp;진행합니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;-&amp;nbsp;로또&amp;nbsp;번호는&amp;nbsp;1~45&amp;nbsp;사이의&amp;nbsp;중복하지&amp;nbsp;않는&amp;nbsp;7개의&amp;nbsp;무작위&amp;nbsp;값을&amp;nbsp;통해&amp;nbsp;당첨&amp;nbsp;여부를&amp;nbsp;결정합니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;-&amp;nbsp;로또&amp;nbsp;구입&amp;nbsp;금액을&amp;nbsp;입력하면&amp;nbsp;구입&amp;nbsp;금액에&amp;nbsp;해당하는&amp;nbsp;만큼&amp;nbsp;로또를&amp;nbsp;발행해야&amp;nbsp;합니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;-&amp;nbsp;당첨&amp;nbsp;내역&amp;nbsp;및&amp;nbsp;수익률을&amp;nbsp;출력하고&amp;nbsp;로또&amp;nbsp;게임을&amp;nbsp;종료합니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;-&amp;nbsp;잘못된&amp;nbsp;입력이&amp;nbsp;들어올&amp;nbsp;경우&amp;nbsp;**`IllegalArgumentException`**이&amp;nbsp;발생하며&amp;nbsp;애플리케이션이&amp;nbsp;종료됩니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&amp;lt;br&amp;gt;&lt;br /&gt;&lt;br /&gt;###&amp;nbsp; &amp;nbsp;기능&amp;nbsp;요구&amp;nbsp;사항&amp;nbsp;&amp;nbsp;&lt;br /&gt;1.&amp;nbsp;**로또&amp;nbsp;번호**&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;발행&amp;nbsp;횟수만큼&amp;nbsp;1~45&amp;nbsp;사이의&amp;nbsp;중복하지&amp;nbsp;않는&amp;nbsp;6개의&amp;nbsp;무작위&amp;nbsp;값을&amp;nbsp;뽑아&amp;nbsp;출력합니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;2.&amp;nbsp;**번호&amp;nbsp;비교**&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;구매한&amp;nbsp;로또&amp;nbsp;번호와&amp;nbsp;당첨&amp;nbsp;번호를&amp;nbsp;비교하여&amp;nbsp;당첨&amp;nbsp;개수를&amp;nbsp;기록합니다.&lt;br /&gt;&lt;br /&gt;3.&amp;nbsp;**당첨&amp;nbsp;결과**&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;당첨&amp;nbsp;내역&amp;nbsp;및&amp;nbsp;수익률을&amp;nbsp;출력합니다.&lt;br /&gt;&lt;br /&gt;4.&amp;nbsp;**예외&amp;nbsp;처리**&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;입력&amp;nbsp;값이&amp;nbsp;유효하지&amp;nbsp;않을&amp;nbsp;경우&amp;nbsp;**`IllegalArgumentException`**을&amp;nbsp;발생시키고&amp;nbsp;애플리케이션을&amp;nbsp;종료합니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&amp;lt;br&amp;gt;&lt;br /&gt;&lt;br /&gt;###&amp;nbsp; &amp;nbsp;입출력&amp;nbsp;요구&amp;nbsp;사항&amp;nbsp;&amp;nbsp;&lt;br /&gt;**입력**&amp;nbsp;&amp;nbsp;&lt;br /&gt;1.&amp;nbsp;로또&amp;nbsp;구입&amp;nbsp;금액(1,000원&amp;nbsp;단위)&lt;br /&gt;2.&amp;nbsp;당첨&amp;nbsp;번호(,로&amp;nbsp;구분된&amp;nbsp;6개의&amp;nbsp;중복되지&amp;nbsp;않는&amp;nbsp;1-45사이의&amp;nbsp;정수)&lt;br /&gt;3.&amp;nbsp;보너스&amp;nbsp;번호(당첨&amp;nbsp;번호와&amp;nbsp;중복되지&amp;nbsp;않는&amp;nbsp;1-45&amp;nbsp;사이의&amp;nbsp;정수)&lt;br /&gt;&lt;br /&gt;**출력**&amp;nbsp;&amp;nbsp;&lt;br /&gt;-&amp;nbsp;구매한&amp;nbsp;개수&amp;nbsp;만큼의&amp;nbsp;6개의&amp;nbsp;로또&amp;nbsp;번호&lt;br /&gt;-&amp;nbsp;당첨&amp;nbsp;통계(내역&amp;nbsp;및&amp;nbsp;수익률)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;br&amp;gt;&lt;br /&gt;&lt;br /&gt;###&amp;nbsp; &amp;nbsp;3주차&amp;nbsp;미션&amp;nbsp;진행시&amp;nbsp;개선할&amp;nbsp;점&lt;br /&gt;이번&amp;nbsp;구현에서&amp;nbsp;코드&amp;nbsp;리뷰와&amp;nbsp;공통&amp;nbsp;피드백을&amp;nbsp;통해&amp;nbsp;개선해야겠다고&amp;nbsp;느낀&amp;nbsp;부분:&lt;br /&gt;&lt;br /&gt;-&amp;nbsp;계층형&amp;nbsp;아키텍처에&amp;nbsp;대해&amp;nbsp;학습&lt;br /&gt;-&amp;nbsp;단위테스트하기&lt;br /&gt;-&amp;nbsp;도메인에서&amp;nbsp;뷰를&amp;nbsp;직접적으로&amp;nbsp;참조하지&amp;nbsp;않도록&amp;nbsp;하기&lt;br /&gt;-&amp;nbsp;매직넘버는&amp;nbsp;상수로&amp;nbsp;빼기&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;br&amp;gt;&lt;br /&gt;&lt;br /&gt;###&amp;nbsp;미션을&amp;nbsp;끝마친&amp;nbsp;후&amp;nbsp;소감&lt;br /&gt;이번&amp;nbsp;미션이&amp;nbsp;가장&amp;nbsp;아쉬움이&amp;nbsp;많이&amp;nbsp;남았다..!&amp;nbsp;예외&amp;nbsp;테스트를&amp;nbsp;실패하기도&amp;nbsp;했지만,&amp;nbsp;매주&amp;nbsp;새롭게&amp;nbsp;코드를&amp;nbsp;발전시키면서&amp;nbsp;작성하고&amp;nbsp;있기&amp;nbsp;때문에&amp;nbsp;내가&amp;nbsp;지금&amp;nbsp;하고&amp;nbsp;있는게&amp;nbsp;잘&amp;nbsp;하는건가..?&amp;nbsp;이걸&amp;nbsp;여기에&amp;nbsp;작성하는게&amp;nbsp;맞나?&amp;nbsp;하는&amp;nbsp;의심을&amp;nbsp;계속&amp;nbsp;품으면서&amp;nbsp;코드를&amp;nbsp;작성했던&amp;nbsp;기억이&amp;nbsp;난다.&lt;br /&gt;개선할&amp;nbsp;점이라고&amp;nbsp;작성헀던&amp;nbsp;부분들&amp;nbsp;중,&amp;nbsp;지키지&amp;nbsp;못해&amp;nbsp;아쉬웠던건&amp;nbsp;뷰를&amp;nbsp;컨트롤러에서&amp;nbsp;서비스랑&amp;nbsp;연결시켰어야&amp;nbsp;했는데,&amp;nbsp;서비스에서&amp;nbsp;뷰와&amp;nbsp;연결시켜서&amp;nbsp;너무&amp;nbsp;많은&amp;nbsp;책임을&amp;nbsp;두게&amp;nbsp;했던&amp;nbsp;것&amp;nbsp;같아서&amp;nbsp;그게&amp;nbsp;아쉽다!&lt;br /&gt;그리고&amp;nbsp;매직&amp;nbsp;넘버를&amp;nbsp;상수로&amp;nbsp;빼보려고&amp;nbsp;했는데,&amp;nbsp;일관성이&amp;nbsp;없게&amp;nbsp;몇&amp;nbsp;개만&amp;nbsp;상수로&amp;nbsp;뺀게&amp;nbsp;적절하지는&amp;nbsp;않았던&amp;nbsp;것&amp;nbsp;같다.&amp;nbsp;다른&amp;nbsp;분들의&amp;nbsp;코드를&amp;nbsp;보니&amp;nbsp;정해진&amp;nbsp;값은&amp;nbsp;대부분&amp;nbsp;모두&amp;nbsp;상수로&amp;nbsp;빼서,&amp;nbsp;반성하게&amp;nbsp;됐고&amp;nbsp;상수&amp;nbsp;값들을&amp;nbsp;한&amp;nbsp;클래스에&amp;nbsp;정리해서&amp;nbsp;분리하는&amp;nbsp;것도&amp;nbsp;좋을&amp;nbsp;것&amp;nbsp;같다!&lt;br /&gt;&lt;br /&gt;&amp;lt;br&amp;gt;&lt;br /&gt;&lt;br /&gt;###&amp;nbsp;코드&amp;nbsp;리뷰를&amp;nbsp;하며&amp;nbsp;고쳐야겠다고&amp;nbsp;생각했던&amp;nbsp;점&lt;br /&gt;-&amp;nbsp;예외&amp;nbsp;메시지나&amp;nbsp;정해진&amp;nbsp;값&amp;nbsp;모아서&amp;nbsp;정의해두기&amp;nbsp;(enum&amp;nbsp;활용해보기)&lt;br /&gt;-&amp;nbsp;참조&amp;nbsp;자료형을&amp;nbsp;그대로&amp;nbsp;리턴하기보단,&amp;nbsp;복사한&amp;nbsp;리스트를&amp;nbsp;반환하는&amp;nbsp;방식을&amp;nbsp;사용하기&amp;nbsp;(방어적복사)&lt;br /&gt;-&amp;nbsp;클래스의&amp;nbsp;내용에&amp;nbsp;맞게&amp;nbsp;이름&amp;nbsp;짓기&amp;amp;&amp;nbsp;클래스에서는&amp;nbsp;유사한&amp;nbsp;기능만&amp;nbsp;실행하도록&amp;nbsp;!&lt;br /&gt;-&amp;nbsp;메서드에서&amp;nbsp;파라미터&amp;nbsp;값&amp;nbsp;변경하지&amp;nbsp;않기(불변&amp;nbsp;취급),&amp;nbsp;의미없는&amp;nbsp;코드&amp;nbsp;작성하지&amp;nbsp;않기&lt;br /&gt;-&amp;nbsp;예외&amp;nbsp;처리&amp;nbsp;후&amp;nbsp;다시&amp;nbsp;입력받도록&amp;nbsp;구현하기&lt;br /&gt;&lt;br /&gt;&amp;lt;br&amp;gt;&lt;br /&gt;&lt;br /&gt;이번&amp;nbsp;4주차는&amp;nbsp;마지막인만큼&amp;nbsp;고쳐야겠다고&amp;nbsp;생각한&amp;nbsp;점들을&amp;nbsp;꼭&amp;nbsp;다&amp;nbsp;지켜보고싶다&amp;nbsp;!&lt;/p&gt;</description>
      <category>우아한 테크코스 프리코스</category>
      <author>bogyeom</author>
      <guid isPermaLink="true">https://dingco-ringco.tistory.com/10</guid>
      <comments>https://dingco-ringco.tistory.com/10#entry10comment</comments>
      <pubDate>Wed, 26 Feb 2025 16:53:34 +0900</pubDate>
    </item>
    <item>
      <title>[우테코 프리코스 BE] 2주차 회고록</title>
      <link>https://dingco-ringco.tistory.com/9</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1599&quot; data-origin-height=&quot;1201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mzcgb/btsMyD4huAt/4e5KKKhuD70MuQpMikQCGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mzcgb/btsMyD4huAt/4e5KKKhuD70MuQpMikQCGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mzcgb/btsMyD4huAt/4e5KKKhuD70MuQpMikQCGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMzcgb%2FbtsMyD4huAt%2F4e5KKKhuD70MuQpMikQCGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1599&quot; height=&quot;1201&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1599&quot; data-origin-height=&quot;1201&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;3주차&amp;nbsp;과제를&amp;nbsp;진행하기&amp;nbsp;전&amp;nbsp;회고록을&amp;nbsp;급하게&amp;nbsp;작성하려고&amp;nbsp;한다&amp;nbsp;!&amp;nbsp;반성할&amp;nbsp;점은&amp;nbsp;뭔지,&amp;nbsp;더&amp;nbsp;개선해야할&amp;nbsp;점은&amp;nbsp;뭔지&amp;nbsp;정리를&amp;nbsp;하면&amp;nbsp;훨씬&amp;nbsp;코드를&amp;nbsp;짜는&amp;nbsp;데에&amp;nbsp;도움이&amp;nbsp;될&amp;nbsp;것&amp;nbsp;같다.&lt;br /&gt;&lt;br /&gt;[&lt;a href=&quot;https://github.com/woowacourse-precourse/java-racingcar-7/pull/982](https://github.com/woowacourse-precourse/java-racingcar-7/pull/982)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/woowacourse-precourse/java-racingcar-7/pull/982](https://github.com/woowacourse-precourse/java-racingcar-7/pull/982)&lt;/a&gt;&lt;br /&gt;&amp;lt;hr&amp;gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;##&amp;nbsp;2주차&amp;nbsp;[문자열&amp;nbsp;덧셈&amp;nbsp;계산기]&lt;br /&gt;&lt;br /&gt;###&amp;nbsp; &amp;nbsp;프로젝트&amp;nbsp;소개&amp;nbsp;&amp;nbsp;&lt;br /&gt;이&amp;nbsp;프로젝트는&amp;nbsp;**자동차&amp;nbsp;경주&amp;nbsp;게임**을&amp;nbsp;구현합니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;사용자가&amp;nbsp;입력한&amp;nbsp;**경주할&amp;nbsp;자동차&amp;nbsp;이름**으로&amp;nbsp;주어진&amp;nbsp;**횟수만큼&amp;nbsp;이동**하며&amp;nbsp;경기를&amp;nbsp;진행합니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;-&amp;nbsp;각&amp;nbsp;자동차는&amp;nbsp;0~9&amp;nbsp;사이의&amp;nbsp;무작위&amp;nbsp;값을&amp;nbsp;통해&amp;nbsp;전진&amp;nbsp;여부를&amp;nbsp;결정합니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;-&amp;nbsp;**4&amp;nbsp;이상**의&amp;nbsp;값이&amp;nbsp;나왔을&amp;nbsp;때&amp;nbsp;해당&amp;nbsp;자동차가&amp;nbsp;전진합니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;-&amp;nbsp;**여러&amp;nbsp;대의&amp;nbsp;자동차**가&amp;nbsp;동일한&amp;nbsp;거리에&amp;nbsp;도달할&amp;nbsp;수&amp;nbsp;있으며,&amp;nbsp;이&amp;nbsp;경우&amp;nbsp;공동&amp;nbsp;우승자가&amp;nbsp;됩니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;-&amp;nbsp;잘못된&amp;nbsp;입력이&amp;nbsp;들어올&amp;nbsp;경우&amp;nbsp;**`IllegalArgumentException`**이&amp;nbsp;발생하며&amp;nbsp;애플리케이션이&amp;nbsp;종료됩니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&amp;lt;br&amp;gt;&lt;br /&gt;&lt;br /&gt;###&amp;nbsp; &amp;nbsp;기능&amp;nbsp;요구&amp;nbsp;사항&amp;nbsp;&amp;nbsp;&lt;br /&gt;1.&amp;nbsp;**자동차&amp;nbsp;이름**&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;쉼표(`,`)로&amp;nbsp;구분된&amp;nbsp;문자열로&amp;nbsp;입력합니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;각&amp;nbsp;이름은&amp;nbsp;**최대&amp;nbsp;5자**까지만&amp;nbsp;허용됩니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;2.&amp;nbsp;**이동**&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;사용자가&amp;nbsp;입력한&amp;nbsp;횟수만큼&amp;nbsp;경기를&amp;nbsp;반복합니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;각&amp;nbsp;시도마다&amp;nbsp;**자동차별로&amp;nbsp;0~9&amp;nbsp;사이**의&amp;nbsp;무작위&amp;nbsp;값을&amp;nbsp;생성합니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;**4&amp;nbsp;이상**일&amp;nbsp;경우&amp;nbsp;자동차가&amp;nbsp;전진합니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;3.&amp;nbsp;**우승자&amp;nbsp;결정**&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;가장&amp;nbsp;멀리&amp;nbsp;전진한&amp;nbsp;자동차가&amp;nbsp;우승자가&amp;nbsp;됩니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;우승자가&amp;nbsp;여러&amp;nbsp;명일&amp;nbsp;경우,&amp;nbsp;**쉼표(,)로&amp;nbsp;구분**하여&amp;nbsp;표시합니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;4.&amp;nbsp;**예외&amp;nbsp;처리**&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;입력&amp;nbsp;값이&amp;nbsp;유효하지&amp;nbsp;않을&amp;nbsp;경우&amp;nbsp;**`IllegalArgumentException`**을&amp;nbsp;발생시키고&amp;nbsp;애플리케이션을&amp;nbsp;종료합니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&amp;lt;br&amp;gt;&lt;br /&gt;&lt;br /&gt;###&amp;nbsp; &amp;nbsp;입출력&amp;nbsp;요구&amp;nbsp;사항&amp;nbsp;&amp;nbsp;&lt;br /&gt;**입력**&amp;nbsp;&amp;nbsp;&lt;br /&gt;1.&amp;nbsp;쉼표로&amp;nbsp;구분된&amp;nbsp;경주할&amp;nbsp;자동차&amp;nbsp;이름&amp;nbsp;목록&amp;nbsp;&amp;nbsp;&lt;br /&gt;2.&amp;nbsp;시도할&amp;nbsp;횟수&amp;nbsp;(정수)&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;**출력**&amp;nbsp;&amp;nbsp;&lt;br /&gt;-&amp;nbsp;매&amp;nbsp;시도마다&amp;nbsp;자동차별&amp;nbsp;전진&amp;nbsp;결과를&amp;nbsp;출력합니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;-&amp;nbsp;경기&amp;nbsp;종료&amp;nbsp;후&amp;nbsp;최종&amp;nbsp;우승자를&amp;nbsp;출력합니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&amp;lt;br&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;gt;&amp;nbsp;이번엔&amp;nbsp;저번주에&amp;nbsp;반성해야할&amp;nbsp;점에&amp;nbsp;대해&amp;nbsp;작성한&amp;nbsp;것을&amp;nbsp;지키려고&amp;nbsp;최대한&amp;nbsp;노력해보았다.&amp;nbsp;추가로&amp;nbsp;더&amp;nbsp;학습해야할&amp;nbsp;부분도&amp;nbsp;많겠지만&amp;nbsp;너무&amp;nbsp;욕심내지&amp;nbsp;않고,&amp;nbsp;한&amp;nbsp;단계씩&amp;nbsp;확실하게&amp;nbsp;공부하는&amp;nbsp;것이&amp;nbsp;목표기&amp;nbsp;때문에&amp;nbsp;전에&amp;nbsp;작성한&amp;nbsp;반성해야할&amp;nbsp;점을&amp;nbsp;위주로&amp;nbsp;지키려고&amp;nbsp;했다.&lt;br /&gt;&lt;br /&gt;&amp;lt;br&amp;gt;&lt;br /&gt;&lt;br /&gt;###&amp;nbsp;2주차&amp;nbsp;목표&lt;br /&gt;-&amp;nbsp;예외&amp;nbsp;상황에&amp;nbsp;대해&amp;nbsp;더&amp;nbsp;깊이&amp;nbsp;고민해볼&amp;nbsp;것&lt;br /&gt;-&amp;nbsp;메서드&amp;nbsp;이름을&amp;nbsp;어떤&amp;nbsp;동작을&amp;nbsp;하는지&amp;nbsp;알&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;정확히&amp;nbsp;작성할&amp;nbsp;것&lt;br /&gt;-&amp;nbsp;책임을&amp;nbsp;분리하도록&amp;nbsp;더&amp;nbsp;자사히&amp;nbsp;메소드를&amp;nbsp;분리할&amp;nbsp;것&lt;br /&gt;-&amp;nbsp;if문이&amp;nbsp;많이&amp;nbsp;중첩되지&amp;nbsp;않도록&amp;nbsp;할&amp;nbsp;것&lt;br /&gt;-&amp;nbsp;테스트&amp;nbsp;코드&amp;nbsp;작성해볼&amp;nbsp;것&lt;br /&gt;-&amp;nbsp;배열&amp;nbsp;대신&amp;nbsp;컬렉션&amp;nbsp;사용해볼&amp;nbsp;것&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;br&amp;gt;&lt;br /&gt;&lt;br /&gt;###&amp;nbsp;배운&amp;nbsp;점&amp;nbsp;&amp;amp;&amp;nbsp;깨달은&amp;nbsp;점(+코드&amp;nbsp;리뷰)&lt;br /&gt;&lt;br /&gt;2주차&amp;nbsp;목표를&amp;nbsp;지키기&amp;nbsp;위해서&amp;nbsp;하나하나&amp;nbsp;체크해가며&amp;nbsp;코드를&amp;nbsp;작성하였다.&amp;nbsp;다른&amp;nbsp;것은&amp;nbsp;최대한&amp;nbsp;지키려고&amp;nbsp;노력했지만&amp;nbsp;졸업&amp;nbsp;작품도&amp;nbsp;같이&amp;nbsp;준비하다보니,&amp;nbsp;테스트&amp;nbsp;코드까지&amp;nbsp;작성하기엔&amp;nbsp;시간이&amp;nbsp;좀&amp;nbsp;부족했다..!&amp;nbsp;통합적인&amp;nbsp;큰&amp;nbsp;테스트만&amp;nbsp;작성했는데,&amp;nbsp;**이번&amp;nbsp;주엔&amp;nbsp;단위&amp;nbsp;테스트까지&amp;nbsp;작성하는게&amp;nbsp;목표**이다.&amp;nbsp;테스트&amp;nbsp;코드를&amp;nbsp;짤&amp;nbsp;때,&amp;nbsp;어려움을&amp;nbsp;겪느라&amp;nbsp;더&amp;nbsp;오래걸렸다.&amp;nbsp;큰&amp;nbsp;단위의&amp;nbsp;테스트는&amp;nbsp;단순히&amp;nbsp;결과값이&amp;nbsp;일치하는지&amp;nbsp;테스트&amp;nbsp;코드만&amp;nbsp;짜면&amp;nbsp;되지만&amp;nbsp;랜덤&amp;nbsp;값을&amp;nbsp;추출하는&amp;nbsp;경우엔&amp;nbsp;어떻게&amp;nbsp;그&amp;nbsp;값을&amp;nbsp;테스트하지..?라는&amp;nbsp;의문이&amp;nbsp;들면서&amp;nbsp;그&amp;nbsp;부분에&amp;nbsp;대해서는&amp;nbsp;테스트를&amp;nbsp;진행하지&amp;nbsp;못했다.&amp;nbsp;다른&amp;nbsp;분들의&amp;nbsp;코드를&amp;nbsp;보니&amp;nbsp;코드를&amp;nbsp;짤&amp;nbsp;때부터&amp;nbsp;테스트를&amp;nbsp;하기&amp;nbsp;위한&amp;nbsp;코드들을&amp;nbsp;작성하셨다.&amp;nbsp;이런&amp;nbsp;부분은&amp;nbsp;학습&amp;nbsp;후에&amp;nbsp;다음주&amp;nbsp;회고록을&amp;nbsp;작성할&amp;nbsp;때&amp;nbsp;배운&amp;nbsp;내용을&amp;nbsp;적어봐야겠다!&lt;br /&gt;그리고&amp;nbsp;계층형&amp;nbsp;아키텍처에&amp;nbsp;대해&amp;nbsp;알아보면&amp;nbsp;어떠냐는&amp;nbsp;리뷰도&amp;nbsp;받았다.&amp;nbsp;사실&amp;nbsp;저번주에&amp;nbsp;작성한대로&amp;nbsp;기본적인&amp;nbsp;domain(model)-view-controller로만&amp;nbsp;작성하느라&amp;nbsp;각&amp;nbsp;클래스에&amp;nbsp;너무&amp;nbsp;많은&amp;nbsp;역할을&amp;nbsp;하게&amp;nbsp;만들었던&amp;nbsp;것&amp;nbsp;같다.&amp;nbsp;또한&amp;nbsp;**도메인&amp;nbsp;계층에서&amp;nbsp;뷰&amp;nbsp;계층을&amp;nbsp;직접&amp;nbsp;참조하는&amp;nbsp;것은&amp;nbsp;좋지&amp;nbsp;않다**는&amp;nbsp;것도&amp;nbsp;처음&amp;nbsp;알았다.&amp;nbsp;정말&amp;nbsp;배워도&amp;nbsp;배워도&amp;nbsp;배울게&amp;nbsp;계속&amp;nbsp;나오고&amp;nbsp;모르는&amp;nbsp;것도&amp;nbsp;정말&amp;nbsp;많은&amp;nbsp;것&amp;nbsp;같다.&amp;nbsp;그리고&amp;nbsp;그만큼&amp;nbsp;코드&amp;nbsp;리뷰를&amp;nbsp;하는&amp;nbsp;것이&amp;nbsp;정말&amp;nbsp;중요하다는&amp;nbsp;것도&amp;nbsp;깨달았다.&lt;br /&gt;&lt;br /&gt;&amp;lt;br&amp;gt;&lt;br /&gt;&lt;br /&gt;###&amp;nbsp;다음주&amp;nbsp;목표&amp;nbsp;&lt;br /&gt;-&amp;nbsp;계층형&amp;nbsp;아키텍처에&amp;nbsp;대해&amp;nbsp;학습&lt;br /&gt;-&amp;nbsp;단위테스트(1주&amp;nbsp;차&amp;nbsp;피드백&amp;nbsp;강의&amp;nbsp;참고)&lt;br /&gt;-&amp;nbsp;도메인에서&amp;nbsp;뷰를&amp;nbsp;직접적으로&amp;nbsp;참조하지&amp;nbsp;않도록&amp;nbsp;하기&lt;br /&gt;-&amp;nbsp;매직넘버는&amp;nbsp;상수로&amp;nbsp;빼기&lt;/p&gt;</description>
      <category>우아한 테크코스 프리코스</category>
      <author>bogyeom</author>
      <guid isPermaLink="true">https://dingco-ringco.tistory.com/9</guid>
      <comments>https://dingco-ringco.tistory.com/9#entry9comment</comments>
      <pubDate>Wed, 26 Feb 2025 16:53:00 +0900</pubDate>
    </item>
    <item>
      <title>[우테코 프리코스 BE] 1주차 회고록</title>
      <link>https://dingco-ringco.tistory.com/8</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRtvUQ/btsMv3p7DGM/LCEcSDUlQSugiRYhQMabk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRtvUQ/btsMv3p7DGM/LCEcSDUlQSugiRYhQMabk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRtvUQ/btsMv3p7DGM/LCEcSDUlQSugiRYhQMabk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRtvUQ%2FbtsMv3p7DGM%2FLCEcSDUlQSugiRYhQMabk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;늦었지만 1주차부터 프리코스 과정을 되돌아보고자 한다 !&lt;br /&gt;&lt;a href=&quot;https://github.com/woowacourse-precourse/java-calculator-7/pull/1323&quot;&gt;https://github.com/woowacourse-precourse/java-calculator-7/pull/1323&lt;/a&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1주차 [문자열 덧셈 계산기]&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  프로젝트 소개&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트는 &lt;b&gt;문자열 덧셈 계산기&lt;/b&gt;를 구현합니다.&lt;br /&gt;사용자가 입력한 문자열에서 &lt;b&gt;숫자를 추출해 합을 계산&lt;/b&gt;하며, &lt;b&gt;쉼표(,)&lt;/b&gt;와 &lt;b&gt;콜론(:)&lt;/b&gt;을 기본 구분자로 사용합니다.&lt;br /&gt;또한, &lt;b&gt;커스텀 구분자&lt;/b&gt;를 정의할 수 있으며, 잘못된 입력이 들어오면 예외를 발생시킵니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  기능 요구 사항&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;기본 구분자&lt;/b&gt;: 쉼표(,)와 콜론(:)을 기준으로 문자열을 분리합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: &lt;code&gt;&quot;1,2,3&quot;&lt;/code&gt; &amp;rarr; 결과: 6&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;커스텀 구분자&lt;/b&gt;: &lt;code&gt;&quot;//[구분자]\n&quot;&lt;/code&gt; 형식으로 입력하면 커스텀 구분자로 분리합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: &lt;code&gt;&quot;//;\n1;2;3&quot;&lt;/code&gt; &amp;rarr; 결과: 6&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;빈 문자열&lt;/b&gt;: 입력이 빈 문자열이면 0을 반환합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: &lt;code&gt;&quot;&quot;&lt;/code&gt; &amp;rarr; 결과: 0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예외 처리&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;숫자가 아닌 값이나 음수가 입력되면 &lt;code&gt;IllegalArgumentException&lt;/code&gt;을 발생시키고 프로그램이 종료됩니다.&lt;/li&gt;
&lt;li&gt;예: &lt;code&gt;&quot;1,-2,3&quot;&lt;/code&gt; &amp;rarr; 예외 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  입출력 요구 사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;입력&lt;/b&gt;: 구분자와 양수로 이루어진 문자열&lt;/li&gt;
&lt;li&gt;&lt;b&gt;출력&lt;/b&gt;: 입력된 숫자들의 합&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;springboot 프로젝트를 진행한 경험도 있고, 공부도 했지만 mvc패턴에 대해서 정확히 이해를 못하고 있었던 것 같다. 그래서 1주차는 mvc패턴에 대해 학습하고, 기본적인 패턴만 지키면서 구현해보는 것을 목표로 두고 진행하였다 !&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1주차 목표&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;mvc 패턴에 대해 익숙해지기&lt;/li&gt;
&lt;li&gt;기능별로 메서드, 클래스 자세히 나눠보기&lt;/li&gt;
&lt;li&gt;readme에 기능을 자세히 작성해두기&lt;/li&gt;
&lt;li&gt;다양한 예외 상황 생각해보기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배운 점 &amp;amp; 깨달은 점&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/pbk0398/post/ca0efdba-445f-4ede-aa53-e5abefdb9949/image.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아예 손도 못대고 있었던 상황이라 최대한 기본적인 구조만 익힌 후, 그 패턴에 맞게 구현해보는 것을 목표로 하고 직접 써서 정리해보며 파일을 구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 직접 손으로 써가면서 이해하고 구현하니까 어느정도 MVC패턴에 대해 익히게 되었고, 단일 책임 원칙을 지켜보려 노력했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드까지는 작성하지 못했는데, 다른 분들의 코드를 보며 테스트 코드의 중요성을 느꼈다. 기본적인 테스트는 통과했지만, 테스트 코드를 예외 상황에 따라 정말 세세하게 여러 상황을 생각하여 작성한 분들이 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;반성해야할 점 (+코드 리뷰)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예외 상황에 대해 더 깊이 고민해볼 것&lt;/li&gt;
&lt;li&gt;메서드 이름을 어떤 동작을 하는지 알 수 있도록 정확히 작성할 것&lt;/li&gt;
&lt;li&gt;책임을 분리하도록 더 자사히 메소드를 분리할 것&lt;/li&gt;
&lt;li&gt;if문이 많이 중첩되지 않도록 할 것&lt;/li&gt;
&lt;li&gt;테스트 코드 작성해볼 것&lt;/li&gt;
&lt;li&gt;배열 대신 컬렉션 사용해볼 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 아직도 공부해아할 것이 많고, 부족한 것이 많지만 시간이 없는 틈에 지원한 것이기 때문에 매주 조금씩이라도 성장한 점이 있다면 만족할 것 같다 !&lt;br /&gt;매주 하나라도 더 배워 나가는 것이 앞으로의 목표이다 !&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>우아한 테크코스 프리코스</category>
      <author>bogyeom</author>
      <guid isPermaLink="true">https://dingco-ringco.tistory.com/8</guid>
      <comments>https://dingco-ringco.tistory.com/8#entry8comment</comments>
      <pubDate>Wed, 26 Feb 2025 16:51:25 +0900</pubDate>
    </item>
    <item>
      <title>[programmers] 같은 숫자는 싫어</title>
      <link>https://dingco-ringco.tistory.com/7</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1422&quot; data-origin-height=&quot;1162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qt4D4/btsJG6bflqr/OKzQ6fseIJQQ6Ix3oKmFBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qt4D4/btsJG6bflqr/OKzQ6fseIJQQ6Ix3oKmFBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qt4D4/btsJG6bflqr/OKzQ6fseIJQQ6Ix3oKmFBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqt4D4%2FbtsJG6bflqr%2FOKzQ6fseIJQQ6Ix3oKmFBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1422&quot; height=&quot;1162&quot; data-origin-width=&quot;1422&quot; data-origin-height=&quot;1162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 풀이: 연속돼서 중복인 숫자가 들어가지 않도록 배열을 다시 저장함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726833979265&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.Stack;

public class Solution {
    public int[] solution(int []arr) {
        Stack&amp;lt;Integer&amp;gt; stack = new Stack&amp;lt;&amp;gt;();
        
        for (int i : arr) {
            if (!stack.isEmpty() &amp;amp;&amp;amp; stack.peek() == i) {
                continue;
            } else {
                stack.push(i);
            }
        }
        
        int[] answer = new int[stack.size()];
        
        for (int i = stack.size() - 1; i &amp;gt;= 0; i--) {
            answer[i] = stack.pop();
        }

        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열, ArrayList, Stack 세 개의 방법 모두 가능하지만, 스택 문제라 스택으로 풀이 해 보았음&lt;/p&gt;</description>
      <category>알고리즘 오답노트/JAVA</category>
      <author>bogyeom</author>
      <guid isPermaLink="true">https://dingco-ringco.tistory.com/7</guid>
      <comments>https://dingco-ringco.tistory.com/7#entry7comment</comments>
      <pubDate>Fri, 20 Sep 2024 21:09:55 +0900</pubDate>
    </item>
    <item>
      <title>[programmers] 의상</title>
      <link>https://dingco-ringco.tistory.com/6</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1976&quot; data-origin-height=&quot;1540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oHy7g/btsJCWtmGTA/ThlSrXtKWuqFLk4A8Jj3Bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oHy7g/btsJCWtmGTA/ThlSrXtKWuqFLk4A8Jj3Bk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oHy7g/btsJCWtmGTA/ThlSrXtKWuqFLk4A8Jj3Bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoHy7g%2FbtsJCWtmGTA%2FThlSrXtKWuqFLk4A8Jj3Bk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1976&quot; height=&quot;1540&quot; data-origin-width=&quot;1976&quot; data-origin-height=&quot;1540&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 풀이 : 의상의 이름은 중요하지 않고, 의상의 종류별로 몇 가지의 의상이 있는지 확인한 후 조합하는 경우의 수를 구해야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 옷마다 의상의 수+1이 종류 별 경우의 수인데, 예를 들어 위 표에서 얼굴은 안경을 쓰는 경우, 선글라스를 쓰는 경우, 아무것도 쓰지 않는 경우 이렇게 2+1개의 종류이므로 총 경우의 수는 3x2x2x2임&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때! 모든 하나도 안 입는 경우가 발생하므로 1을 빼주어 3x2x2x2-1이 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726832217501&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.HashMap;

class Solution {
    public int solution(String[][] clothes) {
        int answer = 1;
        
        HashMap&amp;lt;String, Integer&amp;gt; map = new HashMap&amp;lt;&amp;gt;();
        
        for(String[] c: clothes) {
            String category = c[1];
            map.put(category, map.getOrDefault(category, 0)+1);
        }
        
        for(int i: map.values()) {
            answer *= (i+1);
        }
        
        return answer-1;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;몰랐던 부분&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;map.put(category, map.getOrDefault(category, 0)+1);에서 getOrDefault는 키가 카테고리인 것의 값이 없으면 기본 값을 0으로 갖고 1을 더한다는 뜻임&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 처음에는 처음 저장되는 값이므로 기본값 0에 +1을 하여 해당 카테고리의 값이 1로 저장되고, 그 다음엔 1+1인 2로 저장됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 방식으로 해당 카테고리의 의상이 몇 개인지 알 수 있게 됨&lt;/p&gt;</description>
      <category>알고리즘 오답노트/JAVA</category>
      <author>bogyeom</author>
      <guid isPermaLink="true">https://dingco-ringco.tistory.com/6</guid>
      <comments>https://dingco-ringco.tistory.com/6#entry6comment</comments>
      <pubDate>Fri, 20 Sep 2024 20:37:41 +0900</pubDate>
    </item>
    <item>
      <title>[programmers] 전화번호 목록</title>
      <link>https://dingco-ringco.tistory.com/5</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1444&quot; data-origin-height=&quot;908&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qywoN/btsJDEZER91/7moBsHCGGgK1bv5TVN4Zyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qywoN/btsJDEZER91/7moBsHCGGgK1bv5TVN4Zyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qywoN/btsJDEZER91/7moBsHCGGgK1bv5TVN4Zyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqywoN%2FbtsJDEZER91%2F7moBsHCGGgK1bv5TVN4Zyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1444&quot; height=&quot;908&quot; data-origin-width=&quot;1444&quot; data-origin-height=&quot;908&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 풀이 : 배열과 해시로 모두 풀이할 수 있음 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 번호가 다음 번호의 접두사인지 확인해야하는 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726230491141&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.Arrays;

class Solution {
    public boolean solution(String[] phone_book) {
        boolean answer = true;
        
        Arrays.sort(phone_book);
        
        for(int i=0; i&amp;lt;phone_book.length-1; i++) {
            if(phone_book[i+1].startsWith(phone_book[i])) {
                answer = false;
            }
        }        
        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 배열로 풀이해보았는데, 먼저 배열을 정렬한 뒤 startsWith를 사용해 접두사인지 확인하여 문제를 해결함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726231761955&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.HashSet;

class Solution {
    public boolean solution(String[] phone_book) {
        
        boolean answer = true;
        
        HashSet&amp;lt;String&amp;gt; set = new HashSet&amp;lt;&amp;gt;();
        
        for(String phone: phone_book) {
            set.add(phone);
        }
        
        for(String phone: phone_book) {
            for (int i = 1; i &amp;lt; phone.length(); i++) {
                if(set.contains(phone.substring(0,i))) {
                    return false;
                }
            }
        }
        
        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값 하나를 비교하는거라 HashMap보다는 HashSet이 더 적합할 것이라고 생각해서 HashSet을 사용했음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 문제라 이게 더 간단할 것이라고 생각해서 해시를 이용하여 다시 풀어본 건데, 오히려 더 복잡하고 길고 시간이 오래 걸리는 것 같아 좋은 방법은 아닌 것 같음&lt;/p&gt;</description>
      <category>알고리즘 오답노트/JAVA</category>
      <author>bogyeom</author>
      <guid isPermaLink="true">https://dingco-ringco.tistory.com/5</guid>
      <comments>https://dingco-ringco.tistory.com/5#entry5comment</comments>
      <pubDate>Fri, 13 Sep 2024 21:51:39 +0900</pubDate>
    </item>
    <item>
      <title>[programmers] 완주하지 못한 선수</title>
      <link>https://dingco-ringco.tistory.com/4</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1756&quot; data-origin-height=&quot;1052&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biNySs/btsJx7u3uKU/Ax7KInS8jNlPjlkt0Ddvc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biNySs/btsJx7u3uKU/Ax7KInS8jNlPjlkt0Ddvc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biNySs/btsJx7u3uKU/Ax7KInS8jNlPjlkt0Ddvc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiNySs%2FbtsJx7u3uKU%2FAx7KInS8jNlPjlkt0Ddvc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1756&quot; height=&quot;1052&quot; data-origin-width=&quot;1756&quot; data-origin-height=&quot;1052&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 풀이 : 마라톤에 참여한 선수 중 완주하지 못한 선수의 이름을 출력해야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hash는 키 값이 중복되지 않게 저장되는데 마라톤 선수의 이름이 중복될 수 있다고 해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법이 생각이 나지 않아 chatgpt에게 방법을 물어봤음 ( 다음에 다시 혼자 풀어볼 것 ! )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;key값에 참가자 이름, value값에 사람 수를 작성하라고 알려줌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726039447100&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.HashMap;

class Solution {
    public String solution(String[] participant, String[] completion) {

        String result = &quot;&quot;;

        HashMap&amp;lt;String, Integer&amp;gt; map = new HashMap&amp;lt;&amp;gt;();

        for(String name : participant) {
            if(map.containsKey(name)) {
                int count = map.get(name);
                map.put(name, count+1);
            } else {
                map.put(name, 1);
            }
        }

        for(String name : completion) { 
            if(map.get(name)==1) {
                map.remove(name);
            } else {
                int count = map.get(name);
                map.put(name, count-1);
            }
        }

        for (String name : map.keySet()) {
            if (map.get(name) &amp;gt; 0) {
                result = name;
            }
        }

        return result;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 작성한 코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막에 출력 부분도 어떻게 string 형태로 출력하는지 몰라 오래 걸렸는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 hashmap을 잘 몰라서 그런 것 같아서 개념 공부의 필요성을 느낌&lt;/p&gt;</description>
      <category>알고리즘 오답노트/JAVA</category>
      <author>bogyeom</author>
      <guid isPermaLink="true">https://dingco-ringco.tistory.com/4</guid>
      <comments>https://dingco-ringco.tistory.com/4#entry4comment</comments>
      <pubDate>Wed, 11 Sep 2024 16:27:31 +0900</pubDate>
    </item>
    <item>
      <title>[programmers] 폰켓몬</title>
      <link>https://dingco-ringco.tistory.com/3</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2188&quot; data-origin-height=&quot;1482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byjc8Z/btsJzgxJvLb/GQuFQcgJ7ptgnJAZ7kUbK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byjc8Z/btsJzgxJvLb/GQuFQcgJ7ptgnJAZ7kUbK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byjc8Z/btsJzgxJvLb/GQuFQcgJ7ptgnJAZ7kUbK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbyjc8Z%2FbtsJzgxJvLb%2FGQuFQcgJ7ptgnJAZ7kUbK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;583&quot; data-origin-width=&quot;2188&quot; data-origin-height=&quot;1482&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;문제 풀이 : 해당 문제는 중복되지 않는 수가 몇 개인지 찾아 출력하는 문제인데, 이때 출력 값은 N/2보다 클 수 없는 조건을 갖고 있음&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 해시 타입을 사용해 본 적이 없어서 막무가내로 배열을 만들어 하나하나 비교해보았음&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;시간 복잡도가 너무 올라가고, 정확도가 떨어져 해시맵을 사용해 봄&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726036352320&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.HashMap;

class Solution {
    public int solution(int[] nums) {
        int answer = 0;
        int a = nums.length;
         HashMap&amp;lt;Integer, Integer&amp;gt; map = new HashMap&amp;lt;&amp;gt;();
        for(int i=0; i&amp;lt;a; i++) {
            map.putIfAbsent(nums[i], i);
        }

        if(map.size() &amp;gt;= a/2) {
            answer = a/2;
        } else{
            answer = map.size();
        }

        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 이 방법도 정답이지만 더 간단하게 할 방법이 있을 것 같아 다른 분들의 풀이 방법과 ChatGPT를 사용해 HashSet이 더 적합하다는 걸 알았음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 숫자만 비교하는 문제이기 때문에, 중복된 숫자는 삽입되지 않는 HashSet을 사용하는 것으로 수정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726036543268&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.HashSet;

class Solution {
    public int solution(int[] nums) {
        int answer = 0;
        
        HashSet&amp;lt;Integer&amp;gt; set = new HashSet&amp;lt;&amp;gt;();
        
        for(int i : nums) {
            set.add(i);
        }
        
        answer = Math.min(set.size(), nums.length/2);
        
        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 수정 후 훨씬 빠르고 간결해졌음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1550&quot; data-origin-height=&quot;890&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JgRKo/btsJzqmAoUe/jkBFrlras5hsVGI5of8tcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JgRKo/btsJzqmAoUe/jkBFrlras5hsVGI5of8tcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JgRKo/btsJzqmAoUe/jkBFrlras5hsVGI5of8tcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJgRKo%2FbtsJzqmAoUe%2FjkBFrlras5hsVGI5of8tcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1550&quot; height=&quot;890&quot; data-origin-width=&quot;1550&quot; data-origin-height=&quot;890&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java Streams API를 사용하여 풀이하신 분도 있었는데, 다음에는 스트림에 대해 공부해서 이를 이용해 다시 풀이해보고 싶음&lt;/p&gt;</description>
      <category>알고리즘 오답노트/JAVA</category>
      <author>bogyeom</author>
      <guid isPermaLink="true">https://dingco-ringco.tistory.com/3</guid>
      <comments>https://dingco-ringco.tistory.com/3#entry3comment</comments>
      <pubDate>Wed, 11 Sep 2024 15:41:51 +0900</pubDate>
    </item>
  </channel>
</rss>