2022-01-21 Exception / LRU 알고리즘

GGAE99·2022년 1월 22일
0

진도

목록 보기
13/43

오늘은 Exception과 LRU알고리즘에 대해 공부했다.

Exception

사실 이 개념에 대해 많이 공부하지 않았다. 오늘은 어제 공부했던 Map, ArrayList, Set에 대해 더 자세히 공부하고 프로그래밍 해보고 그랬는데, 여기다가 쓰지 않으려고한다. 오늘 배운 개념은 이것밖에 없어서 정리해보려고한다. 근데 오늘 딱 try 까지만 하고 다음에 또 할거다.

프로그래밍을 하다보면 다양한 종류의 오류를 만날 수 있다. 정리해보자.

  1. 컴파일 에러 (compile - time error) : 소스상의 문법 에러
  2. 런타임 에러 ( runtime error) : 입력 값이 틀렸거나, 배열의 인덱스범위를 벗어났거나,
    계산식의 오류 등으로 발생하는 에러
  3. 논리 에러 (logical error) : 문법상 문제가 없고, 런타임 에러도 발생하지 않지만,
    개발자의 의도대로 작동하지 않는 경우
  4. 시스템 에러 (system error) : 컴퓨터 오작동으로 인한 에러 -> 소스코드로 해결 불가능

이러한 상황들은 문제를 찾아내서 해결해주면 된다. 하지만 오늘 배울 개념은 Exception이다.

Exception

  • 예외 라는 뜻을 가지고 있으며, 예외는 예기치 못한 상황을 말한다.
  • 프로그래밍을 하다 보면 수많은 오류상황을 직면한다.
  • 자바에서 예외란 프로그램을 만든 개발자가 예상한 정상적인 처리에서 벗아나는 경우에
    이를 처리하기 위한 방법이다.
  • 예측 가능한 에러를 처리하는 것 이라고 볼 수 있다.

이렇게 예외처리를 하는 이유는 프로그램의 비정상적인 종료를 막고, 정상적인 실행상태를 유지하기 위함이다. 예외 상황이 발생된 경우 어떻게 처리할지에 대한 논리를 미리 구현해놓는 방식으로 이를 해결한다. 그때 이 try문이 나오는거다. 코드로 한번 보자.

public class ExceptionTest {
	public void test1() {
		Scanner sc = new Scanner(System.in);
		while(true) {
			System.out.print("정수를 입력하세요 : ");
			int num=0;
			try{
				num = sc.nextInt();			
			}catch (Exception e) {
				System.out.println("정수를입력하라구.");
				sc.nextLine();
				continue;//그래도 finally는 실행됨
			}finally {
				System.out.println("이건 무조건 실행!");
			}			
			System.out.println("입력한 정수는 : "+num);			
		}
	}
}

오늘은 이 코드 하나로 싹 정리할거다. 그만큼 내용이 없다.
위의 코드는 정수형 변수 num에 정수를 입력받도록 하고 그 값을 다시 출력해주는 코드이다.
그런데 사용자가 여기서 정수값을 입력하지 않으면 오류가 발생한다. 그때 이 try문을 사용한다.
문자열 abc를 입력하면 이 코드는 마지막 출력코드

System.out.println("입력한 정수는 : "+num); 

으로 가지 않고,

catch (Exception e) {
	System.out.println("정수를입력하라구.");
	sc.nextLine();
	continue;//그래도 finally는 실행됨
}

요 부분으로 간다. 이 부분은 코드가 오류나지않으면 그냥 실행되는 부분이다.
정상적인 값을 입력했을 때의 출력값과, 그렇지 않았을때의 출력값으로 정리해보자.

  • 정상적인 값 입력
    출력값 :
    정수를 입력하세요 : 10
    이건 무조건 실행!
    입력한 정수는 : 10
  • 비정상적인 값 입력
    출력값:
    정수를 입력하세요 : affhs
    정수를입력하라구.
    이건 무조건 실행!

이렇게 되는거다. 여기서 왜 이건 무조건 실행! 이라는 부분은 계속 보이는가?
그건 finally문으로 작성되었기 때문이다. 이 부분은 에러가 나든 말든 무조건 실행된다.
Exception은 여기서 마치도록하자.

LRU 알고리즘

LRU 알고리즘이란, 쉽게 말해 배열의 저장공간이 다 차면, 가장 오래된 값을 버리고 새 값을 받아오는 알고리즘이다. 카카오 코딩테스트에 나왔던 문제를 보면서 구현해보자.



필자는 이 문제를 ArrayList와 HashMap 자료구조를 같이 사용하려고 뻘짓하다가 오랜시간 못풀었다. 근데 이 문제 난이도 하 다. 프로그래밍을 얼마 하지도 않았으면서 사치스럽게도 자괴감이든다. 안되는건 내가 못해서다. 더 공부해라 필자. 여튼 얼른 문제를 풀어보자.

필자는 ArrayList만 사용해서 풀 것이다. 그런데 사실 LinkedList를 사용하면 더 빠르고 쉽게 풀 수 있다고한다. 그건 다음에 해봐서 따로 올리겠다. 일단 필요한 자료들부터, Scanner와 ArrayList객체를 추가해주고 실행시간을 출력해줄 변수를 선언해준다.

public void main() {
		ArrayList<String> lru = new ArrayList<String>(); //알고리즘을 구현할 자료구조 객체
		ArrayList<String> cities = new ArrayList<String>(); // 사용자한테서 입력받은 값을 저장할 자료구조 객체
		Scanner sc = new Scanner(System.in); // 그냥 스캐너
		int totalTime = 0; //총 시간

왜 저렇게 했는지 주석에 써 놓았으니 궁금하신 분들은 읽어주시길 바란다. 근데 사실 내 글을 보러오시는 분들이 없다. 나도 안다. 그냥 나 공부한거 정리하려고 하는거다...
다음은 사용자한테서 캐시 사이즈와 도시의 배열을 직접 입력 받는다.

System.out.print("캐시의 크기를 입력하세요(0~30) : ");
int cacheSize = sc.nextInt();// 캐시의 크기
System.out.print("도시의 문자열을 입력하세요 : ");
sc.nextLine();//버퍼 비우기
String cityName = sc.nextLine();//도시들 이름 받기

위의 사진을 보면 "Jeju", "Pangyo", "Seoul", "NewYork", "LA", "Jeju", "Pangyo", "Seoul", "NewYork", "LA" 요런식으로 입력한다는 것을 알 수 있다.
입력받은 문자열을 StringTokenizer로 나눠주자.

StringTokenizer st = new StringTokenizer(cityName, ", ");// 입력받은 배열을 ","기준으로 나눔
	while (st.hasMoreTokens()) {
		String str1 = st.nextToken().toLowerCase();//모두 소문자로 변환해서 리턴
		cities.add(str1);//ArrayList형 자료구조 cities에다 값을 입력
	}

입력받은 값을 ", "기준으로 나눠주고, 그 값을 처음에 만들었던 cities에 넣어줬다.
시간을 계산하는 일만 남았다. 입력받은 캐시의 값이 0일때부터 만든다.

else {// if end // 캐시 사이즈가 0 이상인 경우
	for (int i = 0; i < cities.size(); i++) {
		String city = cities.get(i);
		//현재 lru에 이번회차 도시이름이 존재하는지 확인 indexOf
		//indexOf(매개변수) : 매개변수로 전달된 객체가 리스트의 몇번째에 존재하는지 리턴하는 메소드(존재하지않으면 -1 리턴)
		int result = lru.indexOf(city);

indexOf를 사용해 다음에 받을 값을 result에 받아줬다. 이 변수로 lru의 값이 같은게 있는지 판단해준다. 그리고 상황마다 다르게 코드를 써준다.

if(result != -1) {	 //현재 캐시에 해당하는 도시명이 존재하는 경우
	lru.add(0,lru.remove(result)); //원래 있던 값을 삭제하고 0번에 집어넣는 과정
	totalTime += 1;	 //현재 값 +1
}

현재 같은 값이 lru에 들어있다면 원래 값이 들어있던 부분을 삭제하고, 키값이 0인 위치에 다시한번 값을 넣어줌으로써 lru.size를 늘리지 않늗다. 같은 값이 있을때는 걸린 시간을 +1해준다.

}else {		//현재 캐시에 해당하는 도시명이 존재하지 않는 경우
	if(lru.size() == 0) {	// 리스트에 아무 값도 없을 때
	lru.add(city);

이번엔 캐시에 입력받은 도시명이 존재하지 않을 경우이다. 이 경우 lru에 이미 값이 있을 경우와 없을 경우로 나눠서 코드를 짠다. 없을 경우에는 그냥 add해주면 된다.

}else {
	if(lru.size()==cacheSize) {		//리스트길이와 캐시 최대크기를 비교
			lru.remove(lru.size()-1);	//캐시가 꽉 차있으면 마지막데이터 삭제
	}
		lru.add(0,city);  	// 리스트에 값이 하나라도 있을 때
	}
	totalTime += 5;  //현재 값 +5

이미 lru에 데이터가 있을 경우 입력받은 캐시사이즈와 현재 lru.size를 비교해서 만약 캐시사이즈와 lru.size가 같다면 lru.size가 캐시사이즈를 넘어가지 못하도록 가장 마지막 값을 삭제해준다. 왜 lru.size-1값을 삭제해주는지 알아보자.

lru.size-1 해주는 이유
캐시사이즈를 3이라고 가정해보자. 그리고 여태 lru에 넣어준 값이 3개라고 가정하면, lru는 ArrayList기 때문에 입력받은 값이 a,b,c라면

  • lru(0,"c");
  • lru(1,"b");
  • lru(2,"a");
    이렇게 될 것이다. 왜냐하면 우리가 넣어줄 때 가장 최근에 넣은 코드의 키값을 0으로 설정했기 때문이다. 그래서 가장 최근에 넣은 값이 키값 0 에 들어간다. 그러면 원래 존재하던 값들은 키값이 1씩 밀리면서 위치가 바뀐다.
    가장 오래된 값이 가장 마지막에 위치하도록 만들었다는 것 이다.
    lru알고리즘은 데이터의 크기가 꽉 찼을때, 가장 오래된 값을 삭제해주는 알고리즘이다. 그러므로 위와같은 상황에서는 lru(2,"a")값을 삭제해 주어야한다. 우리가 캐시 사이즈를 정해서 그 범위를 넘어가지 않게 해 놓았음으로 항상 가장 마지막 키값은 2일 것 이다. 그리고 2는 우리가 3으로 가정한 캐시사이즈-1의 값이다.
    캐시 사이즈가 어떤 값이더라도 이 논리는 성립한다. 데이터가 꽉 찼을때, 가장 오래된 키값은 언제나 캐시 사이즈-1, 즉 lru.size-1의 값일 것 이라는거다.

위와 같은 논리가 성립하기 때문에 lru.size-1위치의 값을 삭제해주고 다시 0의 위치에 값을 넣어주는 것 이다. 그리고 위 코드와 값에 아무것도 들어있지 않았을 경우(윗윗 코드) 모두 총 시간을 5 더해준다. 마지막으로 총 시간이 몇인지 출력해주는 코드까지 작성하면 끝이다.

} // else end
		System.out.println("총 시간 : "+totalTime);

끝이다! 고생했다!

원래 포스팅을 어제 올리려고 했는데, 새로나온 유희왕 마스터 듀얼 해보고싶어서 친구랑 추억새기면서 밤새 했다... 그래서 마저 작성하지 못하고 너무 늦게 자버렸다. 아침에 일어나서야 올린다...! 근데 유희왕 진짜 재밌다. 꿈에서도 썬더 드래곤 덱 굴리는 꿈을 꿨다.

0개의 댓글