이터러블이란 다양한 데이터 컬렉션에서 데이터를 순회할 때 같은 인터페이스를 이용하기 위해서 사용됩니다.
아래는 이터러블 패턴를 적용했을 때의 Java 코드 입니다. 유저가 같은 인터페이스를 사용하여 데이터를 순화는 것을 볼 수 있습니다.
MyList.java
public class MyList {
public static class Node {
private int data;
private Node prev, next;
Node(int data) {
this.data = data;
}
public int getData() {
return data;
}
}
private Node dummy;
public MyList() {
dummy = new Node(Integer.MIN_VALUE);
dummy.prev = dummy.next = dummy;
}
public void addHead(int value) {
Node node = new Node(value);
node.next = dummy.next;
node.prev = dummy;
dummy.next.prev = node;
dummy.next = node;
}
public void addTail(int value) {
Node node = new Node(value);
node.next = dummy;
node.prev = dummy.prev;
dummy.prev.next = node;
dummy.prev = node;
}
public Node getHeadNode() {
return getNextNode(dummy);
}
public Node getNextNode(Node node) {
Node next = node.next;
return next != dummy ? next : null;
}
}
MyArray.java
public class MyArray {
private int[] data;
private int count;
public MyArray() {
this(8);
}
public MyArray(int size) {
data = new int[size];
count = 0;
}
// data 배열의 크기는 두 배 확장한다
private void expand() {
data = Arrays.copyOf(data, data.length * 2);
}
// 배열의 끝에 value를 추가한다.
public void add(int value) {
if (count == data.length) expand(); // 배열이 꽉 찼으면 두 배 확장한다
data[count++] = value;
}
public int get(int index) {
return data[index];
}
public int getCount() {
return count;
}
}
사용자 사용 코드
public class Example1 {
static void doSomething1(int count) {
// MyArray 객체에 0 부터 9 까지 저장한다
MyArray a = new MyArray();
for (int i = 0; i < count; ++i)
a.add(i);
// MyArray 탐색
for (int i = 0; i < a.getCount(); ++i)
System.out.printf("%d ", a.get(i));
System.out.println();
}
static void doSomething2(int count) {
// MyList 객체에 0 부터 9 까지 저장한다
MyList list = new MyList();
for (int i = 0; i < count; ++i)
list.addTail(i);
// MyList 탐색
MyList.Node node = list.getHeadNode();
while (node != null) {
System.out.printf("%d ", node.getData());
node = list.getNextNode(node);
}
System.out.println();
}
public static void main(String[] args) {
doSomething1(10);
doSomething2(10);
}
}
위에서 유저 입장에서 다른 데이터 컬렉션의 경우에 추가와 순회 동작 각각 다른 인터페이스를 사용하는 것을 볼 수 있습니다. 데이터 컬렉션이 생길수록 유저는 많이 불편해보입니다.
아래는 Iterator를 추가하여 이런 문제점을 해소하였습니다.
MyList.java
public class MyList implements MyCollection {
private static class Node {
private int data;
private Node prev, next;
Node(int data) {
this.data = data;
}
}
private Node dummy;
public MyList() {
dummy = new Node(Integer.MIN_VALUE);
dummy.prev = dummy.next = dummy;
}
public void addHead(int value) {
Node node = new Node(value);
node.next = dummy.next;
node.prev = dummy;
dummy.next.prev = node;
dummy.next = node;
}
public void addTail(int value) {
Node node = new Node(value);
node.next = dummy;
node.prev = dummy.prev;
dummy.prev.next = node;
dummy.prev = node;
}
@Override
public void add(int value) {
addTail(value);
}
private class MyListIterator implements MyIterator {
private Node current;
MyListIterator() {
current = dummy.next;
}
@Override
public int getNext() {
int r = current.data;
current = current.next;
return r;
}
@Override
public boolean isEnd() {
return current == dummy;
}
}
@Override
public MyIterator getIterator() {
return new MyListIterator();
}
}
MyArray.java
public class MyArray implements MyCollection {
private int[] data;
private int count;
public MyArray() {
this(8);
}
public MyArray(int size) {
data = new int[size];
count = 0;
}
private void expand() {
data = Arrays.copyOf(data, data.length * 2);
}
@Override
public void add(int value) {
if (count == data.length) expand();
data[count++] = value;
}
public int get(int index) {
return data[index];
}
public int getCount() {
return count;
}
private class MyArrayIterator implements MyIterator {
private int current;
public MyArrayIterator() {
current = 0;
}
@Override
public int getNext() {
return data[current++];
}
@Override
public boolean isEnd() {
return current >= count;
}
}
@Override
public MyIterator getIterator() {
return new MyArrayIterator();
}
}
public class Example6 {
static void print(MyIterator it) {
while (!it.isEnd())
System.out.printf("%d ", it.getNext());
System.out.println();
}
static void doSomething(MyCollection col, int count) {
for (int i = 0; i < count; ++i)
col.add(i);
print(col.getIterator());
}
public static void main(String[] args) {
doSomething(new MyArray(), 10);
doSomething(new MyList(), 10);
}
}
자바스크립트의 이터러블을 알기 전에 ES6에 도입된 이터레이션 프로토콜에 대한 이해가 필요합니다. 이터레이션 프로토콜에는 이터러블 프로토콜과 이터레이터 프로토콜이 있습니다.
-
이터러블 프로토콜
Well-known Symbol인 Symbol.iterator를 프로퍼티 키로 사용한 메서드를 직접 구현하거나 프로토테입 체인을 통해 상속받은 Symbol.iterator 메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환합니다. 이터러블 프로토콜을 준수한 객체를 이터러블이라고 합니다. -
이터레이터 프로토콜
이터러블의 Symbol.iterator 메서드를 호출된 이터레이터는 next 메서드를 소유하여 next 메서드를 호출하면 이터러블을 순회하며 value와 done 프로퍼티를 갖는 이터레이터 result 객체를 반환합니다. 이터레이터 프로토콜을 준수한 객체를 이터레이터라 합니다.
그럼 어떻게 이터레이터를 만들까요?
JavaScript의 iterator는 잠재적으로 순회가 끝날 때까지 값을 return하는 객체입니다. 생성 시, 아래 두가지 프로퍼티(value,done)를 return하는 next() 메소드를 소유해야합니다.
- value
iterator 순서의 다음 값 - done
iterator의 마지막 값인지 확인하는 값으로 true이 순회가 끝난 것을 의미합니다.
아래는 예제는 start부터 step 단위로 iterator하는 간단한 범위를 갖는 iterator입니다 마지막 return 값은 sequence의 크기가 됩니다.
function makeRangeIterator(start=0, end=Infinity, step=1) {
let nextIndex = start;
let iterationCount = 0;
const rangeIterator = {
next: function() {
let result;
if(nextIndex<Infinity) {
result = {value: nextIndex, done:false};
nextIndex+=step;
iterationCount++;
return result;
}
return {value:iterationCount, done:true};
}
}
return rangeIterator
}
아래 처럼 사용할 수 있다.
const it = makeRangeIterator(1,10,2);
let result = it.next();
while(!result.done) {
console.log(result.value); // 1 3 5 7 9
result = it.next();
}
console.log(`Iterated over sequence of size: `, result.value);
위처럼 유용한 이터레이터를 만들 수 있으나 내부 state를 유지하기 위해서 조심스러운 programming을 해서 만들어야하는 잠재적 위험이 있습니다.
JavaScript은 잠재적 위험을 없애기 위햇 Generator 함수를 제공하였습니다.
Generator 함수는 iterative algorithm 정의하는 것을 허락하며, Generator functions는 function* syntax를 사용하여 작성합니다.
호출할 때, generator function는 코드를 초기화하지 않습니다. 대신에 그들은 iterator의 특별한 type을 return합니다. generator의 next 메소드 호출에 의해 값이 consume될 때, generator 함수는 yield 키워드를 만나기 전까지 실행됩니다.
function* makeRangeIterator(start=0, end=100, step=1) {
let iterationCount = 0;
for(let i=start; i<end; i+=step) {
iterationCount++;
yield i;
}
return iterationCount;
}