입구가 두 개인 스택 구현하기, 스택을 위한 프리티 프린터

들어가며

  • 보통 스택은 하나의 입구만 가지고 있다.
  • 이번에 구현한 스택은 하나의 배열이 두 개의 스택이 있다.
  • 프린터 기능을 최대한 예쁘게 구현해봤다.

pretty printer

  • 글자의 길이에 따라 가변적으로 작동한다.
  • 영문/영어로 사용한다.
import java.util.ArrayList;
import java.util.List;

public class PrettyPrinter {

    private final String value;
    private int maxLength;

    public PrettyPrinter(Object value) {
        try {
            this.value =  String.valueOf(value);
        }catch (Exception e) {
            throw new IllegalArgumentException("Wapper class 혹은 primitive type 을 넣어주세요");
        }
    }

    public void setMaxLength(int maxLength) {
        this.maxLength = maxLength;
    }

    public int getLength() {
        return value.length();
    }

    public String toStringWithSpace() {
        StringBuilder sb = new StringBuilder();
        sb.append(value);
        for(int i=0; i<maxLength- value.length(); i++){
            sb.append(" ");
        }
        return sb.toString();
    }

    public static int maxLength(int... targets) {
        int max = 0;
        for(int i=0; i<targets.length; i++) {
            if(targets[i]>max)
                max = targets[i];
        }
        return max;
    }
}
  • 테스터
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

class PrettyPrinterTest {

    @Test
    void 프리티프린터테스트(){
        int a = 99999;
        int b = 11;
        int c = 666;

        int num = 3;

        // 처음부터 글자를 늘릴 수는 없음. 최대값이 나와야 함.
        // 1. 각자의 길이를 구한다.
        // 2. 모두의 길이 중 최대 길이를 구한다.
        // 3. 최대 길이와 각자의 길이의 남은 숫자만큼 스페이스를 삽입한다.

        PrettyPrinter aa = new PrettyPrinter(a);
        PrettyPrinter bb = new PrettyPrinter(b);
        PrettyPrinter cc = new PrettyPrinter(c);

        int max = PrettyPrinter.maxLength(aa.getLength(), bb.getLength(), cc.getLength());

        aa.setMaxLength(max);
        bb.setMaxLength(max);
        cc.setMaxLength(max);

        System.out.println(aa.toStringWithSpace()+"!");
        System.out.println(bb.toStringWithSpace()+"!");
        System.out.println(cc.toStringWithSpace()+"!");
    }
}

실제 스택 구현

import utils.PrettyPrinter;

import java.util.ArrayList;
import java.util.List;

public class IntDoubleStack {
    private int max;
    private int leftPointer;
    private int rightPointer;
    private int[] stack;

    public void clear(){
        stack = new int[max];
        leftPointer = 0;
        rightPointer = max-1;
    }
    public IntDoubleStack(int capacity){
        max = capacity;
        stack = new int[max];
        leftPointer = 0;
        rightPointer = max-1;
    }

    public void pushLeft(int v){
        if(max <= getTotalSize())
            throw new OverFlowIntDoubleStackException();

        stack[leftPointer++] = v;
    }

    public void pushRight(int v){
        if(max <= getTotalSize())
            throw new OverFlowIntDoubleStackException();

        stack[rightPointer--] = v;
    }

    public int popLeft(){
        if(leftPointer<=0)
            throw new EmptyIntDoubleStackException();

        return stack[--leftPointer];
    }

    public int popRight(){
        if(rightPointer>=max-1)
            throw new EmptyIntDoubleStackException();

        return stack[++rightPointer];
    }

    public int getTotalSize() {
        return getLeftSize() + getRightSize();
    }

    public int getRightSize(){
        return (max-rightPointer-1);
    }

    public int getLeftSize(){
        return leftPointer;
    }


    public boolean isFull(){
        return getTotalSize()>=max-1?true:false;
    }
    public boolean isEmpty(){
        return getTotalSize()<=0?true:false;
    }


    // 0 1 2 3 4 5 6 7 8 9
    // 1 2 3 0 0 0 1 0 2 0
    //     L       R
    public void print(){
        StringBuilder sb1 = new StringBuilder();
        StringBuilder sb2 = new StringBuilder();
        StringBuilder sb3 = new StringBuilder();

        for(int i=0; i<stack.length; i++){
            PrettyPrinter line1 = new PrettyPrinter(i);
            PrettyPrinter line2 = new PrettyPrinter(stack[i]);

            String status  = "";
            if(leftPointer==i+1)
                status = "L";
            else if(rightPointer == i-1)
                status = "R";
            PrettyPrinter line3 = new PrettyPrinter(status);

            int max = PrettyPrinter.maxLength(line1.getLength(), line2.getLength(), line3.getLength());

            line1.setMaxLength(max);
            line2.setMaxLength(max);
            line3.setMaxLength(max);

            sb1.append(line1.toStringWithSpace()).append(" ");
            sb2.append(line2.toStringWithSpace()).append(" ");
            sb3.append(line3.toStringWithSpace()).append(" ");
        }
        System.out.println(sb1.toString());
        System.out.println(sb2.toString());
        System.out.println(sb3.toString());

    }

    public class OverFlowIntDoubleStackException extends RuntimeException{}

    public class EmptyIntDoubleStackException extends RuntimeException{}

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

import static org.junit.jupiter.api.Assertions.*;

class IntDoubleStackTest {

    @Test
    void 예외테스트(){
        //given
        final IntDoubleStack stack = new IntDoubleStack(5);

        // 초과 입력
        Assertions.assertThatThrownBy(() -> {
            stack.popLeft();
        }).isInstanceOf(IntDoubleStack.EmptyIntDoubleStackException.class);

        Assertions.assertThatThrownBy(() -> {
            stack.popLeft();
        }).isInstanceOf(IntDoubleStack.EmptyIntDoubleStackException.class);

        stack.print();
        stack.pushLeft(1);
        stack.print();
        stack.pushLeft(2);
        stack.print();
        stack.pushLeft(3);
        stack.print();
        stack.pushLeft(4);
        stack.print();
        stack.pushRight(9);
        stack.print();

        // 초과 입력
        Assertions.assertThatThrownBy(() -> {
            stack.pushRight(8);
        }).isInstanceOf(IntDoubleStack.OverFlowIntDoubleStackException.class);

        Assertions.assertThatThrownBy(() -> {
            stack.pushLeft(8);
        }).isInstanceOf(IntDoubleStack.OverFlowIntDoubleStackException.class);
    }

    @Test
    void 사이즈테스트(){
        //given
        final IntDoubleStack stack = new IntDoubleStack(5);

        // when
        stack.pushLeft(1);
        stack.pushLeft(2);
        stack.pushRight(5);
        stack.pushRight(4);
        stack.pushRight(3);

        // then
        Assertions.assertThat(stack.getLeftSize()).isEqualTo(2);
        Assertions.assertThat(stack.getRightSize()).isEqualTo(3);
        Assertions.assertThat(stack.getTotalSize()).isEqualTo(5);
    }

    @Test
    void fullOrEmpty(){
        // given
        final IntDoubleStack stack = new IntDoubleStack(5);

        // when

        // then 비었을 때
        Assertions.assertThat(stack.isEmpty()).isEqualTo(true);
        Assertions.assertThat(stack.isFull()).isEqualTo(false);

        // when
        stack.pushLeft(1);
        stack.pushLeft(2);
        stack.pushRight(5);

        // then 어설프게 채웠을 떄
        Assertions.assertThat(stack.isEmpty()).isEqualTo(false);
        Assertions.assertThat(stack.isFull()).isEqualTo(false);

        // when
        stack.pushRight(4);
        stack.pushRight(3);

        // then 가득 찼을 떄
        Assertions.assertThat(stack.isEmpty()).isEqualTo(false);
        Assertions.assertThat(stack.isFull()).isEqualTo(true);
    }

    @Test
    void clearTest(){

        // when
        final IntDoubleStack stack = new IntDoubleStack(5);

        // given
        stack.pushLeft(1);
        stack.pushLeft(2);
        stack.pushRight(5);
        stack.pushRight(4);
        stack.pushRight(3);
        stack.print();

        // then
        stack.clear();
        stack.print();
        Assertions.assertThat(stack.getTotalSize()).isEqualTo(0);
        Assertions.assertThat(stack.getLeftSize()).isEqualTo(0);
        Assertions.assertThat(stack.getRightSize()).isEqualTo(0);
    }

    @Test
    void 테스트_푸쉬_팝(){
        final IntDoubleStack stack = new IntDoubleStack(5);

        // 왼쪽에 넣고 왼쪽을 뺀다.
        stack.pushLeft(1);
        stack.print();
        Assertions.assertThat(stack.popLeft()).isEqualTo(1);
        Assertions.assertThat(stack.getLeftSize()).isEqualTo(0);

        // 왼쪽에 두 번 넣고 왼쪽에 두 번 뺀다.
        stack.clear();
        stack.pushLeft(1);
        stack.pushLeft(2);
        Assertions.assertThat(stack.popLeft()).isEqualTo(2);
        Assertions.assertThat(stack.popLeft()).isEqualTo(1);
        Assertions.assertThat(stack.getLeftSize()).isEqualTo(0);

        // 오른쪽에 넣고 오른쪽에 뺀다.
        stack.clear();
        stack.pushRight(1);
        Assertions.assertThat(stack.popRight()).isEqualTo(1);
        Assertions.assertThat(stack.getRightSize()).isEqualTo(0);

        // 오른쪽에 두 번 넣고 오른 쪽에 두 번 뺸다.
        stack.clear();
        stack.pushRight(1);
        stack.pushRight(2);
        Assertions.assertThat(stack.popRight()).isEqualTo(2);
        Assertions.assertThat(stack.popRight()).isEqualTo(1);
        Assertions.assertThat(stack.getRightSize()).isEqualTo(0);

        // 왼쪽에 넣고 오른쪽에 넣고 왼쪽에 뺀다.
        stack.clear();
        stack.pushLeft(1);
        stack.pushRight(2);
        stack.print();
        Assertions.assertThat(stack.popLeft()).isEqualTo(1);
        Assertions.assertThat(stack.getRightSize()).isEqualTo(1);

        // 오른쪽에 넣고 왼쪽에 넣고 오른쪽에 뺀다.
        stack.clear();
        stack.pushRight(2);
        stack.pushLeft(1);
        stack.print();
        Assertions.assertThat(stack.popRight()).isEqualTo(2);
        Assertions.assertThat(stack.getRightSize()).isEqualTo(0);

        // 왼쪽 오른쪽 왼쪽 오른쪽에 넣고 왼쪽에 두 번 빼고 오른쪽에 두 번 뺴고 비어있는지 확인한다.
        stack.clear();
        stack.pushLeft(1);
        stack.pushRight(2);
        stack.pushLeft(3);
        stack.pushRight(4);
        stack.print();
        Assertions.assertThat(stack.popRight()).isEqualTo(4);
        Assertions.assertThat(stack.popRight()).isEqualTo(2);
        Assertions.assertThat(stack.getRightSize()).isEqualTo(0);
    }
}

나아가며

  • 요새는 테스트코드 짜는게 참 재밌다. 약간의 정리병을 자극한다.
  • 세세하게 테스트를 잘 짜면, 코드를 수정하더라도 그것으로 인한 사이드 이펙트를 즉각적으로 알 수 있다. 테스트코드는 너무도 매력적인 개발 스타일이다.