코드 그라데이션

<보충> 컬랙션 추가 공부 (2) Map, Set, Queue 본문

Java/Mega

<보충> 컬랙션 추가 공부 (2) Map, Set, Queue

완벽한 장면 2023. 4. 28. 09:43

1. 

public class MapTest1 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner sc = new Scanner(System.in);
		Random r = new Random();
		System.out.print("보 바위 가위 선택하세요 : ");
		int user = sc.nextInt();
		
		int com = r.nextInt(3); // 0~2
		
		HashMap<Integer, String> comMap = new HashMap<>();
		
		comMap.put(0,"보");
		comMap.put(1,"바위");
		comMap.put(2,"가위");
		
		System.out.println("User : " + comMap.get(user)+", Com "+comMap.get(com));
			
	}

}

 

Hash 함수

어떤 인풋이 왔을 때 정해진 길이의 아웃풋으로 바꿔주는 역할.

 

Map도 결국엔 배열의 일환

배열은 어디에 저장을 한 지 인덱스를 알아야 함.

put(2,"가위"); 일 때, 2 자리에 숫자 대신에 문자가 들어올 수도 있다.

이 때 어떻게든 이 자리에 오는 것을 칸에 매핑해야 한다.

이런 상황

 

그런데 hash함수는 이 변환 작업이 아주아주 빠르다.

값을 넣음과 동시에 자리를 찾아감. 순식간. 그래서 HashMap이 빠른 것.

 

HashMap은 두 개의 원소가 하나의 쌍을 이뤄서 작동.

앞의 0, 1, 2는 인덱스와는 별로 상관이 없다.

언제든 다른 값이 들어갈 수 있다.

 

get은 무조건 key를 기준으로.

key는 절대 중복되지 않아야 함.

 

 

class C {
	int k;
}

class D{
	String str;
}

public class MapTest2 {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		HashMap<C, D> map = new HashMap<>();
		C c = new C();
		C c1 = new C();
		map.put(c, new D());
		map.get(c1); // null
		map.get(c);//객체 D를 리턴
		
		System.out.println(map.get(c1));
		System.out.println(map.get(c));
	}

}

 

데이터베이스는 특이하게도 들어갈 때와 나올 때의 값이 다르게 판단이 되는데

우리는 이 값이 "사실상 동일"하다는 것을 인지하고 있음.

그래서 이것을 같다고 판단 되게 만들고 싶음.(동등성을 판단하고 싶다면)

현재 이 코드에서는 동일성을 판단하고 있다.

 

어떻게 하면 되려나?

HashMap 내부적으로 equals에 대한 검사를 하면 된다.

=> C의 equals를 내가 재정의 하면 된다.

class C {
	int k;
    
    // 추가한 부분    
    public boolean equals(Object other) {
    	C c = (C) other;
        return this.k = c.k;
}

class D{
	String str;
}

public class MapTest2 {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		HashMap<C, D> map = new HashMap<>();
		C c = new C();
		C c1 = new C();
		map.put(c, new D());
		map.get(c1); // null
		map.get(c);//객체 D를 리턴
		
		System.out.println(map.get(c1));
		System.out.println(map.get(c));
	}

}

=> 이렇게 만들면 물리적 주소를 비교하는 게 아니라,

   물리적 주소가 다르더라도 가지고 있는 k 가 같으면 같다고 판단하게 만든 것.

 

클래스의 필드는 의도적 초기화를 시키지 않으면 기본값으로 0으로 초기화 되게 되어있음.

그래서 k는 0일 것이고, 만든 객체 c와 c1은 둘 다 int 0이 들어가 있을 것.

따라서 이 때는 0과 0을 비교하게 되므로 같다고 나올 것.

// 하지만 약간의 한계가 있음. Hash코드까지 완전하게 구현해줘야 원하는 결과가 정확하게 나오게 됨.

class C {
	int k;
    
    // 추가한 부분    
    //public boolean equals(Object other) {
    // 	C c = (C) other;
    //    return this.k = c.k;
    
    
    // 요렇게 완전체
    @Override
    public boolean equals(Object ob) {
    	if(this == ob) {
        	return true;
        }
        if(ob == null || getClass() != ob.getClass()) {
        	return false
        }
        
        C c = (C) ob;
        return k == c.k;
    }
    
    @Override
    public int hashCode() {
    	return Object.hash(k);
    }
}


class D{
	String str;
}

public class MapTest2 {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		HashMap<C, D> map = new HashMap<>();
		C c = new C();
		C c1 = new C();
		map.put(c, new D());
		map.get(c1); // null
		map.get(c);//객체 D를 리턴
		
		System.out.println(map.get(c1));
		System.out.println(map.get(c));
	}

}

 

아까 그림을 보면

hash코드는

'유진'이 들어오면 항상 같은 곳을 가리키게 해야 되는데, 

해시코드가 구현이 안 되어 있으면 엉뚱한 데 가서 매핑이 됨. 그래서 문제

 

equals는 어느정도 충돌을 허용해줌.

 

'유진'이 어떤 칸(b3)으로 들어왔다고 가정했을 때,

b3칸에는 유진, 이서, 원영 등 여러 이름들이 있다.(충돌허용)

이 때 b3에서 '유진'을 찾을 때 equals()를 쓰는 것.

 

그래서 hash코드와 equals가 쌍으로 정확하게 구현이 있어야 제대로 작동한다.

 


2.

public class SetTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		HashSet<String> a = new HashSet<>();
		HashSet<Integer> b = new HashSet<>();
		
		String[] color = {"빨간색","주황색","노란색","초록색","파란색","남색","보라색"};
		
		for(int i = 0;i<color.length;i++) {
			a.add(color[i]);
			b.add(i);
		}
		
		a.add("빨간색");
		
		boolean checkChange = a.add("무지개색");
		
		System.out.println(checkChange);
		
		System.out.println("b의 가장 큰값은 ?" + Collections.max(b));
		System.out.println("b의 가장 최소값은 ?" + Collections.min(b));
		
		Iterator<String> it = a.iterator();
		
		while(it.hasNext()) {
			System.out.println(it.next());
		}		
	}
}

 

얘도 원리는 동일

hash함수를 통해

"빨간색" 이 들어왔을 때 그것과 맞는 버킷으로 안내. 

 

얘는 Key만 있기 때문에 역시 이것은 중복이 되면 안 된다.

 

Set은 중복을 허용하지 않기 때문에

데이터의 중복 제거 할 때만 특히 사용.

순서 개념도 없다.

 

그래서 안에 있는 원소를 가져오려면 내부를 돌아다닐

Iterator가 필요.

(거의 모든 컬랙션이 이터레이터를 가지고 있다.)

 

이터레이터의 역할

1. 끝인지 아닌지

2. 지금 가리키고 있는 게 뭔지

를 판단.

 

따라서 

hasNext()는 끝인지 아닌지를 판단하고,

끝이 아니라면

지금 가지고 있는 거 줘 next()

가 되는 것.

 

next() 하는 일도 두 가지

1. 지금 있는 것 가져오기

2. 뒤로 한 칸 이동시기

이렇게.

 

사실 더 간단한 방법은 Iterator를 굳이 쓰지 않고

향상된 for문(for-each문)을 사용해서 가져오면 됨.

public class Sample {
	public static void main(String[] args) {
    	Set<String> setA = new HashSet<>();
        setA.add("가");
        setA.add("나");
        setA.add("다");
        setA.add("라");
        setA.add("마");
        
        for(String element : setA) {
        	System.out.plintln(element);
        }
    	
    }
}

 

Set은 index가 없으니까 for문을 돌리기도 어렵다.

반복문은 위와 같은 형태만 일단 짜야한다.

 

내부적으로는 정렬을 하고 있음(아마 기본적으로 배열 형태여서 그런 듯.)

하지만 Set자체는 정렬을 보장하진 않는다(by.클린코드)


3.

public class QueueTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		LinkedList<String> q = new LinkedList<String>();
		System.out.println("큐 offer : " + q.offer("한국"));
		System.out.println("큐 offer : " + q.offer("중국"));
		System.out.println("큐 offer : " + q.offer("미국"));
		System.out.println("큐 offer : " + q.offer("러시아"));
		System.out.println("큐 offer : " + q.offer("우크라이나"));
		System.out.println("큐 offer : " + q.offer("북한"));
		
		System.out.println("===============================");
		int index = q.indexOf("우크라이나");
		if(index != -1) {
			System.out.println("큐에서 숫자 \"우크라이나\"의 위치는 : " + index + "번째 입니다.");
		}
		else {
			System.out.println("큐에서 숫자 \"우크라이나\"가 없습니다.");
		}
		System.out.println("===============================");
		while(!q.isEmpty()) {
			String obj = q.poll();
			System.out.println("poll : "+ obj);
		}
	}

}

String obj = q.poll();는 

 

pop처럼 가지고 있는 것 그냥 달라는 명령어

 

리스트는 어디서 달라고 지정을 해야하는데

Stack이나 Queue는 넣는 위치와 빼는 위치가 정해져 있기 때문에 지정하지 않아도 된다.

 

큐는 인터페이스이기 때문에

Queue<String> q = new Queue<String>();

이게 안 된다.

무조건 구현체가 와야함.

 

~011430

728x90
Comments