Iterator Pattern: একে একে প্রতিটা element-এ যাও
Iterator pattern শেখো ট্রেনের টিকেট চেকারের গল্প দিয়ে। TypeScript-এ custom iterator বানাও, for...of আর generator ব্যবহার করো, আর C#-এ IEnumerable দেখো।
টিকেট চেকার S4 বগিতে ঢোকে
ধরো রাত ৮টার ট্রেন। ঢাকা থেকে চট্টগ্রাম যাচ্ছে। ট্রেন ছাড়ার কিছুক্ষণ পর জামাল ভাই — টিকেট চেকার, হাতে chart, গায়ে ইউনিফর্ম — S4 বগির দরজা খুলে ঢোকেন টিকেট চেক করতে।
কিন্তু কীভাবে করেন তিনি? রেলওয়ের reservation computer-এ data কোন database-এ, কোন format-এ আছে — এটা কি তাঁকে জানতে হয়? একদমই না। তিনি শুধু ১ নম্বর সিট থেকে শুরু করেন আর এগিয়ে যান: সিট ১, সিট ২, সিট ৩... প্রতিটা সিটে জিজ্ঞেস করেন "টিকেট দেখান", দেখেন, তারপর next সিটে যান। শেষ সিটে পৌঁছালে বুঝতে পারেন এই বগি শেষ — পরের বগিতে যাওয়ার সময়।
আজ রাতে S4 বগিতে চার যাত্রী: ফাতেমা (সিট ১, হাতেই টিকেট), রুবেল (সিট ২, বইয়ের ভেতর গোঁজা টিকেট), সুমাইয়া (সিট ৩, টিকেট নেই — বিপদে পড়বে!), আর তারিক (সিট ৪, ঘুমিয়ে পড়েছে, তবে টিকেট আছে)।
দেখো জামাল ভাইয়ের কাজ করতে কী কী লাগে:
- next যাত্রী কে সেটা জানার উপায়।
- আর কেউ আছে কিনা সেটা জানার উপায়।
- এখন কোথায় আছেন সেটার নিজস্ব memory (সিট ৩, S4 বগি)।
ব্যস, এটুকুই। বগিতে berth কীভাবে সাজানো, যাত্রীরা কোন order-এ উঠেছে, seating chart কীভাবে তৈরি — কিছুই জানতে হয় না। বগি নিজের arrangement নিজের কাছে রাখতে পারে। জামাল ভাই শুধু একটা একটা সিটে যাবেন।
আর একটা সুন্দর ব্যাপার: জামাল ভাই যখন সামনে থেকে টিকেট চেক করছেন, তখন করিম ভাই pantry car থেকে পেছন থেকে ঢুকে রাতের খাবারের order নিচ্ছেন। দুজন একই বগিতে, উল্টো দিক থেকে হাঁটছেন — কিন্তু কেউ কাউকে bother করছে না, কারণ দুজনের নিজস্ব position আলাদা।
জামাল ভাই হলেন একটা iterator। বগিটা হলো collection। এই পুরো lesson-টা হলো তোমার code-কে নিজের টিকেট চেকার দেওয়ার গল্প।
তাঁর কাজের flow টা দেখো — মাত্র দুটো প্রশ্ন, বারবার:
আর এই পুরো যাত্রাটা সন্ধ্যার গল্প হিসেবে:
Iterator pattern কী জিনিস?
Iterator (আরেক নাম Cursor) হলো Gang of Four (GoF) বইয়ের (১৯৯৪) একটা behavioral design pattern।
নোটের জন্য একটা definition:
Iterator pattern তোমাকে একটা collection-এর element গুলো একে একে traverse করতে দেয় — ভেতরে কীভাবে store করা সেটা না জেনেই। traversal logic — current position, পরেরটা কী, শেষ হলো কিনা — এগুলো collection থেকে বের হয়ে একটা আলাদা iterator object-এ চলে যায়।
Pattern-এর চারটা অংশ। ট্রেনের সাথে মেলাও:
| Pattern-এর অংশ | কী করে | ট্রেনের version |
|---|---|---|
| Iterator interface | hasNext() আর next() declare করে | "যে সিট walk করে" তার job description |
| Concrete iterator | position track করে, পরেরটা কী জানে | জামাল ভাই, সিট ৩ মনে রেখে |
| Collection interface | createIterator() declare করে | বগির দরজা বলছে "এখান থেকে শুরু করো" |
| Concrete collection | data store করে, শুধু storage | S4 বগি আর তার seating chart |
১. Iterator interface — সাধারণত দুটো method: hasNext() ("আর আছে?") আর next() ("এখনকারটা দাও, এগিয়ে যাও")।
২. Concrete iterator — একটা collection আর একটা traversal order-এর জন্য এই method গুলো implement করে। current position ধরে রাখে। আমাদের জামাল ভাই।
৩. Collection interface — একটা factory method declare করে, createIterator(), যেটা fresh iterator দেয়। বগি বলছে "নাও, সিট ১ থেকে শুরু করো।"
৪. Concrete collection — আসল data structure, শুধু storage নিয়ে ব্যস্ত।
বাইরে থেকে সব iterator একরকম দেখায় বলে, client-এর loop কখনো বদলায় না — data array-তে থাকুক, tree-তে থাকুক, hash set-এ থাকুক, বা file থেকে lazily আসুক।
মনে রাখার trick: একটা iterator মাত্র দুটো প্রশ্নের উত্তর দেয়, বারবার — "আর কেউ আছে?" আর "পরের জন কে?" তোমার data-র জন্য এই দুটো প্রশ্নের উত্তর দিতে পারে এমন একটা object বানাতে পারলেই iterator বানিয়ে ফেললে। বাকিটা detail।
এই pattern-এর GoF-এর patterns-এর মধ্যে একটা বিশেষ জায়গা আছে — এটা এত useful যে প্রায় সব modern language নিজের syntax-এ এটা নিয়ে নিয়েছে। JavaScript-এর for...of, Python-এর for...in, C#-এর foreach, Java-র enhanced for — সবই Iterator pattern, language-এর নিজের পোশাক পরে। নিচে hand-made version আর built-in version দুটোই দেখবো।
পুরো lesson টা একটা mind map হিসেবে, নোটবুকের জন্য:
সমস্যাটা কোথায়?
Client সরাসরি collection-এর ভেতরে হাত দিলে কী হয়? দেখো।
ধরো রেলওয়ে S4 বগির যাত্রীদের একটা plain array-তে রেখেছে। একটা naive ticket-checking program এভাবে করতে পারে:
// ❌ The client reaches inside the collection's private structure
for (let i = 0; i < coach.passengerArray.length; i++) {
checkTicket(coach.passengerArray[i]);
}দুটো বড় সমস্যা এখানে লুকিয়ে।
সমস্যা ১: ভেতরের structure বাইরে বেরিয়ে যায়। Client এখন জেনে গেছে coach-এ passengerArray নামে একটা array আছে। pantry program-ও জানে। cleaning schedule program-ও জানে। কাল রেলওয়ে storage বদলে Map-এ গেলে — এই সব loop ভেঙে পড়বে। Private layout public knowledge হয়ে গেছে, আর সেটা বদলাতে গেলে codebase-এ প্রতিটা loop খুঁজে বেড়াতে হবে। যেন জামাল ভাই, করিম ভাই, আর বগির attendant সবাইকে reservation database-এর schema মুখস্থ করতে হচ্ছে শুধু বগিতে হাঁটার জন্য।
সমস্যা ২: "next" সবসময় obvious না। Array-তে "next" সহজ: index plus one। কিন্তু data যদি tree হয়? ধরো রেলওয়ে coaches → compartments → berths এভাবে tree-তে রেখেছে। "পরের যাত্রী" কে এখন? Depth-first? Breadth-first? বাম থেকে ডান? প্রতিটা client-কে tree walk শিখতে হবে, একই stack-based traversal code লিখতে হবে, আর সম্ভবত একটু ভুলও করবে। Walking logic প্রতিটা জায়গায় copy-paste হবে, আর সেটার কোনো proper ঘর থাকবে না।
আর তৃতীয় একটা শান্ত সমস্যা: traversal code যদি collection-এর ভেতরেই থাকে — এখানে walkDepthFirst(), ওখানে walkBreadthFirst() — তাহলে collection class টা algorithm-এর ভিড়ে স্ফীত হয়ে যায়। আর "current position"-এর একটাই ঘর থাকায় দুটো simultaneous traversal অসম্ভব হয়ে পড়ে। জামাল ভাই আর করিম ভাইকে একটাই bookmark share করতে হবে — কার position কোথায় সেটা নিয়ে ঝামেলা।
Iterator pattern এই তিনটা সমস্যা একসাথে fix করে: traversal নিজের ছোট্ট object-এ চলে যায়, collection-এর ভেতর লুকানো থাকে, প্রতিটা traversal order একটা tested class, আর প্রতিটা iterator নিজের position বহন করে — তাই অনেকগুলো parallel-এ চলতে পারে।
কীভাবে কাজ করে, ধাপে ধাপে
চলো ট্রেনের বগিটা ঠিকমতো বানাই।
ধাপ ১: Iterator interface declare করো — hasNext(): boolean আর next(): Passenger।
ধাপ ২: Collection interface declare করো — একটাই factory method, createIterator(): Iterator। মনে রেখো, createIterator() আসলে Factory Method pattern — iterators তৈরি করছে।
ধাপ ৩: Concrete collection implement করো। Coach যাত্রীদের যেভাবে খুশি রাখুক — এখানে seat-এর array। শুধু storage code।
ধাপ ৪: Concrete iterator implement করো। এটা coach-এর reference আর একটা position field রাখে। "পরের সিট কোনটা" এই পুরো logic এখানে, clients থেকে আলাদা করা।
ধাপ ৫: Client interface দিয়ে loop করে। while (it.hasNext()) check(it.next())। একই loop যেকোনো collection-এ কাজ করে যেটা iterator দিতে পারে।
ধাপ ৬ (modern step): Language-এর iteration protocol-এ plug in করো। JavaScript/TypeScript-এ Symbol.iterator, C#-এ IEnumerable<T>, Python-এ __iter__ — আর client native for...of / foreach / for...in loop ব্যবহার করতে পারবে।
Runtime-এ কথোপকথনটা দেখো। Coach-কে একবারই জিজ্ঞেস করা হয় — একটা iterator দেওয়ার জন্য। তারপর client শুধু iterator-এর সাথে কথা বলে:
পেছন থেকে হাঁটার দরকার? মানে করিম ভাইয়ের মতো? একটা ReverseIterator class আর একটা second factory method যোগ করো। Coach-এর storage code ছোঁয়া লাগবে না, client-এর loop-ও বদলাবে না। এটাই Open/Closed Principle।
একটা iterator-এর জীবনটাও ছোট আর নির্দিষ্ট। জন্ম হয়, হাঁটে, আর একদিন exhausted হয়ে যায় — exhausted iterator আর কখনো হাঁটে না:
চিত্র ৫ মনে রেখো নিচের generator section-এর জন্য — "exhausted iterators stay exhausted" এটাই beginners-এর সবচেয়ে common bug-এর কারণ।
Real-life code example
এখানে TypeScript-এ পুরো railway coach। দুটো style দেখাবো: hand-made classic iterator, আর modern Symbol.iterator / generator style যেটা for...of-কে power করে।
// The element we iterate over
interface Passenger {
seatNo: number;
name: string;
hasTicket: boolean;
}
// ----- Step 1: the Iterator interface -----
interface PassengerIterator {
hasNext(): boolean;
next(): Passenger;
}
// ----- Step 2: the collection interface -----
interface IterableCoach {
createIterator(): PassengerIterator;
}
// ----- Step 3: the concrete collection (storage only) -----
class Coach implements IterableCoach {
// Private! No client ever touches this directly.
private seats: Passenger[] = [];
board(name: string, hasTicket: boolean): void {
this.seats.push({ seatNo: this.seats.length + 1, name, hasTicket });
}
get seatCount(): number {
return this.seats.length;
}
passengerAt(index: number): Passenger {
return this.seats[index];
}
// Factory methods — one per traversal order
createIterator(): PassengerIterator {
return new TTEIterator(this); // front to back
}
createReverseIterator(): PassengerIterator {
return new PantryIterator(this); // back to front
}
}
// ----- Step 4: concrete iterators (walking only) -----
class TTEIterator implements PassengerIterator {
private position = 0; // Mr. Verma's own memory of where he stands
constructor(private coach: Coach) {}
hasNext(): boolean {
return this.position < this.coach.seatCount;
}
next(): Passenger {
return this.coach.passengerAt(this.position++);
}
}
class PantryIterator implements PassengerIterator {
private position: number;
constructor(private coach: Coach) {
this.position = coach.seatCount - 1; // Santosh starts from the last seat
}
hasNext(): boolean {
return this.position >= 0;
}
next(): Passenger {
return this.coach.passengerAt(this.position--);
}
}
// ----- Step 5: the client drives the loop -----
const coachS4 = new Coach();
coachS4.board("Riya", true);
coachS4.board("Arjun", true);
coachS4.board("Meera", false); // oh no!
coachS4.board("Kabir", true);
console.log("--- Mr. Verma checking tickets (front to back) ---");
const tte = coachS4.createIterator();
while (tte.hasNext()) {
const p = tte.next();
console.log(
`Seat ${p.seatNo}: ${p.name} — ${p.hasTicket ? "ticket OK" : "NO TICKET, fine!"}`,
);
}
console.log("--- Santosh taking orders (back to front) ---");
const pantry = coachS4.createReverseIterator();
while (pantry.hasNext()) {
console.log(`Seat ${pantry.next().seatNo}: dinner order taken`);
}Output:
--- Mr. Verma checking tickets (front to back) ---
Seat 1: Riya — ticket OK
Seat 2: Arjun — ticket OK
Seat 3: Meera — NO TICKET, fine!
Seat 4: Kabir — ticket OK
--- Santosh taking orders (back to front) ---
Seat 4: dinner order taken
Seat 3: dinner order taken
Seat 2: dinner order taken
Seat 1: dinner order takenদুটো iterator একই program-এ একই coach walk করলো, একে অপরকে disturb না করেই — কারণ প্রতিটার নিজস্ব position আছে। Client কখনো seats array দেখেনি। জামাল ভাই আর করিম ভাই, দুজনের নিজস্ব bookmark।
তুমি যে class structure বানালে সেটা দেখো:
আজ রাতে পুরো ট্রেনে টিকেট চেকে কী পাওয়া গেল — iteration data তৈরি করে, আর data চার্টে সুন্দর লাগে:
Built-in পথ: Symbol.iterator আর generators
Modern language গুলো এই pattern নিজেদের syntax-এ বেক করে নিয়েছে। JavaScript আর TypeScript-এ যেকোনো object যার [Symbol.iterator]() method আছে যেটা next(): { value, done } shaped object return করে সে iterable, আর for...of সেটা happily walk করবে। Generator function (function* আর yield দিয়ে) হলো shortcut — iterator object নিজেই বানিয়ে দেয়, position bookkeeping লাগে না, class লাগে না:
class ModernCoach {
private seats: Passenger[] = [];
board(name: string, hasTicket: boolean): void {
this.seats.push({ seatNo: this.seats.length + 1, name, hasTicket });
}
// A generator: the language writes the iterator for us.
// Execution PAUSES at each yield and resumes on the next loop turn.
*[Symbol.iterator](): Generator<Passenger> {
for (const p of this.seats) {
yield p;
}
}
// A second, filtered traversal — lazily yields only defaulters
*ticketlessPassengers(): Generator<Passenger> {
for (const p of this.seats) {
if (!p.hasTicket) yield p;
}
}
}
const coach = new ModernCoach();
coach.board("Riya", true);
coach.board("Meera", false);
coach.board("Kabir", true);
// The famous for...of — this IS the Iterator pattern, built in
for (const p of coach) {
console.log(`${p.name} is in the coach`);
}
for (const p of coach.ticketlessPassengers()) {
console.log(`${p.name}, please pay the fine.`); // Meera, please pay the fine.
}ticketlessPassengers() compare করো উপরের ১৫ লাইনের PantryIterator class-এর সাথে — generator চার লাইনে একই কাজ করে। Generator গুলো lazy-ও: একবারে একটা value তৈরি হয়, শুধু চাওয়া হলে। মানে তুমি এমন জিনিসও iterate করতে পারো যা কখনো শেষ হয় না:
// An infinite iterator — only possible because values come lazily
function* seatNumbers(): Generator<number> {
let n = 1;
while (true) yield n++; // pauses here after each value
}
const seats = seatNumbers();
console.log(seats.next().value, seats.next().value, seats.next().value); // 1 2 3
// No infinite loop — we only pulled three values.Classic hand-made iterator ব্যবহার করো যখন পুরো control দরকার (resettable cursors, custom invalidation rules) বা যখন শেখাচ্ছো। Everyday code-এ generators আর for...of ব্যবহার করো — এগুলোই pattern, idiomatically।
একটু deeper যাই: Generator হলো তোমার lazy evaluation-এর প্রথম স্বাদ — একটা value তখনই compute করো যখন কেউ আসলেই চাইছে। Eager approach পুরো list আগে বানায়, তারপর দেয়। Lazy approach একটা একটা করে দেয় আর pause করে। এটা কী কী সম্ভব করে: ১০ GB log file constant memory-তে line by line stream করা, infinite sequence model করা (সব seat number, সব prime), আর lazy stages একসাথে chain করে pipeline বানানো যেখানে final consumer pull না করা পর্যন্ত কিছুই চলে না। Haskell পুরো language-এর জন্য laziness default করে; Python-এর generator, JavaScript-এর function*, আর C#-এর yield return তোমাকে চাইলেই একই superpower দেয়। Mental model: একটা generator হলো একটা paused function যেটা কোথায় থেমেছিল মনে রাখে — জামাল ভাই বগির মাঝখানে এক পা তুলে freeze হয়ে আছেন, যতক্ষণ না কেউ next() call করছে।
Memory-র পার্থক্যটা dramatic। ধরো একটা program-কে এক লক্ষ booking record process করতে হবে:
Lazy-র bar টা typo না। Generator এক লক্ষ record-এর মধ্যে একটা record একসাথে hold করে — current টা — ট্রেন যত লম্বাই হোক।
C#-এ একই ব্যাপার
C# এই pattern express করে IEnumerable<T> / IEnumerator<T> দিয়ে, আর yield return keyword generator-এর কাজ করে। তুমি এতদিন যে foreach loop লিখেছো — সবই ছিল Iterator pattern ছদ্মবেশে:
public record Passenger(int SeatNo, string Name, bool HasTicket);
public class Coach : IEnumerable<Passenger>
{
private readonly List<Passenger> _seats = new();
public void Board(string name, bool hasTicket)
=> _seats.Add(new Passenger(_seats.Count + 1, name, hasTicket));
// yield return = C#'s generator. The compiler builds the
// iterator class (position tracking and all) behind the scenes.
public IEnumerator<Passenger> GetEnumerator()
{
foreach (var p in _seats)
yield return p; // pause here, resume on next iteration
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
// A second traversal: only the ticketless, lazily
public IEnumerable<Passenger> Ticketless()
{
foreach (var p in _seats)
if (!p.HasTicket)
yield return p;
}
}
// Client
var coach = new Coach();
coach.Board("Riya", true);
coach.Board("Meera", false);
coach.Board("Kabir", true);
foreach (var p in coach) // foreach drives the iterator
Console.WriteLine($"Seat {p.SeatNo}: {p.Name}");
foreach (var p in coach.Ticketless())
Console.WriteLine($"{p.Name}, please pay the fine.");
// LINQ is a pipeline of iterators chained together:
var names = coach.Where(p => p.HasTicket).Select(p => p.Name).ToList();yield return line চলার সময় method pause করে, caller-কে একটা item দেয়, পরের loop turn-এ একই জায়গা থেকে resume করে। Memory-তে আগে থেকে কিছু store হয় না — item গুলো চাহিদামতো তৈরি হয়। পুরো LINQ (Where, Select, Take...) হলো lazy pipeline-এ chain করা iterator object গুলো।
Python-ও একই গল্প বলে __iter__/__next__, generator function, আর for ... in loop দিয়ে:
class Coach:
def __init__(self):
self._seats = []
def board(self, name, has_ticket):
self._seats.append((len(self._seats) + 1, name, has_ticket))
def __iter__(self):
# A generator method: Python builds the iterator for us
for seat in self._seats:
yield seat
def ticketless(self):
for seat_no, name, has_ticket in self._seats:
if not has_ticket:
yield (seat_no, name)
coach = Coach()
coach.board("Riya", True)
coach.board("Meera", False)
for seat_no, name, has_ticket in coach: # for...in drives __iter__
print(f"Seat {seat_no}: {name}")
for seat_no, name in coach.ticketless():
print(f"{name}, please pay the fine.") # Meera, please pay the fine.একটু deeper যাই: LINQ আর Python-এর itertools দেখায় কেন lazy iterator pipeline গুরুত্বপূর্ণ। coach.Where(...).Select(...).Take(3)-এ, pipeline বানানোর সময় কোনো কাজ হয় না — প্রতিটা stage শুধু আরেকটা iterator wrap করা। কেউ pull করলে তখনই data বয়, আর একবারে একটা item সব stage পেরিয়ে যায়, Take(3)-এর পর তৃতীয় match পেলেই সব থেমে যায়। এটাই deferred execution। আর এর classic trap হলো: query প্রতিবার enumerate করলে দুবার কাজ হয় — আর data এর মাঝে বদলে গেলে আলাদা answer পাবে। Stable snapshot দরকার হলে ToList() দিয়ে materialize করো।
Real software-এ কোথায় দেখবে
Iterator সম্ভবত পৃথিবীর সবচেয়ে বেশি ব্যবহার হওয়া design pattern — বেশিরভাগ সময় চোখেই পড়ে না।
১. প্রতিটা for...of, foreach, আর for...in loop। JavaScript array, string, Map, আর Set সবই Symbol.iterator implement করে। Spread operator [...thing] আর destructuring-ও ভেতরে iterator drive করে।
২. Python generator আর itertools। Python-এর for loop ভেতরে iter() আর next() call করে। Generator (yield) lazily value তৈরি করে, তাই ১০ GB log file প্রায় কোনো memory ছাড়াই line by line stream করা যায়। itertools module হলো composable iterator-এর toolbox।
৩. C# IEnumerable আর LINQ। yield return method memory-তে collection বানায় না — চাওয়া হলে কীভাবে item তৈরি করতে হবে সেটা describe করে। LINQ এই lazy sequence গুলো query pipeline-এ chain করে।
৪. Java Collections। java.util.Iterator আর Iterable enhanced for-loop-কে power করে ArrayList, HashSet, TreeMap সব জায়গায়। Java-র iterator fail-fast: মাঝপথে collection modify করলে ConcurrentModificationException আসে, silent corruption না।
৫. Database আর API। Database cursor (মনে রেখো, "Cursor" এই pattern-এর alternate নাম) query result row by row walk করে পুরো result set load না করেই। Paginated API ("পরের ২০টা result দাও") হলো network-এর উপর দিয়ে iterator — "next page" token হলো iterator-এর saved position।
৬. File system আর stream। File line by line পড়া, directory tree walk করা, message queue consume করা — সব iterator-shaped: আর আছে? পরেরটা কে?
| Real software | The collection | The iterator |
|---|---|---|
JavaScript for...of | Array, Map, Set, string | [Symbol.iterator]() থেকে আসা object |
Python for loop | __iter__ আছে এমন যেকোনো object | Generator / iterator object |
C# foreach + LINQ | IEnumerable<T> implement করা যেকোনো কিছু | IEnumerator<T> (প্রায়ই compiler yield দিয়ে বানায়) |
| Java collections | ArrayList, HashSet... | java.util.Iterator (fail-fast) |
| Database | Query result set | Cursor |
| Web API | Server-side data | Pagination token ("next page") |
| Message queue | Stream of messages | Consumer-এর offset |
কখন ব্যবহার করবে, কখন করবে না
| পরিস্থিতি | ব্যবহার করবে? | কারণ |
|---|---|---|
| Collection-এর ভেতরের structure জটিল (tree, graph, paged data) আর লুকানো রাখতে চাও | হ্যাঁ | Client layout না জেনেই walk করতে পারে |
| একই data কয়েকটা traversal order-এ দরকার (forward, reverse, filtered) | হ্যাঁ | প্রতিটা order-এ একটা iterator class, storage অস্পৃষ্ট |
| Traversal code অনেক জায়গায় copy-paste হয়েছে | হ্যাঁ | Walk একটা tested iterator-এ centralize করো |
| এমন একটা loop দরকার যা বিভিন্ন collection type-এ কাজ করে | হ্যাঁ | Iterator interface-এর বিপরীতে code করো |
| Data বিশাল বা infinite, lazily produce করতে হবে | হ্যাঁ | Generator / yield চাহিদামতো item stream করে |
| Custom data structure বানাচ্ছো যেটা অন্যরা loop করবে | হ্যাঁ | Language-এর iteration protocol implement করো |
| ছোট plain array, একটা জায়গায় ব্যবহার | না | Language-এর built-in loop-ই যথেষ্ট |
| Random jump দরকার (সরাসরি ৫৭ নম্বর element চাই) | না | Iterator sequential, indexing ব্যবহার করো |
| হাঁটার সময় collection heavily modify করবে | সাবধান | Iterator invalidation — আগে collect করো বা fail-safe design ব্যবহার করো |
নিজের situation এই map-এ রাখো। Structure যত লুকানো, আর walk করার উপায় যত বেশি দরকার, custom iterator তত বেশি justified:
Students যে ভুলগুলো করে
ভুল ১: Iterate করার সময় collection modify করা। জামাল ভাই যদি হাঁটার সময় একজন টিকেটবিহীন যাত্রীকে array থেকে সরিয়ে দেন, তারপরের সব সিট shift করবে — iterator-এর saved position ভুল মানুষ point করবে, কিছু যাত্রী skip হবে। Java-তে ConcurrentModificationException আসে, JavaScript-এ silently element skip হয়। Fix: আগে কী remove করবে collect করো, loop শেষে remove করো।
ভুল ২: একটা iterator একমুখী আর single-use — এটা ভুলে যাওয়া। শেষ হয়ে যাওয়া generator exhausted — দ্বিতীয়বার loop করলে কিছুই পাবে না, আর beginners-রা খালি second loop দেখে অবাক হয়ে যায়। চিত্র ৫ মনে করো: Exhausted থেকে Walking-এ কোনো arrow নেই। আবার traverse করতে হলে collection থেকে fresh iterator চাও (createIterator() আবার call করো, বা generator function আবার call করো)।
আরো কিছু trap:
hasNext()check না করেnext()call করা। শেষ সিটের পরে গেলেundefinedreturn করে (বা throw করে)। সবসময় দুটো প্রশ্ন একসাথে — অথবাfor...ofব্যবহার করো, সেটা নিজেই pair করে।- Traversal logic আবার collection-এর ভেতরে নিয়ে যাওয়া।
Coachclass-এ যদিwalkForward(),walkBackward(),walkTicketless()method গুলো position manage করে, তুমি pattern-টা undo করে ফেলেছো। Position থাকে iterator object-এ। - দুটো loop-এ একটাই iterator share করা। প্রতিটা traversal-এর নিজস্ব iterator দরকার, নইলে দুটো loop একটা position নিয়ে ঝগড়া করে — জামাল ভাই আর করিম ভাই একটাই bookmark share করার মতো।
- যেখানে order promise নেই সেখানে iteration order assume করা। Hash-based set কোনো order promise নাও করতে পারে। শুধু collection যা document করে সেটার উপর depend করো।
- Lazy pipeline দুবার enumerate করা ভুলে। প্রতিটা enumeration-এ কাজ আবার হয় (deferred execution corner দেখো)। একই answer দুবার দরকার হলে
ToList()/list(...)/[...spread]দিয়ে snapshot নাও।
প্রতিবেশী pattern-দের সাথে তুলনা
| Pattern | মূল প্রশ্ন | Iterator-এর সাথে সম্পর্ক |
|---|---|---|
| Iterator | "প্রতিটা element একে একে কীভাবে visit করবো?" | — |
| Composite | "Object-এর tree-কে একটা object হিসেবে কীভাবে treat করবো?" | Iterator হলো Composite tree walk করার clean পথ, node expose না করেই |
| Factory Method | "কোন concrete object বানাবে সেটা কে decide করে?" | createIterator() আসলে একটা factory method যেটা সঠিক iterator return করে |
| Visitor | "প্রতিটা element-এ একটা operation কীভাবে চালাবো?" | Iterator decide করে কীভাবে move করবে, Visitor decide করে প্রতিটা stop-এ কী করবে |
| Memento | "State কীভাবে save আর restore করবো?" | Traversal-এর position snapshot করতে পারে — দীর্ঘ walk পরে resume করার জন্য |
| Command | "Request-কে object হিসেবে কীভাবে package করবো?" | ভিন্ন goal — কিন্তু command-এর queue সাধারণত worker দ্বারা iterate হয় |
মনে রাখার জুটি হলো Iterator + Visitor: iterator হলো জামাল ভাইয়ের ট্রেনে হাঁটার route, visitor হলো প্রতিটা সিটে তিনি কী করেন (টিকেট চেক? dinner order? বাচ্চা গণনা?)। "কীভাবে traverse করবে" আর "কী করবে" আলাদা করলে দুটো মিলিয়ে match করতে পারো — একই forward walk সকালে টিকেট চেক আর রাতে কম্বল বিতরণ দুটোতেই কাজ করে।
Quick revision box
+=====================================================================+
| ITERATOR PATTERN — QUICK REVISION |
+=====================================================================+
| WHAT : Walk a collection element by element WITHOUT exposing |
| how it stores things inside. |
| STORY : Mr. Verma the TTE checks tickets seat by seat. He needs |
| only "anyone left?" and "who is next?" plus his own |
| position. Santosh walks the same coach the other way. |
| ROLES : Iterator (hasNext/next) | ConcreteIterator (position) |
| Collection (createIterator factory) | Client (the loop) |
| MODERN : for...of + Symbol.iterator (JS/TS), function* + yield, |
| IEnumerable + yield return (C#), __iter__ (Python) |
| LAZY : generators produce values on demand -> huge or infinite |
| sequences cost almost no memory |
| WINS : hidden internals, many traversal orders, parallel and |
| pausable walks, one loop for every collection |
| RISKS : modifying while iterating, exhausted single-use |
| iterators, overkill for tiny arrays |
| SEEN IN : every for-of / foreach loop, LINQ, Python itertools, |
| database cursors, paginated APIs |
+=====================================================================+Practice exercise
নিজে type করো। হাত শেখায় মাথাকে।
Task 1: Attendance roll-call। একটা ClassRoom collection বানাও যেটা student গুলো record হিসেবে রাখে — roll number, name, আর isPresent flag। একটা classic hand-made RollCallIterator লেখো (hasNext()/next() সহ) যেটা student গুলো roll number order-এ visit করে, এমনকি যদি random order-এ যোগ করা হয়ে থাকে। Client loop print করবে "Roll 1: রুবেল — Present", আর client কখনো internal array ছুঁতে পারবে না।
Task 2: দুটো traversal, একটা collection। তোমার ClassRoom-এ একটা generator method *absentees() যোগ করো যেটা lazily শুধু absent student-দের yield করে, আর [Symbol.iterator] implement করো যাতে plain for (const s of classRoom) সব student-এর জন্য কাজ করে। একই program-এ একই object-এর উপর দুটো loop চালাও — প্রমাণ করো দুটো iterator একে অপরকে disturb করে না। তোমার নিজের জামাল ভাই আর করিম ভাই।
Task 3 (challenge): পাতার বই। একটা Book model করো যার chapter গুলোতে page আছে (ছোট tree: book → chapters → pages)। একটা PageIterator লেখো যেটা পুরো বইয়ের প্রতিটা page reading order-এ walk করে — chapter structure সম্পূর্ণ লুকিয়ে রেখে। Client শুধু for (const page of book) করবে। Hint: chapterIndex আর pageIndex iterator-এর ভেতরে রাখো, অথবা দুটো nested loop-এর generator-কে bookkeeping করতে দাও। Bonus: একটা infinite revisionQuestions() generator বানাও যেটা বইয়ের প্রশ্নগুলো forever cycle করে, আর শুধু প্রথম দশটা pull করো loop break করে।
Task 3 শেষ করলে তুমি সেটাই করলে যেটা for...of প্রতিদিন তোমার জন্য করে — আর বুঝলে কেন GoF iterator-কে humble "cursor" বলেছে যেটা চুপচাপ পুরো programming দুনিয়া চালায়, একটা একটা সিট করে।
সচরাচর জিজ্ঞাসা
- Iterator pattern আসলে কী, সহজ করে বলো?
- এটা তোমাকে একটা collection-এর element গুলো একে একে দেখতে দেয় — ভেতরে কীভাবে store করা আছে সেটা না জেনেই। কোন position-এ আছি, পরেরটা কী, শেষ হলো কিনা — এই logic টা আলাদা একটা iterator object-এ থাকে।
- সাধারণ for loop দিয়ে index করলেই তো হয়, তাহলে iterator লাগবে কেন?
- index দিয়ে loop করা শুধু array-তে কাজ করে। Tree, set, linked list, বা network থেকে আসা data-তে সহজ index নেই। Iterator দিয়ে যেকোনো ধরনের collection একইভাবে walk করতে পারো।
- JavaScript-এর for...of কি Iterator pattern?
- হ্যাঁ। for...of যেকোনো object-এ কাজ করে যেটা Symbol.iterator implement করে আর একটা object return করে যার next() method আছে। Array, string, Map, Set — সবার ভেতরে built-in iterator আছে, তাই for...of এগুলোতে কাজ করে।
- iterator আর generator-এর মধ্যে পার্থক্য কী?
- generator হলো iterator লেখার shortcut। position track করার জন্য আলাদা class না লিখে, তুমি yield দিয়ে একটা function লেখো — language নিজেই iterator object বানিয়ে দেয়।
- একই collection-এ কি দুটো iterator একসাথে চলতে পারে?
- হ্যাঁ, আর এটাই iterator-এর বড় সুবিধা। প্রতিটা iterator object নিজের position মনে রাখে, তাই দুটো traversal একে অপরকে disturb করে না। তবে iterator চলার সময় collection modify করলে সমস্যা হতে পারে।
আরো দেখো
সম্পর্কিত পাঠ
Chain of Responsibility Pattern: যে যেটা পারে সে সামলাও, নইলে পাস করো
Chain of Responsibility pattern শেখো একটা school leave application-এর গল্পের মাধ্যমে — সহজ TypeScript আর C# code, diagram, table, আর practice task সহ।
Command Pattern: প্রতিটি কাজকে একটি অর্ডার স্লিপে বদলে দাও
রেস্তোরাঁর অর্ডার স্লিপের গল্প দিয়ে Command pattern শেখো। TypeScript আর C#-এ undo ও redo সহ পুরো কোড, diagram, table, আর practice task।
Composite Pattern: বাক্সের ভেতরে বাক্স — একটা জিনিস আর অনেক জিনিসকে একই চোখে দেখো
কুরিয়ারের পার্সেলে বাক্সের ভেতরে বাক্সের উদাহরণ দিয়ে Composite pattern শেখো। একটা আইটেম আর পুরো গ্রুপকে একই interface দিয়ে ট্রিট করো, আর সহজ recursion দিয়ে মোট হিসাব বের করো।
Factory Method Pattern: Branch নিজেই ঠিক করুক কোন Vehicle
Factory Method design pattern শেখো রাহিমের টিফিন সার্ভিসের গল্পের মাধ্যমে — সহজ TypeScript আর C# code, diagram, real-world উদাহরণ, আর practice সহ।