[Live Study 4주차] 제어문

이호석·2022년 7월 11일
0

목표

자바가 제공하는 제어문을 학습하세요.

학습할 것

  • 선택문
  • 반복문

과제

  • 과제 0. JUnit 5 학습하세요.
  • 과제 1. live-study 대시 보드를 만드는 코드를 작성하세요.
  • 과제 2. LinkedList를 구현하세요.
  • 과제 3. Stack을 구현하세요.
  • 과제 4. 앞서 만든 ListNode를 사용해서 Stack을 구현하세요.
  • 과제 5. Queue를 구현하세요.


0. 제어문

Java 소스파일 내의 명령문은 보통 표시되는 순서로 위에서 아래로 순차적으로 실행된다. 그러나 controlflow(제어 흐름 문)은 선택문(Decision-making), 반복문, 분기문 등을 사용하여 실행 흐름을 분할하고 프로그램이 특정 코드 블록을 조건부로 실행할 수 있도록 한다.

Java Programming Language에서 지원하는 것은 선택문(if-then, if-then-else, switch), 루프문(while, do-while) 및 분기문(break, continue, return)이 존재한다.



1. 선택문(Decision-Making)

1-1. if-then statement

if-then문은 모든 제어 흐름문 중 가장 기본적이다. 특정 조건이 참일 경우 프로그램 코드의 특정 블록을 실행하도록 지시한다.

// if-then statement
class Test {
	public static void main(String[] args) {
    	Scanner sc = new Scanner(System.in);
    	String username = "Hoseok Lee"
        int hisAge = 25;
        int myAge = sc.nextInt();
        
        if (hisAge == myAge) {
        	System.out.println("They are the same age!");
        }
        // if문 내의 명령이 한 줄 이라면 중괄호를 생략할 수 있다.
    }
}

위의 코드는 기존의 25살인 Hoseok Lee라는 사람이 존재하고, 사용자로부터 당신의 나이를 입력받는다. 이후 if (hisAge == myAge)에서 Hoseok Lee의 나이와 사용자가 입력한 나이가 동일하다면(true) 해당 if 블록의 They are the same age! 가 콘솔에 출력된다.

만약 사용자가 입력한 나이가 다르다면(false) if-then명령문의 끝으로 이동하여 해당 블록이 실행되지 않는다.


1-2. if-then-else statement

if-then-else문은 if가 false로 평가될 경우 두 번째 실행경로인 else 블록이 존재한다. 즉, if문의 조건식이 true일 경우 if문에 해당되는 블록이 실행되고, false일 경우 else문에 해당되는 블록이 실행된다.

// if-then-else statement
class Test {
	public static void main(String[] args) {
    	Scanner sc = new Scanner(System.in);
    	String username = "Hoseok Lee"
        int hisAge = 25;
        int myAge = sc.nextInt();
        
        if (hisAge == myAge) {
        	System.out.println("They are the same age!");
        } else {
        	System.out.println("They aren't the same age!");
        }
    }
}

if-else에서 다뤘던 예제에서 만약 입력받은 사용자의 나이와 Hoseok Lee의 나이가 다르다면 else문의 블록이 실행되어 They aren't the same age!를 콘솔에 출력한다.


1-3. switch, else-if

1-2. 에서 살펴본 if-then 및 if-then-else statement와 달리 switch는 여러개의 실행 경로를 설정할 수 있다. switch문은 byte, short, char, int와 같은 기본형 자료형과 함께 동작하고,
Enum, String class 및 기본형을 감싼 Wrapper class에서도 동작한다.(Enum, String, Character, Byte, Short, Integer)

// switch
class Test {
	public static void main(String[] args) {
    	Scanner sc = new Scanner(System.in);
    	String username = "Hoseok Lee"
        int age = sc.nextInt();
        
        switch(age) {
        	case 5: 
            	System.out.println("5 years old");
                break;
            case 10: 
            	System.out.println("10 years old");
                break;
            case 20: 
            	System.out.println("20 years old");
                break;
            case 25: 
            	System.out.println("25 years old");
                break;
            default: 
            	System.out.println(age + " years old");
                break;
        }
    }
}

위의 예시에서 각각의 case들을 if문의 조건이라 생각하면 된다. 만약 사용자가 10을 입력했다면 화면엔 10 years old를 출력하고 break만나 switch문을 벗어나게 된다.

만약 case 20:의 break가 없다면, 사용자가 20을 입력하게되면 case 25:에 해당되는 문구까지 출력하고 case 25:의 break를 만나 switch를 빠져나간다.
이런 switch문의 특성을 잘 이용하면 공통되는 부분은 같이 묶어서 한번에 처리할 수 도 있다.

위의 switch문은 참고로 아래와 같이 if-else if로 바꿀수도 있다.

// if-else if
class Test {
	public static void main(String[] args) {
    	Scanner sc = new Scanner(System.in);
    	String username = "Hoseok Lee"
        int age = sc.nextInt();
        
        if (age == 5) {
        	System.out.println("5 years old");
        } else if (10) {
        	System.out.println("10 years old");
        } else if (20) {
        	System.out.println("20 years old");
        } else if (25) {
        	System.out.println("25 years old");
        } else {
        	System.out.println(age + " years old");
        }
}

다만 위와 같이 분기가 많다면 if-else if보다 switch가 좀 더 효율적이라고 볼 수 있다.

if문의 중괄호는 해당 블록내의 명령문이 한 줄 이라면 생략할 수 있다.
개인적으로는 블록내의 명령문이 한 줄 이어도 중괄호를 생략하지 않는것이 가독성이 좋다고 생각한다.



2. 반복문

반복문은 for, while, do-while 3가지의 종류가 있다.
반복문은 특정 조건이 만족하면 동일한 작업을 계속해서 반복하는것을 말한다.


2-1. for문

위 3가지 반복문 중 가장 많이 사용된다고 할 수 있다. for문의 구조는 다음과 같다.
for (초기화; 조건식; 증감식) {반복할 코드 블록} for문 내의 다른 영역끼리는 세미콜론으로 구분하고 모든 식이 필수 값은 아니다.

for문은 아래와 같이 실행된다. 초기화식은 처음 for문을 실행할 때 한번만 실행하게 된다.

  • 초기화 -> 조건식 -> 참이면 코드블록 실행 -> 증감식 -> 조건식 -> 참이면 코드블록 실행 -> 반복하다 조건식이 거짓이면 종료
class Test {
	public static void main(String[] args) {
    	int[] arr = new int[10];
        
        // 1. for 문 사용 전 수동으로 초기화
        arr[0] = 1;
        arr[1] = 2;
        arr[2] = 3;
        arr[3] = 4;
        arr[4] = 5;
        arr[5] = 6;
        arr[6] = 7;
        arr[7] = 8;
        arr[8] = 9;
        arr[9] = 10;
        
        // 2. for 문 사용
        for (int i = 0; i < 10; i++) {
        	arr[i] = i + 1;
        }
        
        // 3. 증감식을 꼭 단항연산자를 이용하지 않아도 된다.
        for (int i = 0; i < 10; i += 1) {
        	arr[i] = i + 1;
        }
        
        // 4. 변수를 바깥에서 미리 선언하고 사용 가능
        int index = 0;
        for (index = 0; index < 10; index++) {
        	System.out.println("pass");
        }
        
        // 5. 무한 루프
        for (; ; ) {
        	// 무한히 실행됨
        }
        
        // 6.
        for (int i = 0; i < 10; i++) {
        	if ((i + 1) % 2 != 0) {
            	continue;
            }
            System.out.println(i + 1); // i + 1이 짝수일때만 출력
        }
        
        // 7.
        for (int i = 0; i < 10; i++) {
        	if (i == 5) {
            	break;
            }
            System.out.println(i + 1); // i + 1이 짝수일때만 출력
        }
    }
}

위와같이 다양하게 for문을 사용할 수 있다. for문 역시 블록내의 명령문이 한 줄 일경우 중괄호를 생략할 수 있다.

6, 7번의 for문은 내부에 if문과 함꼐 break, continue 키워드를 사용한다.
먼저 break는 만나는 즉시 break와 가장 가까이에 있는 반복문을 벗어난다. 따라서 2개의 중첩 반복문이고 가장 안쪽 반복문에서 break를 한다면 가장 안쪽의 반복문만 종료하게 된다.

continue의 경우 만나는 즉시 바로 증감식을 거쳐서 조건식으로 가게된다. 즉 다음 단계로 즉시 이동한다고 생각할 수 있다.

따라서 위의 6번 for문은 짝수일때만 i + 1이 출력되고 홀수인경우 즉시 for문의 증감식 및 조건식으로 넘어가게 된다.
7번 for문은 i의 값이 5가되면 if문이 참이되고 break를 만나 for문을 종료하게 된다.


2-2. while문

while문은 조건식 하나만 가지고 동작하는 반복문이다.
while(조건식) {코드 블록}
따라서 조건식이 참일경우 계속 반복되며, 코드를 작성하다 보면 무한루프가 자주 발생되는 반복문이기도 하다.

class Test {
	public static void main(String[] args) {
    	
        // 1.
        int i = 0;
        while (i++ < 10) {
        	System.out.println(i + "번째 반복하고 있습니다.");
        }
        
        // 2.
        while(true) {
        	// 무한 반복
        }
        
        // 3.
        // break, continue의 사용: 홀수 건너뛰고, 짝수인 경우만 출력하되 10일경우 종료
        int num = 1;
        while (true) {
            if (num % 2 == 1) {
                num++;
                continue;
            }
            if (num == 10) {
                break;
            }
            System.out.println(num++);
        }
    }
}
[1. 출력]
1번째 반복하고 있습니다.
2번째 반복하고 있습니다.
3번째 반복하고 있습니다.
4번째 반복하고 있습니다.
5번째 반복하고 있습니다.
6번째 반복하고 있습니다.
7번째 반복하고 있습니다.
8번째 반복하고 있습니다.
9번째 반복하고 있습니다.
10번째 반복하고 있습니다.

[3. 출력]
2
4
6
8

따라서 while문의 조건식은 보통 while문 바깥에서 조건식으로 사용할 변수를 지정하고 while문을 시작한다. 두 번째 while문은 조건식이 true이므로 무한히 반복하게 된다. for(;;)와 동일하다.

while문에서도 continue, break 키워드를 사용할 수 있는데, 보통 while의 조건을 true로 두고 내부에서 특정 조건을 만족하는 경우 break를 사용해 while문을 종료하는 방식으로 많이 작성한다.

다만 이렇게 이용하는 경우 반드시 종료될 수 있게 보장해주어야 하므로 주의해야 한다.


2-3. do-while

while의 경우 조건식을 먼저 판별하고 반복문을 실행할지 말지를 결정하지만, do-while의 경우 먼저 초기에 한번의 반복문을 실행하고 이후 while의 조건식을 판별해 다시 실행할지 말지를 결정하는 부분이 가장 큰 차이다.

do-while에서도 break, continue를 사용할 수 있으며 조건식이 하단에 존재한다.

class Test {
	public static void main(String[] args) {
    	
        // 1. while과 do-while의 차이점
        int a = 1;
    	while (a != 1) {
        	System.out.println("pass: while");
		}
    
        do {
        	System.out.println("pass: do-while");
        } while (a != 1);	// 세미콜론을 붙여줘야 함
        
        // 2. break, continue의 사용
        a = 0;
        do {
        	a++;
            if (a == 10) {
            	break;
            }
            if (a % 2 == 0) {
            	continue;
            }
            System.out.println(a);
            
        } while (true);
    }
}
[1. 출력]
pass: do-while

[2. 출력]
1
3
5
7
9

실제로 위의 코드를 실행하면 while은 초기에 a 값이 1이므로 조건식이 거짓이므로 반복문을 실행하지 않지만, do-while문은 일단 처음은 실행을 해보고 후에 조건식을 판별하므로 한 번의 콘솔 출력이 발생함을 확인할 수 있다.

do-while문도 마찬가지로 무한루프에 빠지지 않게 주의해야 한다.
2번째의 do-while문에서 만약 a++을 지우고, System.out.println(a++);로 작성하면 초기값이 0인 a는 두번째 if문이 항상 참이므로 무한루프에 빠지게 된다.

while, do-while의 경우 항상 무한 루프에 빠지지 않도록 주의해야 하지만, 오히려 무한루프를 이용한 프로그램도 있을 수 있다.
대표적으로 소켓 통신을 이용해 채팅을 구현하는 경우 사용자의 메시지가 언제들어올지 모르는 상황에서 계속 입력을 대기해야 하므로 무한루프를 사용하기도 한다.


2-4. 향상된 for문

향상된 for문은 기존의 for문보다 편리하게 사용할 수 있다.
향상된 for문은 Arrays, Collections 혹은 Iterable<T>을 상속받은 객체에서 사용할 수 있다.

기존의 for문보다 타이핑하는 양도 적고, 보기에도 편하다. 하지만 직접적으로 루프를 다룰 수 없고 반드시 순차적으로 반복문이 진행된다.

import java.util.*;

class Test {
	public static void main(String[] args) {
 		int[] arr = {4, 2, 3, 6, 1, 7, 9};
        
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("hi");
        list.add("developer");
        
        // Arrays를 이용한 향상된 for문
        for (int num : arr) {
        	System.out.print(num + " ");
        }
        System.out.println();
    
    	// Collections 객체를 이용한 향상된 for문
    	for (String str : list) {
        	System.out.print(str + " ");
        }
        System.out.println();
    
    }
}

코드를 살펴보면 기존의 for문과 다르게 배열, Collections의 인덱스를 이용해 접근하는것이 아닌 값을 직접적으로 접근하여 바로 사용할 수 있는점이 특징이다. 따라서 기존에 사용하던 초기화식도 필요가 없어지고, 코드가 간결해진것을 알 수 있다.



과제0. JUnit5 학습하기

JUnit5의 모든 부분을 다루기에는 양이 방대하므로, 개인적인 생각을 조금 보태어 테스트를 진행할때 필수적으로 알고 있으면 좋을법한 내용들을 다루었습니다!

JUnit5는 3개의 서브 프로젝트로 이루어져 있다.
JUnit Platform + JUnit Jupiter + JUnit Vintage

  • JUnit Platform: JVM에서 테스트 프레임워크를 실행하는데 기초를 제공하고, TestEngine API를 제공해 테스트 프레임워크를 개발할 수 있다.
  • JUnit Jupiter: JUnit5에서 테스트를 작성하고 확장을 하기 위한 새로운 프로그래밍 모델 및 확장 모델의 조합
  • JUnit Vintage: JUnit3, 4기반 테스트를 실행하기 위한 테스트 엔진을 제공(단 클래스 경로 또는 모듈 경로에 JUnit4.12 이상이 있어야 한다.

0-1. JUnit5의 다양한 어노테이션

JUnit은 다양한 어노테이션을 제공하여 해당 기능들을 사용할 수 있게한다.

AnnotationDescription
@Test해당 메소드가 테스트 메소드임을 나타냄, @Test 어노테이션은 어떤 속성도 선언하지 않음
@DisplayName테스트 클래스 및 메소드에 이름을 붙여줄 때 사용
@BeforeEach테스트 메소드 실행전에 이 어노테이션이 달린 메서드를 반드시 실행한다.(@Test, @RepeatedTest, @ParameterizedTest, @TestFactory가 붙은 테스트 메소드가 실행하기 전)
@AfterEach@Test, @RepeatedTest, @ParameterizedTest, @TestFactory가 붙은 테스트 메소드가 실행되고 난 후 실행됨
@BeforeAll@Test, @RepeatedTest, @ParameterizedTest가 실행되기 전에 실행되야 하며 한 번만 실행된다.
@AfterAll테스트가 완전히 끝나고 딱 한번만 실행된다.
@Nested주석이 달린 클래스가 정적이 아닌 중청된 테스트 클래스임을 나타냄
@Tag테스트를 필터링할 때 사용함, 클래스 또는 메소드 레벨서 사용
@Disabled테스트 클래스, 메소드의 테스트를 비활성화 한다.
@Timeout주어진 시간안에 테스트가 끝나지 않으면 실패
@TestClassOrder테스트 클래스에 대한 클래스 실행 순서를 구성할 때 사용됨
@TestMethodOrder해당 주석이 달린 테스트 클래스에 대해 테스트 메소드의 실행 순서를 구성하는데 사용됨

0-2. 테스트 클래스와 메소드

  • 테스트 클래스: 최상위 클래스, 스태틱 멤버 클래스, @Nested 클래스에 적어도 한개의 @Test 어노테이션이 달린 테스트 메소드가 포함되있는 클래스 (abstract이면 안되고, 하나의 생성자가 존재해야함)

  • 테스트 메소드: @Test ,@RepeatedTest ,@ParamterizedTest, @TestFactory ,@TestTemplate 같은 메타 어노테이션이 메소드에 붙여진 메소드를 말한다.

  • 라이프 사이클 메소드: @BeforeAll, @AfterAll, @BeforeEach, @AfterEach같은 메타 어노테이션이 메소드에 붙여진 메소드

테스트 메소드, 라이프 사이클 메소드는 테스트 할 클래스, 상속한 부모 클래스, 인터페이스에 선언됨 그리고 테스트 메소드와 라이프 사이클 메소드는 abstract면 안되고, 반드시 값을 반환하면 안된다.(테스트 메소드, 라이프 사이클 메소드는 public일 필요는 없지만 private으로 선언하면 안됨 또한 일반적으로 public도 생략하는걸 권장)


0-3. Assertions의 메소드들

JUnit Jupiter는 JUnit4로부터 온 assertion 메소드와 자바 8 람다 표현식으로 추가된 메소드들이 존재함 모든 JUnit Jupiter assertion은 정적 메소드다.

대표적으로 사용되는 메소드들을 알아보자! (모든 메소드들 org.junit.jupiter.api.Assertions 클래스에 존재한다.


class AssertionsDemoTest {

    private final Calculator calculator = new Calculator();
    private final Person person = new Person("Jane", "Doe");
    
// [기본 사용]
    @Test
    void standardAssertions() {
        assertEquals(2, calculator.add(1, 1));
        assertEquals(4, calculator.multiply(2, 2),
                "하나의 옵션인 추가 메시지는 마지막 파라미터에 넣음");
        assertTrue('a' < 'b', () -> "Assertion messages는 지연로딩과 같이 동작 하여 -- "
                + "복잡한 메시지를 불필요하게 구성하지 않도록 한다.");
    }

기본적인 assertion, assertEquals로 값을 비교하고, assertTrue로 참, 거짓을 판별한다.
assertEquals에서 3번째 인자는 실패할경우 출력되는 메시지를 입력할 수 있다.


// [그룹]
    @Test
    void groupedAssertions() {
        // 그룹 assertion에서 모든 assertions가 실행되고 모든 실패가 같이 보고됩니다.
        assertAll("person",
                () -> assertEquals("Jane", person.getFirstName()),
                () -> assertEquals("Doe", person.getLastName())
        );
    }
    
    @Test
    void dependentAssertions() {
        // 코드 블록내에서 특정 assertion이 실패하면 동일 블록에서 후속 코드는 생략합니다.
        assertAll("properties",
                () -> {
                    String firstName = person.getFirstName();
                    assertNotNull(firstName);

                    //assertNotNull()이 검증 됐을경우에만 실행됨
                    assertAll("first name",
                            () -> assertTrue(firstName.startsWith("J")),
                            () -> assertTrue(firstName.endsWith("e"))
                    );
                },
                () -> {
                    // 그룹화된 assertion, 이전의 이름 assertion과 독립적으로 처리된다.
                    String lastName = person.getLastName();
                    assertNotNull(lastName);

                    // assertNotNull(lastName);이 검증됐을 경우에만 실행됨
                    assertAll("last name",
                            () -> assertTrue(lastName.startsWith("D")),
                            () -> assertTrue(lastName.endsWith("e"))
                    );
                }
        );
    }

그룹으로 assertion을 할 수 있다. 그룹 assertion은 모든 assertions가 실행되고 하나라도 실패하면 해당 그룹 테스트는 실패함 assertAll을 이용 (람다식을 이용해 독립적으로 테스트 함)


// [예외]
    @Test
    void exceptionTesting() {
        Exception exception = assertThrows(ArithmeticException.class, () ->
                calculator.divide(1, 0));
        assertEquals("/ by zero", exception.getMessage());
    }

assertThrows를 통해 예외를 Exception객체에 저장하고, assertEquals로 해당 예외를 검증해 예외가 발생했을때의 테스트를 구성할 수 있다.


// [시간초과]
    @Test
    void timeoutNotExceeded() {
        // The following assertion succeeds.
        assertTimeout(ofMinutes(2), () -> {
            // 2분내로 테스트가 종료되므로 성공
        });
    }

    @Test
    void timeoutNotExceededWithResult() {
        // 테스트가 성공하면 String 타입의 결과 반환
        String actualResult = assertTimeout(ofMinutes(2), () -> {
            return "a result";
        });
        // 시간초과 테스트가 성공했는지 검증
        assertEquals("a result", actualResult);
    }

    @Test
    void timeoutNotExceededWithMethod() {
        // AssertionsDemoTest 클래스의 메소드 참조를 호출하고 객체를 반환받는다.
        String actualGreeting = assertTimeout(ofMinutes(2), AssertionsDemoTest::greeting);
        // 반환받은 객체(메시지) 검증
        assertEquals("Hello, World!", actualGreeting);
    }

    @Test
    void timeoutExceeded() {
        // 10밀세컨 뒤에 타임아웃이 발생하지만 람다 내부에서 실행하는 본문은 100밀세컨을 기다리므로 테스트 실패
        assertTimeout(ofMillis(10), () -> {
            // Simulate task that takes more than 10 ms.
            Thread.sleep(100);
        });
    }

    @Test
    void timeoutExceededWithPreemptiveTermination() {
        // 위와 동일한 이유로 테스트 실패
        assertTimeoutPreemptively(ofMillis(10), () -> {
            // Simulate task that takes more than 10 ms.
            new CountDownLatch(1).await();
        });
    }

	// 테스트서 사용되는 메소드
    private static String greeting() {
        return "Hello, World!";
    }

}

시간초과를 테스트하기 위해서는 assertTimeout, assertTimeoutPreemptively이 이용된다.

assertTimeoutPreemptively() 메소드는 내부의 executable, supplier를 다른 스레드에서 실행하므로 여기서 실행된 코드들이 ThreadLocal에 의존하게 되면 사이드이펙트가 발생할 수 있다.


0-4. Third-Party Library

JUnit Jupiter가 제공해주는 assertion 외에도 AssertJ, Hancrest, Truth등 다앙햔 써드파티 라이브러리를 사용하기를 권장하고 있다.

개인적으로는 AssertJ에서 제공하는 assertThat() 메소드는 메소드 체이닝 방식을 구현하고 있기 때문에 기존 JUnit Jupiter에서 여러 인자를 입력할때 인자의 순서가 헷갈리는 경우를 방지할 수 있어서 추천합니다!



과제1. live-study 대시 보드

package dashboard;

import org.kohsuke.github.*;

import java.io.IOException;
import java.util.*;

public class LiveStudyDashboard {

    private int issuesSize;
    private final Map<String, Double> resultMap = new HashMap<>();

    public static void main(String[] args) {
        LiveStudyDashboard dashboard = new LiveStudyDashboard();
        try {
            dashboard.run();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void run() throws IOException {
        // 깃허브 연결
        GitHub gitHub = new GitHubBuilder().
                withOAuthToken("token", "myUser").
                build();

        // GitHub에서 "리포지토리 이름"으로 호출하는 'owner/repo' 문자열에서 리포지토리 개체를 가져옵니다.
        GHRepository ghRepository = gitHub.getRepository("whiteship/live-study");
        // Gets issues. List<GHIssue> type
        List<GHIssue> issues = ghRepository.getIssues(GHIssueState.ALL);
        issuesSize = issues.size();
        // Key: 참여한 userId, Value: 참여 횟수
        Map<String, Integer> participantMap = new HashMap<>();

        for (int i = 0; i < issuesSize; i++) {
            GHIssue issue = issues.get(i);
            // 하나의 이슈에서 모든 댓글 가져옴
            List<GHIssueComment> commentList = issue.getComments();
            Set<String> set = new HashSet<>();
            // 하나의 이슈에서 중복 제거
            addParticipantInSet(commentList, set);
            // 중복 제거한 유저 목록을 카운트해 Map에 담기
            countParticipantInMap(participantMap, set);
        }
        // 카운트 -> 비율 변경
        changeCountToRatioInMap(participantMap);
        // 출력
        printUserIdAndParticipationRate();

    }

    private void printUserIdAndParticipationRate() {
        Set<String> set = resultMap.keySet();
        System.out.println("Live Study Dash Board");
        System.out.println("======================================================");
        for (String userId : set) {
            System.out.println(userId + " : " + resultMap.get(userId) + "%");
        }
        System.out.println("======================================================");
    }

    private void changeCountToRatioInMap(Map<String, Integer> participantMap) {
        Set<String> keySet = participantMap.keySet();
        for (String userId : keySet) {
            int count = participantMap.get(userId);
            double ratio = getCountToRatio(issuesSize, count);
            resultMap.put(userId, ratio);
        }
    }

    private double getCountToRatio(int standard, int count) {
        // 소수점 두번째 자리까지 출력
        return Math.round((double) count / (double) standard * 100 * 100) / 100.0;
    }


    private void countParticipantInMap(Map<String, Integer> participantMap, Set<String> set) {
        for (String userId : set) {
            participantMap.put(userId, participantMap.getOrDefault(userId, 0) + 1);
        }
    }

    // 하나의 이슈에 동일한 사람이 여럿 있는경우 카운트하는 것을 방지
    private void addParticipantInSet(List<GHIssueComment> commentList, Set<String> set) throws IOException {
        for (GHIssueComment comment : commentList) {
            set.add(comment.getUser().getLogin());
        }
    }
}

LiveStudyDashboard Code



과제2. LinkedList를 구현

package structure;

/**
 * LinkedList에 대해 공부하세요.
 * 정수를 저장하는 ListNode 클래스를 구현하세요.
 * ListNode add(ListNode head, ListNode nodeToAdd, int position)를 구현하세요.
 * ListNode remove(ListNode head, int positionToRemove)를 구현하세요.
 * boolean contains(ListNode head, ListNode nodeTocheck)를 구현하세요.
 */
public class ListNode {
    private int element;
    private ListNode next;
    private boolean headCheck;

    public ListNode() {
        this.headCheck = true;
    }

    public ListNode(int element) {
        this.element = element;
    }

    public int getElement() {
        return element;
    }

    public ListNode getNext() {
        return next;
    }

    public void setNext(ListNode node) {
        next = node;
    }

    public boolean isHead() {
        if (headCheck) {
            return true;
        }
        return false;
    }

    public int size() {
        if (!isHead()) {
            return -1;
        }
        int count = 0;
        ListNode countNode = this;
        while (countNode.getNext() != null) {
            count++;
            countNode = countNode.getNext();
        }
        return count;
    }

    
    public boolean isValidation(int position) {
    	// head만 리스트에 특정 명령 수행 가능
        if (!isHead()) {
            return false;
        }
        if (size() < position) {
            return false;
        }
        if (position < 0) {
            return false;
        }
        return true;

    }

    public ListNode add(ListNode head, ListNode nodeToAdd, int position) {
        if (!isValidation(position)) {
            return null;
        }
        for (int i = 0; i < position; i++) {
            head = head.getNext();
        }
        nodeToAdd.setNext(head.getNext());
        head.setNext(nodeToAdd);

        return nodeToAdd;
    }

    public ListNode remove(ListNode head, int positionToRemove) {
        if (!isValidation(positionToRemove) || size() == positionToRemove) {
            return null;
        }
        for (int i = 0; i < positionToRemove; i++) {
            head = head.getNext();
        }
        // 지워야 할 노드
        ListNode nodeToRemove = head.getNext();
        head.setNext(nodeToRemove.getNext());
        return nodeToRemove;
    }

    public boolean contains(ListNode head, ListNode nodeTocheck) {
        if (size() == 0) {
            return false;
        }
        if (!isHead()) {
            return false;
        }
        for (int i = 0; i < size(); i++) {
            head = head.getNext();
            if (head == nodeTocheck) {
                return true;
            }
        }
        return false;
    }
}

ListNode Code

ListNode Test Code



과제3. Stack을 구현

package structure;

/**
 * int 배열을 사용해서 정수를 저장하는 Stack을 구현하세요.
 * void push(int data)를 구현하세요.
 * int pop()을 구현하세요.
 */

public class ArrayStack {
    private static final int SIZE = 5;
    private int[] array;
    private int index;

    public ArrayStack() {
        array = new int[SIZE];
    }

    // 데이터를 넣고 인덱스 증가
    public void push(int data) {
        if (isPossibleSize()) {
            // 배열에 공간이 있으므로 그냥 push
            array[index++] = data;
        } else {
            // 사이즈를 늘려주고, 배열 이전 후 push
            increaseArray();
            array[index++] = data;
        }
    }

    // 인덱스 감소 후 데이터 pop
    public int pop() {
        if (index == 0) {
            System.out.println("Stack is Empty");
            return -1;
        }
        return array[--index];
    }

    public int size() {
        return index;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("{ ");
        for (int i = 0; i < index; i++) {
            sb.append(array[i]).append(" ");
        }
        sb.append("}");
        return sb.toString();
    }

    protected void increaseArray() {
        int[] temp = array;

        // 기존 사이즈에서 5 증가
        array = new int[temp.length + SIZE];
        for (int i = 0; i < temp.length; i++) {
            array[i] = temp[i];
        }
    }

    protected boolean isPossibleSize() {
        if (array.length <= index) {
            return false;
        } else {
            return true;
        }
    }
}

ArrayStack Code

ArrayStack Test Code



과제4. 앞서 만든 ListNode를 사용해서 Stack을 구현

package structure;

/**
 * ListNode head를 가지고 있는 ListNodeStack 클래스를 구현하세요.
 * void push(int data)를 구현하세요.
 * int pop()을 구현하세요.
 */
public class ListNodeStack {
    private final ListNode head;
    private int index;

    public ListNodeStack() {
        head = new ListNode();
    }
    // 데이터를 넣고 인덱스 증가
    public void push(int data) {
        ListNode node = new ListNode(data);
        head.add(head, node, index++);
    }

    // 인덱스 감소 후 데이터 pop
    public int pop() {
        if (index == 0) {
            System.out.println("Stack is Empty");
            return -1;
        }
        ListNode node = head.remove(head, --index);
        return node.getElement();
    }

    public int size() {
        return index;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        ListNode move = head;

        sb.append("{ ");
        for (int i = 0; i < index; i++) {
            move = move.getNext();
            sb.append(move.getElement()).append(" ");
        }
        sb.append("}");

        return sb.toString();
    }
}

ListNodeStack Code

ListNodeStack Test Code



과제5. Queue를 구현

배열 이용한 Queue

정해진 데이터의 개수를 넣다가 큐가 꽉차는 경우 배열의 사이즈를 증가시킵니다.
또한 poll 동작을 하면 큐의 위치를 다시 앞으로 당겨옵니다.

package structure;

public class ArrayQueue {
    private static final int SIZE = 10;
    private int[] array;
    private int head;
    private int tail;

    public ArrayQueue() {
        array = new int[SIZE];
    }


    public void offer(int data) {
        if (isPossibleSize()) {
            array[tail++] = data;
        } else {
            increaseArray();
            array[tail++] = data;
        }
    }

    public int poll() {
        if (tail == 0) {
            System.out.println("Queue is empty");
            return -1;
        }
        int data = array[head];
        // 위치 조정
        for (int i = 1; i < tail; i++) {
            array[i - 1] = array[i];
        }
        tail--;
        return data;
    }

    public int size() {
        return tail;
    }

    protected void increaseArray() {
        int[] temp = array;

        // 기존 사이즈에서 5 증가
        array = new int[temp.length + SIZE];
        for (int i = 0; i < temp.length; i++) {
            array[i] = temp[i];
        }
    }

    protected boolean isPossibleSize() {
        if (array.length <= tail) {
            return false;
        } else {
            return true;
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("{ ");
        for (int i = 0; i < tail; i++) {
            sb.append(array[i]).append(" ");
        }
        sb.append("}");
        return sb.toString();
    }

}

ArrayQueue Code

ArrayQueue Test Code


ListNode를 이용한 Queue

package structure;

public class ListNodeQueue {
    private ListNode head;
    private int index;

    public ListNodeQueue() {
        head = new ListNode();
    }

    public void offer(int data) {
        ListNode node = new ListNode(data);
        head.add(head, node, index++);
    }

    public int poll() {
        if (index == 0) {
            System.out.println("ListNodeQueue is empty");
            return -1;
        }
        ListNode node = head.remove(head, 0);
        index--;
        return node.getElement();
    }

    public int size() {
        return head.size();
    }

    @Override
    public String toString() {
        ListNode move = head;
        StringBuilder sb = new StringBuilder();

        sb.append("{ ");
        for (int i = 0; i < index; i++) {
            move = move.getNext();
            sb.append(move.getElement()).append(" ");
        }
        sb.append("}");

        return sb.toString();
    }
}

ListNodeQueue Code

ListNodeQueue Test Code



References

profile
꾸준함이 주는 변화를 믿습니다.

0개의 댓글