본문 바로가기
Study/Live-Study

[Live-Study] 제어문

by 검프 2021. 1. 27.

목표

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

학습할 것 (필수)

  • 선택문
  • 반복문

이번엔 과제가 많습니다. 옵션이긴 하지만.. 자바 기본 문법에 직결되는 부분이 많은거같아 다 진행해보려 합니다.

선택문을 진행하기 앞서, 조건문도 정리하면 좋을 것 같아 정리했습니다.

조건문

if(조건문)에서 사용되는 조건문이란 참과 거짓을 판단하는 문장을 말합니다.

조건문이 참인지 거짓인지 따라 실행문의 제어가 결정되는데요 에제를 통해 바로 알아보도록 하겠습니다.

if 문, 나이가 20살 이하인 학생은 어린 학생으로 보겠다

class Student {
        int age;
        int tall;

        public Student(int age, int tall) {
            this.age = age;
            this.tall = tall;
        }

        public boolean isYoung() {
            if (age <= 20) {
                return true;
            }
            return false;
        }
    }

위의 예제에선 age 파라미터에 20 이하의 값이 들어오면 true를 반환합니다.

여기서의 실행문은 return true임을 알 수 있습니다.

if, else if 문,
나이가 20살 이하면 어린학생, 21살 이상이고 키가 180 이상이면 성숙한 학생으로 보겠다

class Student {
    int age;
    int tall;

    public Student(int age, int tall) {
        this.age = age;
        this.tall = tall;
    }

    public String isYoung() {
        if (age <= 20) {
            return "어린학생";
        } else if (tall <= 180) {
            return "성숙한학생";
        }
        return "나이가 좀 있는 학생";
    }
}

앞서 배운 연산자를 통해 참과 거짓을 판별할 수 있습니다.

[Live-Study] 연산자

if((n1 > n2) && (n3 > n4))
if((n1 > n2) & (n3 > n4))
if((n1 > n2) || (n2 > n3 + n4))
if((n1 > n2++) | (++n3 > n4))

선택문(switch 문)

변수에 일치하는 경우의 값에 따라 실행문의 제어가 결정됩니다. Java 12부터는 Switch 연산자가 추가되었습니다.

public class JEP354 {

    public static void main(String[] args) {

        System.out.println(getValueViaYield("a"));
        System.out.println(getValueViaYield("c"));
        System.out.println(getValueViaYield("e"));
        System.out.println(getValueViaYield("z"));

    }

    // 기본적인 형태의 switch문
    private static int getValueBefore12(String mode) {
        int result;
        switch (mode) {
            case "a":
            case "b":
                result = 1;
                break;
            case "c":
                result = 2;
                break;
            case "d":
            case "e":
            case "f":
                result = 3;
                break;
            default:
                result = -1;
        }
        ;
        return result;
    }

    // Java 12부터 쉼표(, 콤마)를 사용하여 여러 case를 한줄에 나열
    private static int getValueMultipleLabels(String mode) {
        int result;
        switch (mode) {
            case "a", "b":
                result = 1;
                break;
            case "c":
                result = 2;
                break;
            case "d", "e", "f":
                result = 3;
                break;
            default:
                result = -1;
        }
        ;
        return result;
    }

        // break 구문은 Java 13에서 컴파일되지 않고, 대신 yield를 사용합니다. 
    // Java 12에선 break를 통해 값을 리턴할 수 있었습니다. 
    private static int getValueViaBreak(String mode) {
        int result = switch (mode) {
            case "a":
            case "b":
                break 1;
            case "c":
                break 2;
            case "d":
            case "e":
            case "f":
                break 3;
            default:
                break -1;
        };
        return result;
    }

    // Java 12부터 화살표 (arrow ->)를 사용해여 결과를 반활 할 수 있습니다. 
        // 아래 예제는 Java 13에서 사용
    private static int getValueViaArrow(String mode) {
        int result = switch (mode) {
            case "a", "b" -> 1;
            case "c" -> 2;
            case "d", "e", "f" -> {
                // do something here...
                System.out.println("Supports multi line block!");
                yield 3;
            }
            default -> -1;
        };
        return result;
    }

    // Java 13, switch expression returns a value via yield
    private static int getValueViaYield(String mode) {
        int result = switch (mode) {
            case "a", "b":
                yield 1;
            case "c":
                yield 2;
            case "d", "e", "f":
                // do something here...
                System.out.println("Supports multi line block!");
                yield 3;
            default:
                yield -1;
        };
        return result;
    }
}

더 자세한것은

[Live-Study] 연산자_Java 13 switch 연산자 에 정리했습니다.

반복문

while문

반복해서 문장을 수행해야 할 경우 while문을 사용합니다.

다음은 while문의 기본 구조입니다.

while (조건문) {
    <수행할 문장1>
    <수행할 문장2>
    <수행할 문장3>
    ...
}

조건문이 참인 동안 while문 아래의 문장들을 계속해서 수행 하게 됩니다.

“열 번 찍어 안 넘어 가는 나무 없다” 라는 속담을 적용시켜 보면 다음과 같이 될 것입니다.

int treeHit = 0;
while (treeHit < 10) {
    treeHit++;
    System.out.println("나무를  " + treeHit + "번 찍었습니다.");
    if (treeHit == 10) {
        System.out.println("나무 넘어갑니다.");
    }
}

결과는 다음과 같을 것입니다.

나무를  1번 찍었습니다.
나무를  2번 찍었습니다.
나무를  3번 찍었습니다.
나무를  4번 찍었습니다.
나무를  5번 찍었습니다.
나무를  6번 찍었습니다.
나무를  7번 찍었습니다.
나무를  8번 찍었습니다.
나무를  9번 찍었습니다.
나무를  10번 찍었습니다.
나무 넘어갑니다.

위의 예에서 while문의 조건문은 treeHit < 10 입니다. 즉 treeHit가 10보다 작은 동안에 while 문 안의 문장들을 계속 수행하게 됩니다. whlie문 안의 문장을 보면 제일 먼저 treeHit++로 treeHit값이 계속 1씩 증가합니다. 그리고 나무를 treeHit번 만큼 찍었음을 알리는 문장을 출력하고 treeHit가 10이 되면 “나무 넘어갑니다”라는 문장을 출력하고 treeHit < 10라는 조건문이 거짓이 되어 while문을 빠져 나가게 됩니다

여기서 treeHit++ 는 프로그래밍을 할 때 매우 자주 쓰이는 기법으로 treeHit의 값을 1만큼씩 증가시킬 목적으로 쓰이는 것입니다. 이것은 treeHit += 1처럼 쓰기도 합니다.

무한루프(Loop)

무한 루프라 함은 무한히 반복한다는 의미입니다. 자바에서 무한루프는 while문으로 구현 할 수가 있습니다. 우리가 사용하는 프로그램들 중에서 이 무한루프의 개념을 사용하지 않는 프로그램은 하나도 없을 정도로 이 무한루프는 자주 사용됩니다. 다음은 무한루프의 기본적인 형태입니다.

while (true) {    
    <수행할 문장1>     
    <수행할 문장2>
    ...
}

while의 조건문이 true 이므로 조건문은 항상 참이됩니다. while은 조건문이 참인 동안에 while에 속해 있는 문장들을 계속해서 수행하므로 위의 예는 무한하게 while문 내의 문장들을 수행할 것입니다.

while (true) {
    System.out.println("Ctrl-C를 눌러야 while문을 빠져 나갈 수 있습니다.");
}

다음과 같은 문장이 출력됩니다

Ctrl-C를 눌러야 while문을 빠져 나갈 수 있습니다.
Ctrl-C를 눌러야 while문을 빠져 나갈 수 있습니다.
...

위의 문장이 영원히 출력될 것입니다. Ctrl-C를 눌러서 빠져 나가도록 합니다.

while문 빠져 나가기(break)

while 문은 조건문이 참인 동안 계속해서 while문 안의 내용을 수행하게 됩니다. 하지만 강제로 while문을 빠져나가고 싶을 때가 생기게 됩니다..

커피 자판기를 생각해 봤을 때, 커피가 자판기 안에 충분하게 있을 때는 항상 "돈을 받으면 커피를 줘라" 라는 조건문을 가진 while문이 수행됩니다.. 자판기가 제 역할을 하려면 커피의 양을 따로이 검사를 해서 커피가 다 떨어지면 while문을 멈추게 하고 "판매중지"란 문구를 자판기에 보여야 할 것입니다. 이렇게 while문을 강제로 멈추게 하는 것을 가능하게 해 주는 것이 바로 break입니다.

다음의 예는 위의 가정을 자바로 표현해 본 것입니다.

예) break의 사용

int coffee = 10;
int money = 300;

while (money > 0) {
    System.out.println("돈을 받았으니 커피를 줍니다.");
    coffee--;
    System.out.println("남은 커피의 양은 " + coffee + "입니다.");
    if (coffee == 0) {
        System.out.println("커피가 다 떨어졌습니다. 판매를 중지합니다.");
        break;
    }
}

money가 300으로 고정되어 있으니까 while (money > 0)에서 money는 0보다 크기 때문에 항상 참입니다. 따라서 무한 루프를 돌게 됩니다. 그리고 while문의 내용을 한번 수행할 때 마다 coffee--에 의해서 coffee의 개수가 한 개씩 줄어들게 됩니다. 만약 coffee가 0이 되면 if (coffee == 0) 라는 문장이 참이 되므로 if문 다음의 문장들이 수행이 되고 break가 호출되어 while문을 빠져 나가게 됩니다.

while문 조건문으로 돌아가기(continue)

while문 안의 문장을 수행할 때 어떤 조건을 검사해서 조건에 맞지 않는 경우 while문을 빠져나가는 것이 아니라 다시 while문의 맨 처음(조건문)으로 돌아가게 하고 싶을 경우가 생기게 됩니다. 만약 1부터 10까지의 수중에서 홀수만을 출력하는 것을 while문을 이용해서 작성해봅니다.

예) continue의 사용

int a = 0;
while (a < 10) {
    a++;
    if (a % 2 == 0) {
        continue;
    }
    System.out.println(a);
}

위의 예는 1부터 10까지의 수 중 홀수만을 출력하는 예입니다. a가 10보다 작은 동안 a는 1만큼씩 계속 증가합니다.. if (a % 2 == 0) (2로 나누었을 때 나머지가 0인 경우)이 참이 되는 경우는 a가 짝수일 때입니다. 즉, a가 짝수이면 continue 문장을 수행합니다. 이 continue문은 while문의 맨 처음(조건문: a<10)으로 돌아가게 하는 명령어입니다. 따라서 위의 예에서 a가 짝수이면 System.out.println(a)는 수행되지 않을 것입니다.

For문

while문과 마찬가지로 조건이 참인 동안 문장들을 반복해서 실행합니다.

for (초기치; 조건문; 증가치){

}

예제로는 아래와 같습니다

String[] numbers = {"one", "two", "three"};
for(int i=0; i<numbers.length; i++) {
    System.out.println(numbers[i]);
}

continue를 사용함으로써 for문의 처음으로 들아갑니다.

String[] numbers = {"one", "two", "three"};
for(int i=0; i<numbers.length; i++) {
    if(numbers[i].equals("one")){
            continue;
        }
        System.out.println(numbers[i]);
}

----output----
two
three

for each

for each는 J2SE 5.0 부터 추가되었습니다. for each 라는 키워드가 따로 있는 것은 아니고 동일한 for를 이용합니다.

String[] numbers = {"one", "two", "three"};
for(String number: numbers) {
    System.out.println(number);
}

과제 (옵션)

과제 0. JUnit 5 학습하세요.

  • 인텔리J, 이클립스, VS Code에서 JUnit 5로 테스트 코드 작성하는 방법에 익숙해 질 것.
  • 이미 JUnit 알고 계신분들은 다른 것 아무거나!
  • 더 자바, 테스트 강의도 있으니 참고하세요~

평소에 스프링부트 기반 프로젝트 개발시 단위 테스트를 진행하고 있습니다. 머리속으로는 대충 무엇인지는 알지만, 머리로만 아는것은 좋지 않다고 생각듭니다.

JUnit 5란?

이전 버전들과 달리 JUnit 5는 세 가지 하위 프로젝트의 모듈로 구정됩니다.

JUnit 5 = JUnit Platform(Extension, 실행, 관리) + JUnit Jupiter(5 버전 모듈) + JUnit Vintage(3, 4 버전 호환성)

JUnit5의 Life-Cycle

  • @BeforeAll : 모든 @Test 메소드들이 실행되기 전에 실행
  • @BeforeEach : 각각의 @Te st 메소드가 실행되기 전에 실행
  • @AfterEach : 각각의 @Test 메소드가 실행된 후에 실행
  • @AfterAll : 모든 @Test 메소드들이 실행된 후에 실행
  • @TestInstance(TestInstance.Lifecycle.PER_CLASS) : default가 PER_METHOD, 즉 인스턴스를 클래스단위로 공유
  • @TestMethodOrder(MethodOrderer.OrderAnnotation.class) : @Order() 어노테이션어로 각 메소드의 실행순서(테스트의 순서)를 정함
import org.junit.jupiter.api.*;

public class JUnit5Test {
    @BeforeAll
    static void beforeAll() {
        System.out.println("BeforeAll Test");
    }
    @BeforeEach
    static void beforeEach(){
        System.out.println("BeforeEach");
    }

    @Test
    @DisplayName("테스트 1☆")
    static void testing() {
        System.out.println("testing");
    }

    @AfterEach
    static void afterEach() {
        System.out.println("AfterEach");
    }

    @AfterAll
    static void afterAll() {
        System.out.println("AfterAll");
    }

JUnit5 Feature

  • @DisplayName : 한글, 스페이스,이모지를 통해 테스트 이름의 표기가능
  • @Nested : 계층 구조 테스트가 가능하게 지원
  • @ParameterizedTest : 여러개의 테스트 데이터를 매개변수 형태로 간편하게 사용 가능, NullSource, ValueSource, EmptySource, CsvSource, EnumSource, MethodSource등 최소 하나의 소스 어노테이션이 필요 → 여기서 정의되는 데이터들이 하나씩 테스트에 전달인자로 돌아감
//DisplayName을 이용한 테스트 이름 작성
@Test
@DisplayName("테스트 1")
static void testing() {
    System.out.println("testing");
}

//Nested를 이용한 계층 구조 테스트
@Nested
@DisplayName("people")
class People {
    @Nested
  @DisplayName("man")
  class Man {
      @Test
    static void manTest() {
        System.out.println("man");
    }
  }
    @Nested
  @DisplayName("woman")
  class Woman {
      @Test
    static void womanTest() {
        System.out.println("woman");
    }
  }
}

//ParameterizedTest를 이용한 매개변수이용 
@ParameterizedTest
@ValueSource(ints = {1,2,3,4,5})
static void isOdd(int num){
    assertTrue(Numbers.isOdd(num));
}

JUnit Assert

기존 JUnit4는 assert 메소드가 하나라도 실패하면 다음 assert를 실행하지 않았습니다. 하지만 JUnit5는 assertAll이라는 메소드를 통해 여러개의 assert를 실행하게 하여 실패하더라도 모든 결과를 확인할 수 있게 지원합니다.

@Test
static assertAllTest() {
    int num = 10;
    assertAll("assertAll test",
                  () -> assertEquals(10,num),
          () -> assertEquals(13,num+5),
          () -> assertEquals(15,num+5)
    );
}

또한, JUnit4의 경우 라이브러리를 이용해 예외 검증이 가능했다면, JUnit5는 assertThrows를 이용해 예외 검증이 가능하게 되었습니다.

저는 주로 assertThat을 사용합니다.

import org.junit.jupiter.api.DisplayName;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Assertions;

@Test
static void assertThrowsTest() {
    Exception exception = assertThrows(
                  IllegalArgumentException.class, () -> {
                        throw new IllegalArgumentException("a message");
                    }
    );
  assertEquals("message",exception.getMessage());
}

@Test
@DisplayName("입력 값에 0이 있을시, 에러가 발생한다")
void validateNaturalNumberError() {
    //given
    List<Integer> integers = Arrays.asList(new Integer[]{0, 2, 3});
    //then
    assertThatThrownBy(() -> Number.of(integers))
            .isInstanceOf(IllegalArgumentException.class)
            .hasMessage("자연수만 입력해 주세요");
}

그리고, 어노테이션을 이용해 테스트 실행시간을 확인한것에 반해 assertTimeout을 이용해 테스트 실행시간에 대한 검증이 가능하게 되었습니다.

@Test
static void assertTimeoutTest() {
    assertTimeout(ofSeconds(1), () -> {
      // 1초 이내에 수행해야함
  });
}

과제 1. live-study 대시 보드를 만드는 코드를 작성하세요.

  • 깃헙 이슈 1번부터 18번까지 댓글을 순회하며 댓글을 남긴 사용자를 체크 할 것.
  • 참여율을 계산하세요. 총 18회에 중에 몇 %를 참여했는지 소숫점 두자리가지 보여줄 것.
  • Github 자바 라이브러리를 사용하면 편리합니다.
  • 깃헙 API를 익명으로 호출하는데 제한이 있기 때문에 본인의 깃헙 프로젝트에 이슈를 만들고 테스트를 하시면 더 자주 테스트할 수 있습니다.

깃허브 access token 발급받기

코드에서 git에 접근하기 위해서는 access token을 발급받아야합니다.

https://user-images.githubusercontent.com/48986787/105813430-278a5180-5ff3-11eb-9146-adb7cbbe1d59.png

저는 github라는 이름의, repo권한만 중 상태입니다.

의존성 추가 ( Gradle )

https://user-images.githubusercontent.com/48986787/105813652-8354da80-5ff3-11eb-95c1-4d0256af0803.png

코드 작성

과제 2. LinkedList를 구현하세요.

  • LinkedList에 대해 공부하세요.
  • 정수를 저장하는 ListNode 클래스를 구현하세요.
  • ListNode add(ListNode head, ListNode nodeToAdd, int position)를 구현하세요.
  • ListNode remove(ListNode head, int positionToRemove)를 구현하세요.
  • boolean contains(ListNode head, ListNode nodeTocheck)를 구현하세요.

LinkedList에 대해 공부하세요.

단순 연결 리스트(Linked List)는 노드가 하나의 링크 필드에 의해서 다음 노드와 연결되는 구조를 가진 연결리스트입니다.

https://user-images.githubusercontent.com/48986787/105816458-abded380-5ff7-11eb-9e13-6eee54689733.png

  • 장점 : 삽입, 삭제가 빠름

하지만 중간에 있는 노드를 삭제하는 경우 탐색에 소요되는 시간이 있기 때문에

맨 앞에 있는 요소를 삽입, 삭제하는 경우 O(1),

중간요소를 삽입, 삭제하는 경우 O(n)의 시간복잡도를 가집니다.

  • 단점 : 탐색이 느림

탐색의 경우 배열이 index를 이용하기 때문에 더빠릅니다.

출처: https://songeunjung92.tistory.com/13

LinkedList 인터페이스 선언

public interface LinkedList {
    ListNode add(ListNode head, ListNode nodeToAdd, int position);

    ListNode remove(ListNode head, int positionToRemove);

    boolean contains(ListNode head, ListNode nodeTocheck);
}

ListNode 구현

import java.util.Objects;

public class ListNode implements LinkedList {
    private int data;
    private ListNode next;

    public ListNode(int data) {
        this.data = data;
        this.next = null;
    }

    @Override
    public ListNode add(ListNode head, ListNode nodeToAdd, int position) {
        ListNode listNode = head;

        for (int i = 0; i < position - 1; i++) {
            listNode = listNode.next;
        }
        nodeToAdd.next = listNode.next;
        listNode.next = nodeToAdd;

        return head;
    }

    @Override
    public ListNode remove(ListNode head, int positionToRemove) {
        ListNode listNode = head;

        for (int i = 0; i < positionToRemove - 1; i++) {
            listNode = listNode.next;
        }

        ListNode deleteNode = listNode.next;
        listNode.next = deleteNode.next;
        deleteNode.next = null;

        return deleteNode;
    }

    @Override
    public boolean contains(ListNode head, ListNode nodeTocheck) {
        while (head.next != null) {
            if (head.equals(nodeTocheck)) {
                return true;
            }
            head = head.next;
        }
        return false;
    }

    public int size() {
        ListNode listNode = this;
        int count = 1;

        while (listNode.next != null) {
            count++;
            listNode = listNode.next;
        }
        return count;
    }

    public ListNode get(int position) {
        ListNode listNode = this;

        for (int i = 0; i < position; i++) {
            listNode = listNode.next;
        }
        return listNode;
    }

    public void add(ListNode nodeToAdd) {
        ListNode listNode = this;

        while (listNode.next != null) {
            listNode = listNode.next;
        }
        listNode.next = nodeToAdd;
    }

    public void print() {
        System.out.println("Node status");
        System.out.println(toString());
    }

    public int getData() {
        return data;
    }

    @Override
    public String toString() {
        return "ListNode{" +
                "data=" + data +
                ", next=" + next +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ListNode listNode = (ListNode) o;
        return data == listNode.data;
    }

    @Override
    public int hashCode() {
        return Objects.hash(data);
    }
}

Test 코드

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import static org.assertj.core.api.Assertions.assertThat;

class ListNodeTest {
    private static ListNode listNode;

    @BeforeEach
    void initListNode() {
        listNode = new ListNode(1);
        ListNode firstNode = new ListNode(5);
        ListNode secondNode = new ListNode(10);
        ListNode thirdNode = new ListNode(15);

        listNode.add(firstNode);
        listNode.add(secondNode);
        listNode.add(thirdNode);
    }

    @DisplayName("add가 잘 되는지 확인한다")
    @ParameterizedTest
    @ValueSource(ints = {1, 3, 2})
    void addTest(int position) {
        //given
        ListNode nodeToAdd = new ListNode(20);

        //when
        listNode.add(listNode, nodeToAdd, position);

        //then
        listNode.print();
        assertThat(listNode.size()).isEqualTo(5);
        assertThat(listNode.get(position).getData()).isEqualTo(nodeToAdd.getData());
    }

    @DisplayName("remove가 잘 되는지 획인한다")
    @ParameterizedTest
    @ValueSource(ints = {1, 3, 2})
    void removeTest(int position) {
        //given
        ListNode nodeToAdd = new ListNode(20);
        listNode.add(nodeToAdd);

        //when
        ListNode findNode = ListNodeTest.listNode.get(position);
        ListNode remove = ListNodeTest.listNode.remove(ListNodeTest.listNode, position);

        //then
        assertThat(remove.getData()).isEqualTo(findNode.getData());
    }

    @DisplayName("contains이 잘되는지 확인한다")
    @Test
    void containsTest() {
        //given
        ListNode nodeToNotAdd = new ListNode(20);
        ListNode nodeToAdd = new ListNode(25);

        //when
        listNode.add(nodeToAdd);

        //then
        assertThat(listNode.contains(listNode, nodeToNotAdd)).isEqualTo(false);
        assertThat(listNode.contains(listNode, nodeToAdd)).isEqualTo(false);
    }
}

과제 3. Stack을 구현하세요.

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

Stack 인터페이스 선언

public interface Stack {
    void push(int data);

    int pop();
}

Stack 구현

public class IntStack implements Stack {
    private int[] elements = new int[10];
    private int size = 0;
    private int lastIndex = -1;

    @Override
    public void push(int data) {
        reAllocate();
        elements[++lastIndex] = data;
        size++;
    }

    @Override
    public int pop() {
        if (size == 0) {
            throw new NullPointerException();
        }

        int element = elements[lastIndex];
        size--;
        lastIndex--;
        return element;
    }

    private void reAllocate() {
        if (size >= elements.length) {
            int newSize = size * 2;
            int[] newElements = new int[newSize];
            for (int i = 0; i < elements.length; i++) {
                newElements[i] = elements[i];
            }
            elements = newElements;
        }
    }

    public int getSize() {
        return size;
    }
}

Test 코드

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class StackTest {

    private IntStack intStack;

    @BeforeEach
    void init() {
        intStack = new IntStack();
        intStack.push(5);
        intStack.push(10);
    }

    @DisplayName("push가 잘되는지 확인한다")
    @Test
    void pushTest() {
        //given
        int data = 15;

        //when
        intStack.push(data);

        //then
        assertThat(intStack.getSize()).isEqualTo(3);
    }

    @DisplayName("pop이 잘되는지 확인한다")
    @Test
    void popTest() {
        //given

        //when
        int potData = intStack.pop();

        //then
        assertThat(potData).isEqualTo(10);
    }

    @DisplayName("데이터가 없는데 pop을 하면 에러가 발생한다")
    @Test
    void popErrorTest() {
        //given
        IntStack emptyStack = new IntStack();

        //when

        //then
        assertThatThrownBy(() -> emptyStack.pop())
                .isInstanceOf(NullPointerException.class);
    }
}

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

  • ListNode head를 가지고 있는 ListNodeStack 클래스를 구현하세요.
  • void push(int data)를 구현하세요.
  • int pop()을 구현하세요.

ListNodeStack 구현

public class ListNodeStack implements Stack {
    private int data;
    private ListNodeStack next;

    private ListNodeStack(){}

    public ListNodeStack(int data) {
        this.data = data;
        this.next = null;
    }

    public static ListNodeStack makeHead() {
        return new ListNodeStack();
    }

    @Override
    public void push(int data) {
        ListNodeStack listNodeStack = this;

        while (listNodeStack.next != null) {
            listNodeStack = listNodeStack.next;
        }

        ListNodeStack listNode = new ListNodeStack(data);
        listNodeStack.next = listNode;
    }

    @Override
    public int pop() {
        if (next == null) {
            throw new NullPointerException();
        }
        ListNodeStack listNodeStack = this;

        while (listNodeStack.next.next != null) {
            listNodeStack = listNodeStack.next;
        }
        ListNodeStack deleteDate = listNodeStack.next;
        listNodeStack.next = null;

        return deleteDate.getData();
    }

    public int size() {
        ListNodeStack listNodeStack = this;
        int count = 0;

        while (listNodeStack.next != null) {
            count++;
            listNodeStack = listNodeStack.next;
        }
        return count;
    }

    public int getData() {
        return data;
    }

    @Override
    public String toString() {
        return "ListNodeStack{" +
                "data=" + data +
                ", next=" + next +
                '}';
    }
}

Test 코드

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class ListNodeStackTest {

    private ListNodeStack headNode;

    @BeforeEach
    void initListNodeStack() {
        //given
        headNode = ListNodeStack.makeHead();
        int firstAddData = 5;
        int secondAddData = 10;
        int thirdAddData = 15;

        //when
        headNode.push(firstAddData);
        headNode.push(secondAddData);
        headNode.push(thirdAddData);
    }

    @DisplayName("push가 잘되는지 체크한다")
    @Test
    void pushTest() {
        //given
        int addData = 15;

        //when
        headNode.push(addData);

        //then
        assertThat(headNode.size()).isEqualTo(4);
    }

    @DisplayName("pop이 잘되는지 체크한다")
    @Test
    void popTest() {
        //given
        int addData = 15;

        //when
        headNode.push(addData);
        int data = headNode.pop();

        //then
        assertThat(data).isEqualTo(addData);
    }

    @DisplayName("노드가 비어있을 시 pop을 시도하면 에러가 발생한다.")
    @Test
    void popErrorTest() {
        //given
        ListNodeStack listNodeStack = ListNodeStack.makeHead();
        int addData = 15;

        //when
        listNodeStack.push(addData);
        listNodeStack.pop();

        //then
        assertThatThrownBy(() -> listNodeStack.pop())
                .isInstanceOf(NullPointerException.class);
    }
}

과제 5. Queue를 구현하세요.

  • 배열을 사용해서 한번
  • ListNode를 사용해서 한번.

Queue의 특징

1. 먼저 들어간 자료가 먼저 나오는 구조 FIFO(First In FIrst Out) 구조

2. 큐는 한 쪽 끝은 프런트(front)로 정하여 삭제 연산만 수행함

3. 다른 한 쪽 끝은 리어(rear)로 정하여 삽입 연산만 수행함

4. 그래프의 넓이 우선 탐색(BFS)에서 사용

5. 컴퓨터 버퍼에서 주로 사용, 마구 입력이 되었으나 처리를 하지 못할 때, 버퍼(큐)를 만들어 대기 시킴

https://user-images.githubusercontent.com/48986787/105959591-71d90480-60bf-11eb-98a7-ceac337a0565.png

배열을 이용한 Queue 구현

Queue 인터페이스 정의

public interface Queue {
    void offer(int data);

    int poll();
}

IntQueue 구현

import java.util.Arrays;

public class IntQueue implements Queue {
    private int[] elements = new int[10];
    private int size = 0;

    @Override
    public void offer(int data) {
        reAllocate();
        elements[size] = data;
        size++;
    }

    @Override
    public int poll() {
        if (size == 0) {
            throw new NullPointerException();
        }
        int firstData = elements[0];
        int[] tempElements = new int[elements.length];
        for (int i = 1; i < tempElements.length; i++) {
            tempElements[i - 1] = elements[i];
        }
        elements = tempElements;
        size--;

        return firstData;
    }

    private void reAllocate() {
        if (size >= elements.length) {
            int newSize = size * 2;
            int[] newElements = new int[newSize];
            for (int i = 0; i < elements.length; i++) {
                newElements[i] = elements[i];
            }
            elements = newElements;
        }
    }

    public int getSize() {
        return size;
    }

    @Override
    public String toString() {
        return "IntQueue{" +
                "elements=" + Arrays.toString(elements) +
                ", size=" + size +
                '}';
    }
} 

Test 코드

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class IntQueueTest {

    private IntQueue intQueue;

    @BeforeEach
    void init() {
        intQueue = new IntQueue();
        intQueue.offer(5);
        intQueue.offer(10);
    }

    @DisplayName("offer가 잘 동작하는지 확인한다")
    @Test
    void offerTest() {
        //given
        int addData = 15;

        //when
        intQueue.offer(addData);

        //then
        assertThat(intQueue.getSize()).isEqualTo(3);
    }

    @DisplayName("poll 잘 동작하는지 확인한다")
    @Test
    void pollTest() {
        //given
        int addData = 15;

        //when
        intQueue.offer(addData);
        int pollData = intQueue.poll();

        //then
        assertThat(pollData).isEqualTo(5);
    }

    @DisplayName("비어있을 때 poll을 하면 에러가 발생한다.")
    @Test
    void pollErrorTest() {
        //given
        IntQueue testQueue = new IntQueue();
        int addData = 15;

        //when
        testQueue.offer(addData);
        testQueue.poll();

        //then
        assertThatThrownBy(() -> testQueue.poll())
                .isInstanceOf(NullPointerException.class);
    }
}

ListNode를 이용한 Queue 구현

public class ListNodeQueue implements Queue{
    private int data;
    private ListNodeQueue next;

    private ListNodeQueue() {
    }

    public ListNodeQueue(int data) {
        this.data = data;
        this.next = null;
    }

    public static ListNodeQueue makeHead() {
        return new ListNodeQueue();
    }

    @Override
    public void offer(int data) {
        ListNodeQueue listNodeQueue = this;
        while (listNodeQueue.next != null) {
            listNodeQueue = listNodeQueue.next;
        }
        ListNodeQueue addData = new ListNodeQueue(data);
        listNodeQueue.next = addData;
    }

    @Override
    public int poll() {
        if (next == null) {
            throw new NullPointerException();
        }

        ListNodeQueue listNodeQueue = this;

        int pollData = listNodeQueue.next.getData();
        listNodeQueue.next = listNodeQueue.next.next;

        return pollData;
    }

    @Override
    public String toString() {
        return "ListNodeQueue{" +
                "data=" + data +
                ", next=" + next +
                '}';
    }

    public int getData() {
        return data;
    }

    public int size() {
        int count = 0;
        ListNodeQueue listNodeQueue = this;

        while (listNodeQueue.next != null) {
            count++;
            listNodeQueue = listNodeQueue.next;
        }

        return count;
    }
}

Test 코드

package whitehip.week4;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class ListNodeQueueTest {

    private ListNodeQueue head;

    @BeforeEach
    void init() {
        head = ListNodeQueue.makeHead();
        int firstData = 5;
        int secondData = 10;
        int thirdData = 15;

        head.offer(firstData);
        head.offer(secondData);
        head.offer(thirdData);
    }

    @DisplayName("offer가 제대로 동적하는지 확인한다.")
    @Test
    void offerTest() {
        //given
        int addData = 20;

        //when
        head.offer(addData);

        //then
        assertThat(head.size()).isEqualTo(4);
    }

    @DisplayName("poll 동작확인")
    @Test
    void pollTest() {
        //given
        int addData = 20;

        //when
        head.offer(addData);
        System.out.println(head.toString());
        int pollData = head.poll();

        //then
        assertThat(pollData).isEqualTo(5);
    }

    @DisplayName("비어있을 때 poll을 하면 에러가 발생한다.")
    @Test
    void pollErrorTest() {
        //given
        ListNodeQueue testQueue = ListNodeQueue.makeHead();
        int addData = 15;

        //when
        testQueue.offer(addData);
        testQueue.poll();

        //then
        assertThatThrownBy(() -> testQueue.poll())
                .isInstanceOf(NullPointerException.class);
    }
}

댓글