পাঠ ২৫ · ৫৮-এর মধ্যে · মডিউল ৬
Home / Courses / Full-Stack Web Frameworks / পেজিনেশন ও সর্টিং

পেজিনেশন, ফিল্টারিং ও সর্টিং

Pagination, filtering & sorting
৯ মিনিট পড়া মধ্যবর্তী · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • পেজিনেশনের গাণিতিক নিয়ম — 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$টি আইটেম।

যদি ceiling না নিয়ে সাধারণ ভাগ (23 // 5 = 4) ব্যবহার করা হতো, তাহলে শেষ ৩টি আইটেম কোনো পেজেই দেখানো হতো না — এটি পেজিনেশনের একটি সাধারণ, কিন্তু গুরুত্বপূর্ণ বাগ।

২ · paginate() — বাস্তব যাচাই

নিচের কোড সেলে ২৩টি "আর্টিকেল" রেকর্ডের একটি লিস্ট তৈরি করা হচ্ছে, তারপর paginate() ফাংশন page_size=5 দিয়ে প্রতিটি পেজে চালিয়ে প্রকৃত আইটেম সংখ্যা গণনা করে দেখানো হচ্ছে — উপরের গণিত সত্যিই মিলছে কি না।

Python
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'])}টি)")

    
আউটপুট নিশ্চিত করে: page 1-4 প্রতিটিতে ৫টি করে আইটেম (মোট ২০টি), আর page 5-এ বাকি থাকা ঠিক ৩টি আইটেম (id ২১, ২২, ২৩) — মোট $20 + 3 = 23$, যা মূল তালিকার দৈর্ঘ্যের সমান। এটাই math.ceil() দিয়ে total_pages হিসাব করার বাস্তব প্রমাণ।

৩ · ফিল্টারিং ও সর্টিং চেইন করা

বাস্তব API-তে ক্লায়েন্ট প্রায়ই একসাথে ফিল্টার, সর্ট আর পেজিনেশন চায় — যেমন "শুধু python ক্যাটাগরির আর্টিকেল, সবচেয়ে বেশি viewed আগে, প্রথম পেজ, পেজপ্রতি ৩টি"। নিচের কোড সেলে filter_items() ও sort_items() লিখে আগের paginate()-এর সাথে চেইন করা হচ্ছে — সঠিক ক্রম: ফিল্টার → সর্ট → পেজিনেট।

Python
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$), ফলে ৭টি থেকে কমে ৬টি আইটেম থাকে।
মূল কথা · Key takeaway

পেজিনেশন গণিতের মূল সূত্র 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$ হয়, যাতে একটি অতিরিক্ত (৫ম) পেজ তৈরি হয় যেখানে বাকি থাকা ৩টি আইটেম রাখা যায় — এটাই নিশ্চিত করে কোনো আইটেম বাদ না পড়ে।

অনুশীলন

  1. চিন্তা করুন: ৩০টি আইটেম, page_size=10 হলে কয়টি পেজ হবে এবং শেষ পেজে কয়টি আইটেম থাকবে?

    $30 / 10 = 3.0$, আর $\lceil 3.0 \rceil = 3$ — অর্থাৎ ঠিক ৩টি পেজ, প্রতিটিতে পূর্ণ ১০টি করে আইটেম (৩০ সংখ্যাটি ১০ দিয়ে নিঃশেষে বিভাজ্য বলে কোনো "অসম্পূর্ণ" শেষ পেজ থাকবে না — এটি এই লেসনের ২৩-আইটেম উদাহরণের চেয়ে ভিন্ন, যেখানে remainder ছিল বলে ৫ম পেজ অসম্পূর্ণ ছিল)।

  2. পরীক্ষা করুন: উপরের দ্বিতীয় কোড সেলে 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 — সব এক জায়গায়।
আগের পাঠ
HTTP ভার্ব ও স্ট্যাটাস কোড সঠিকভাবে ব্যবহার