<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>사랑의 개발일지</title>
    <link>https://developtrace.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 5 Aug 2026 13:42:36 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>사랑박</managingEditor>
    <image>
      <title>사랑의 개발일지</title>
      <url>https://tistory1.daumcdn.net/tistory/5626616/attach/6d20df4994494d5eb964df1ed77ca18a</url>
      <link>https://developtrace.tistory.com</link>
    </image>
    <item>
      <title>프로그래머스 SQL - 서울에 위치한 식당 목록 출력하기</title>
      <link>https://developtrace.tistory.com/58</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 문제&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/131118&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/131118&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1721565635535&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/131118&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dUXBWX/hyWCJNJFT3/6Q3igLeQp0DiWcSDtn6x61/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ppcqV/hyWCNP9YtU/wEUbU9byGukwgksqPxiKR1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/131118&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/131118&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dUXBWX/hyWCJNJFT3/6Q3igLeQp0DiWcSDtn6x61/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ppcqV/hyWCNP9YtU/wEUbU9byGukwgksqPxiKR1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 풀이&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제에서 LIKE '%서울'은 정답인데 '%서울%'은 오답됨 ㅠ 어이 X&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;LIKE&lt;/b&gt;&lt;/h4&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;HAVING&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GROUP BY와 꼭 같이 써야됨&lt;/li&gt;
&lt;li&gt;특정 그룹 선택하고 싶을 때&lt;/li&gt;
&lt;li&gt;COUNT, SUM, AVG, MAX, MIN 등&amp;nbsp;집계함수&amp;nbsp;사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 정답&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1721565671186&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT I.REST_ID, I.REST_NAME, I.FOOD_TYPE, I.FAVORITES, I.ADDRESS, ROUND(AVG(R.REVIEW_SCORE),2) AS SCORE
FROM REST_INFO I, REST_REVIEW R
WHERE I.REST_ID = R.REST_ID
GROUP BY I.REST_ID
HAVING I.ADDRESS LIKE '서울%'
ORDER BY SCORE DESC, I.FAVORITES DESC&lt;/code&gt;&lt;/pre&gt;</description>
      <category>SQL</category>
      <category>MySQL</category>
      <category>Oracle</category>
      <category>sql</category>
      <category>서울에 위치한 식당 목록 출력하기</category>
      <category>프로그래머스</category>
      <author>사랑박</author>
      <guid isPermaLink="true">https://developtrace.tistory.com/58</guid>
      <comments>https://developtrace.tistory.com/58#entry58comment</comments>
      <pubDate>Sun, 21 Jul 2024 21:48:25 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 SQL - 대장균의 크기에 따라 분류하기 2</title>
      <link>https://developtrace.tistory.com/57</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 문제&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/301649&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/301649&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1721562824759&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/301649&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bdivEj/hyWCLx1vb4/cYfNqjoOt10SE84jCgiu51/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bufNZi/hyWCCOz4bB/y0oSQj9YcmQSveaNvrTshk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/301649&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/301649&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bdivEj/hyWCLx1vb4/cYfNqjoOt10SE84jCgiu51/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bufNZi/hyWCCOz4bB/y0oSQj9YcmQSveaNvrTshk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 풀이&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;MySQL의 `NTITLE&lt;span&gt; 함수` 사용해서 풀이할 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;NTILE&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;정의&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;특정한 컬럼의 값을 기준으로 N개 등급으로 분류하는 함수&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;사용법&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1721563225180&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT NTILE([나눌 정수]) OVER (PARTITION BY [컬럼1] ORDER BY [컬럼2])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PATITION BY [컬럼1] 하면 [컬럼1]에서 파티션 나누고 파티션 내에서 N 등급&lt;/li&gt;
&lt;li&gt;PATITION BY 는 생략 가능&lt;/li&gt;
&lt;li&gt;전체 행 개수를 나누고 싶은 그룹 수(나눌 정수)로 나눴을 때 나머지가 발생하면 첫 번째 그룹부터 크기가 1씩 추가됨&lt;/li&gt;
&lt;li&gt;ex) 17개 데이터를 3그룹으로 나누면(나머지가 2) &lt;b&gt;1번째 그룹 6개, 2번째 그룹 6개&lt;/b&gt;, 3번째 그룹 5개가 됨 !&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 정답&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1721562900457&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WITH COLONY_LEVEL AS (
    SELECT ID, 
    NTILE(4) OVER (ORDER BY SIZE_OF_COLONY DESC) AS COLONY_LEVEL
    FROM ECOLI_DATA
)

SELECT A.ID, 
    CASE
        WHEN B.COLONY_LEVEL = 1 THEN &quot;CRITICAL&quot;
        WHEN B.COLONY_LEVEL = 2 THEN &quot;HIGH&quot;
        WHEN B.COLONY_LEVEL = 3 THEN &quot;MEDIUM&quot;
        WHEN B.COLONY_LEVEL = 4 THEN &quot;LOW&quot;
    END AS COLONY_NAME
FROM ECOLI_DATA A, COLONY_LEVEL B
WHERE A.ID = B.ID;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>SQL</category>
      <category>MySQL</category>
      <category>NTILE</category>
      <category>Oracle</category>
      <category>sql</category>
      <category>대장균의 크기에 따라 분류하기 2</category>
      <category>프로그래머스</category>
      <author>사랑박</author>
      <guid isPermaLink="true">https://developtrace.tistory.com/57</guid>
      <comments>https://developtrace.tistory.com/57#entry57comment</comments>
      <pubDate>Sun, 21 Jul 2024 21:10:04 +0900</pubDate>
    </item>
    <item>
      <title>[백준 11404] 플로이드 (플로이드 와샬 알고리즘) - 자바(JAVA)</title>
      <link>https://developtrace.tistory.com/56</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 문제&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11404&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.acmicpc.net/problem/11404&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1697001630081&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;11404번: 플로이드&quot; data-og-description=&quot;첫째 줄에 도시의 개수 n이 주어지고 둘째 줄에는 버스의 개수 m이 주어진다. 그리고 셋째 줄부터 m+2줄까지 다음과 같은 버스의 정보가 주어진다. 먼저 처음에는 그 버스의 출발 도시의 번호가 &quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://www.acmicpc.net/problem/11404&quot; data-og-url=&quot;https://www.acmicpc.net/problem/11404&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/xPgOZ/hyUdT5gcUp/0rpSvLfaWGphlKbicKTMdK/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11404&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.acmicpc.net/problem/11404&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/xPgOZ/hyUdT5gcUp/0rpSvLfaWGphlKbicKTMdK/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;11404번: 플로이드&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;첫째 줄에 도시의 개수 n이 주어지고 둘째 줄에는 버스의 개수 m이 주어진다. 그리고 셋째 줄부터 m+2줄까지 다음과 같은 버스의 정보가 주어진다. 먼저 처음에는 그 버스의 출발 도시의 번호가&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.acmicpc.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 풀이&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모든 정점 쌍에 대해서&lt;/b&gt; A에서 B로 가는데 필요한 비용의 최솟값을 구할 때는 &lt;b&gt;플로이드 와샬 알고리즘&lt;/b&gt;을 사용해서 구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 코드&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1697001799544&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {

    static int N,M;
    static int[][] adj;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        N=Integer.parseInt(br.readLine());
        M=Integer.parseInt(br.readLine());
        adj=new int[N+1][N+1];
        for(int i=0;i&amp;lt;M;i++){
            StringTokenizer st=new StringTokenizer(br.readLine());
            int s=Integer.parseInt(st.nextToken());
            int e=Integer.parseInt(st.nextToken());
            int v=Integer.parseInt(st.nextToken());
            if(adj[s][e]==0 || adj[s][e]&amp;gt;v) // 값이 없거나 최소일 때
                adj[s][e]=v;
        }

        floydWarshall();

        StringBuilder sb=new StringBuilder();
        for(int i=1;i&amp;lt;=N;i++) {
            for(int j=1;j&amp;lt;=N;j++) {
                sb.append(adj[i][j]).append(&quot; &quot;);
            }
            sb.append(&quot;\n&quot;);
        }
        System.out.println(sb);
    }

    private static void floydWarshall() {
        for(int k=1;k&amp;lt;=N;k++){
            for(int i=1;i&amp;lt;=N;i++){
                for(int j=1;j&amp;lt;=N;j++){
                    if(i==j||adj[i][k]==0||adj[k][j]==0)
                        continue;
                    if(adj[i][j]==0 || adj[i][k]+adj[k][j]&amp;lt;adj[i][j])
                        adj[i][j]=adj[i][k]+adj[k][j];
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘</category>
      <author>사랑박</author>
      <guid isPermaLink="true">https://developtrace.tistory.com/56</guid>
      <comments>https://developtrace.tistory.com/56#entry56comment</comments>
      <pubDate>Wed, 11 Oct 2023 14:23:24 +0900</pubDate>
    </item>
    <item>
      <title>[백준 1197] 최소 스패닝 트리 (MST, 크루스칼 알고리즘) - 자바(JAVA)</title>
      <link>https://developtrace.tistory.com/55</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 문제&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1197&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.acmicpc.net/problem/1197&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1697000816125&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;1197번: 최소 스패닝 트리&quot; data-og-description=&quot;첫째 줄에 정점의 개수 V(1 &amp;le; V &amp;le; 10,000)와 간선의 개수 E(1 &amp;le; E &amp;le; 100,000)가 주어진다. 다음 E개의 줄에는 각 간선에 대한 정보를 나타내는 세 정수 A, B, C가 주어진다. 이는 A번 정점과 B번 정점이 &quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://www.acmicpc.net/problem/1197&quot; data-og-url=&quot;https://www.acmicpc.net/problem/1197&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/oDe4a/hyUdMZmMxs/AtKHZ0iJDs0kk3svmPozIk/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1197&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.acmicpc.net/problem/1197&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/oDe4a/hyUdMZmMxs/AtKHZ0iJDs0kk3svmPozIk/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;1197번: 최소 스패닝 트리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;첫째 줄에 정점의 개수 V(1 &amp;le; V &amp;le; 10,000)와 간선의 개수 E(1 &amp;le; E &amp;le; 100,000)가 주어진다. 다음 E개의 줄에는 각 간선에 대한 정보를 나타내는 세 정수 A, B, C가 주어진다. 이는 A번 정점과 B번 정점이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.acmicpc.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 풀이&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최소 스패닝 트리(MST)는 주어진 그래프의 모든 정점들을 연결하는 부분 그래프 중에서 가중치의 합이 최소인 트리이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MST는 &lt;b&gt;크루스칼, 프림 알고리즘&lt;/b&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;b&gt; Union&amp;nbsp; Find&lt;/b&gt;를 사용하는 크루스칼 알고리즘으로 문제를 풀었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 코드&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1697001119079&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

public class Main {

	static int[] parent;

	static class Edge {
		int start;
		int end;
		int cost;

		public Edge(int start, int end, int cost) {
			this.start = start;
			this.end = end;
			this.cost = cost;
		}
	}

	static int V, E;

	public static void main(String[] args) throws IOException {

		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());

		V = Integer.parseInt(st.nextToken()); // 정점 개수
		E = Integer.parseInt(st.nextToken()); // 간선 개수
		parent = new int[V + 1];
		for (int i = 1; i &amp;lt;= V; i++) {
			parent[i] = i;
		}
		PriorityQueue&amp;lt;Edge&amp;gt; pq = new PriorityQueue&amp;lt;&amp;gt;((o1, o2) -&amp;gt; o1.cost - o2.cost);

		for (int i = 0; i &amp;lt; E; i++) {
			st = new StringTokenizer(br.readLine());
			int start = Integer.parseInt(st.nextToken());
			int end = Integer.parseInt(st.nextToken());
			int cost = Integer.parseInt(st.nextToken());
			pq.add(new Edge(start, end, cost));
		}

		// 크루스칼 알고리즘
		int total = 0;
		while (!pq.isEmpty()) {
			Edge e = pq.poll(); // 가중치 가장 작은거 뽑기
			if (find(e.start) != find(e.end)) { // 부모가 같지 않으면(같은 집합에 속해있지 않음)
				total += e.cost;
				union(e.start, e.end); // 연결
			}
		}

		System.out.println(total);
	}

	public static int find(int v) {
		if (parent[v] == v)
			return v;
		return parent[v] = find(parent[v]);
	}

	public static void union(int v1, int v2) {
		v1 = find(v1);
		v2 = find(v2);
		if (v1 &amp;lt; v2) {
			parent[v2] = v1;
		} else {
			parent[v1] = v2;
		}
	}
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘</category>
      <category>java</category>
      <category>kruskal</category>
      <category>MST</category>
      <category>union find</category>
      <category>백준</category>
      <category>자바</category>
      <category>최소 스패닝 트리</category>
      <category>크루스칼</category>
      <author>사랑박</author>
      <guid isPermaLink="true">https://developtrace.tistory.com/55</guid>
      <comments>https://developtrace.tistory.com/55#entry55comment</comments>
      <pubDate>Wed, 11 Oct 2023 14:12:27 +0900</pubDate>
    </item>
    <item>
      <title>[백준 1753] 최단경로 (다익스트라 알고리즘) - 자바(JAVA)</title>
      <link>https://developtrace.tistory.com/54</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 문제&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1753&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.acmicpc.net/problem/1753&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1697000260142&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;1753번: 최단경로&quot; data-og-description=&quot;첫째 줄에 정점의 개수 V와 간선의 개수 E가 주어진다. (1 &amp;le; V &amp;le; 20,000, 1 &amp;le; E &amp;le; 300,000) 모든 정점에는 1부터 V까지 번호가 매겨져 있다고 가정한다. 둘째 줄에는 시작 정점의 번호 K(1 &amp;le; K &amp;le; V)가 &quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://www.acmicpc.net/problem/1753&quot; data-og-url=&quot;https://www.acmicpc.net/problem/1753&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cH1foe/hyT9F1SM9z/2KSF22TEw5BDWfhW3st6rK/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1753&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.acmicpc.net/problem/1753&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cH1foe/hyT9F1SM9z/2KSF22TEw5BDWfhW3st6rK/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;1753번: 최단경로&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;첫째 줄에 정점의 개수 V와 간선의 개수 E가 주어진다. (1 &amp;le; V &amp;le; 20,000, 1 &amp;le; E &amp;le; 300,000) 모든 정점에는 1부터 V까지 번호가 매겨져 있다고 가정한다. 둘째 줄에는 시작 정점의 번호 K(1 &amp;le; K &amp;le; V)가&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.acmicpc.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 풀이&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방향그래프가 주어질 때 한 시작점에서 다른 모든 정점으로의 최댄 경로를 구하는 문제이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다익스트라 알고리즘으로 문제를 풀 수 있다. 다익스트라 알고리즘은 PriorityQueue를 사용한다 !&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 코드&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1697000570125&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    static int V,E, start;
    static int[] dis;
    static boolean[] visit;
    static ArrayList&amp;lt;Edge&amp;gt;[] adj;

    static class Edge{
        int v; // 도착 정점
        int c; // 비용
        public Edge(int v,int c){
            this.v=v;
            this.c=c;
        }
    }
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st=new StringTokenizer(br.readLine());
        V=Integer.parseInt(st.nextToken()); // 정점개수
        E=Integer.parseInt(st.nextToken()); // 간선개수
        start=Integer.parseInt(br.readLine()); // 시작 위치

        // 간선 입력 받기 + 인접 리스트
        adj=new ArrayList[V+1];
        for(int i=0;i&amp;lt;=V;i++){
            adj[i]=new ArrayList&amp;lt;Edge&amp;gt;();
        }
        for(int i=0;i&amp;lt;E;i++){
            st=new StringTokenizer(br.readLine());
            int s=Integer.parseInt(st.nextToken());
            int e=Integer.parseInt(st.nextToken());
            int c=Integer.parseInt(st.nextToken());
            adj[s].add(new Edge(e,c));
        }

        // 경로 비용 저장하는 배열 - 무한대로 초기화
        dis=new int[V+1];
        for(int i=1;i&amp;lt;=V;i++){
            dis[i]=Integer.MAX_VALUE;
        }
        visit=new boolean[V+1];
        // 다익스트라
        dijkstra();
        // 정답 출력
        StringBuilder sb=new StringBuilder();
        for(int i=1;i&amp;lt;=V;i++){
            if(dis[i]==Integer.MAX_VALUE){
                sb.append(&quot;INF&quot;);
            }else {
                sb.append(dis[i]);
            }
            sb.append(&quot;\n&quot;);
        }
        System.out.println(sb);
    }

    public static void dijkstra(){
        // Comparator : 람다 -&amp;gt; 가중치 작은 순서로 정렬
        PriorityQueue&amp;lt;Edge&amp;gt; pq=new PriorityQueue&amp;lt;&amp;gt;((o1, o2) -&amp;gt; o1.c - o2.c);
        dis[start]=0;
        pq.offer(new Edge(start,0));

        while(!pq.isEmpty()){
            Edge now=pq.poll();
            if(visit[now.v])
                continue;
            visit[now.v]=true;
            for(Edge e : adj[now.v]){ // 현재 정점에서 갈 수 있는 정점&amp;amp;비용 탐색
                if(!visit[e.v] &amp;amp;&amp;amp; dis[now.v] + e.c &amp;lt; dis[e.v]){ // 최소 비용으로 방문
                    dis[e.v]=dis[now.v]+e.c;
                    pq.offer(new Edge(e.v, dis[e.v]));
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘</category>
      <category>Dijkstra</category>
      <category>Graph</category>
      <category>java</category>
      <category>그래프</category>
      <category>다익스트라</category>
      <category>백준</category>
      <category>자바</category>
      <category>최단경로</category>
      <author>사랑박</author>
      <guid isPermaLink="true">https://developtrace.tistory.com/54</guid>
      <comments>https://developtrace.tistory.com/54#entry54comment</comments>
      <pubDate>Wed, 11 Oct 2023 14:03:54 +0900</pubDate>
    </item>
    <item>
      <title>모던 자바 인 액션 - CH06.  스트림으로 데이터 수집(2)</title>
      <link>https://developtrace.tistory.com/53</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;6.4 분할&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;분할은 분할 함수라 불리는 프레디케이트를 분류 함수로 사용하는 특수한 그룹화 기능이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;분할 함수는 불리언을 반환하므로 맵의 키 형식은&amp;nbsp; Boolean이다. -&amp;gt; 그룹화 맵은 최대 (참 or 거짓) 두 개의 그룹으로 분류됨&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694478822381&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 채익 요리와 채식이 아닌 요리로 분류
Map&amp;lt;Boolean, List&amp;lt;Dish&amp;gt;&amp;gt; partitionedMenu =
		menu.stream().collect(partitioningBy(Dish::isVegetarian)); // 분할 함수
// 채식 요리를 얻기
List&amp;lt;Dish&amp;gt; vegitarianDishes = partitionedMenu.get(true);

// 프레디케이트로 필터링한 다음에 별도의 리스트에 결과를 수집해도 같은 결과를 얻을 수 있다.
List&amp;lt;Dish&amp;gt; vegitarianDishes = 
	menu.stream().filter(Dish::isVegetarian).collect(toList());&lt;/code&gt;&lt;/pre&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;6.4.1 분할의 장점&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;분할 함수가 반환하는 참, 거짓 두 가지 요소의 스트림 리스트를 모두 유지한다는 것이 분할의 장점이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;컬렉터를 두 번째 인수로 전달할 수 있는 오버로드된 버전의 partitioningBy 메서드가 있다. 채식 요리의 스트림과 채식이 아닌 요리의 스트림을 각각 요리 종류로 그룹화해서 두 수준의 맵이 반환되었다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694479167181&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Map&amp;lt;Boolean, List&amp;lt;Dish&amp;gt;&amp;gt; partitionedMenu = menu.stream().collect(
	partitioningBy(Dish::isVegetarian, groupingBy(Dish::getType)));
// {false=FISH=[prawns, salmon], MEAT=[pork, beef, chicken]}, true={OTHER=[french fires, rice, season fruit, pizza]}}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이전 코드를 활용하면 채식 요리와 채식이 아닌 요리 각각의 그룹에서 가장 칼로리가 높은 요리도 찾을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694479330771&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Map&amp;lt;Boolean, List&amp;lt;Dish&amp;gt;&amp;gt; mostCaloricPartitionedByVegetarian =
menu.stream().collect(
	partitioningBy(Dish::isVegetarian,
    		collectingAndThen(maxBy(comparingInt(Dish::getCalories)), Optional::get)));
// {false=pork, true=pizza}&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 style=&quot;font-family: 'Nanum Gothic';&quot;&gt;partitioningBy가 반환한 맵 구현은 참과 거짓 두 가지 키만 포함하므로 더 간결하고 효과적이다.&lt;/span&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;6.4.2 숫자를 소수와 비소수로 분할하기&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;정수 n을 인수로 받아서 2에서 n까지의 자연수를 소수와 비소수로 나누는 프로그램을 구현하자.&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 style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저 주어진 수가 소수인지 아닌지 판단하는 프레디케이트를 구현하자&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694479789843&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public boolean isPrime(int candidate) {
  return IntStream.range(2, candidate).noneMatch(i -&amp;gt; candidate % i == 0);
  //스트림의 모든 정수로 candidate를 나눌 수 없으면 참을 반환
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;소수의 대상을 주어진 수의 제곱근 이하의 수로 제한한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694479913699&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public boolean isPrime(int candidate) {
  int candidateRoot = (int) Math.sqrt((double)candidate);
  return IntStream.rangeClosed(2, candidateRoot).noneMatch(i -&amp;gt; candidate % i == 0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;n개의 숫자를 포함하는 스트림을 만든 다음에 우리가 구현한 isPrime 메서드를 프레디케이트로 이용하고 partitioningBy 컬렉터로 리듀스해서 숫자를 소수와 비소수로 분류할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694480073636&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public Map&amp;lt;Boolean, List&amp;lt;Integer&amp;gt;&amp;gt; partitionPrimes(int n) {
  return IntStream.rangeClosed(2, n).boxed().collect(partitioningBy(candidate -&amp;gt; isPrime(candidate)));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;6.5 Collector 인터페이스&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Collector 인터페이스는 리듀싱 연산(즉, 컬렉터)을 어떻게 구현할지 제공하는 메서드 집합으로 구성된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;우리가 Collector 인터페이스를 구현하는 리듀싱 연산을 만들 수도 있다.&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 style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다음은 Collector 인터페이스의 시그니처와 다섯 개의 메서드 정의를 보여준다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694481413533&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface Collector&amp;lt;T, A, R&amp;gt; {
  Supplier&amp;lt;A&amp;gt; supplier();
  BiConsumer&amp;lt;A, T&amp;gt; accumulator();
  Function&amp;lt;A, R&amp;gt; finisher();
  BinaryOperator&amp;lt;A&amp;gt; combiner();
  Set&amp;lt;Characteristics&amp;gt; characteristics();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위 코드를 다음처럼 설명할 수 있다.&lt;/span&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;font-family: 'Nanum Gothic';&quot;&gt;T는 수집될 스트림 항목의 제네릭 형식이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;A는 누적자, 즉 수집 과정에서 중간 결과를 누적하는 객체의 형식이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;R은 수집 연산 결과 객체의 형식(항상 그런 것은 아니지만 대개 컬렉션 형식)이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;예를 들어 Stream&amp;lt;T&amp;gt;의 모든 요소를 List&amp;lt;T&amp;gt;로 수집하는 ToListCollector&amp;lt;T&amp;gt;라는 클래스를 구현할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694481667645&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ToListCollector&amp;lt;T&amp;gt; implements Collector&amp;lt;T, List&amp;lt;T&amp;gt;, List&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;&lt;/pre&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;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;6.5.1 Collector 인터페이스의 메서드 살펴보기&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 Collector 인터페이스에 정의된 다섯 개의 메서드를 하나씩 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;네 개의 메서드는 collect 메서드에 실행하는 함수를 반환하는 반면,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;characteristics는 collect 메서드가 어떤 최적화(예를 들면 병렬화)를 이용해서 리듀싱 연산을 수행할 것인지를 결정하도록 돕는 힌트 특성 집합을 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;supplier 메서드 : 새로운 결과 컨테이너 만들기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;supplier 메서드는 빈 결과로 이루어진 Supplier를 반환해야한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;즉, supplier는 수집 과정에서 빈 누적자 인스턴스를 만드는 파라미터가 없는 함수다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;ToListCollector처럼 누적자를 반환하는 컬렉터에서는 빈 누적자가 비어있는 스트림의 수집 과정의 결과가 될 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694482468645&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public Supplier&amp;lt;List&amp;lt;T&amp;gt;&amp;gt; supplier() {
  return() -&amp;gt; new ArrayList&amp;lt;T&amp;gt;();
}

//생성자 참조 방식으로 전달도 가능
public Supplier&amp;lt;List&amp;lt;T&amp;gt;&amp;gt; supplier() {
  return ArrayList::new;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;accumulator 메서드 : 결과 컨테이너에 요소 추가하기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;accumulator 메서드는 리듀싱 연산을 수행하는 함수를 반환한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;스트림에서 n번째 요소를 탐색할 때 두 인수, 즉 누적자(스트림의 첫 n-1개 항목을 수집한 상태)와 n번째 요소를 함수에 적용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;함수의 반환값은 void, 즉 요소를 탐색하면서 적용하는 함수에 의해서 누적자 내부상태가 바뀌므로 누적자가 어떤 값일지 단정할 수 없다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694482662204&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public BiConsumer&amp;lt;List&amp;lt;T&amp;gt;, T&amp;gt; acuumulator() {
  return (list, item) -&amp;gt; list.add(item);
}

//다음처럼 메서드 참조를 이용하면 코드가 더 간결해진다.
public BiConsumer&amp;lt;List&amp;lt;T&amp;gt;, T&amp;gt; accumulator() {
  return List::add;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;finisher 메서드 : 최종 변환값을 결과 컨테이너로 적용하기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;finisher 메서드는 스트림 탐색을 끝내고 누적자 객체를 최종 결과로 변환하면서 누적 과정을 끝낼 때 호출할 함수를 반환해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;때로는 ToListCollector에서 볼 수 있는 것처럼 누적자 객체가 이미 최종 결과인 상황도 있다. 이런 때는 변환 과정이 필요하지 않으므로 finisher 메서드는 항등 함수를 반환한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694483065829&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public Function&amp;lt;List&amp;lt;T&amp;gt; List&amp;lt;T&amp;gt;&amp;gt; finisher() {
  return Function.identity();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;combiner 메서드 : 두 결과 컨테이너 병합&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;combiner는 스트림의 서로 다른 서브파트를 병렬로 처리할 때 누적자가 이 결과를 어떻게 처리할지 정의한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;toList의 combiner는 비교적 쉽게 구현할 수 있다. 즉, 스트림의 두 번째 서브파트에서 수집한 항목 리스트를 첫 번째 서브파트 결과 리스트의 뒤에 추가하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694483220614&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public BinaryOperator&amp;lt;List&amp;lt;T&amp;gt;&amp;gt; combiner() {
  return (list1, list2) -&amp;gt; {
    liat.addAll(list2);
    return list1;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Charactoeristics 메서드&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;characteristics 메서드는 컬렉터의 연산을 정의하는 Characteristics 형식의 불변 집합을 반환한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Characteristics는 스트림을 병렬로 리듀스 할지와 병렬로 리듀스한다면 어떤 최적화를 선택해야할지 힌트를 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Characteristics는 다음 세 항목을 포함하는 열거형이다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #5c5c5c; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;UNORDERED : 리듀싱 결과는 스트림 요소의 방문 순서나 누적 순서에 영향을 받지 않는다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;CONCURRENT : 다중 스레드에서 accumulator 함수를 호출할 수 있으며 이 컬렉터는 스트림의 병렬 리듀싱을 수행할 수 있다. 컬렉터의 플래그에 UNORDERED를 함께 설정하지 않았다면 데이터 소스가 정렬되어있지 않은 상황에서만 병렬 리듀싱을 수행할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;IDENTITY_FINISH : finisher 메서드가 반환하는 함수는 단순히 identity를 적용할 뿐이므로 이를 생략할 수 있다. 따라서 리듀싱 과정의 최종 결과로 누적자 객체를 바로 사용할 수 있으며, 누적자 A를 결과 R로 안전하게 형변환할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;6.5.2 응용하기&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;지금까지 살펴본 다섯 가지 메서드를 이용해서 자신만의 커스텀 ToListCollector를 구현할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694483909614&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ToListCollector&amp;lt;T&amp;gt; implements Collect&amp;lt;T, List&amp;lt;T&amp;gt;, List&amp;lt;T&amp;gt;&amp;gt; {
  @Override
  public Supplier&amp;lt;List&amp;lt;T&amp;gt;&amp;gt; supplier() {
    return ArrayList::new; // 수집 연산의 시발점
  }
  
  @Override
  public BiConsumer&amp;lt;List&amp;lt;T&amp;gt;, T&amp;gt; accumulator() {
    return List::add; // 탐색한 항목을 누적하고 바로 누적자를 고친다.
  }
  
  @Override
  public Function&amp;lt;List&amp;lt;T&amp;gt; List&amp;lt;T&amp;gt;&amp;gt; finisher() {
    return Function.identity(); // 항등 함수
  }
  
  @Override
  public BinaryOperator&amp;lt;List&amp;lt;T&amp;gt;&amp;gt; combiner() {
    return (list1, list2) -&amp;gt; { // 두 번째 콘텐츠와 합쳐서 첫 번째 누적자를 고친다.
      liat.addAll(list2); // 변경된 첫 번째 누적자를 반환한다.
      return list1;
    }
  }
    
  @Override
  public Set&amp;lt;Characteristics&amp;gt; characteristics() {
    return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH, CONCURRENT)); // 컬렉터의 플래그 설정
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 자바에서 제공하는 API 대신 우리가 만든 컬렉터를 메뉴 스트림의 모든 요리를 수집하는 예제에 사용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694484124518&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Dish&amp;gt; dishies = menuStream.collect(new ToListCollector&amp;lt;Dish&amp;gt;());&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;size18&quot;&gt;&lt;b&gt;컬렉터 구현을 만들지 않고도 커스텀 수집 수행하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;IDENTITY_FINISH 수집 연산에서는 Collector 인터페이스를 완전히 새로 구현하지 않고도 같은 결과를 얻을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Stream은 세 함수(발행, 누적, 합침)를 인수로 받는 collect&amp;nbsp; 메서드를 오버로드하여 각각의 메서드는 Collector 인터페이스의 메서드가 반환하는 함수와 같은 기능을 수행한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694484257637&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Dish&amp;gt; dishes = menuStream.collect(
  ArrayList::new, //발행
  List::add,  //누적
  List:addAll); //합침&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위 코드는 가독성이 떨어지고, Characteristics를 전달할 수 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;6.6 커스텀 컬렉터를 구현해서 성능 개선하기&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;6.4절에서 커스텀 컬렉터로 n까지의 자연수를&amp;nbsp; 소수와 비소수로 분할하였다. 그리고 제곱근 이하로 대상의 숫자 범위를 제한해서 isPrime 메서드를 개선했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1695026616836&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public boolean isPrime(List&amp;lt;Integer&amp;gt; primes, int candidate) {
  int candidateRoot = (int) Math.sqrt((double)candidate);
  return primes.stream()
    .talkWhile(i -&amp;gt; i &amp;lt;= candidateRoot);
    .noneMatch(i -&amp;gt; candidate % i == 0);
}&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;6.6.1 소수로만 나누기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제수(devisor)가 소수가 아니면 소용없으므로 제수를 현재 숫자 이하에서 발견한 소수로 제한할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 숫자가 소수인지 아닌지 판단해야 한다 -&amp;gt; 지금까지 발견한 소수 리스트에 접근해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉터로는 컬렉터 수집 과정에서 부분 결과에 접근할 수 없다. -&amp;gt; 커스텀 컬렉터 클래스로 이 문제를 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간 결과 리스트가 있다면 isPrime 메서드로 중간 결과 리스트를 전달하도록 다음과 같이 코드를 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JAVA</category>
      <category>collector</category>
      <category>java</category>
      <category>stream</category>
      <category>스트림</category>
      <category>자바</category>
      <category>자바8</category>
      <category>컬렉터</category>
      <author>사랑박</author>
      <guid isPermaLink="true">https://developtrace.tistory.com/53</guid>
      <comments>https://developtrace.tistory.com/53#entry53comment</comments>
      <pubDate>Tue, 12 Sep 2023 09:55:10 +0900</pubDate>
    </item>
    <item>
      <title>모던 자바 인 액션 - CH06.  스트림으로 데이터 수집(1)</title>
      <link>https://developtrace.tistory.com/52</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가기 전에...&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 장에서는 reduce가 그랬던 것처럼 collect 역시 다양한 요소 누적 방식을 인수로 받아서 스트림을 최종 결과로 도출하는 리듀싱 연산을 수행할 수 있음을 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stream에 toList를 사용하는 대신 더 범용적인 컬렉터 파라미터를 collect 메서드에 전달함으로써 원하는 연산을 간결하게 구현할 수 있음을 지금부터 배우게 될 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1693811410737&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Map&amp;lt;Currency, List&amp;lt;Transaction&amp;gt;&amp;gt; transactionsByCurrencies = 
                transactions.stream().collect(groupingBy(Transaction::getCurrency));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6.1 컬렉터란 무엇인가?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 예제엇 collect 메서드로 Collector 인터페이스 구현을 전달했다. Collector 인터페이스의 구현은 스트림의 요소를 어떤 식으로 도출할지 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 groupingBy를 이용해서 `각 키 (통화) 버킷 그리고 각 키 버킷에 대응하는 요소 리스트를 값으로 포함하는 맵(Map)을 만들라`는 동작을 수행한다.&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;b&gt;6.1.1 고급 리듀싱 기능을 수행하는 컬렉터&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스트림에 collect를 호출하면 스트림의 요소에 (컬렉터로 파라미터화된) 리듀싱 연산이 수행된다.&lt;/li&gt;
&lt;li&gt;명령형 프로그래밍에서는 우리가 직접 구현해야 했던 작업이 자동으로 수행된다.&lt;/li&gt;
&lt;li&gt;collect에서는 리듀싱 연산을 이용해서 스트림의 각 요소를 방문하면서 컬렉터가 작업을 처리한다.&lt;/li&gt;
&lt;li&gt;보통 함수를 요소로 변환(toList처럼 데이터 자체를 변환하는 것보다는 데이터저장 구조를 변활할 때가 많다)할 때는 컬렉터를 적용하며 최종 결과를 저장하는 자료구조에 값을 누적한다.&lt;/li&gt;
&lt;li&gt;Collector 인터페이스의 메서드를 어떻게 구현하느냐에따라 스트림에 어떤 리듀싱 연산을 수행할지 결정된다.&lt;/li&gt;
&lt;li&gt;Collectors 유틸리티클래스는 자주 사용하는 컬렉터 인스턴스를 손쉽게 생성할 수 있는 정적 팩토리 메서드를 제공한다.(예를들어 가장 많이 사용하는 직관적인 정적 메서드로 toList를 꼽을 수 있다.&lt;/li&gt;
&lt;li&gt;toList는 스트림의 모든 요소를리스트로 수집한다.&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;&lt;b&gt;6.1.2 미리 정의된컬렉터&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6장에서는 미리 정의된 컬렉터,즉 groupingBy와 같이 Collecors 클래스에서 제공하는 팩토리메서드의 기능을 설명한다.&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;Collectors에서 제공하는 메서드의 기능은 크게 세 가지로 구분할 수 있다.&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;li&gt;요소 분할 - 한 개의인수를 받아 불리언을 반환하는 함수, 프레디케이트를 그룹화 함수로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6.2 리듀싱과 요약&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트림의 모든 항목을 하나의 결과로 합칠 수 있다. 트리를 구성하는 다수준 맵, 메뉴의 칼로리 합계를 가리키는 단순한 정수 등 다양한 형식으로 결과가 도출될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 예제로 counting()이라는 팩토리 메서드가 반환하는 컬렉터로 메뉴에서 요리 수를 계산한다.&lt;/p&gt;
&lt;pre id=&quot;code_1693816662987&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Long howManyCars = carList.stream().collect(Collectors.counting());

// 다음처럼 불필요한 과정을 생략할 수 있다.
Long howManyCars = carList.stream().count();&lt;/code&gt;&lt;/pre&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;b&gt;6.2.1 스트림값에서 최댓값과 최솟값 검색&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Collectors.maxBy, Collectors.minBy 두 개의 메서드를 이용해서 스트림의 최댓값과 최솟값을 계산할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 컬렉터는 스트림의 요소를 비교하는 데 사용할 Comparator를 인수로 받는다.&lt;/p&gt;
&lt;pre id=&quot;code_1693817087392&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Comparator를 구현한다음에 Collectors.maxBy로 전달하는 코드이다.
Comparator&amp;lt;Dish&amp;gt; dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories);

Optional&amp;lt;Dish&amp;gt; mostCalorieDish = 
	menu.stream()
    	    .collect(maxBy(dishCaloriesComparator));&lt;/code&gt;&lt;/pre&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6.2.2 요약 연산&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Collectors 클래스는 Collectors.summingInt라는 특별한 요약 팩토리 메서드를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;summingInt는 객체를 int로 매핑하는 함수를 인수로 받는다.&lt;br /&gt;summingInt의 인수로 전달된 함수는 객체를 int로 매핑한 컬렉터를 반환한다. 그리고 summingInt가 collect 메서드로 전달되면 요약 작업을 수행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1693817641024&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Collectors.averagingInt, averagingLong, averagingDouble 등으로 다양한 형식으로 이루어진 숫자 집합의 평균을 계산할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1693817873597&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;double avgCalories =
	menu.stream().collect(averagingInt(Dish::getCalories));&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;종종 두 개 이상의 연산을 한 번에 수행해야 할 때도 있다. 이런 상황에서는 팩토리 메서드 summarizingInt가 반환하는 컬렉터를 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1693967083059&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 메뉴에 있는 요소 수, 요리의 칼로리 합계, 평균, 최댓값, 최솟값 등을 계산하는 코드
IntSummaryStatistics menuStatistics = 
	menu.stream().collect(summarizingInt(Dish::getCalories));
    
// munuStatistics 출력 결과
IntSummaryStatistics{count=9, sum=4300, min=120, average=477.777778, max=800}&lt;/code&gt;&lt;/pre&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;b&gt;6.2.3 문자열 연결&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉터에 joinig 팩토리 메서드를 이용하면 스트림의 각 객체에 toString 메서드를 호출해서 추출한 모든 문자열을 하나의 문자열로 연결해서 반환한다. joining메서드는 내부적으로 StringBuilder를 이용해서 문자열을 하나로 만든다.&lt;/p&gt;
&lt;pre id=&quot;code_1693967299066&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String shortMenu = menu.stream().map(Dish::getName).collect(joining());
// Dish 클래스가 요리명을 반환하는 toString 메서드를 포함하고 있다면 map으로 각 요리의 이름을 추출하는 과정을 생략 가능
String shortMenu = menu.stream().collect(joining());
// 결과
porkbeefchickenfrench friesriceseason fruitpizzaprawnssalmon

// 콤마로 구분 가능
String shortMenu = menu.stream().map(Dish::getName).collect(joining(&quot;, &quot;));
// 결과
pork, beef, chicken, french, fries, rice, season, fruit, pizza, prawns, salmon&lt;/code&gt;&lt;/pre&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;b&gt;6.2.4 범용 리듀싱 요약 연산&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 살펴본 모든 컬렉터는 reducint 팩토리 메서드로도 정의할 수 있다. 즉, 범용 Collectors.reducing으로도 구현할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1693967999723&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 인수 3개
int totalCalories = menu.stream().collect(reducing(0, Dish::getCalories, (i, j) -&amp;gt; i + j));
// 인수 1개
Optional&amp;lt;Dish&amp;gt; mostCalorieDish = 
	menu.stream().collect(reducing(
    		(d1, d2) -&amp;gt; d1.getCalories() &amp;gt; d2.getCalories() ? d1 : d2));&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;컬렉션 프레임워크 유연성 : 같은 연산도 다양한 방식으로 수행할 수 있다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reducing 컬렉터를 사용한 이전 예제에서 람다 표현식 대신 Integer 클래스의 sum 메서드 참조를 이용하면 코드를 좀 더 단순화할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1693968825322&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 같은 결과
int totalCalories = menu.stream().collect(reducing(0, Dish::getCalories, Integer::sum));
int totalCalories = menu.stream().map(Dish::getCalories).reduce(Integer::sum).get
int totalCalories = menu.stream().mapToInt(Dish::getCalories).sum();&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;++ &lt;/b&gt;&lt;b&gt;제네릭 와일드카드 '?' 사용법&lt;/b&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6.3 그룹화&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팩토리 메서드 Collectors.groupingBy를 이용해서 쉽게 메뉴를 그룹화할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1693969390738&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Map&amp;lt;Dish.type, List&amp;lt;Dish&amp;gt;&amp;gt; dishedByType =
	menu.stream().collect(groupingBy(Dish::getType));&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 메서드 참조 대신 람다 표현식으로 필요한 로직을 구현할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1693969543825&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum CaloricLevel { DIET, NORMAL, FAT }

Map&amp;lt;CaloricLevel, List&amp;lt;Dish&amp;gt;&amp;gt; dishesByCaloricLevel = menu.stream().collect(
  groupingBy(dish -&amp;gt; {
    if (dish.getCalories() &amp;lt;= 400 ) return CaloricLevel.DIET;
    else if (dish.getCalories() &amp;lt;= 700 ) return CaloricLevel.NORMAL;
    else return CaloricLevel.FAT;
  }));&lt;/code&gt;&lt;/pre&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;b&gt;6.3.1 그룹화된 요소 조작&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요소를 구룹화 한 다음에는 각 결과 그룹의 요소를 조작하는 연산이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FISH 종류 요리는 없으므로 결과 맵에서 해당 키 자체가 사라진다. Collectors 클래스는 일반적인 분류 함수에 Collector 형식의 두 번째 인수를 갖도록 groupingBy 팩토리 메서드를 오버로드해 이 문제를 해결한다.&lt;/p&gt;
&lt;pre id=&quot;code_1693973927692&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Map&amp;lt;Dish, Type, List&amp;lt;Dish&amp;gt;&amp;gt; caloricDishesByType = menu.stream()
  .filter(dish -&amp;gt; dish.getCalories() &amp;gt; 500)
  .collect(groupingBy(Dish::getType));
// 결과 : {OTHER=[french fries, pizza], MEAT=[pork, beef]}

Map&amp;lt;Dish, Type, List&amp;lt;Dish&amp;gt;&amp;gt; caloricDishesByType = menu.stream()
  .collect(groupingBy(Dish::getType, filtering(dish -&amp;gt; getCalrories() &amp;gt; 500, toList())));
// 결과 : {OTHER=[french fries, pizza], MEAT=[pork, beef], FISH=[]}&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Collectors 클래스는 매핑 함수와 각 항목에 적용한 함수를 모으는 데 사용하는 또 다른 컬렉터를 인수로 받는 mapping 메서드를 제공한다.&lt;/p&gt;
&lt;pre id=&quot;code_1693974341592&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Map&amp;lt;Dish, Type, List&amp;lt;Sting&amp;gt;&amp;gt; dishNamesByTypes = menu.stream()
  .collect(groupingBy(Dish::Type, mapping(Dish::getName, toList())));&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 style=&quot;font-family: 'Nanum Gothic'; background-color: #ffffff; color: #666666; text-align: start;&quot;&gt;각 그룹이 리스트 형태라면 flatMap 변환을 사용해서 추출할 수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693974355900&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Map&amp;lt;String, List&amp;lt;String&amp;gt;&amp;gt; dishTags = new HashMap&amp;lt;&amp;gt;();
dishTag.push(&quot;pork&quot;, asList(&quot;greasy&quot;, &quot;salty&quot;));
dishTag.push(&quot;beef&quot;, asList(&quot;salty&quot;, &quot;roasted&quot;));
dishTag.push(&quot;chicken&quot;, asList(&quot;fried&quot;, &quot;crisp&quot;));
dishTag.push(&quot;rice&quot;, asList(&quot;light&quot;, &quot;natural&quot;));

Map&amp;lt;Dish.Type, Set&amp;lt;String&amp;gt;&amp;gt; dishNamesByType = menu.stream()
  .collect(groupingBy(Dish::getType,
    flatMapping(dish -&amp;gt; dishTags.get(dish.getName()).stream(),
    toSet())));
    
// 결과 : {MEAT=[salty, greasy, roasted, fried, crisp], FISH=[roasted, tasty, fresh, delicious], OTHER=[salty, greasy, natural, light, tasty, fresh, fried]}&lt;/code&gt;&lt;/pre&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;b&gt;6.3.2 다수준 그룹화&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바깥쪽 groupingBy 메서드에 스트림의 항목을 분류할 두 번째 기준을 정의하는 내부 groupingBy를 전달해서 두 수준으로 스트림의 항목을 그룹화할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1693974669125&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Map&amp;lt;Dish.Type, Map&amp;lt;CalricLevel, List&amp;lt;Dish&amp;gt;&amp;gt;&amp;gt; dishesByTypeCaloricLevel = menu.stream()
  .collect(groupingBy(Dish::getType, // 첫 번째 수준의 분류함수
    groupingBy(dish -&amp;gt; { // 두 번째 수준의 분류함수
      if (dish.getCalories() &amp;lt;= 400) return CaloricLevel.DIET;
      else if (dish.getCalories() &amp;lt;= 700) return CaloricLevel.NORMAL;
      else return CaloricLevel.FAT;
    })
  )
};
// 결과 : {MEAT={DIET=[chicken], NORMAL=[beef], FAT=[pork]}, FISH={DIET=[prawns], NORMAL=[salmon]}, OTHER={...}}&lt;/code&gt;&lt;/pre&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;b&gt;6.3.3 서브그룹으로 데이터 수집&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; background-color: #ffffff; color: #666666; text-align: start;&quot;&gt;groupingBy 메서드의 두번째 인수로 전달받는 컬렉터의 형식은 제한이 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; background-color: #ffffff; color: #666666; text-align: start;&quot;&gt;분류 함수 한 개의 인수를 갖는 groupingBy(f)는 사실 groupingBy(f, toList())의 축양형이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693976865327&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 메뉴에서 요리의 수를 종류별로 계산
Map&amp;lt;Dish.Type, Long&amp;gt; typesCount = menu.stream().collect(groupingBy(Dish::getType, counting()));
// 결과 : {MEAT=3, FISH=2, OTHER=4}

// 메뉴에서 가장 높은 칼로레를 가진 요리를 찾는 프로그램
Map&amp;lt;Dish.Type, Optional&amp;lt;Dish&amp;gt;&amp;gt; mostCaloricByType = menu.stream()
  .collect(groupingBy(Dish::getType, maxBy(CompaingInt(Dish::getCalories))));
// 결과 : {FISH=Optional[salmon], OTHER=Optional[pizza], MEAT=Optional[pork]}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;컬렉터 결과를 다른 형식에 적용하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 그룹화 연산에서 맵의 모든 값을 Optional로 감쌀 필요가 없으므로 Optional을 삭제할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음처럼 팩토리 메서드 Collectors.collectingAndThen으로 컬렉터가 반환한 결과를 다른 형식으로 활용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1693977137709&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Map&amp;lt;Dish.Type, Optional&amp;lt;Dish&amp;gt;&amp;gt; mostCaloricByType = menu.stream()
  .collect(groupingBy(Dish::getType,
    collectingAndThen(maxBy(CompaingInt(Dish::getCalories)), Optional::get)));
// 결과 : {FISH=salmon, OTHER=pizza, MEAT=pork}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JAVA</category>
      <author>사랑박</author>
      <guid isPermaLink="true">https://developtrace.tistory.com/52</guid>
      <comments>https://developtrace.tistory.com/52#entry52comment</comments>
      <pubDate>Mon, 4 Sep 2023 17:58:13 +0900</pubDate>
    </item>
    <item>
      <title>모던 자바 인 액션 - CH05. 스트림 활용</title>
      <link>https://developtrace.tistory.com/51</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5.1 필터링&lt;/b&gt;&lt;/h2&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;b&gt;5.1.1 프레디케이트로 필터링&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트림 인터페이스는 filter 메서드를 지원한다. filter 메서드는 프레디케이트(불리언을 반환하는 함수)를 인수로 받아서 프레디케이트와 일치하는 모든 요소를 포함하는 스트림을 반환한다.&lt;/p&gt;
&lt;pre id=&quot;code_1692606667848&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Dish&amp;gt; vegetarianMenu = menu.stream()
				.filter(Dish::isVegetarian)
                                .collect(toList());&lt;/code&gt;&lt;/pre&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;b&gt;5.1.2 고유 요소 필터링&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트림은 고유 요소로 이루어진 스트림을 반환하는 distinct 메서드도 지원한다. 고유 여부는 스트림에서 만든 객체의 hashCode, equals로 결정된다.&lt;/p&gt;
&lt;pre id=&quot;code_1692606749936&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Integer&amp;gt; numbers = Arrays.asList(1,2,3,4,5,6,7,8,123,41,5,1,2,3,4,5,5,6,7,8)
        .stream()
        .filter(number-&amp;gt;number%2==0)
        .distinct()
        .collect(Collectors.toList());&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5.2 스트림 슬라이싱&lt;/b&gt;&lt;/h2&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;b&gt;5.2.1 프레디케이트를 이용한 슬라이싱&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 9은 스트림의 요소를 효과적으로 선택할 수 있도록&amp;nbsp; takeWhile, dropWhile 두 가지 새로운 메서드를 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;THAKEWHILE 활용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트가 이미 정렬되어 있다는 사실을 이용해 320 칼로리보다 크거나 같은 요리가 나왔을 때 반복 작업을 중단할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1692607121375&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Dish&amp;gt; filterMenu = specialMenu.stream()
                            .takeWhile(dish -&amp;gt; dish.getCalories() &amp;lt; 320)
                            .collect(toList());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;DROPWHILE 활용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dropWhile은 takeWhile과 정반대의 작업을 수행한다. dropWhile은 프레디케이트가 처음으로 거짓이 되는 지점까지 발견된 요소를 버린다. 프레디케이트가 거짓이 되면 그 지점에서 작업을 중단하고 남은 모든 요소를 반환한다. dropWhile은 무한한 남은 요소를 가진 무한 스트림에서도 동작한다.&lt;/p&gt;
&lt;pre id=&quot;code_1692607163576&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Dish&amp;gt; filterMenu = specialMenu.stream()
                            .dropWhile(dish -&amp;gt; dish.getCalories() &amp;lt; 320)
                            .collect(toList());&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5.2.2 스트림 축소&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트림은 주어진 값 이하의 크기를 갖는 새로운 스트림을 반환하는 &lt;b&gt;limit (n)&lt;/b&gt; &lt;b&gt;메서드&lt;/b&gt;를 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트림이 정렬되어 있으면 최대 요소 n개를 반환할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트림이 정렬되지 않아도 limit을 사용할 수 있다. -&amp;gt; limit의 결과도 정렬되지 않은 상태로 반환된다.&lt;/p&gt;
&lt;pre id=&quot;code_1692681575041&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Dish&amp;gt; dishes = specialMenu.stream()
                            .filter(dish -&amp;gt; dish.getCalories() &amp;gt; 300)
                            .limit(3) // 3개로 축소
                            .collect(toList());&lt;/code&gt;&lt;/pre&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;b&gt;5.2.3 요소 건너뛰기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트림은 처음 n개 요소를 제외한 스트림을 반환하는 &lt;b&gt;skip (n)&lt;/b&gt; &lt;b&gt;메서드&lt;/b&gt;를 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;n개 이하의 요소를 포함하는 스트림에 skip (n) 을 호출하면 빈 스트림이 반환된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;limit (n) 과 skip (n)은 상호 보완적인 연산을 수행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1692681798571&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  List&amp;lt;Dish&amp;gt; dishes = menu.stream()
                            .filter(dish -&amp;gt; dish.getCalories() &amp;lt; 300)
                            .skip(2) // 2개 건너뛰기
                            .collect(toList());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5.3 매핑&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 객체에서 특정 데이터를 선택하는 작업은 데이터 처리과정에서 자주 수행되는 연산이다. 스트림 API의 &lt;b&gt;map과 flatMap 메서드&lt;/b&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;b&gt;5.3.1 스트림의 각 요소에 함수 적용하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트림은 함수를 인스로 받는 &lt;b&gt;map 메서드&lt;/b&gt;를 지원한다. 인수로 제공된 함수는 각 요소에 적용되며 함수를 적용한 결과가 새로운 요소로 매핑된다.&lt;/p&gt;
&lt;pre id=&quot;code_1692682109426&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    // 요리명을 추출
    List&amp;lt;String&amp;gt; dishNames = menu.stream()
                            .map(Dish::getName)
                            .collect(toList());
                            
                            
    // 단어의 길이 추출
    List&amp;lt;String&amp;gt; words = Arrays.asList(&quot;Modern&quot;, &quot;Java&quot;, &quot;In&quot;, &quot;Action&quot;);
    List&amp;lt;Integer&amp;gt; wordLengths = words.stream()
          .map(String::length)
          .collect(toList());


    // 요리명의 길이 추출
    List&amp;lt;String&amp;gt; dishNames = menu.stream()
                            .map(Dish::getName)
                            .map(String::length)
                            .collect(toList());&lt;/code&gt;&lt;/pre&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;b&gt;5.3.2 스트림 평면화&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트에서 고유 문자로 이루어진 리스트를 반환해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[&quot;Hello&quot;, &quot;World&quot;] 리스트가 있다면 결과로 [&quot;H&quot;, &quot;e&quot;, &quot;l&quot;, &quot;o&quot;, &quot;w&quot;, &quot;r&quot;, &quot;d&quot;]를 포함하는 리스트가 반환되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;flatMap 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flatMap은 각 배열을 스트림이 아니라 스트림의 콘텐츠로 매핑한다. 즉, map (Arrays::stream)과 달리 flatMap은 하나의 평면화된 스트림을 반환한다.&lt;/p&gt;
&lt;pre id=&quot;code_1692682884613&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  List&amp;lt;String&amp;gt; uniqueCharacters = words.stream()
          .map(word -&amp;gt; word.split(&quot;&quot;)) // 각 단어를 개별 문자를 포함하는 배열로 변환 
          .flatMap(Arrays::stream) // 생성된 스트림을 하나의 스트림으로 평면화
          .distinct()
          .collect(toList());&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5.4 검색과 매칭&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 속성이 데이터 집합에 있는지 여부를 검색하는 데이터 처리도 자주 사용된다. 스트림 API는 &lt;b&gt;allMatch, anyMatch, noneMatch, findFirst, findAny&lt;/b&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;b&gt;5.4.1 프레디케이트가 적어도 한 요소와 일치하는지 확인&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레디케이트가 주어진 스트림에서 적어도 한 요소와 일치하는지 확일할 때 &lt;b&gt;anyMatch 메서드&lt;/b&gt;를 이용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;anyMatch는 불리언을 반환하므로 최종 연산이다.&lt;/p&gt;
&lt;pre id=&quot;code_1692683350796&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (menu.stream().anyMath(Dish::isVegetarian) {
	System.out.println(&quot;The menu is (somewhat) vegetarian friendly!!&quot;);
}&lt;/code&gt;&lt;/pre&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;b&gt;5.4.2 프레디케이트가 모든 요소와 일치하는지 검사&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;allMatch 메서드&lt;/b&gt;는 anyMatch와 달리 스트림의 모든 요소가 주어진 프레디케이트와 일치하는지 검사한다.&lt;/p&gt;
&lt;pre id=&quot;code_1692683493544&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;boolean isHealthy = menu.stream()
			.allMatch(dish -&amp;gt; dish.getCalories() &amp;lt; 1000);&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;size18&quot;&gt;&lt;b&gt;NONEMATCH&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;noneMatch는 allMatch와 반대 연산을 수행한다. 즉, noneMatch는 주어진 프레디케이트와 일치하는 요소가 없는지 확인한다.&lt;/p&gt;
&lt;pre id=&quot;code_1692683708171&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;boolean isHealthy = menu.stream()
			.noneMatch(dish -&amp;gt; dish.getCalories() &amp;lt; 1000);&lt;/code&gt;&lt;/pre&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;b&gt;5.4.3 요소 검색&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;findAny 메서드&lt;/b&gt;는 현재 스트림에서 임의의 요소를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;findAny 메서드를 다른 스트림 연산과 연결해서 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1692684329235&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; Optional&amp;lt;Dish&amp;gt; dish = 
 	menu.stream()
    	    .filter(Dish::isVegetarian)
            .findAny();	//findAny는 Optional 객체를 반환&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Optional이란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Optional&amp;lt;T&amp;gt; 클래스(java.util.Optional)는 값의 존재나 부재 여부를 표현하는 컨테이너 클래스다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 예제에서 findAny는 아무 요소도 반환하지 않을 수 있다. null은 쉽게 에러를 일으킬 수 있으므로 자바 8 라이브러리 설계자는 Optional&amp;lt;T&amp;gt;를 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Optional은 값이 존재하는지 확인하고 값이 없을 때 어떻게 처리할지 강제하는 기능을 제공한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;isPresent()는 Optional이 값을 포함하면 참(true)를 반환하고, 값을 포함하지 않으면 거짓(false)를 반환한다.&lt;/li&gt;
&lt;li&gt;ifPresent(Consumer&amp;lt;T&amp;gt; block)은 값이 있으면 주어진 블록을 실행한다.&lt;/li&gt;
&lt;li&gt;T get()은 값이 존재하면 값을 반환하고, 값이 없으면 NoSuchElementException을 일으킨다.&lt;/li&gt;
&lt;li&gt;T orElse(T other) 는 값이 있으면 값을 반환하고, 값이 없으면 기본값을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1692684890515&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; 	menu.stream()
    	    .filter(Dish::isVegetarian)
            .findAny();	// Optional&amp;lt;Dish&amp;gt;반환
            .ifPresent(dish-&amp;gt; System.out.println(dish.getName()); // 값이 있으면 출력되고 값이 없으면 아무 일도 일어나지 않는다.&lt;/code&gt;&lt;/pre&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;b&gt;5.4.4 첫 번째 요소 찾기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트 또는 정렬된 연속 데이터로부터 생성된 스트림처럼 일부 스트림에는 논리적인 아이템 순서가 정해져 있을 수 있다. 이런 스트림에서 첫 번째 요소를 찾으려면 어떻게 해야 할까?&lt;/p&gt;
&lt;pre id=&quot;code_1692685053651&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Integer&amp;gt; someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional&amp;lt;Integer&amp;gt; first = someNumbers.stream()
        .map(n -&amp;gt; n * n)
        .filter(n -&amp;gt; n % 3 == 0)
        .findFirst(); // 첫 번째 요소 찾기&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5.5 리듀싱&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 절에서 리듀스(reduce) 연산을 이용해서 `메뉴의 모든 칼로리의 합계를 구하시오.`, `메뉴에서 칼로리가 가장 높은 요리는?` 같이 스트림 요소를 조합해서 더 복잡한 질의를 표현하는 방법을 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 질의를 수행하려면 Integer 같은 결과가 나올 때까지 스트림의 모든 요소를 반복적으로 처리해야 한다. 이런 질의를 &lt;b&gt;리듀싱 연산&lt;/b&gt;(모든 스트림 요소를 처리해서 값으로 도출하는)이라고 한다. 함수형 프로그래밍 언어 용어로는 이 과정이 마치 종리(우리의 스트림)를 작은 조각이 될 때까지 반복해서 접는 것과 비슷하다는 의미로 &lt;b&gt;폴드&lt;/b&gt;(fold)라고 부른다.&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;b&gt;5.5.1 요소의 합&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reduce를 이용해서 다음처럼 스트림의 모든 요소를 더할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1693186000645&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int sum = numbers.stream().reduce(0, (a,b) -&amp;gt; a + b);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reduce는 두 개의 인수를 갖는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초깃값 0&lt;/li&gt;
&lt;li&gt;두 요소를 조합해서 새로운 값을 만드는 BinaryOperator&amp;lt;T&amp;gt;. 예제에서는 람다 표현식 (a,b) -&amp;gt; a+b를 사용했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reduce로 다른 람다, 즉(a+b) -&amp;gt; a*b를 넘겨주면 모든 요소에 곱셈을 적용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1693186286678&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int product = numbers.stream().reduce(1, (a,b) -&amp;gt; a*b);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 참조를 이용해서 이 코드를 좀 더 간결하게 만들 수 있다. 자바 8에서는 Integer 클래스에 두 숫자를 더하는 정적 sum 메서드를 제공한다. 따라서 직접 람다 코드를 구현할 필요가 없다.&lt;/p&gt;
&lt;pre id=&quot;code_1693186407557&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int sum = numbers.stream().reduce(0, Integer::sum);&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;초깃값 없음&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초깃값을 받지 않도록 오버로드된 reduce도 있다. 그러나 이 reduce는 Optional 객체를 반환한다.&lt;/p&gt;
&lt;pre id=&quot;code_1693186520773&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Optional&amp;lt;Integer&amp;gt; sum = numbers.stream().reduce((a,b) -&amp;gt; (a+b));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트림에 아무 요소도 없을 때 초깃값이 없으므로 reduce는 합계를 반환할 수 없다. 따라서 합계가 없음을 가리킬 수 있도록 Optional 객체로 감싼 결과를 반환한다.&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;b&gt;5.5.2 최댓값과 최솟값&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최댓값과 최솟값을 찾을 때 reduce를 활용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1693186732974&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 최댓값
Optional&amp;lt;Integer&amp;gt; max = numbers.stream().reduce(Integer::max);
// 최솟값
Optional&amp;lt;Integer&amp;gt; min = numbers.stream().reduce(Integer::min);
// 람다 표현식 사용
Optional&amp;lt;Integer&amp;gt; min = numbers.stream().reduce((x, y) -&amp;gt; x &amp;lt; y ? x:y);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5.7 숫자형 스트림&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음처럼 메뉴의 칼로리 합계를 계산할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1693195480875&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int calories = menu.stream()
		   .map(Dish::getCalories)
        	   .reduce(0, Integer::sum);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 박싱 비용이 숨어있다. 내부적으로 합계를 계산하기 전에 Integer를 기본형으로 언박싱해야 한다. 다음 코드처럼 직접 sum 메서드를 호출할 수 있다면 더 좋지 않을까?&lt;/p&gt;
&lt;pre id=&quot;code_1693195570833&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int calories = menu.stream()
		   .map(Dish::getCalories)
        	   .sum();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 위 코드처럼 sum 메서드를 직접 호출할 수 없다. map 메서드가 Stream&amp;lt;T&amp;gt;를 생성하기 때문이다. 스트림 요소 형식은 Integer지만 인터페이스에는 sum 메서드가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행이도 스트림 API 숫자 스트림을 효율적으로 처리할 수 있도록 &lt;b&gt;기본형 특화 스트림&lt;/b&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;b&gt;5.7.1 기본형 특화 스트림&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 8에서는 세 가지 기본형 특화 스트림을 제공한다. 스트림 API는 박싱 비용을 피할 수 있도록&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;int 요소에 특화된 IntStream&lt;/li&gt;
&lt;li&gt;double 요소에 특화된 DoubleStream&lt;/li&gt;
&lt;li&gt;long 요소에 특화된 LongStream&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 제공한다. 각각의 인터페이스는 sum, max 같이 자주 사용하는 숫자 관련된 리듀싱 연산 수행 메서드를 제공한다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;숫자 스트림으로 매핑&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트림을 특화 스트림으로 변환할 때는 mapToInt, mapToDouble, mapToLong 세 가지 메서드를 가장 많이 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이들 메서드는 map과 정확히 같은 기능을 수행하지만, Stream&amp;lt;T&amp;gt; 대신 특화된 스트림을 반환한다.&lt;/p&gt;
&lt;pre id=&quot;code_1693196307715&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int calories = menu.stream() // Stream&amp;lt;Dish&amp;gt; 반환
		   .mapToInt(Dish::getCalories) // IntStream 반환
		   .sum();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IntStream의 sum 메서드는 스트림이 비어있으면 기본값 0을 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IntStream은 max, min, average 등 다양한 유틸리티 메서드도 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;객체 스트림으로 복원하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;boxed 메서드를 이용해서 특화 스트림을 일반 스트림으로 변환할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1693267827318&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream&amp;lt;Integer&amp;gt; stream = intStream.boxed();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;b&gt;기본값 : OptionalInt&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Optional을 Integer, String 등의 참조 형식으로 파라미터화할 수 있다. 또한 OptionalInt, OptionalDouble, OptionalLong 세 가지 기본형 특화 스트림 버전도 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음처럼 OptionalInt를 이용해서 IntStream의 최댓값요소를 찾고 최댓값이 없는 상황에 사용할 기본값을 명시적으로 정의할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1693268264758&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;OptionalInt maxCalories = menu.stream()
			      .maptoInt(Dish::getCalories)
			      .max();

int max = maxCalories.orElse(1);&lt;/code&gt;&lt;/pre&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;b&gt;5.7.2 숫자 범위&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 8의 IntStream과 LongStream에서는 &lt;b&gt;range와 rangeClosed&lt;/b&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;rang 메서드는 시작값과 종료값이 결과에 포함되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rangClosed는 시작값과 종료값이 결과에 포함된다.&lt;/p&gt;
&lt;pre id=&quot;code_1693268699782&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;IntStream evenNumbers= IntStream.rangeClosed(1, 100) // [1, 100] 범위를 나타냄
				.filter(n -&amp;gt; n % 2 == 0) // 1부터 100까지 짝수 스트림
System.out.println(evenNumbers.count()); // 50&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;여기서 rangeClosed 대신에 IntStream.range(1, 100)을 사용하면 1과 100을 포함하지 않으므로 짝수 49개를 반환한다.&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;b&gt;5.7.3 숫자 스트림 활용 : 피타고라스 수&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`피타고라스 수` 스트림을 만들어 보자!&lt;/p&gt;
&lt;pre id=&quot;code_1693270636207&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Stream&amp;lt;int[]&amp;gt; pythagoreanTriples =
                IntStream.rangeClosed(1, 100).boxed()
                         .flatMap(a -&amp;gt;
                                 IntStream.rangeClosed(a, 100)
                                          .filter(b -&amp;gt; Math.sqrt(a * a + b * b) % 1 == 0)
                                          .mapToObj(b -&amp;gt;
                                                  new int[]{a, b, (int) Math.sqrt(a * a + b * b)})
                         );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 a에 사용할 1부터 100까지의 숫자를 만든다. 그리고 주어진 a를 이용해서 세 수의 스트림을 만든다. 스트림 a의 값을 매핑하면 스트림의 스트림이 만들어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 flatMap 메서드는 생성된 각각의 스트림을 하나의 평준화된 스트림으로 만들어준다. 결과적으로 세 수로 이루어진 스트림을 얻을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;개선할 점?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서는 제곱근을 두 번 계산한다. 따라서 (a*a, b*b, a*a+b*b) 형식을 만족하는 세 수를 만든 다음에 우리가 원하는 조건에 맞는 결과만 필터링하는 것이 더 최적화된 방법이다.&lt;/p&gt;
&lt;pre id=&quot;code_1693271210640&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Stream&amp;lt;double[]&amp;gt; pythagoreanTriples =
                IntStream.rangeClosed(1, 100).boxed()
                         .flatMap(a -&amp;gt; IntStream.rangeClosed(a, 100)
                                          .mapToObj(b -&amp;gt;
                                                  new double[]{a, b, Math.sqrt(a * a + b * b)}) // 만들어진 세 수
                                 .filter(t -&amp;gt; t[2] % 1 == 0)); // 세 수의 세 번째 요소는 반드시 정수여야 함&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5.8 스트림 만들기&lt;/b&gt;&lt;/h2&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;b&gt;5.8.1 값으로 스트림 만들기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임의의 수를 인수로 받는 정적 메서드 Stream.of를 이용해서 스트림을 만들 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1693379446219&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Stream&amp;lt;String&amp;gt; stream = Stream.of(&quot;Modern&quot;, &quot;Java&quot;, &quot;In&quot;, &quot;Action&quot;);
// 스트림의 모든 문자열을 대문자로 변환한 후 하나씩 출력
stream.map(String::toUpperCase).forEach(System.out::println);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음처럼 empty 메서드를 이용해서 스트림을 비울 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1693379508481&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Stream&amp;lt;String&amp;gt; emptyStream = Stream.empty();&lt;/code&gt;&lt;/pre&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;b&gt;5.8.2&amp;nbsp; null이 될 수 있는 객체로 스트림 만들기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 9에서는 null이 될 수 있는 개체를 스트림으로 만들 수 있는 새로운 메소드가 추가되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 System.getProperty는 제공된 키에 대응하는 속성이 없으면 null을 반환한다. 이런 메소드를 스트림에 활용하려면 다음처럼 null을 명시적으로 확인해야했다.&lt;/p&gt;
&lt;pre id=&quot;code_1693380783219&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String homeValue = System.getProperty(&quot;home&quot;);
Stream&amp;lt;String&amp;gt; homeValueStream = homeValue == null ? Stream.empty() : Stream.of(homeValue);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stream.ofNullable을 이용해 다음처럼 코드를 구현할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1693380834506&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Stream&amp;lt;String&amp;gt; homeValueStream = Stream.ofNullable(System.getProperty(&quot;home&quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;null이 될 수 있는 객체를&amp;nbsp; 포함하는 스트림값을 flatMap과 함께 사용하는 상황에서는 이 패턴을 더 유용하게 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1693380896732&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Stream&amp;lt;String&amp;gt; values = Stream.of(&quot;config&quot;, &quot;home&quot;, &quot;user&quot;)
                              .flatMap(key -&amp;gt; Stream.ofNullable(System.getProperty(key)));&lt;/code&gt;&lt;/pre&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;b&gt;5.8.3 배열로 스트림 만들기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열을 인수로 받는 정적 메서드 Arrays.stream을 이용해서 스트림을 만들 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1693380958146&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();&lt;/code&gt;&lt;/pre&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;b&gt;5.8.4 파일로 스트림 만들기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;파일을 처리하는 등의 I/O 연산에 사용하는 자바의 NIO API(비블록 I/O)도 스트림 API를 활용할 수 있도록 업데이트 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693381319854&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;long uniqueWords = 0;
try (Stream&amp;lt;String&amp;gt; lines = Files.lines(Path.get(&quot;data.txt&quot;), Charset.defaultCharset())) { // 스트림은 자원을 자동으로 해제할 수 있는 AutoCloseable이므로 try-finally가 필요 없다.
		uniqueWords = lines.flatMap(line -&amp;gt; Arrays.stream(line.split(&quot; &quot;))) // 고유 단어 수 계산
				   .distinct() // 중복 제거
                	       	   .count(); // 단어 스트림 생성
}
catch (IOException e) {
}&lt;/code&gt;&lt;/pre&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;b&gt;5.8.5 함수로 무한 스트림 만들기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;스트림 API는 함수에서 스트림을 만들 수 있는 두 정적 메서드 &lt;b&gt;Stream.iterate와 Stream.generate&lt;/b&gt;를 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;두 연산을 이용해서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;무한 스트림(infinite stream)&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;, 즉 고정된 컬렉션에서 고정된 크기로 스트림을 만들었던 것과 달리 크기가 고정되지 않은 스트림을 만들 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;iterate와 generate에서 만든 스트림은 요청할 때마다 주어진 함수를 이용해 값을 만든다. 따라서 무제한으로 값을 계산할 수 있다. 하지만 보통 무한한 값을 출력하지 않도록 limit(n) 함수를 함께 연결해서 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;iterate 메서드&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1693381629532&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Stream.iterate(0, n -&amp;gt; n + 2)
      .limit(10)
      .forEach(System.out::println);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 9의 iterate 메소드는 프레디케이트를 지원한다.&lt;/p&gt;
&lt;pre id=&quot;code_1693381820501&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 0에서 시작해서 100보다 크면 숫자 생성을 중단하는 코드
IntStream.iterate(0, n -&amp;gt; n &amp;lt; 100, n -&amp;gt; n + 4)
         .forEach(System.out::println);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;generate 메서드&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iterate와 달리 generate는 생산된 각 값을 연속적으로 계산하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;generate는 Supplier&amp;lt;T&amp;gt;를 인수로 받아서 새로운 값을 생산한다.&lt;/p&gt;
&lt;pre id=&quot;code_1693382526324&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Stream.generate(Math::random)
      .limit(5)
      .forEach(System.out::println);&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;</description>
      <category>JAVA</category>
      <category>java</category>
      <category>lamda</category>
      <category>stream</category>
      <category>람다</category>
      <category>스트림</category>
      <category>자바</category>
      <category>자바 8</category>
      <author>사랑박</author>
      <guid isPermaLink="true">https://developtrace.tistory.com/51</guid>
      <comments>https://developtrace.tistory.com/51#entry51comment</comments>
      <pubDate>Tue, 22 Aug 2023 14:58:23 +0900</pubDate>
    </item>
    <item>
      <title>모던 자바 인 액션 - CH04. 스트림 소개</title>
      <link>https://developtrace.tistory.com/50</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4.1 스트림이란 무엇인가?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트림(Streams)은 자바 8 API에 새로 추가된 기능이다.&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;p data-ke-size=&quot;size16&quot;&gt;다음 예제는 저칼로리 요리명을 반환하고, 칼로리를 기준으로 요리를 정렬하는 자바7 코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1692096654221&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Dish&amp;gt; lowCaloricDishes = new ArrayList&amp;lt;&amp;gt;();

for(Dish dish: menu) {
		if(dish.getCalories() &amp;lt; 400) {
			lowCaloricDishes.add(dish);
		}
}

Collections.sort(lowCaloricDishes, new Comparator&amp;lt;Dish&amp;gt;() {
	public int compare(Dish dish1, Dish dish2) {
		return Integer.compare(dish.getCalories(), dish2.getCalories());
	}
});

List&amp;lt;String&amp;gt; lowCaloricDishesName = new ArrayList&amp;lt;&amp;gt;();

for(Dish dish: lowCaloricDishes) {
		lowCaloricDishesName.add(dish.getName());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서는 lowCaloricDishes라는 `가비지 변수`를 사용했다. 즉, lowCaloricDishes는 컨테이너 역할만 하는 중간 변수다. 자바 8에서 이런 세부 구현은 라이브러리 내에서 모두 처리한다.&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;다음은 자바 8 코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1692096777313&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; lowCaloricDishesName = menu.stream()
					.filter(d -&amp;gt; d.getCalories() &amp;lt; 400)
					.sorted(comparing(Dish::getCalories))										
                                        .map(Dish::getName)
                                        .collect(toList());

// parallelStream()을 이용하여 멀티코어 아키텍처에서 병렬로 실행
List&amp;lt;String&amp;gt; lowCaloricDishesName = menu.parallelStream()
					.filter(d -&amp;gt; d.getCalories() &amp;lt; 400)
					.sorted(comparing(Dish::getCalories))
					.map(Dish::getName)
					.collect(toList());&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;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;선언형으로 코드를 구현할 수 있다. 즉, 루프와 if 조건문 등의 제어 블록을 사용해서 어떻게 동작을 구현할지 지정할 필요 없이 `저칼로리 요리만 선택하라`같은 동작의 수행을 지정할 수 있다.&lt;/li&gt;
&lt;li&gt;filter, sorted, map, collect 같은 여러 빌딩 블록 연산을 연결해서 복잡한 데이터 처리 파이프라인을 만들 수 있다. 여러 연산을 파이프라인으로 연결해도 여전히 가독성과 명확성이 유지된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt; 자바 8의 스트림 API의 특징 &amp;gt;&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;&lt;b&gt;조립할 수 있음&lt;/b&gt; : 유연성이 좋아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;병렬화&lt;/b&gt; : 성능이 좋아진다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4.2 스트림 시작하기&lt;/b&gt;&lt;/h2&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;b&gt;연속된 요소&lt;/b&gt; : 컬렉션과 마찬가지로 스트림은 특정 요소 형식으로 이루어진 연속된 값 집합의 인터페이스를 제공한다. 컬렉션에서는 (ex. ArrayList를 사용할 것인지, LinkedList를 사용할 것인지) 시간과 공간의 복잡성과 관련된 요소 저장 및 접근 연산이 주를 이룬다. 반면 스트림은 filter, sorted, map처럼 표현 계산식이 주를 이룬다. 즉, 컬렉션의 주제는 데이터이고 스트림의 주제는 계산이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;소스&lt;/b&gt; : 스트림은 컬렉션, 배열 I/O 자원 등의 데이터 제공 소스로부터 데이터를 소비한다. 정렬된 컬렉션으로 스트림을 생성하면 정렬이 그대로 유지된다. 즉, 리스트로 스트림을 만들면 스트림의 요소는 리스트의 요소와 같은 순서를 유지한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 처리 연산&lt;/b&gt; : 스트림은 함수형 프로그래밍 언어에서 지원하는 연산과 데이터베이스와 비슷한 연산을 지원한다. 예를 들어, filter, map, reduce, find, match, sort 등으로 데이터를 조작할 수 있다. 스트림 연산은 순차적으로 또는 병렬로 실행할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&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;/li&gt;
&lt;li&gt;내부 반복 : 반복자를 이용해서 명시적으로 반복하는 컬렉션과 달리 스트림은 내부 반복을 지원한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제로 확인해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1692098019685&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; threeHighCaloricDishNames =
             	 menu.stream()
                     .filter(dish -&amp;gt; dish.getCalories() &amp;gt; 300)
                     .map(Dish::getName)
                     .limit(3)
                     .collect(Collectors.toList());
  
System.out.println(threeHighCaloricDishNames);&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;스트림에 filter, map, limit, collect로 이어지는 일련의 데이터 처리 연산을 적용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;collect를 제외한 모든 연산은 서로 파이프라인을 형성할 수 있도록 스트림을 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이프라인은 소스에 적용하는 질의 같은 존재다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 collect 연산으로 파이프라인을 처리해서 결과를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;filter, map, limit, collect는 각각 다음과 같은 작업을 수행한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;filter&lt;/b&gt; : 람다를 인수로 받아 스트림에서 특정 요소를 제외시킨다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;map&lt;/b&gt; : 람다를 이용해서 한 요소를 다른 요소로 변환하거나 정보를 추출한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;limit&lt;/b&gt; : 정해진 개수 이상의 요소가 스트림에 저장되지 못하게 스트림 크기를 축소 truncate 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;collect&lt;/b&gt; : 스트림을 다른 형식으로 변환한다. 일단 collect가 다양한 변환 방법을 인수로 받아 스트림에 누적된 요소를 특정 결과로 변환시키는 기능을 수행하는 정도로 이해하자.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4.3 스트림과 컬렉션&lt;/b&gt;&lt;/h2&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;이제 컬렉션과 스트림의 차이를 살펴보자.&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;&lt;b&gt;컬렉션&lt;/b&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;&lt;b&gt;스트림&lt;/b&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;결과적으로 스트림은 생산자와 소비자 관계를 형성한다.&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4.3.1 딱 한 번만 탐색할 수 있다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복자(Iterator)와 마찬가지로 스트림도 한 번만 탐색할 수 있다. =&amp;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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4.3.2 외부 반복과 내부 반복&lt;/b&gt;&lt;/h3&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;반면 스트림 라이브러리는 반복을 알아서 처리하고 결과 스트림값을 어딘가에 저장해주는 &lt;b&gt;내부 반복&lt;/b&gt;을 사용한다. 함수에 어떤 작업을 수행할지만 지정하면 모든 것이 알아서 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드를 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1692101015568&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 컬렉션 : for-each 루프를 이용하는 외부 반복
List&amp;lt;String&amp;gt; names = new ArrayList&amp;lt;&amp;gt;();
for(Dish dish : menu) { // 메뉴 리스트를 명시적으로 순차 반복
	names.add(dish.getName()); // 이름을 추출해서 리스트에 추가
}

// 스트림 : 내부 반복
List&amp;lt;String&amp;gt; names = menu.stream()
			 .map(Dish::getName)
                         .collect(toList());&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;스트림 라이브러리의 내부 반복은 데이터 표현과 하드웨어를 활용한 병렬성 구현을 자동으로 선택한다. 반면 for-each를 이용하는 외부 반복에서는 병렬성을 스스로 관리해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 8에서는 컬렉션 인터페이스와 비슷하면서도 반복자가 없는 무엇이 절실했으며, 결국 스트림이 탄생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트림은 내부 반복을 사용하므로 반복 과정을 우리가 신경쓰지 않아도 된다. 하지만 이와 같은 이점을 누리려면 filter나 map같이 반복을 숨겨주는 연산 리스트가 미리 정의되어 있어야 한다. 반복을 숨겨주는 대부분의 연산은 람다 표현식을 인수로 받으므로 3장에서 배운 동작 파라미터화를 활용할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4.4 스트림 연산&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1692104365714&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; lowCaloricDishesName = menu.stream()
					.filter(d -&amp;gt; d.getCalories() &amp;lt; 400)
					.sorted(comparing(Dish::getCalories))										
                                        .map(Dish::getName)
                                        .collect(toList());&lt;/code&gt;&lt;/pre&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;filter, map, limit는 서로 연결되어 파이프라인을 형성한다.&lt;/li&gt;
&lt;li&gt;collect로 파이프라인을 실행한 다음에 닫는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결할 수 있는 스트림 연산을 &lt;b&gt;중간 연산&lt;/b&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4.4.1 중간 연산&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;filter나 sorted 같은 중간 연산은 다른 스트림은 반환한다. 따라서 여러 중간 연산을 연결해서 질의를 만들 수 있다.&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;pre id=&quot;code_1692104608645&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; names = menu.stream()
                .filter(dish -&amp;gt; {
                    System.out.println(&quot;filtering:&quot; + dish.getName());
                    return dish.getCalories() &amp;gt; 300;
                })
                .map(dish -&amp;gt; {
                    System.out.println(&quot;mapping:&quot; + dish.getName());
                    return dish.getName();
                })
                .limit(3)
                .collect(toList());
                
// 출력
filtering:pork
mapping:pork
filtering:beef
mapping:beef
filtering:chicken
mapping:chicken
[pork, beef, chicken]&lt;/code&gt;&lt;/pre&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;b&gt;4.4.2 최종 연산&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 연산은 스트림 파이프라인에서 결과를 도출한다. 보통 최종 연산에 의해 List, Integer, void 등 스트림 이외의 결과가 반환된다. 예를 들어 다음 파이프라인에서 forEach는 소스의 각 요리에 람다를 적용한 다음에 void를 반환하는 최종 연산이다. ex) menu.stream().forEach(System.out::println);&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;b&gt;4.4.3 스트림 이용하기&lt;/b&gt;&lt;/h3&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;li&gt;스트림 파이프라인을 실행하고 결과를 만들 최종 연산&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트림 파이프라인의 개념은 빌터 패턴과 비슷하다.&lt;/p&gt;</description>
      <category>JAVA</category>
      <category>java</category>
      <category>모던 자바 인 액션</category>
      <category>스트림</category>
      <category>자바</category>
      <category>자바8</category>
      <author>사랑박</author>
      <guid isPermaLink="true">https://developtrace.tistory.com/50</guid>
      <comments>https://developtrace.tistory.com/50#entry50comment</comments>
      <pubDate>Tue, 15 Aug 2023 20:21:43 +0900</pubDate>
    </item>
    <item>
      <title>[백준 11060] 점프 점프 - 자바(JAVA)</title>
      <link>https://developtrace.tistory.com/49</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 문제&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11060&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.acmicpc.net/problem/11060&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1691674684225&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;11060번: 점프 점프&quot; data-og-description=&quot;재환이가 1&amp;times;N 크기의 미로에 갇혀있다. 미로는 1&amp;times;1 크기의 칸으로 이루어져 있고, 각 칸에는 정수가 하나 쓰여 있다. i번째 칸에 쓰여 있는 수를 Ai라고 했을 때, 재환이는 Ai이하만큼 오른쪽으로 &quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://www.acmicpc.net/problem/11060&quot; data-og-url=&quot;https://www.acmicpc.net/problem/11060&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/K78aO/hyTCBEAWLS/CtNYPCExQQ0KlDQJMk2PGk/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11060&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.acmicpc.net/problem/11060&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/K78aO/hyTCBEAWLS/CtNYPCExQQ0KlDQJMk2PGk/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;11060번: 점프 점프&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;재환이가 1&amp;times;N 크기의 미로에 갇혀있다. 미로는 1&amp;times;1 크기의 칸으로 이루어져 있고, 각 칸에는 정수가 하나 쓰여 있다. i번째 칸에 쓰여 있는 수를 Ai라고 했을 때, 재환이는 Ai이하만큼 오른쪽으로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.acmicpc.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 풀이&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`DP`&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;로 문제를 풀었습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;dp배열은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;해당 칸까지 가는데 걸리는 최소 점프 횟수&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;만약에 data[3]=3이고 dp[3]=2라면&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;3번째 칸 까지 가는 최소 점프 횟수는 2번인데&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;3번째 칸에서 4, 5, 6칸을 갈 수 있는데 점프를 하니까 3번(dp[3]+1)만에 갈 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;최소면 갱신하고 아니라면 무시하면 됩니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;이렇게 최솟값을 갱신하면서 dp[0]부터 dp[N-1]까지 차례대로 구해주었습니다.&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;DP외에도 BFS, DFS 등 다양한 풀이 방법이 있는 것 같은데 나중에 시간이 된다면 다른 알고리즘으로 다시 풀어봐야 겠습니다ㅎㅎ&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 코드&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1691674801950&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;

public class BOJ11060 {

	public static void main(String[] args) throws NumberFormatException, IOException {

		BufferedReader br=new BufferedReader(new InputStreamReader(System.in));

		int N=Integer.parseInt(br.readLine());
		int[] data=new int[N];
		int[] dp=new int[N];

		String[] input=br.readLine().split(&quot; &quot;);

		for(int i=0;i&amp;lt;N;i++) {
			data[i]=Integer.parseInt(input[i]);
			dp[i]=Integer.MAX_VALUE;
		}

    		// 첫 번째 칸이 0이면 종료
		if(N&amp;gt;=1 &amp;amp;&amp;amp; data[0]==0) {
			System.out.println(-1);
			return;
		}

		dp[0]=0;

		for(int i=0;i&amp;lt;N-1;i++) {
			if(data[i]==0||dp[i]==Integer.MAX_VALUE) { // 다음칸으로 점프할 수 없음 || 오는 길이 없음
				continue;
			}
			for(int j=1;j&amp;lt;=data[i];j++) { // 점프할 수 있는 만큼 가보자
				if(i+j&amp;gt;=N) break; // 인덱스 벗어나면 종료
				dp[i+j]=Math.min(dp[i+j], dp[i]+1);
			}
		}

		if(dp[N-1]==Integer.MAX_VALUE) {
			System.out.println(-1);
		}else {
			System.out.println(dp[N-1]);
		}
	}
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘</category>
      <category>DP</category>
      <category>java</category>
      <category>다이나믹프로그래밍</category>
      <category>백준</category>
      <category>자바</category>
      <category>점프 점프</category>
      <author>사랑박</author>
      <guid isPermaLink="true">https://developtrace.tistory.com/49</guid>
      <comments>https://developtrace.tistory.com/49#entry49comment</comments>
      <pubDate>Thu, 10 Aug 2023 22:40:29 +0900</pubDate>
    </item>
  </channel>
</rss>