본문 바로가기
Java

[Java] HashSet 컬렉션

by jn4624 2022. 5. 1.
반응형

1. HashSet이란

HashSet은 Set 인터페이스를 구현한 클래스다. 그렇기 때문에 Set의 성질을 그대로 상속받는다.

Set은 객체를 중복해서 저장할 수 없고 하나의 null값만 저장할 수 있다.

또한 저장 순서가 유지되지 않는다. 만약 요소의 저장 순서를 유지해야 한다면 LinkedHashSet을 사용해야 한다.

Set 인터페이스를 구현한 클래스로는 HashSet과 TreeSet이 있는데 HashSet의 경우 정렬을 해주지 않고 TreeSet의 경우 자동정렬을 해준다는 차이점이 있다.

Set의 가장 큰 장점은 중복을 자동으로 제거해준다는 점이다. 만약 한 편의점에서 오늘 방문한 손님의 총 숫자를 계산하고 싶을 경우도 있을 것이다. 이럴 경우 오늘 하루 동안 편의점을 여러번 방문한 손님은 한번으로 체크해주어야 정확한 손님의 숫자가 나올 것이다. 이럴 때 Set이 유용하게 쓰일 수 있는 것이다.

 

Set은 주머니 형태로 되어 있다. 비선형 구조이기에 순서가 없으며 그렇기에 인덱스도 존재하지 않는다.

그렇기에 값을 추가하거나 삭제할 때는 내가 추가 혹은 삭제하고자 하는 값이 Set 내부에 있는지 검색한 뒤 추가나 삭제를 해야 하므로 속도가 List 구조에 비해 느리다.

 

2. 중복을 걸러내는 과정

HashSet은 객체를 저장하기 전에 먼저 객체의 hashCode() 메소드를 호출하여 해시코드를 얻어낸 다음 저장되어 있는 객체들의 해시코드와 비교한 뒤 같은 해시코드가 있다면 다시 equals() 메소드로 두 객체를 배교해서 true가 나오면 동일한 객체로 판단하고 중복 저장을 하지 않는다.

문자열을 HashSet에 저장할 경우, 같은 문자열을 갖는 String 객체는 동일한 객체로 간주되고 다른 문자열을 갖는 String 객체는 다른 객체로 간주되는데 그 이유는 String 클래스가 hashCode()와 equals() 메소드를 재정의해서 같은 문자열일 경우 hashCode()의 리턴 값을 같게, equals()의 리턴값은 true가 나오도록 했기 때문이다.

 

3. HashSet 사용법

a. HashSet 선언

HashSet을 기본으로 생성했을 때 initial capacity(16), load factor(0.75)의 값을 가진 HashSet 객체가 생성된다.

HashSet도 저장공간보다 값이 추가로 들어오면 List처럼 공간을 늘리는데 Set은 한칸씩 저장공간을 늘리지 않고 저장용량을 약 두배로 늘리는데 여기서 과부하가 많이 발생한다.

그렇기에 초기에 저장할 데이터 갯수를 알고 있다면 Set의 초기용량을 지정해주는 것이 좋다.

HashSet<Integer> set1 = new HashSet<Integer>();//HashSet생성
HashSet<Integer> set2 = new HashSet<>();//new에서 타입 파라미터 생략가능
HashSet<Integer> set3 = new HashSet<Integer>(set1);//set1의 모든 값을 가진 HashSet생성
HashSet<Integer> set4 = new HashSet<Integer>(10);//초기 용량(capacity)지정
HashSet<Integer> set5 = new HashSet<Integer>(10, 0.7f);//초기 capacity,load factor지정
HashSet<Integer> set6 = new HashSet<Integer>(Arrays.asList(1,2,3));//초기값 지정

 

b. HashSet 추가

HashSet에 값을 추가하려면 HashSet의 add(value) 메소드를 사용하면 된다.

입력되는 값이 HashSet 내부에 존재하지 않는다면 그 값을 HashSet에 추가하고 true를 반환하며 내부에 값이 존재한다면 false를 반환한다.

HashSet<Integer> set = new HashSet<Integer>();//HashSet생성
set.add(1); //값 추가
set.add(2);
set.add(3);

 

c. HashSet 삭제

HashSet에 값을 제거하려면 HashSet의 remove(value) 메소드를 사용하면 된다.

매개변수 value의 값이 HashSet 내부에 존재한다면 그 값을 삭제한 후 true를 반환하고 없다면 false를 반환한다.

모든 값을 제거하려면 clear() 메소드를 사용하면 된다.

HashSet<Integer> set = new HashSet<Integer>(Arrays.asList(1,2,3));//HashSet생성
set.remove(1);//값 1 제거
set.clear();//모든 값 제거

 

d. HashSet 크기

HashSet의 크기를 구하려면 size() 메소드를 사용하면 된다.

HashSet<Integer> set = new HashSet<Integer>(Arrays.asList(1,2,3));//HashSet생성
System.out.println(set.size());//set 크기 : 3

 

e. HashSet 값 출력

Set 컬렉션을 그냥 print하게 되면 대괄호 []로 묶여서 Set의 전체 값이 출력된다.

Set에는 인덱스로 객체를 가져오는 get(index) 메소드가 없기 때문에 전체 객체 대상을 한번씩 반복해 가져오는 반복자(Iterator)를 제공한다.

반복자 Iterator는 Iterator 인터페이스를 구현한 객체를 말하는데 iterator() 메소드를 호출하면 얻을 수 있다.

Iterator에서 하나의 객체를 가져올 때는 next() 메소드를 사용한다. next() 메소드를 사용하기 전에 먼저 가져올 객체가 있는지 여부부터 확인하는 것이 좋다.

hasNext() 메소드는 가져올 객체가 있으면 true를 리턴하고 없으면 false를 리턴한다.

HashSet<Integer> set = new HashSet<Integer>(Arrays.asList(1,2,3));//HashSet생성

System.out.println(set); //전체출력 [1,2,3]
		
Iterator iter = set.iterator();	// Iterator 사용
while(iter.hasNext()) {//값이 있으면 true 없으면 false
    System.out.println(iter.next());
}

 

f. HashSet 값 검색

HashSet 내부에 원하는 값이 있는지 확인하고 싶다면 contains(value) 메소드를 사용하면 된다.

파라미터로 주어진 객체가 HashSet에 존재한다면 true, 아니면 false를 반환한다.

HashSet<Integer> set = new HashSet<Integer>(Arrays.asList(1,2,3));//HashSet생성
System.out.println(set.contains(1)); //set내부에 값 1이 있는지 check : true

 

4. HashSet 사용 예제

👉 프로그래머스 [ 포켓몬 ]

당신은 폰켓몬을 잡기 위한 오랜 여행 끝에, 홍 박사님의 연구실에 도착했습니다. 홍 박사님은 당신에게 자신의 연구실에 있는 총 N 마리의 폰켓몬 중에서 N/2마리를 가져가도 좋다고 했습니다.
홍 박사님 연구실의 폰켓몬은 종류에 따라 번호를 붙여 구분합니다. 따라서 같은 종류의 폰켓몬은 같은 번호를 가지고 있습니다. 예를 들어 연구실에 총 4마리의 폰켓몬이 있고, 각 폰켓몬의 종류 번호가 [3번, 1번, 2번, 3번]이라면 이는 3번 폰켓몬 두 마리, 1번 폰켓몬 한 마리, 2번 폰켓몬 한 마리가 있음을 나타냅니다. 이때, 4마리의 폰켓몬 중 2마리를 고르는 방법은 다음과 같이 6가지가 있습니다.

  1. 첫 번째(3번), 두 번째(1번) 폰켓몬을 선택
  2. 첫 번째(3번), 세 번째(2번) 폰켓몬을 선택
  3. 첫 번째(3번), 네 번째(3번) 폰켓몬을 선택
  4. 두 번째(1번), 세 번째(2번) 폰켓몬을 선택
  5. 두 번째(1번), 네 번째(3번) 폰켓몬을 선택
  6. 세 번째(2번), 네 번째(3번) 폰켓몬을 선택

이때, 첫 번째(3번) 폰켓몬과 네 번째(3번) 폰켓몬을 선택하는 방법은 한 종류(3번 폰켓몬 두 마리)의 폰켓몬만 가질 수 있지만, 다른 방법들은 모두 두 종류의 폰켓몬을 가질 수 있습니다. 따라서 위 예시에서 가질 수 있는 폰켓몬 종류 수의 최댓값은 2가 됩니다.
당신은 최대한 다양한 종류의 폰켓몬을 가지길 원하기 때문에, 최대한 많은 종류의 폰켓몬을 포함해서 N/2마리를 선택하려 합니다. N마리 폰켓몬의 종류 번호가 담긴 배열 nums가 매개변수로 주어질 때, N/2마리의 폰켓몬을 선택하는 방법 중, 가장 많은 종류의 폰켓몬을 선택하는 방법을 찾아, 그때의 폰켓몬 종류 번호의 개수를 return 하도록 solution 함수를 완성해주세요.

import java.util.HashSet;

public class Solution {
	public static void main(String[] args) {
		int[] nums1 = {3,1,2,3};
		int[] nums2 = {3,3,3,2,2,4};
		int[] nums3 = {3,3,3,2,2,2};
		
		System.out.println(solution(nums1));
		System.out.println(solution(nums2));
		System.out.println(solution(nums3));
	}
    
    public static int solution(int[] nums) {
		HashSet<Integer> hs = new HashSet<>();
		
		for(int i=0; i<nums.length; i++) {
			hs.add(nums[i]);
		}
		
		if( hs.size() > nums.length/2 ) {
			return nums.length/2;
		}
		
		return hs.size();
	}
}

위 문제에서는 포켓몬의 종류 번호가 담긴 배열 nums의 중복값을 제거하기 위해 HashSet을 사용하였다.

 

 

🙏 참조 ::

반응형