top bar

글 목록

레이블이 container인 게시물을 표시합니다. 모든 게시물 표시
레이블이 container인 게시물을 표시합니다. 모든 게시물 표시

2015년 11월 13일 금요일

[JAVA] 우선순위(PriorityQueue) 큐

1. 개요


일반적으로 Queue라는 자료구조는 '선입선출'(First-In, First-Out)의 대기열 규칙(queuing discipline)을 가지고 있다. 말그대로 먼저들어온 놈이 먼저 나간다는 것이다.

하지만 JAVA에서 제공하는 'PriorityQueue'는 우선순위를 결정하여 들어온 순서와 상관없이 그 우선순위가 높은 엘리먼트가 나가게 된다. 이제부터 간단한 예제를 통해 알아보겠다.


2. 예제


아래와 같은 Prisoner(죄수) 클래스가 있다.
class Prisoner {

    String name;
    int weight; // 형량

    public Prisoner(String name, int weight) {
        super();
        this.name = name;
        this.weight = weight;
    }
}
이 클래스는 'name' 와 'weight(형량)' 의 2가지 필드가 있다. 이 Prisoner 클래스를 PriorityQueue에 넣고, 형량에 따라 큐에서 나오게(출소하게) 하려고 한다.

물론 일반적으로, 형량이 낮으면 먼저 출소하는 것이 인지상정. 이제 이 Prisoner 클래스에 Comparable 인터페이스를 구현 해보자.
class Prisoner implements Comparable<Prisoner> {

    String name;
    int weight; // 형량

    public Prisoner(String name, int weight) {
        super();
        this.name = name;
        this.weight = weight;
    }

    @Override
    public int compareTo(Prisoner target) {
        if (this.weight > target.weight) {
            return 1;
        } else if (this.weight < target.weight) {
            return -1;
        }
        return 0;
    }
}
위에 'Comparable' 인터페이스를 구현한 Prisoner 클래스에 있다. 형량이 낮은 Prisoner 객체를 먼저 꺼내기 위해 compareTo 메소드를 오름차순으로 정렬 되도록 구현하였다.

* PriorityQueue 사용

먼저 테스트 클래스에 'getPriorityQueue' 메소드를 구현한다. 이 메소드는 5개의 'Prisoner' 객체를 생성하여 'PriorityQueue'에 넣고 해당 'PriorityQueue' 객체를 반환한다.
private static PriorityQueue<Prisoner> getPriorityQueue() {

    Prisoner prisoner1 = new Prisoner("james", 5);
    Prisoner prisoner2 = new Prisoner("schofield", 99);
    Prisoner prisoner3 = new Prisoner("alex", 14);
    Prisoner prisoner4 = new Prisoner("silvia", 10);
    Prisoner prisoner5 = new Prisoner("thomson", 1);

    PriorityQueue<Prisoner> priorityQueue = new PriorityQueue<Prisoner>();

    priorityQueue.offer(prisoner1);
    priorityQueue.offer(prisoner2);
    priorityQueue.offer(prisoner3);
    priorityQueue.offer(prisoner4);
    priorityQueue.offer(prisoner5);
    
    return priorityQueue;
}
5명의 죄수 객체를 생성하여 우선순위 큐에 넣었다. 이 죄수 객체들은 각각 형량이 다르다.

만약 'Prisoner' 클래스에 Comparable 인터페이스를 구현하지 않은채 PriorityQueue에 집어넣으면 어떻게 될까? 우선순위 큐의 'offer'는 큐 한쪽 끝에 엘리먼트를 저장하는데, 이때 '다형성'을 이용하여 추가 되는 엘리먼트 객체를 'Comparable' 인터페이스로 'Up Casting' 한다. 하지만 'Comparable' 인터페이스를 구현한 객체가 아니라면 당연히 아래와 같은 에러메시지가 노출 될 것이다.
Exception in thread "main" java.lang.ClassCastException: Prisoner cannot be cast to java.lang.Comparable

이제 main 메소드를 구현하여 돌려보자.
public static void main(String[] args) {

        PriorityQueue<Prisoner> priorityQueue = getPriorityQueue();

        System.out.println("=============== Normal Order");

        while (!priorityQueue.isEmpty()) {
            Prisoner prisoner = priorityQueue.poll();
            System.out.println(prisoner.name);
        }
}
결과 >
=============== Normal Order
thomson
james
silvia
alex
schofield
형량이 낮은 'thomson' 이라는 녀석부터 출소한다. 형량이 '99년'인 schofield는 제일 나중에 나올 것이다.

이것이 우선순위 큐(PriorityQueue) 이다.

* Reversed Order

갑자기 교도소의 정책이 바뀌었다. 교도소장 생일을 맞이하여(.. 억지스럽더라고 걍 넘어가자) 형량이 가장 높은 죄수부터 차례로 출소하게 된것이다. 이럴때는 어떻게 해야할까? 'compareTo' 메소드를 오름차순에서 내림차순으로 바꿔서 구현해야 할까?

기존의 코드를 그대로 두고 Order를 뒤집는 방법이 있다. 아래와 같다.
public static void main(String[] args) {

        PriorityQueue<Prisoner> priorityQueue = getPriorityQueue();

        PriorityQueue<Prisoner> reversedPriorityQueue = 
         new PriorityQueue<Prisoner>(priorityQueue.size(), Collections.reverseOrder());
        reversedPriorityQueue.addAll(priorityQueue);

        System.out.println("=============== Reversed Order!");

        while (!reversedPriorityQueue.isEmpty()) {
            Prisoner prisoner = reversedPriorityQueue.poll();
            System.out.println(prisoner.name);
        }
}
결과 >
=============== Reversed Order!
schofield
alex
silvia
james
thomson
'Collections.reverseOrder()' 메소드를 살펴보면 알겠지만, 두 Camparable 객체를 비교하는 순서를 뒤집은 'Comparator' 객체를 리턴한다. 이로서 'reversedPriorityQueue' 컨테이너가 생성이 되고, 바로 위에 생성한 오름차순의 'priorityQueue' 컨테이너를 'addAll' 하게되면 'reversedPriorityQqueue'의 우선순위 정책으로 인해서 컨테이너 안의 엘리먼트의 우선순위가 뒤바뀐다.

그래서 위와 같은 결과를 도출 하는 것이다.

2015년 9월 2일 수요일

[JAVA] Set

개요



Set은 각 객체 값에 대해 하나의 인스턴스만 저장한다. 즉, 동일한 객체의 인스턴스를 추가하려고 하면 중복을 막는다. Set의 가장 일반적인 용도는 특정 객체의 존재 유무를 확인하는 것이고 때문에 contain 메소드를 사용한 '검색'이 Set에서 가장 중요한 기능이다. 일반적으로 해싱함수를 사용한 HashSet이 가장 많이 쓰인다.


예제



아래는 Integer 객체를 저장하는 HashSet 사용 예이다.
Random rand = new Random(47);

Set<Integer> intset = new HashSet<Integer>();
for (int i = 0; i < 10000; i++) {
    intset.add(rand.nextInt(30));
}
System.out.println(intset);
> 결과
[0, 1, 3, 2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 12, 15, 17, 16, 19, 18, 21, 20, 23, 22, 25, 24, 27, 26, 29, 28]
0부터 29 사이의 값에 대해서 10000개의 무작위 수가 Set에 추가되었지만 위에서 보는바와 같이 중복을 허용하지 않고 있다.

만약 정렬된 결과를 얻고 싶다면 TreeSet을 사용한다.
SortedSet<Integer> intset = new TreeSet<Integer>();

for (int i = 0; i < 10000; i++) {
    intset.add(rand.nextInt(30));
}
System.out.println(intset);
> 결과
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
TreeSet의 요소들은 red-black tree(2진 정렬 트리)의 구조로 데이터 정렬을 유지하여 위와같은 결과가 나온다

LinkedHashSet도 정렬을 유지한다. 하지만 정렬의 기준은 '추가된 순서' 이다. 아래와 같다.
LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<Integer>();
        
linkedHashSet.add(1);
linkedHashSet.add(10);
linkedHashSet.add(5);
linkedHashSet.add(3);
linkedHashSet.add(7);
        
System.out.println(linkedHashSet);  
> 결과
[1, 10, 5, 3, 7]


2015년 9월 1일 화요일

[JAVA] List - LinkedList

List의 구현체중 가장 많이 쓰이는 'ArrayList'에 대해서는 이전 포스트에 정리했다. 참고
오늘은 LinkedList에 대해서 정리할텐데, 이는 우리가 아주 잘 아는 자료구조 중 하나이다. 가장 일반적인 '단방향 LinkedList'의 경우, 하나의 노드의 포인터가 다음노드를 가리키는 식으로 되어있어 연결된(Linked) 구조를 갖는다.

예제 - LinkedList



(1) getFirtst , element , peek
LinkedList<Pet> pets = new LinkedList<Pet>(Pets.arrayList(5));

System.out.println(pets);   
    
System.out.println("pets.getFirst() : " + pets.getFirst());
System.out.println("pets.element() : " + pets.element());
System.out.println("pets.peek() : " + pets.peek());
> 결과
[Rat, Manx, Cymric, Mutt, Pug]
pets.getFirst() : Rat
pets.element() : Rat
pets.peek() : Rat
일단 이 세 메소드는 동일하다. 가장 첫번째 요소를 반환한다. 단지 element() 메소드는 getFirst() 메소드의 'alias' 일뿐이고, peek() 메소드는 리스트가 비어있을 때 null을 반환한다.

(2)  remove , removeFirst , poll
LinkedList<Pet> pets = new LinkedList<Pet>(Pets.arrayList(5));

System.out.println("pets.remove() : " + pets.remove());
System.out.println("pets.removeFirst() : " + pets.removeFirst());
        
System.out.println("pets : " + pets);

System.out.println("pets.poll() : " + pets.poll());
System.out.println("After pets.poll() : " + pets);
> 결과
[Rat, Manx, Cymric, Mutt, Pug]
pets.remove() : Rat
pets.removeFirst() : Manx
pets : [Cymric, Mutt, Pug]
pets.poll() : Cymric
After pets.poll() : [Mutt, Pug]
세 메소드 역시 동일하며 remove()는 removeFirst()의 alias이다. 가장 첫번째 요소를 삭제하면서 반환한다. poll() 메소드는 리스트가 비어있을때 null을 반환한다.

(3) addFirst , offer , add , addLast
LinkedList<Pet> pets = new LinkedList<Pet>(Pets.arrayList(5));
        
System.out.println(pets);

pets.addFirst(new Rat());       
System.out.println("After addFirst() : " + pets);
    
pets.offer(Pets.randomPet());   
System.out.println("After offer() : " + pets);
    
pets.add(Pets.randomPet()); 
System.out.println("After add() : " + pets);

pets.addLast(new Hamster());    
System.out.println("After addLast() : " + pets);
> 결과
[Rat, Manx, Cymric, Mutt, Pug]
After addFirst() : [Rat, Rat, Manx, Cymric, Mutt, Pug]
After offer() : [Rat, Rat, Manx, Cymric, Mutt, Pug, Cymric]
After add() : [Rat, Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug]
After addLast() : [Rat, Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Hamster]
요소를 추가하는 메소드들이다. addFirst() 는 리스트의 맨앞에 요소를 추가하며, 나머지 세 메소드 offer(), add(), addLast()는 리스트의 끝에 요소를 추가한다.

(4) remove
LinkedList<Pet> pets = new LinkedList<Pet>(Pets.arrayList(5));
        
System.out.println(pets);

System.out.println("pets.removeLast() : " + pets.removeLast());
System.out.println("After removeLast() : " + pets);
>결과
[Rat, Manx, Cymric, Mutt, Pug]
pets.removeLast() : Pug
After removeLast() : [Rat, Manx, Cymric, Mutt]
remove() 메소드는 리스트의 맨끝 요소를 반환하며 삭제한다.




2015년 7월 12일 일요일

[JAVA] Iterator

 Iterator는 하나의 객체로서, 객체가 저장된 컨테이너 사이를 이동하면서 각 객체를 선택해 주는 역할을 한다. 이때, 클라이언트 프로그래머는 그 컨테이너의 기반구조에 대해 알거나 신경쓸 필요가 없다.

어떤 종류의 컨테이너로 이루어 졌는지, 예컨데 ArrayList 인지, LinkedList인지, HashSet 인지 알 필요가 없는 것이다. 공통된 인터페이스를 사용하여 컨테이너의 객체들을 선택할 수 있다. 추상화의 대표적인 모델이라고 할 수 있다.

아래는 Iterator의 기본 인터페이스 이다.

메서드
설명
next()
컨테이너의 다음 요소를 반환한다
hasNext()
꺼낼 요소가 있는지 확인한다
remove()
가장 최근에 반환된 요소를 삭제한다. 따라서 next() 메서드를 호출할 때마다 사용할 수 있다.


아래는 next()와 hasNext() 메서드를 사용한 Iterator의 기본 사용 예제이다.
public class IteratorTest {
    
    public static void display(Iterator<String> it) {
        
        System.out.print("[");
        while(it.hasNext()) {
            String s = it.next();
            System.out.print(" " + s + " ");            
        }
        System.out.println("]");
    }
    
    public static void main(String[] args) {    

        List<String> strings = Arrays.asList("A", "B", "C", "D", "E", "F"); // ArrayList
        
        LinkedList<String> stringsLL = new LinkedList<String>(strings);
        HashSet<String> stringHS = new HashSet<String>(strings);
        TreeSet<String> stringsTS = new TreeSet<String>(strings);
        
        display(strings.iterator());
        display(stringsLL.iterator());
        display(stringHS.iterator());
        display(stringsTS.iterator());
    }
}
> 결과
[ A  B  C  D  E  F ]
[ A  B  C  D  E  F ]
[ D  E  F  A  B  C ]
[ A  B  C  D  E  F ]
위의 예제처럼, display() 메서드는 인자로 전달되는 Iterator의 기반 컨테이너 타입을 모른다. 하지만 추상화된 인터페이스를 통해 아주 잘 동작하는것을 볼 수 있다. 이것이 Iterator의 소소하지만 강력함? 이라고 할 수 있을 것이다.

2015년 5월 24일 일요일

[JAVA] 컨테이너 - 기초

개요



 이번엔 벼르고 별러왔던 자바 컨테이너(Container)에 대한 정리를 하려고 한다. 객체의 '저장' 이라는 관점에서 우리가 흔히 생각하는 것은 배열(array)이다. 특히나 원시(primitive)타입의 값들을 저장하여 다룰때 배열을 많이 사용한다.

 하지만 코드를 작성하는 시점에서, 우리가 필요홀 하는 객체들이 얼마나 되는지, 또는 객체 저장을 위해 더 복잡한 방법이 필요한지 알 수 없다.  따라서 '크기가 한번 정해지면 바꿀 수 없다' 라고 하는 것은 배열의 가장 큰 단점이며, 그로인한 제약은 상당히 크다.

 이러한 문제의 해결 방안으로 java.util 라이브러리에는 컨테이너(container) 클래스 들이 있으며, 그것의 기본 타입들은 List, Set, Queue, Map 이다.

 이번 포스트에는 컨테이너의 기본적인 원리들을 살펴보고, 다음 포스트에 본격적인 컨테이너의 Usage를 정리하려고 한다.


1. Generics와 컨테이너



아래 코드를 보자.

import java.util.ArrayList;

class Apple {

    private static long counter;
    private final long id = counter++;

    public long id() {
        return id;
    }
}

class Orange {
}

/**
 * @author Jupyo Hong
 */
public class ApplesAndOrangesWithoutGenerics {

    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        
        @SuppressWarnings("rawtypes")
        ArrayList apples = new ArrayList();
        
        for(int i = 0; i < 3; i++) {
            apples.add(new Apple());
        }
        
        // 같은 컨테이너에 다른 타입의 객체(Orange)를 추가해도 막지 않는다
        apples.add(new Orange());
        
        for(int i = 0; i < apples.size(); i++) {
            // Orange 타입의 요소를 가져와 id() 메서드를 호출할때 
            // RuntimeException 이 발생한다.
            ((Apple)apples.get(i)).id(); 
        }
    }
}

위 코드의 주석처럼, ArrayList 컨테이너를 생성할때 Generic을 지정하지 않으면, 아무 Type 이나 객체를 저장할 수 있다. 이는 당연히 두번째 for 문에서처럼 Apple 타입으로 캐스팅 후에 id() 메서드를 호출할때 발생하는 RuntimeException 때문에 문제가 된다. 왜냐하면 Orange 클래스에는 id() 메서드가 없기 때문이다.

따라서 아래와 같이 제네릭을 지정한다

ArrayList<Apple> apples = new ArrayList<Apple>();

apples.add(new Orange()); // Compile Error 발생!

제네릭을 지정하면, 지정한 Type외에 다른타입을 추가하려고 할때 Compile 시점에서 Error를 발생시켜 실수를 방지 할 수 있다. 또한 컨테이너에서 요소를 꺼낼때도, 캐스팅을 할 필요없이 제네릭으로 지정한 Type으로 알아서 캐스팅해준다. 아래와 같다.

ArrayList<apple> apples = new ArrayList<apple>();
        
for(int i=0; i<3; i++) {
    apples.add(new Apple());
}       
        
for(int i=0; i<apples.size(); i++) {
    System.out.println(apples.get(i).id()); // 캐스팅을 하지 않는다.
}

따라서 제네릭을 사용하면 컨테이너에 넣는 객체의 Type을 컴파일러가 확인해주고, 구문도 더 깔끔해진다.


2. 기본 개념들



 자바 컨테이너 라이브러리는 다음과 같은 두 개의 서로 다른 개념으로 나뉜다.

Collection
Map
하나 이상의 규칙이 적용되는 개별적인 요소들을 모아 놓은 것이다. List는 Sequencial 하게 객체들을 저장하며, Set은 중복요소를 가질 수 없다. Queue는 Queuing Discipline 에 의해 결정된 순서로 요소들을 산출 한다
키(key)와 그에 대응되는 값(value)의 쌍으로 구성되는 객체들을 모아 놓은 것으로, key를 사용하여 값을 검색할 수 있다. 연관 배열(associative array)이라고도 한다.


3. 컨테이너를 사용한 객체 저장과 출력



다음 코드는 Collection(List, Set)과 Map을 사용하여 객체를 저장하고, 콘솔로 출력하는 예제다. 코드와 출력결과를 잘 살펴보면 각 컨테이너의 특징을 알 수 있다.

public class PrintingContainers {
 
 static Collection fill(Collection<string> collection) {
  collection.add("dog");
  collection.add("rat");
  collection.add("cat");
  collection.add("dog");
  return collection;  
 }
 
 static Map fill(Map<String, String> map) {
  map.put("rat", "Fuzzy");
  map.put("cat", "Rags");
  map.put("dog", "Bosco");
  map.put("dog", "Spot");
  return map;
 }
 
 public static void main(String[] args) {
  
  System.out.println("ArrayList : \t" + fill(new ArrayList<string>()));
  System.out.println("LinkedList : \t" + fill(new LinkedList<string>()));
  System.out.println("HashSet : \t" + fill(new HashSet<string>()));
  System.out.println("TreeSet : \t" + fill(new TreeSet<string>()));
  System.out.println("LinkedHashSet : " + fill(new LinkedHashSet<string>()));
  System.out.println("HashMap : \t" + fill(new HashMap<String, String>()));
  System.out.println("TreeMap : \t" + fill(new TreeMap<String, String>()));
  System.out.println("LinkedHashMap : " + fill(new LinkedHashMap<String, String>()));
 }
}

## 출력결과
ArrayList :     [dog, rat, cat, dog]
LinkedList :    [dog, rat, cat, dog]
HashSet :       [dog, cat, rat]
TreeSet :       [cat, dog, rat]
LinkedHashSet : [dog, rat, cat]
HashMap :       {dog=Spot, cat=Rags, rat=Fuzzy}
TreeMap :       {cat=Rags, dog=Spot, rat=Fuzzy}
LinkedHashMap : {rat=Fuzzy, cat=Rags, dog=Spot}
ArrayList, LinkedList : 중복된 요소가 있으며 요소를 추가한 순서대로 출력된다.
HashSet : 중복이 허용되지 않았고, 순서가 보장되지 않았다.
TreeSet : 중복이 허용되지 않았고, 요소들이 오름차순으로 정렬되어 출력되었다.
HashMap : 키(key)와 값(value)가 '=' 로 묶여서 출력되었다. 순서를 보장하지 않는다.
TreeMap : HashMap과 같은 형식으로출력되며, key들이 정렬되어 출력된다.
LinkedHashMap : HashMap과 같은형식으로 출력되며, 추가된 순서를 보장한다.

 참고로 우리가 컨테이너를 쓰는 가장 큰 이유중 하나는 '크기를 지정할 필요가 없다' 라는 것이다. 이들 컨테이너는, 요소가 추가될 때마다 크기를 동적으로 증가시킨다.