পেজিনেশন, ফিল্টারিং ও সর্টিং
এই পাঠে যা শিখবেন
- পেজিনেশনের গাণিতিক নিয়ম —
total_pagesও শেষ পেজের আইটেম সংখ্যা হিসাব করা - একটি বাস্তব
paginate(items, page, page_size)ফাংশন লেখা ও যাচাই করা **kwargsদিয়ে সাধারণfilter_items()এবং key-ভিত্তিকsort_items()লেখা- তিনটি ফাংশন চেইন করে "ফিল্টার করো → সর্ট করো → পেজিনেট করো" — বাস্তব API-র মতো পাইপলাইন তৈরি করা
১ · পেজিনেশনের গণিত
ধরা যাক একটি তালিকায় total_itemsটি রেকর্ড আছে, এবং প্রতি পেজে page_sizeটি করে
দেখানো হবে। মোট পেজ সংখ্যা:
$$total\_pages = \lceil total\_items / page\_size \rceil$$
এখানে ceiling (ছাদ ফাংশন) জরুরি — কারণ শেষ পেজে প্রায়ই পূর্ণ page_size এর চেয়ে কম আইটেম থাকে।
যেমন ২৩টি আইটেম, page_size = 5 হলে: $23 / 5 = 4.6$, আর ceiling নিলে $\lceil 4.6 \rceil = 5$ —
অর্থাৎ ৫টি পেজ লাগবে, যার মধ্যে প্রথম ৪টি পেজে ৫টি করে (মোট ২০টি) এবং শেষ, ৫ম পেজে বাকি $23 - 20 = 3$টি আইটেম।
23 // 5 = 4) ব্যবহার করা হতো, তাহলে শেষ ৩টি আইটেম কোনো পেজেই
দেখানো হতো না — এটি পেজিনেশনের একটি সাধারণ, কিন্তু গুরুত্বপূর্ণ বাগ।
২ · paginate() — বাস্তব যাচাই
নিচের কোড সেলে ২৩টি "আর্টিকেল" রেকর্ডের একটি লিস্ট তৈরি করা হচ্ছে, তারপর paginate() ফাংশন
page_size=5 দিয়ে প্রতিটি পেজে চালিয়ে প্রকৃত আইটেম সংখ্যা গণনা করে দেখানো হচ্ছে — উপরের গণিত
সত্যিই মিলছে কি না।
import math
def paginate(items, page, page_size):
total_items = len(items)
total_pages = math.ceil(total_items / page_size) if page_size > 0 else 0
start = (page - 1) * page_size
end = start + page_size
return {
"items": items[start:end],
"page": page,
"page_size": page_size,
"total_items": total_items,
"total_pages": total_pages,
}
# ২৩টি আর্টিকেল রেকর্ড তৈরি করা (id 1..23)
articles = []
for i in range(1, 24):
if i % 3 == 0:
category = "python"
elif i % 3 == 1:
category = "javascript"
else:
category = "web"
articles.append({
"id": i,
"title": f"আর্টিকেল {i}",
"category": category,
"views": i * 10,
"published": i % 4 != 0,
})
print(f"মোট আর্টিকেল: {len(articles)}")
first = paginate(articles, 1, 5)
total_pages = first["total_pages"]
print(f"page_size=5 -> total_pages হিসাব করা হলো = {total_pages} (প্রত্যাশিত: ceil(23/5) = 5)\n")
grand_total = 0
for page in range(1, total_pages + 1):
result = paginate(articles, page, 5)
count = len(result["items"])
grand_total += count
ids = [a["id"] for a in result["items"]]
print(f"page {page}: {count}টি আইটেম -> id {ids}")
print(f"\nযাচাই ১ -- সব পেজের আইটেম যোগফল ({grand_total}) == মোট আইটেম ({len(articles)}): {grand_total == len(articles)}")
last_page = paginate(articles, total_pages, 5)
print(f"যাচাই ২ -- শেষ (৫ম) পেজে ঠিক ৩টি আইটেম আছে: {len(last_page['items']) == 3} (আসলে {len(last_page['items'])}টি)")
math.ceil() দিয়ে
total_pages হিসাব করার বাস্তব প্রমাণ।
৩ · ফিল্টারিং ও সর্টিং চেইন করা
বাস্তব API-তে ক্লায়েন্ট প্রায়ই একসাথে ফিল্টার, সর্ট আর পেজিনেশন চায় — যেমন "শুধু python
ক্যাটাগরির আর্টিকেল, সবচেয়ে বেশি viewed আগে, প্রথম পেজ, পেজপ্রতি ৩টি"। নিচের কোড সেলে
filter_items() ও sort_items() লিখে আগের paginate()-এর সাথে চেইন করা
হচ্ছে — সঠিক ক্রম: ফিল্টার → সর্ট → পেজিনেট।
def filter_items(items, **criteria):
return [
item for item in items
if all(item.get(k) == v for k, v in criteria.items())
]
def sort_items(items, key, reverse=False):
return sorted(items, key=lambda item: item[key], reverse=reverse)
# ধাপ ১ -- শুধু category="python" আর্টিকেল ফিল্টার করা
python_articles = filter_items(articles, category="python")
print(f"ফিল্টার category=python -> {len(python_articles)}টি আইটেম (id: {[a['id'] for a in python_articles]})")
# ধাপ ২ -- views অনুযায়ী descending (বেশি viewed আগে) সর্ট করা
sorted_python = sort_items(python_articles, key="views", reverse=True)
print("views অনুযায়ী descending sort:", [(a["id"], a["views"]) for a in sorted_python])
# ধাপ ৩ -- সর্ট করা রেজাল্ট পেজিনেট করা (page 1, page_size 3)
page1 = paginate(sorted_python, page=1, page_size=3)
print(f"\npaginate(page=1, page_size=3) -> total_items={page1['total_items']}, total_pages={page1['total_pages']}")
print("page 1-এর আইটেম:", [(a["id"], a["views"]) for a in page1["items"]])
# দুটো criteria একসাথে ফিল্টার করা -- category=python এবং published=True
published_python = filter_items(articles, category="python", published=True)
print(f"\nফিল্টার category=python AND published=True -> {len(published_python)}টি আইটেম "
f"(id: {[a['id'] for a in published_python]})")
print("(id 12 বাদ পড়েছে, কারণ 12 % 4 == 0 -> published=False)")
python_articles-এ ৭টি আইটেম আসে (id 3, 6, 9, 12, 15, 18, 21 — যাদের id % 3 == 0)।
views দিয়ে descending সর্ট করলে ক্রম হয় 21, 18, 15, 12, 9, 6, 3 (যেহেতু views = id * 10, id
বেশি মানেই views বেশি)। এর প্রথম পেজ (page_size=3) নেয় সবচেয়ে বেশি viewed তিনটি — id 21, 18, 15। দ্বিতীয়
ফিল্টারে published=True যোগ করায় id 12 বাদ পড়ে যায় (কারণ $12 \bmod 4 = 0$), ফলে ৭টি থেকে
কমে ৬টি আইটেম থাকে।
পেজিনেশন গণিতের মূল সূত্র ceil(total_items / page_size) — এবং শেষ পেজে remainder যত আইটেম
থাকবে ঠিক তত আইটেমই দেখানো উচিত, বেশি বা কম নয়। ফিল্টার ও সর্ট সবসময় পেজিনেশনের আগে প্রয়োগ করতে হবে, নইলে
"প্রথম পেজ"-এর মানে বদলে যাবে (আনফিল্টার্ড ডেটার প্রথম পেজ ফিল্টার্ড ডেটার প্রথম পেজের মতো নয়)।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
যদি প্রথমে paginate() করে তারপর সেই পেজের ফলাফলের উপর filter_items() চালানো হতো, তাহলে কী সমস্যা হতো?
তাহলে ভুল ফলাফল আসত — paginate() প্রথমে যদি ২৩টি (অফিল্টার্ড) আর্টিকেল থেকে প্রথম ৫টি নিয়ে
নেয়, তারপর সেই ৫টির মধ্যে category=python ফিল্টার করলে হয়তো ১-২টি বা শূন্যটি মিলবে — অথচ
পুরো তালিকায় আসলে ৭টি python আর্টিকেল আছে। ফিল্টার সবসময় পুরো ডেটাসেটের উপর প্রয়োগ করতে হবে, তারপর
ফিল্টার হওয়া ফলাফলকে পেজিনেট করতে হবে — নাহলে "মোট কতটি মিলেছে" এবং "কোন পেজে কী আছে" দুটোই ভুল হয়ে
যাবে।
প্র ০২
page_size যদি 0 হয়, তাহলে উপরের paginate() ফাংশনে কী ঘটবে?
কোডে page_size > 0 চেক থাকায় total_pages সরাসরি 0 হয়ে যাবে
(ZeroDivisionError এড়ানো হয়েছে)। এরপর start = (page - 1) * 0 = 0 এবং
end = 0 + 0 = 0, ফলে items[0:0] একটি খালি লিস্ট দেবে। বাস্তব API-তে এই অবস্থায়
সাধারণত ক্লায়েন্টকে 400 Bad Request দিয়ে জানানো হয় যে page_size অবশ্যই
ধনাত্মক সংখ্যা হতে হবে — শুধু চুপচাপ খালি ফলাফল দেওয়া বিভ্রান্তিকর।
প্র ০৩
উপরের কোডে total_pages = 5 কেন এলো, 4 নয় (যেহেতু $23 // 5 = 4$)?
কারণ 23 // 5 = 4 (floor division) হিসাব করলে শেষের ৩টি আইটেম (id ২১, ২২, ২৩) কোনো পেজেই
জায়গা পেত না — তারা হারিয়ে যেত। math.ceil(23 / 5) ব্যবহার করায় $4.6$ রাউন্ড আপ হয়ে
$5$ হয়, যাতে একটি অতিরিক্ত (৫ম) পেজ তৈরি হয় যেখানে বাকি থাকা ৩টি আইটেম রাখা যায় — এটাই নিশ্চিত করে
কোনো আইটেম বাদ না পড়ে।
অনুশীলন
-
চিন্তা করুন: ৩০টি আইটেম,
page_size=10হলে কয়টি পেজ হবে এবং শেষ পেজে কয়টি আইটেম থাকবে?$30 / 10 = 3.0$, আর $\lceil 3.0 \rceil = 3$ — অর্থাৎ ঠিক ৩টি পেজ, প্রতিটিতে পূর্ণ ১০টি করে আইটেম (৩০ সংখ্যাটি ১০ দিয়ে নিঃশেষে বিভাজ্য বলে কোনো "অসম্পূর্ণ" শেষ পেজ থাকবে না — এটি এই লেসনের ২৩-আইটেম উদাহরণের চেয়ে ভিন্ন, যেখানে remainder ছিল বলে ৫ম পেজ অসম্পূর্ণ ছিল)।
-
পরীক্ষা করুন: উপরের দ্বিতীয় কোড সেলে
sort_items(python_articles, key="views", reverse=True)-এর বদলেreverse=Falseদিয়ে চালিয়ে দেখুনpage1-এর আইটেমগুলো কীভাবে বদলে যায়।reverse=Falseদিলে ascending (কম views আগে) ক্রম হবে: id 3, 6, 9, 12, 15, 18, 21 — অর্থাৎ views অনুযায়ী উল্টো ক্রম। তখনpage1 = paginate(sorted_python, 1, 3)প্রথম তিনটি হিসেবে id 3, 6, 9 (সবচেয়ে কম viewed) ফেরত দেবে, আগের মতো সবচেয়ে বেশি viewed (21, 18, 15) নয় — এটি দেখায় সর্টিং সবসময় পেজিনেশনের আগে চালানো হলে "প্রথম পেজে কী থাকবে" তা সম্পূর্ণ সর্ট-অর্ডারের উপর নির্ভর করে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ আর্কিটেকচার প্যাটার্ন, ফ্রন্ট-এন্ড/ব্যাক-এন্ড ফ্রেমওয়ার্ক ফান্ডামেন্টাল, স্টেট ম্যানেজমেন্ট, REST API, ORM, অথেন্টিকেশন, রেন্ডারিং স্ট্র্যাটেজি ও ডিপ্লয়মেন্ট — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
-
পূর্ববর্তী পাঠ: HTTP ভার্ব ও স্ট্যাটাস কোড L24
এই পাঠের
articlesলিস্টটি L24-এর CRUD ডেটাবেসের মতোই ইন-মেমরি রেকর্ড হিসেবে মডেল করা হয়েছে। - সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics ও Full-Stack Web Frameworks — সব এক জায়গায়।