সরাসরি পর্বে যান
পর্ব ৩৮ · ৫৫-এর মধ্যে · মডিউল ৬

lambda, functools আর itertools — ফাংশন দিয়ে ডেটার পাইপলাইন

lambda, functools & itertools — describing data transformations with functions

Taught in Bangla, this lesson treats functions as values: lambda as a sort key, map and filter versus comprehensions, reduce, partial and operator.itemgetter, a tour of itertools including accumulate, groupby and its sorting trap, combinations, product, pairwise and batched, and where functional style should stop.

এই পর্বে যা শিখবেন

  • lambda-র syntax, তার সীমা, আর একাধিক key দিয়ে সাজানোর কৌশল
  • কখন map/filter/reduce আর কখন comprehension বা sum — পড়ার যোগ্যতা দিয়ে সিদ্ধান্ত
  • functools.partial, cmp_to_key আর operator module-এর ব্যবহার
  • itertools-এর প্রধান টুলগুলো, বিশেষ করে groupby-এর sorting ফাঁদ আর combinatorics
  • pure function-এর ধাঁচে ভাবা, কোথায় থামতে হবে, আর AI-এর লেখা ফাংশনাল কোড যাচাইয়ের চেকলিস্ট

১ · ফাংশনও একটি মান — হাতে হাতে পাঠানো যায়

পর্ব ১৩-এ আপনি ফাংশন লিখতে শিখেছেন, আর পর্ব ২০ ও ৩৫-এ দেখেছেন ফাংশন অন্য ফাংশন ফেরত দিতে পারে। এই পর্বের সবকিছু একটি সরল সত্যের ওপর দাঁড়িয়ে: Python-এ ফাংশন একটি সাধারণ object, ঠিক একটি সংখ্যা বা string-এর মতো। def লেখা মানে একটি function object তৈরি করে একটি নামের সাথে জুড়ে দেওয়া। তাই ফাংশনকে আরেকটি নামে রাখা যায়, list-এ রাখা যায়, আর argument হিসেবে অন্য ফাংশনে পাঠানো যায়। এই গুণের নাম first-class function.

যে ফাংশন অন্য ফাংশনকে argument হিসেবে নেয় (বা ফেরত দেয়), তাকে বলে higher-order function. আপনি এদের আগেই ব্যবহার করেছেন: sorted(names, key=len)-এ len ফাংশনটিকে ডাকা হয়নি, শুধু পাঠানো হয়েছে — বন্ধনী নেই, লক্ষ করুন। sorted নিজে প্রতিটি নামের ওপর সেটা ডাকে।

একটি গার্মেন্টস কারখানার কাটিং সেকশনের কথা ভাবুন। সুপারভাইজার প্রতিটি কাপড় নিজে মাপেন না। তিনি শ্রমিকের হাতে একটি মাপের নির্দেশ দেন — “কলারের মাপ দেখে সাজাও” বা “রং দেখে সাজাও”. সাজানোর কাজটা একই, শুধু নির্দেশ বদলায়। sorted হলো সেই শ্রমিক, আর key=-এ পাঠানো ফাংশন হলো নির্দেশ। নির্দেশটা যদি খুব ছোট হয় আর একবারই লাগে, আলাদা কাগজে নাম লিখে রাখার দরকার নেই — মুখে বলে দিলেই চলে। সেই “মুখে বলা” নির্দেশটাই lambda.

এই পর্বের পুরো ধারণা এক বাক্যে: লুপের ভেতরে “কীভাবে” করতে হবে তা ধাপে ধাপে না লিখে, ডেটার ওপর “কী” রূপান্তর হবে তা ছোট ছোট ফাংশন দিয়ে বর্ণনা করা। এই ধাঁচকে বলে functional programming. এর উল্টো দিক, যেখানে আপনি ভ্যারিয়েবল বদলে বদলে ধাপে ধাপে নির্দেশ দেন, তাকে বলে imperative programming. Python দুই ধাঁচই সমর্থন করে, আর দুটির মিশ্রণই এখানে স্বাভাবিক।

মূল ধারণা

Python-এ comprehension (পর্ব ১৮) ইতিমধ্যে অনেক ফাংশনাল কাজ সুন্দরভাবে করে। তাই এই পর্বের টুলগুলো “সবসময় ব্যবহার করুন” ধরনের নয়। নিয়মটা হলো: যেখানে এরা লেখাকে সত্যিই পরিষ্কার করে, শুধু সেখানে। একটি key ফাংশন, একটি চলতি যোগফল, একটি জোড়া তৈরি — এগুলো itertools-এ এক লাইনে পরিষ্কার হয়। কিন্তু তিন স্তরের map(lambda ..., filter(lambda ..., ...)) কেউ পড়তে পারে না।

২ · lambda — এক expression-এর নামহীন ফাংশন

lambda দিয়ে একটি ছোট anonymous function বানানো যায়, অর্থাৎ নাম ছাড়া ফাংশন। গঠন:

lambda parameter : একটি expression
lambda p: p.price  ≈  def _(p): return p.price

কোলনের বাঁয়ে parameter, ডানে একটিমাত্র expression, যার মান স্বয়ংক্রিয়ভাবে ফেরত যায় — return লিখতে হয় না, লেখা যায়ও না। কয়েকটি উদাহরণ:

square = lambda x: x * x          # চলে, কিন্তু PEP 8 এটা নিষেধ করে (নিচে দেখুন)
print((lambda a, b: a + b)(3, 4))  # 7 — তৈরি করেই সাথে সাথে ডাকা
print((lambda: "হ্যালো")())         # parameter ছাড়া lambda
grade = lambda m: "পাস" if m >= 33 else "ফেল"   # conditional expression চলে

lambda-র সীমা — কেন শুধু একটি expression

lambda-র body-তে কোনো statement থাকতে পারে না। তাই সেখানে if/else ব্লক, for লুপ, try, return, সাধারণ assignment (x = 5) বা একাধিক লাইন চলে না। শর্ত লাগলে a if cond else b ধরনের conditional expression ব্যবহার করতে হয়। এই সীমা ইচ্ছাকৃত: lambda বড় হতে শুরু করলে Python চায় আপনি def লিখে তাকে একটি নাম আর docstring দিন।

আরও একটি ছোট সমস্যা: lambda-র নাম সবসময় <lambda>. ভেতরে error হলে traceback-এ লেখা থাকে File "...", line 4, in <lambda> — কোন lambda, তা বোঝার উপায় নেই। দশটি lambda থাকলে ডিবাগ করা কঠিন।

সবচেয়ে ভালো ব্যবহার — key= দিয়ে সাজানো

পর্ব ১৪-এ দেখেছেন sorted একটি key function নেয়; min আর max-ও একই রকম key নেয়। ধরুন একটি দোকানের পণ্যের তালিকা দামের ক্রমে সাজাতে হবে, আর সমান দামের পণ্যগুলো নামের ক্রমে। কৌশল হলো key থেকে একটি tuple ফেরত দেওয়া: (p.price, p.name). Python tuple তুলনা করে বাঁ দিক থেকে, ঘরে ঘরে — অভিধানের মতো (lexicographic order). প্রথম ঘর সমান হলে তবেই দ্বিতীয় ঘর দেখা হয়। ধাপে ধাপে দেখুন, ভেতরে আসলে কী হয়:

1shop = [Product("tea", 20), Product("coffee", 60),2        Product("biscuit", 20), Product("cake", 60)]3ordered = sorted(shop,4    key=lambda p: (p.price, p.name))5print([p.name for p in ordered])
sorted-এর ভেতরের হিসাব
হিসাব হওয়া key
এখন কী তুলনা
ক্রম
  1. প্রথম পণ্যের key. sorted lambda-কে প্রথম পণ্য দিয়ে ডাকল। lambda ফেরত দিল একটি tuple (20, 'tea'). lambda নিজে কিছু সাজায় না, শুধু বলে “এই পণ্যকে এই মান দিয়ে বিচার করো”.
  2. প্রতিটি পণ্যের key — ঠিক একবার। বাকি তিনটির জন্যও lambda একবার করে চলল। Python নিশ্চয়তা দেয় যে প্রতিটি জিনিসের key একবারই হিসাব হয়, তুলনা যতবারই হোক। তাই key-তে ভারী কাজ (যেমন একটি string থেকে তারিখ পার্স করা) থাকলেও খরচ শুধু n বার।
  3. প্রথম ঘরে তুলনা। এবার শুধু key-গুলো তুলনা হয়। tuple-এর প্রথম ঘর দাম: 20 আর 60. তাই সব 20-এর পণ্য সব 60-এর পণ্যের আগে। প্রথম ঘর আলাদা হলে দ্বিতীয় ঘর দেখাই হয় না।
  4. সমান হলে দ্বিতীয় ঘর। 20-এর দলে দুটি পণ্য, তাদের প্রথম ঘর সমান। তখন দ্বিতীয় ঘর — নাম: 'biscuit' আগে, কারণ b আসে t-এর আগে। একইভাবে 60-এর দলে 'cake' আগে 'coffee'-এর (a আসে o-এর আগে)।
  5. মূল object ফেরত। sorted key-গুলো ফেলে দিয়ে মূল Product object-গুলো নতুন ক্রমে একটি নতুন list-এ দিল। shop নিজে বদলায়নি। লক্ষ করুন, দাম বেশি থেকে কম কিন্তু নাম a-z চাইলে key হতো (-p.price, p.name) — সংখ্যার চিহ্ন উল্টে দিলেই ক্রম উল্টে যায়।

দুটি কাজের কথা। এক, min আর max-ও একই key নেয়: max(shop, key=lambda p: p.price) সবচেয়ে দামি পণ্য দেয় (সমান হলে প্রথমটি)। দুই, Python-এর sort stable — সমান key-র জিনিস আগের ক্রম বজায় রাখে। তাই tuple-এর বদলে দুবার sort করেও কাজ হয়: আগে নাম দিয়ে, তারপর দাম দিয়ে। যখন একটি key ঊর্ধ্বক্রমে আর অন্যটি নিম্নক্রমে, অথচ সেটা string (যার চিহ্ন উল্টানো যায় না), তখন এই দুই-ধাপের কৌশলই সহজ।

lambda না def — PEP 8 কী বলে

উপরের কোডের প্রথম লাইন square = lambda x: x * x চলে ঠিকই, কিন্তু Python-এর style guide PEP 8 স্পষ্ট বলে: lambda-কে সরাসরি একটি নামে বসাবেন না, def লিখুন। কারণ সহজ — নাম দিতেই যদি চান, তাহলে def square(x): return x * x লিখলে ফাংশনের আসল নাম হয় square (traceback-এ সেটাই দেখাবে), docstring আর type hint যোগ করা যায়, আর একই লাইন-সংখ্যাতেই হয়। lambda-র একমাত্র সুবিধা হলো নামহীনতা; নাম দিলে সেই সুবিধাটাই চলে যায়।

পরিস্থিতিভালো পছন্দ
একবারের key, এক লাইনের হিসাবkey=lambda p: (p.price, p.name)
একই হিসাব দুই বা তার বেশি জায়গায়def দিয়ে নাম দিন
শর্ত, লুপ, try বা একাধিক ধাপdef — lambda-তে সম্ভবই নয়
শুধু একটি ঘর বা attribute বের করাoperator.itemgetter / attrgetter (অংশ ৪)
আগে থেকেই একটি ফাংশন আছেসরাসরি পাঠান: key=len, key=str.lower — lambda s: len(s) নয়

অকারণ মোড়ক: map(lambda x: int(x), items) লিখবেন না — int নিজেই একটি ফাংশন, তাই map(int, items). lambda যদি শুধু আরেকটি ফাংশনকে একই argument দিয়ে ডাকে, সেই ফাংশনটিই সরাসরি পাঠান।

৩ · map আর filter বনাম comprehension

map(f, items) প্রতিটি জিনিসের ওপর f চালায়। filter(pred, items) শুধু সেই জিনিসগুলো রাখে, যাদের ওপর pred সত্য দেয়। Python 3-এ দুটিই list নয়, একটি iterator ফেরত দেয় — অলস, আর একবারই হাঁটা যায় (পর্ব ৩৪-এর exhaustion ফাঁদ এখানেও)। ফল দেখতে list(...) দিয়ে মোড়াতে হয়।

prices = ["120", "45", "300", "80"]
nums = list(map(int, prices))                  # [120, 45, 300, 80]
cheap = list(filter(lambda p: p < 100, nums))  # [45, 80]

# একই কাজ comprehension দিয়ে:
nums = [int(p) for p in prices]
cheap = [p for p in nums if p < 100]

# map একাধিক iterable নিতে পারে — zip-এর মতো জোড়ায় জোড়ায়
print(list(map(pow, [2, 3, 10], [3, 2, 2])))   # [8, 9, 100]

কোনটি লিখবেন? পর্ব ১৮-এর ভাবনার প্রশ্নে Guido van Rossum-এর যুক্তি দেখেছেন। বাস্তব নির্দেশনা একটি টেবিলে:

কাজবেশি পরিষ্কারকারণ
আগে থেকে থাকা ফাংশন প্রয়োগmap(str.strip, lines)ছোট, কোনো নতুন নাম লাগে না
নতুন হিসাব লাগবে (lambda)[p * 1.15 for p in nums]lambda-র বাড়তি শব্দ নেই, সরাসরি পড়া যায়
ছাঁকা আর রূপান্তর একসাথে[p * 2 for p in nums if p > 0]map(filter(...)) উল্টো দিক থেকে পড়তে হয়
falsy মান (0, "", None) বাদ দেওয়াfilter(None, items)Python-এর একটি পরিচিত idiom
শুধু যোগফল বা গণনাsum(p for p in nums if p < 100)মাঝখানে list বানানোর দরকার নেই

গতির দিক থেকে পার্থক্য সাধারণত ছোট। map একটি built-in ফাংশনের (যেমন int) সাথে সামান্য দ্রুত হতে পারে, কারণ প্রতিটি জিনিসের জন্য Python-স্তরের কোনো ফাংশন ডাকতে হয় না। কিন্তু lambda থাকলে প্রতিবার একটি Python ফাংশন ডাকা হয়, আর তখন comprehension প্রায়ই সমান বা দ্রুত। গতি নিয়ে ভাবার আগে মাপুন — পর্ব ৪৩-এ এর পদ্ধতি দেখবেন।

অলসতার সুবিধা: map আর filter generator expression-এর মতোই lazy. তাই sum(map(int, open("amounts.txt"))) বিশাল ফাইলেও প্রায় স্থির মেমরিতে চলে — একসাথে একটিই লাইন। পর্ব ৩৪-এর pipeline-এ এরা স্বাভাবিকভাবে বসে যায়।

৪ · functools আর operator — reduce, partial আর তৈরি key

standard library-র functools module হলো “ফাংশন নিয়ে কাজ করার ফাংশন”-এর সংগ্রহ। পর্ব ৩৫-এ এর দুটি সদস্য দেখেছেন: wraps আর lru_cache/cache — আগের ফল মনে রেখে একই ডাক বারবার না চালানো। এখানে আরও তিনটি।

reduce — বাঁ থেকে ডানে ভাঁজ করা

reduce(f, items, initial) একটি দুই-argument ফাংশন নিয়ে পুরো ধারাকে একটি মানে নামিয়ে আনে: প্রথমে f(initial, প্রথম), তারপর সেই ফল আর দ্বিতীয় জিনিস দিয়ে আবার f, এভাবে শেষ পর্যন্ত। একে বলে fold — একটি লম্বা কাপড় বাঁ দিক থেকে ভাঁজ করতে করতে একটি ছোট বান্ডিল বানানোর মতো। একটি উদাহরণ, যেখানে ক্রমটা সত্যিই গুরুত্বপূর্ণ — অঙ্কের list থেকে একটি সংখ্যা বানানো:

1from functools import reduce2year = reduce(lambda acc, d: acc * 10 + d,3              [2, 0, 2, 6], 0)4print(year)
প্রতিটি ধাপে lambda কী পেল
acc (এতক্ষণের ফল)
d (পরের অঙ্ক)
lambda ফেরত দিল
  1. শুরুর মান। তৃতীয় argument 0 হলো initial — accumulator-এর প্রথম মান। খালি list এলে reduce এটাই ফেরত দেবে। initial না দিলে list-এর প্রথম জিনিসই শুরুর মান হয়, আর খালি list-এ TypeError: reduce() of empty iterable with no initial value.
  2. প্রথম ভাঁজ। lambda পেল acc = 0 আর d = 2. ফল 2 — এটাই নতুন acc.
  3. দ্বিতীয় ভাঁজ। আগের ফল 2 আর পরের অঙ্ক 0: 2 * 10 + 0 = 20. প্রতিটি ধাপ শুধু “এতক্ষণের ফল” আর “পরের জিনিস” দেখে, পুরো list নয়।
  4. তৃতীয় ভাঁজ। 20 * 10 + 2 = 202. এখানে বোঝা যায় কেন ক্রম গুরুত্বপূর্ণ: ডান থেকে ভাঁজ করলে 6202 হতো। Python-এর reduce সবসময় বাঁ থেকে ডানে।
  5. শেষ ভাঁজ। 202 * 10 + 6 = 2026. আর কোনো জিনিস নেই, তাই reduce থামল।
  6. একটি মান ফেরত। চার জিনিসের list একটি সংখ্যায় নেমে এল। তবে লক্ষ করুন, এই কোড বুঝতে আপনাকে প্রতিটি ধাপ মাথায় চালাতে হয়েছে। একই কাজ int("".join(map(str, digits))) দিয়েও হয় — আর প্রায় সবাই সেটা এক নজরে বোঝেন।

শেষ ধাপের কথাটাই reduce-এর মূল সমস্যা। Python-এ প্রায় প্রতিটি সাধারণ reduce-এর জন্য একটি নামওয়ালা, স্পষ্ট ফাংশন আছে:

reduce দিয়েএর বদলে লিখুন
reduce(lambda a, b: a + b, xs, 0)sum(xs)
reduce(lambda a, b: a if a > b else b, xs)max(xs)
reduce(lambda a, b: a or b, flags, False)any(flags) (আর all)
reduce(lambda a, b: a * b, xs, 1)math.prod(xs) (Python 3.8 থেকে)
reduce(lambda a, b: a + b, list_of_lists, [])list(chain.from_iterable(...)) (অংশ ৫) — reduce-রূপটি ধীরও

এই বদলগুলো শুধু পড়তে সহজ নয়, প্রায়ই নিরাপদও: any প্রথম সত্য মান পেলেই থেমে যায় (short-circuit), reduce পুরো ধারা হাঁটে। reduce তখনই রাখুন, যখন ভাঁজের নিয়মটা সত্যিই নিজস্ব আর কোনো তৈরি ফাংশনে মেলে না — যেমন একসারি dict-কে একটির পর একটি merge করা: reduce(operator.or_, configs, {}). এমন ক্ষেত্রেও একটি সাধারণ for লুপ প্রায়ই সমান পরিষ্কার।

partial — কিছু argument আগেই বসিয়ে নতুন ফাংশন

ধরুন add_vat(price, rate) নামে একটি ফাংশন আছে, আর আপনার দোকানে হার সবসময় ১৫%. প্রতিবার rate=0.15 লেখা ক্লান্তিকর আর ভুলপ্রবণ। functools.partial একটি নতুন callable বানায়, যার কিছু argument আগে থেকেই বসানো:

from functools import partial
vat15 = partial(add_vat, rate=0.15)   # rate আটকানো
vat15(200)                            # = add_vat(200, rate=0.15)
to_int_bin = partial(int, base=2)
to_int_bin("1010")                    # 10

এটা পর্ব ২০-এর factory function (make_multiplier)-এর মতো একই কাজ করে, কিন্তু নতুন ফাংশন লিখতে হয় না। partial object-এর .func, .args, .keywords দেখে বোঝা যায় কী বসানো আছে — lambda-তে সেটা দেখা যায় না। আর লুপে callback বানানোর সময় partial সেই মুহূর্তের মান ধরে রাখে, তাই পর্ব ২০-এর late binding ফাঁদে পড়ে না (অংশ ৭)।

operator module — lambda ছাড়াই key

অনেক lambda শুধু একটি ঘর বা attribute বের করে, বা দুটি মান গুণ বা যোগ করে। এসবের জন্য operator module-এ তৈরি ফাংশন আছে, যা C-তে লেখা, তাই সাধারণত একটু দ্রুত, আর নাম দেখেই অর্থ বোঝা যায়:

  • itemgetter(1) ≈ lambda row: row[1]; itemgetter(2, 0) একসাথে দুটি ঘরের tuple দেয় — একাধিক key-র সাজানোয় কাজের।
  • attrgetter("price") ≈ lambda p: p.price; attrgetter("price", "name") tuple দেয়।
  • operator.mul, add, or_ ইত্যাদি ≈ lambda a, b: a * b ইত্যাদি — reduce বা map-এ পাঠানোর জন্য।

cmp_to_key — পুরোনো ধাঁচের তুলনা ফাংশন

Python 2-তে sorted-এ একটি cmp ফাংশন দেওয়া যেত, যা দুটি জিনিস নিয়ে ঋণাত্মক, শূন্য বা ধনাত্মক ফেরত দিত (C-র qsort-এর মতো)। Python 3 এটা সরিয়ে শুধু key রেখেছে, কারণ key প্রতি জিনিসে একবার চলে, আর cmp চলত প্রতিটি তুলনায়। তবু কিছু নিয়ম স্বভাবতই “দুটিকে মিলিয়ে দেখো” ধাঁচের — যেমন সংখ্যাগুলো এমনভাবে সাজানো যাতে জোড়া লাগালে সবচেয়ে বড় সংখ্যা হয় ("9" আর "34": "934" > "349"). তখন cmp_to_key একটি পুরোনো ধাঁচের তুলনা ফাংশনকে key-তে রূপ দেয়: sorted(xs, key=cmp_to_key(compare)). কোডিং ইন্টারভিউতে এই সমস্যাটি বেশ পরিচিত।

এবার সব একসাথে চালিয়ে দেখুন — একাধিক key-তে সাজানো, attrgetter আর itemgetter, partial দিয়ে VAT, আর reduce বনাম sum:

from dataclasses import dataclass
from functools import partial, reduce
from operator import attrgetter, itemgetter, mul

@dataclass
class Product:
    name: str
    price: int

shop = [Product("tea", 20), Product("coffee", 60),
        Product("biscuit", 20), Product("cake", 60)]
ordered = sorted(shop, key=lambda p: (p.price, p.name))
print([p.name for p in ordered])
print(max(shop, key=attrgetter("price")).name)
def add_vat(price, rate):
    return round(price * (1 + rate), 2)
vat15 = partial(add_vat, rate=0.15)
print(vat15(200), vat15(1000), list(map(vat15, [40, 60])))
marks = [("Rahim", 85), ("Karim", 92), ("Mina", 78)]
print(max(marks, key=itemgetter(1)), sorted(marks, key=itemgetter(1))[0])
print(reduce(mul, [1, 2, 3, 4, 5]), sum(p.price for p in shop))

প্রথম লাইন ['biscuit', 'tea', 'cake', 'coffee'] — দাম আগে, সমান দামে নাম, ঠিক উপরের ডায়াগ্রামের মতো। দ্বিতীয় লাইন coffee: coffee আর cake দুটিরই দাম 60, আর সমান হলে max প্রথম পাওয়াটিকে দেয় — list-এ coffee আগে। তৃতীয় লাইন 230.0 1150.0 [46.0, 69.0] — vat15 একটি সাধারণ ফাংশনের মতোই ডাকা যায়, এমনকি map-এও পাঠানো যায়। round(..., 2) না দিলে 200-এর ক্ষেত্রে 229.99999999999997 আসত (পর্ব ০৭-এর float-এর ফাঁদ)। চতুর্থ লাইন ('Karim', 92) ('Mina', 78) — সর্বোচ্চ নম্বর আর সবচেয়ে কম নম্বর, দুটোই itemgetter(1) দিয়ে, কোনো lambda ছাড়া। শেষ লাইন 120 160: reduce দিয়ে 5! (গুণফল), আর পণ্যের মোট দাম sum দিয়ে — যোগের জন্য reduce লাগে না।

৫ · itertools ভ্রমণ — লুপ ছাড়া ডেটার রূপান্তর

পর্ব ৩৪-এ itertools থেকে count আর islice দেখেছেন। পুরো module-টি হলো iterator নিয়ে কাজ করার একটি টুলবক্স। প্রায় সব টুল একটি বা একাধিক iterable নেয় আর একটি অলস iterator দেয় — তাই এদের একটির আউটপুট আরেকটির ইনপুটে জুড়ে দেওয়া যায়, ঠিক লেগো ব্লকের মতো। কিছুই আগেভাগে list হয় না, যতক্ষণ না আপনি list(), for বা sum দিয়ে টানেন।

অসীম iterator — count, cycle, repeat

তিনটি টুল কখনো নিজে থেকে থামে না। এদের বলে infinite iterator, আর এদের সাথে সবসময় একটি সীমা লাগে — islice, zip (সবচেয়ে ছোটটিতে থামে) বা break:

from itertools import count, cycle, repeat, islice
list(islice(count(1001), 3))           # [1001, 1002, 1003] — টোকেন নম্বর
list(zip(["রাফি", "নীলা", "তানিম"], cycle(["লাল", "নীল"])))
# [('রাফি', 'লাল'), ('নীলা', 'নীল'), ('তানিম', 'লাল')] — পালা করে দল ভাগ
list(repeat("—", 3))                   # ['—', '—', '—'] (সংখ্যা দিলে সসীম)

জোড়া লাগানো — chain আর zip_longest

  • chain(a, b, c) কয়েকটি iterable পরপর জুড়ে একটি ধারা বানায় — নতুন list না বানিয়ে। chain.from_iterable(list_of_lists) একটি nested list-কে এক স্তরে চ্যাপ্টা করে: [[1, 2], [3]] থেকে 1, 2, 3.
  • zip_longest(a, b, fillvalue="-") হলো zip-এর ভাই, যে লম্বাটির শেষ পর্যন্ত চলে আর ফাঁকা জায়গা fillvalue দিয়ে ভরে। দুই শাখার বিক্রির তালিকা পাশাপাশি দেখাতে, যেখানে একটির দিন কম — zip চুপচাপ শেষ দিনগুলো ফেলে দিত।

accumulate — চলতি যোগফল আর ব্যাংক ব্যালান্স

accumulate হলো reduce-এর এমন রূপ, যা শুধু শেষ ফল নয়, প্রতিটি ধাপের ফল দেয় — একে বলে running total. ব্যাংকের স্টেটমেন্টের “ব্যালান্স” কলাম ঠিক এটাই। initial= (Python 3.8 থেকে) দিয়ে শুরুর ব্যালান্স দেওয়া যায়। ধাপে ধাপে দেখুন:

1tx = [5000, -1200, -800, 3000, -500]2for bal in accumulate(tx, initial=10000):3    print(bal)
accumulate-এর ভেতরে (ডিফল্ট ফাংশন: যোগ)
এইমাত্র লেনদেন
হিসাব
accumulate দিল (bal)
  1. শুরুর ব্যালান্স আগে। initial=10000 দিলে accumulate প্রথমেই সেটা দেয়, কোনো লেনদেন যোগ করার আগে। তাই ফলের দৈর্ঘ্য লেনদেনের চেয়ে এক বেশি (৬টি)। initial না দিলে প্রথম মান হতো প্রথম লেনদেন নিজেই, 5000.
  2. প্রথম লেনদেন। accumulate মনে রাখা মান (10000) আর পরের জিনিস (5000) যোগ করে 15000 দিল, আর এই নতুন মানটিই মনে রাখল। reduce-এর মতোই, শুধু মাঝের ফলটিও বাইরে পাঠাল।
  3. খরচ মানে ঋণাত্মক সংখ্যা। -1200 যোগ করা মানে বিয়োগ। নতুন ব্যালান্স 13800. accumulate-কে আলাদা করে “জমা” আর “খরচ” বোঝাতে হয় না — চিহ্নই যথেষ্ট।
  4. প্রতিটি মান সাথে সাথে। 13000. লক্ষ করুন, accumulate অলস: পুরো list হিসাব করে রাখে না, প্রতিবার for লুপ চাইলে একটি নতুন ব্যালান্স দেয়। ১০ লাখ লেনদেনেও মেমরিতে থাকে শুধু একটি সংখ্যা।
  5. জমা। 16000 — এ পর্যন্ত সর্বোচ্চ ব্যালান্স। max(accumulate(tx, initial=10000)) লিখলে মাসের সর্বোচ্চ ব্যালান্স সরাসরি পাওয়া যায়, আর min(...) বলবে কখনো ব্যালান্স ঋণাত্মক হয়েছিল কি না।
  6. শেষ ব্যালান্স। 15500 — যা 10000 + sum(tx)-এর সমান। accumulate-এ দ্বিতীয় argument হিসেবে অন্য ফাংশনও দেওয়া যায়: accumulate(xs, max) দেয় “এ পর্যন্ত সর্বোচ্চ”, যেমন একটি ব্যাটারের ইনিংস-ধরে ক্যারিয়ারের সর্বোচ্চ রান।

আরও কয়েকটি দরকারি টুল

টুলছোট উদাহরণবাস্তব কাজ
pairwise (3.10)pairwise([100, 120, 90]) → (100, 120), (120, 90)প্রতিদিনের দামের পরিবর্তন: [b - a for a, b in pairwise(prices)]
batched (3.12)batched(range(1, 8), 3) → (1, 2, 3), (4, 5, 6), (7,)pagination, বা API-তে ১০০টি করে রেকর্ড পাঠানো
takewhiletakewhile(lambda t: t < 30, temps)শর্ত প্রথমবার মিথ্যা হওয়া পর্যন্ত নাও, তারপর থামো
dropwhiledropwhile(lambda ln: ln.startswith("#"), lines)ফাইলের শুরুর মন্তব্য-লাইনগুলো বাদ দিয়ে বাকিটা
starmapstarmap(pow, [(2, 3), (10, 2)]) → 8, 100ডেটা আগেই tuple-এ জোড়া থাকলে: f(*pair)
teea, b = tee(it, 2)একটি iterator থেকে দুটি স্বাধীন কপি; এরপর মূলটি আর ব্যবহার করবেন না

takewhile আর filter-এর পার্থক্য খেয়াল করুন: filter পুরো ধারা হেঁটে সব মিল রাখে; takewhile প্রথম অমিলেই চিরতরে থামে — তাই সাজানো ডেটা বা অসীম ধারায় এটা কাজের। tee সম্পর্কে একটি সতর্কতা: দুটি কপি যদি অনেক দূরে সরে যায় (একটি শেষে, অন্যটি শুরুতে), tee মাঝের সব মান মেমরিতে জমা রাখে। সেক্ষেত্রে সরাসরি list() বানানোই সহজ আর সমান খরচের। batched-এর শেষ দলটি ছোট হতে পারে; Python 3.13 থেকে strict=True দিলে অসম্পূর্ণ শেষ দলে ValueError আসে।

৬ · groupby-এর ফাঁদ আর combinatorics

groupby — পাশাপাশি থাকা সমানদের দল

groupby(items, key) নাম শুনে মনে হয় SQL-এর GROUP BY বা pandas-এর মতো সব সমান key-কে এক দলে আনবে। আসলে না। groupby শুধু পাশাপাশি থাকা সমান key-গুলোকে এক দলে রাখে। key বদলালেই নতুন দল শুরু হয় — সেই key আগে এসে থাকলেও। এটা Unix-এর uniq কমান্ডের মতো, যা শুধু পরপর থাকা একই লাইন মেলায়। এ কারণে প্রায় সবসময় আগে একই key দিয়ে sort করতে হয়। মাসভিত্তিক বিক্রির উদাহরণে ভুল আর সমাধান দেখুন:

1sales = [("2026-01", 300), ("2026-02", 200),2         ("2026-01", 100), ("2026-02", 400)]3for m, g in groupby(sales, key=itemgetter(0)):4    print(m, sum(amt for _, amt in g))5sales.sort(key=itemgetter(0))   # সমাধান: আগে sort
groupby কী দেখছে
এইমাত্র পড়া জিনিস
চলতি দলের key
দলের সংখ্যা
  1. প্রথম দল খোলে। groupby প্রথম জিনিস পড়ল, key '2026-01'. একটি দল শুরু হলো। groupby জানে না পরে আবার জানুয়ারি আসবে কি না — সে শুধু সামনের জিনিসটা দেখে, পেছনে বা সামনে খোঁজে না।
  2. key বদলাল — দল বন্ধ। পরের জিনিসের key '2026-02', আগেরটির থেকে আলাদা। তাই জানুয়ারির দল বন্ধ (মোট 300), ফেব্রুয়ারির দল শুরু। এখনো সব ঠিক দেখাচ্ছে।
  3. বাগ — জানুয়ারি আবার। তৃতীয় জিনিসের key আবার '2026-01'. groupby-এর কাছে এটা শুধু “আগেরটি থেকে আলাদা” — তাই একটি নতুন দল খোলে। আগের জানুয়ারি-দলের সাথে কোনো যোগ নেই। কোনো error নেই, কোনো সতর্কবার্তাও নেই।
  4. ভুল রিপোর্ট। চারটি জিনিস, চারটি দল — প্রতিটি মাস দুবার, আর প্রতিটির যোগফল অর্ধেক। ছোট পরীক্ষার ডেটা যদি আগে থেকেই সাজানো থাকে, এই বাগ ধরা পড়ে না; আসল ডেটা (যা প্রায়ই সময়ের ক্রমে নয়, শাখার ক্রমে আসে) এলেই রিপোর্ট ভেঙে যায়।
  5. সমাধান — একই key দিয়ে আগে sort. sales.sort(key=itemgetter(0)) একই মাসগুলো পাশাপাশি আনে, তারপর groupby দুটি দল দেয়: 400 আর 600. key দুই জায়গায় একই হওয়া জরুরি। “YYYY-MM” রূপে লেখা string সাজালে ক্রমও সময়ের ক্রম হয়, যা “Jan”, “Feb”-এ হতো না। sort না করে শুধু মোট চাইলে defaultdict(int) বা Counter (পর্ব ২৭) আরও সরল আর এক লুপে হয়।

groupby-এর আরেকটি ফাঁদ: প্রতিটি দলের g একটি iterator, যা মূল ধারার সাথে ভাগ করা। groupby পরের দলে গেলে আগের দলের g ফাঁকা হয়ে যায়। তাই groups = list(groupby(...)) লিখে পরে g পড়লে কিছু পাবেন না। দল ধরে রাখতে চাইলে সাথে সাথে list(g) করুন: {k: list(g) for k, g in groupby(...)}.

combinatorics — product, permutations, combinations

“সব সম্ভাব্য জোড়া”, “সব সম্ভাব্য ক্রম” — এ ধরনের হিসাবে nested লুপ লিখলে সূচক নিয়ে ভুল (off-by-one) আর একই জোড়া দুবার গোনার ভয় থাকে। itertools-এর তিনটি টুল এগুলো নির্ভুলভাবে দেয়:

🍽️ product — প্রতিটির সাথে প্রতিটি

Cartesian product: একাধিক তালিকা থেকে একটি করে নিয়ে সব মিশ্রণ। ২ রকম ভাত × ৩ রকম তরকারি = ৬টি মেনু-কম্বো। product("01", repeat=3) দেয় তিন-বিটের ৮টি মিশ্রণ।

nested for লুপের বদলে

🏏 combinations — ক্রম গুরুত্বহীন

combination: ৪ জন খেলোয়াড় থেকে ২ জনের সব জোড়া — রাফি-নীলা আর নীলা-রাফি একই ম্যাচ। ফল ৬ (= 4C2). ৪টি পদ থেকে ২টি বেছে নেওয়ার উপায়ও ৬।

round-robin টুর্নামেন্ট

🥇 permutations — ক্রম গুরুত্বপূর্ণ

permutation: ৪ জন থেকে প্রথম ও দ্বিতীয় পুরস্কার — রাফি প্রথম/নীলা দ্বিতীয় আর উল্টোটা আলাদা। ফল ১২ (= 4P2). সংখ্যা দ্রুত বিশাল হয়: ১০ জনের সব ক্রম ৩৬ লাখের বেশি।

ক্রম বা সময়সূচি

এবার পুরো ভ্রমণ এক জায়গায় চালিয়ে দেখুন — ব্যাংক ব্যালান্স, groupby-এর ভুল আর সমাধান, টুর্নামেন্টের জোড়া, মেনু-কম্বো, pagination আর দামের পরিবর্তন:

from itertools import (accumulate, batched, combinations, cycle,
                       groupby, islice, pairwise, product)
from operator import itemgetter

tx = [5000, -1200, -800, 3000, -500]
print(list(accumulate(tx, initial=10000)))
sales = [("2026-01", 300), ("2026-02", 200), ("2026-01", 100), ("2026-02", 400)]
month = itemgetter(0)
print([(m, sum(a for _, a in g)) for m, g in groupby(sales, key=month)])
sales.sort(key=month)
print({m: sum(a for _, a in g) for m, g in groupby(sales, key=month)})
players = ["Rafi", "Nila", "Tanim", "Sumi"]
matches = list(combinations(players, 2))
print(len(matches), matches[:2])
menu = list(product(["rice", "khichuri"], ["chicken", "fish"]))
print(len(menu), menu[-1])
print(list(batched(range(1, 8), 3)))
prices = [100, 120, 90]
print([b - a for a, b in pairwise(prices)], "".join(islice(cycle("AB"), 5)))

প্রথম লাইন [10000, 15000, 13800, 13000, 16000, 15500] — উপরের ডায়াগ্রামের ব্যালান্স কলাম। দ্বিতীয় লাইনে groupby-এর বাগ: [('2026-01', 300), ('2026-02', 200), ('2026-01', 100), ('2026-02', 400)] — চারটি দল, প্রতিটি মাস দুবার। sort-এর পরে তৃতীয় লাইন {'2026-01': 400, '2026-02': 600} — সঠিক। লক্ষ করুন, key একটি নামে রাখা হয়েছে, কিন্তু সেটা lambda নয়, itemgetter(0) — তাই PEP 8-এর নিয়ম ভাঙেনি, আর sort ও groupby নিশ্চিতভাবে একই key পেয়েছে। চতুর্থ লাইন 6 [('Rafi', 'Nila'), ('Rafi', 'Tanim')] — ৪ জনের round-robin-এ ৬টি ম্যাচ, প্রথম দুটি দেখানো। পঞ্চম লাইন 4 ('khichuri', 'fish') — ২ × ২ = ৪টি কম্বো, শেষটি দেখানো। ষষ্ঠ লাইন [(1, 2, 3), (4, 5, 6), (7,)] — তিনটি করে পাতা, শেষ পাতায় একটি। শেষ লাইন [20, -30] ABABA — দুই দিনের দামের পরিবর্তন, আর অসীম cycle থেকে islice দিয়ে ৫টি অক্ষর।

৭ · ফাংশনাল ধাঁচে ভাবা — pure function, closure আর কোথায় থামবেন

pure function আর side effect

এই পর্বের টুলগুলো সবচেয়ে ভালো কাজ করে pure function-এর সাথে — যে ফাংশন একই ইনপুটে সবসময় একই ফল দেয় আর বাইরের কিছু বদলায় না। lambda p: (p.price, p.name) pure. কিন্তু key ফাংশনের ভেতরে যদি একটি global counter বাড়ে, বা map-এর ফাংশন ফাইলে লেখে — সেটা side effect. সমস্যা হলো, এই টুলগুলো অলস: map(save_to_db, rows) লিখে কেউ না টানলে একটি সারিও সেভ হবে না, কোনো error-ও আসবে না। আর অর্ধেক টেনে থামলে অর্ধেক সেভ হবে।

নিয়ম: side effect-এর জন্য (ছাপানো, ফাইলে লেখা, ডেটাবেসে পাঠানো) সাধারণ for লুপ লিখুন। list(map(print, items)) বা শুধু কাজ করানোর জন্য comprehension — দুটোই অকারণে একটি None-এর list বানায়, আর পাঠককে বিভ্রান্ত করে। map, filter, comprehension রাখুন নতুন মান বানানোর জন্য।

pure function-এর আরেক সুবিধা পরের পর্বে স্পষ্ট হবে: এদের পরীক্ষা করা সবচেয়ে সহজ — ইনপুট দিন, আউটপুট মিলিয়ে দেখুন, কোনো ফাইল বা ডেটাবেস সাজাতে হয় না। আর lru_cache শুধু pure function-এই নিরাপদ, কারণ সে ধরে নেয় একই argument-এ একই ফল।

closure আর late binding — পর্ব ২০-এর পুনরালোচনা

lambda-ও একটি পূর্ণ ফাংশন, তাই সে বাইরের ভ্যারিয়েবল মনে রাখতে পারে — closure. আর পর্ব ২০-এর late binding ফাঁদ এখানে সবচেয়ে বেশি দেখা যায়: [lambda x: x * r for r in (1.05, 1.10)] — দুটি lambda-ই ডাকার সময় r-এর শেষ মান (1.10) দেখে, তৈরির সময়ের মান নয়। তিনটি সমাধান: lambda x, r=r: x * r (default argument তৈরির সময় হিসাব হয়), একটি factory function, অথবা partial(mul, r) — partial তৈরির মুহূর্তের মানটিই রাখে।

কোথায় থামবেন — পড়ার যোগ্যতা আগে

ফাংশনাল টুল শেখার পর অনেকে সবকিছু এক লাইনে লিখতে চান। নিচের দুটি একই কাজ করে — কোনটি ছয় মাস পরে আপনি বা আপনার সহকর্মী বুঝবেন?

# এক লাইনের "চালাক" রূপ
total = reduce(lambda a, b: a + b, map(lambda o: o["qty"] * o["price"],
               filter(lambda o: o["status"] == "paid", orders)), 0)

# Pythonic রূপ
total = sum(o["qty"] * o["price"] for o in orders if o["status"] == "paid")

প্রথমটি পড়তে হয় ভেতর থেকে বাইরে, আর তিনটি lambda-র প্রতিটির parameter-এর নাম মনে রাখতে হয়। দ্বিতীয়টি বাঁ থেকে ডানে একটি বাক্যের মতো: “paid অর্ডারগুলোর qty × price-এর যোগফল”. Python-এর স্রষ্টা নিজেও ফাংশনাল ধাঁচ নিয়ে সংশয়ী ছিলেন (ভাবনার প্রশ্ন ২ ও ৫)। Python-এ নিয়মটা মোটামুটি এমন:

  • একটি key, একটি তৈরি ফাংশন পাঠানো, বা একটি itertools টুল যা নামেই কাজ বলে দেয় (accumulate, pairwise, combinations) — ব্যবহার করুন।
  • lambda-র ভেতরে lambda, তিন স্তরের nesting, বা কাগজে চালিয়ে দেখতে হয় এমন reduce — থামুন; comprehension, নামওয়ালা ছোট ফাংশন বা সাধারণ লুপে ভাঙুন।
  • মাঝের ধাপগুলোকে নাম দিন: paid = (o for o in orders if ...), তারপর amounts = ... — নামই সবচেয়ে ভালো মন্তব্য।
C/C++ জানলে

C-তে lambda নেই; qsort-এ একটি আলাদা ফাংশনের function pointer পাঠাতে হয়, যা দুটি জিনিস তুলনা করে — ঠিক Python-এর পুরোনো cmp ধাঁচ। C++11-এর lambda ([capture](params) { body }) একাধিক statement নিতে পারে, আর capture-এ মান কপি ([=]) না reference ([&]) — তা আপনি বেছে নেন; Python-এর lambda এক expression-এর, আর বাইরের নাম সবসময় late binding-এ দেখে। C++20-এর views::filter/views::transform Python-এর অলস map/filter-এর আত্মীয়, আর std::accumulate হলো reduce. বিস্তারিত দেখুন C++ কোর্সের lambda পর্বে আর ranges ও views পর্বে।

পরীক্ষা ও ইন্টারভিউতে
  • lambda আর def-এর পার্থক্য? — lambda নামহীন, শুধু একটি expression রাখতে পারে, return লাগে না; def নামওয়ালা, যেকোনো statement আর docstring রাখতে পারে।
  • Python 3-এ map() কী ফেরত দেয়? — list নয়, একটি অলস iterator; ফল দেখতে list() লাগে, আর একবারই হাঁটা যায়।
  • Python 3-এ reduce কোথায়? — built-in নয়; from functools import reduce.
  • groupby কেন sort-করা ডেটা চায়? — এটা শুধু পাশাপাশি থাকা সমান key-কে এক দলে রাখে; sort না করলে একই key একাধিক দলে ভাগ হয়।
  • combinations আর permutations-এর পার্থক্য? — combinations-এ ক্রম গুরুত্বহীন (৪ থেকে ২: ৬টি), permutations-এ ক্রম গুরুত্বপূর্ণ (৪ থেকে ২: ১২টি)।

৮ · AI-এর যুগে — AI-এর লেখা ফাংশনাল কোড যাচাই

AI কোড-সহকারীরা অনেক ভাষার কোড থেকে শিখেছে — JavaScript, Scala, Haskell, যেখানে map/filter/reduce-এর শিকল খুব স্বাভাবিক। তাই Python-এ চাইলেও তারা প্রায়ই সেই ধাঁচ আনে, যা চলে ঠিকই কিন্তু Pythonic নয়, আর কখনো কখনো নিঃশব্দে ভুল। বারবার দেখা যায় এমন ভুল:

AI প্রায়ই যা লেখেসমস্যাভালো রূপ
list(map(lambda x: ..., filter(lambda x: ..., xs)))ভেতর থেকে পড়তে হয়, দুটি lambdaএকটি comprehension
reduce(lambda a, b: a + b, xs)খালি list-এ TypeError, পড়তে কঠিনsum(xs)
sort ছাড়া groupbyএকই key একাধিক দলে — ভুল রিপোর্ট, কোনো error নেইআগে একই key-তে sort, বা defaultdict/Counter
get_price = lambda p: p.pricePEP 8 ভঙ্গ, traceback-এ শুধু <lambda>def, বা attrgetter("price")
key=lambda s: len(s)অকারণ মোড়কkey=len
লুপে lambda: i দিয়ে callbacklate binding — সবাই শেষ মান দেখেpartial বা i=i
map(save, rows), কেউ টানে নাঅলস — কিছুই চলে নাside effect-এর জন্য for লুপ

AI-কে ডেটা-রূপান্তরের কোড লিখতে বললে পড়ার যোগ্যতার শর্তগুলো আগেই বলে দিন। নিচের কাঠামোটি কপি করে ব্যবহার করুন:

আমি Python 3.12 বা নতুন version ব্যবহার করি। আমার কাছে একটি দোকানের বিক্রির list আছে, প্রতিটি জিনিস একটি dict: {"date": "2026-03-14", "item": "tea", "qty": 3, "price": 20}. দুটি কাজ করো: (ক) প্রতি মাসের মোট বিক্রি (qty × price), মাসের ক্রমে; (খ) প্রতিদিনের মোট বিক্রির চলতি যোগফল। শর্ত: ১. পড়ার যোগ্যতা আগে — map(lambda...) বা filter(lambda...)-এর বদলে comprehension বা generator expression ব্যবহার করো; lambda-র ভেতরে lambda চলবে না। ২. যোগফলের জন্য reduce নয়, sum ব্যবহার করো। ৩. itertools.groupby ব্যবহার করলে আগে একই key দিয়ে sort করো, আর কেন করলে তা এক লাইনে বলো; অথবা defaultdict ব্যবহার করো। ৪. চলতি যোগফলের জন্য itertools.accumulate ব্যবহার করো। ৫. কোনো lambda-কে নামে বসাবে না; নাম লাগলে def বা operator.itemgetter ব্যবহার করো। ৬. প্রতিটি ফাংশন pure রাখো — ছাপানোর কাজ শুধু শেষে একটি for লুপে। ৭. একটি অসাজানো ছোট নমুনা ডেটা দিয়ে কোডটি চালিয়ে আউটপুট দেখাও, যাতে groupby-এর ভুল থাকলে ধরা পড়ে।

AI-এর লেখা ফাংশনাল কোড পেলে — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলো আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।

প্র ০১“lambda” শব্দটি কোথা থেকে এল? ১৯৩০-এর দশকের একটি গণিতের তত্ত্ব কীভাবে ১৯৫৮ সালের Lisp হয়ে আজকের Python-এ পৌঁছাল?

১৯৩০-এর দশকে Princeton-এর গণিতবিদ Alonzo Church একটি প্রশ্নের উত্তর খুঁজছিলেন, যা তখন গণিতের ভিত্তি নিয়ে বড় বিতর্কের কেন্দ্রে ছিল: “হিসাব করা যায়” — এর সঠিক মানে কী? তাঁর উত্তর ছিল একটি অবাক করা ছোট পদ্ধতি, যার নাম lambda calculus. এতে আছে মাত্র তিনটি জিনিস: ভ্যারিয়েবল, ফাংশন বানানো (গ্রিক অক্ষর λ দিয়ে লেখা — λx. x + 1 মানে “x নিয়ে x + 1 দাও”), আর ফাংশন প্রয়োগ। কোনো সংখ্যা নেই, লুপ নেই, ভ্যারিয়েবল বদলানো নেই — তবু Church আর তাঁর ছাত্ররা দেখালেন, শুধু ফাংশন দিয়েই সংখ্যা, যোগ, শর্ত, এমনকি recursion বানানো যায়।

প্রায় একই সময়ে Alan Turing তাঁর কাল্পনিক যন্ত্র (Turing machine) দিয়ে একই প্রশ্নের উত্তর দেন, আর প্রমাণ হয় যে দুটি পদ্ধতি সমান ক্ষমতার। Turing পরে Princeton-এ Church-এর তত্ত্বাবধানে PhD করেন। এই “যা হিসাব করা যায়, তা দুটি পদ্ধতিতেই করা যায়” ধারণাটি Church–Turing thesis নামে পরিচিত। মজার ব্যাপার, আধুনিক কম্পিউটারের নকশা Turing-এর ধাঁচের — মেমরি বদলে বদলে চলা (imperative), আর ফাংশনাল প্রোগ্রামিং এসেছে Church-এর ধাঁচ থেকে।

১৯৫৮ সালে MIT-তে John McCarthy কৃত্রিম বুদ্ধিমত্তার গবেষণার জন্য একটি ভাষা নকশা করেন — Lisp. তিনি Church-এর ধারণা থেকে নামহীন ফাংশন লেখার উপায় ধার নিলেন, আর শব্দটিও রাখলেন: (lambda (x) (* x x)). ১৯৬০ সালে প্রকাশিত তাঁর গবেষণাপত্রে এই LAMBDA notation-এর উৎস হিসেবে Church-এর নাম আছে। Lisp-এর মাধ্যমে “ফাংশন একটি মান, যা পাঠানো যায়” ধারণাটি প্রোগ্রামিংয়ের মূলধারায় ঢোকে, পরে Scheme, ML, Haskell হয়ে প্রায় সব ভাষায় — JavaScript-এর arrow function, Java 8 আর C++11-এর lambda পর্যন্ত।

Python-এর lambda সেই ঐতিহ্যের একটি ছোট, ইচ্ছাকৃতভাবে সীমিত রূপ: শুধু একটি expression. Lisp-এ সবকিছুই expression, তাই সেখানে এই সীমা কোনো সমস্যা নয়। Python-এ statement আর expression আলাদা, আর indentation-ভিত্তিক ব্লক একটি expression-এর ভেতরে বসানোর কোনো পরিষ্কার উপায় কেউ খুঁজে পাননি — তাই বহু-লাইনের lambda-র প্রস্তাব বারবার এসে বাদ পড়েছে।

মূল উপলব্ধি: আপনি যখন key=lambda p: p.price লেখেন, তখন প্রায় একশো বছরের পুরোনো একটি গণিতের ধারণা ব্যবহার করছেন — যেটা প্রথমে কম্পিউটার তৈরিরও আগে “হিসাব কী” প্রশ্নের উত্তর হিসেবে জন্মেছিল। প্রোগ্রামিং ভাষার অনেক “নতুন” সুবিধা আসলে পুরোনো গণিতের নতুন জামা।

প্র ০২Python-এ lambda, map, filter আর reduce কীভাবে এল, আর ২০০৫ সালে Guido van Rossum কেন এদের সরাতে চাইলেন? শেষে reduce কেন functools-এ গেল, অথচ map আর filter রয়ে গেল?

১৯৯৪ সালের জানুয়ারিতে প্রকাশিত Python 1.0-এ lambda, map, filter আর reduce যোগ হয়। Guido van Rossum পরে (২০০৯ সালে তাঁর “History of Python” ব্লগে) লেখেন যে এগুলো তাঁর নিজের পরিকল্পনা ছিল না — একজন Lisp-প্রেমী ব্যবহারকারী এগুলোর অভাব বোধ করে কাজ করা patch পাঠিয়েছিলেন, আর তিনি সেগুলো গ্রহণ করেন। তিনি এটাও বলেন, Python-কে তিনি কখনো ফাংশনাল ভাষাগুলোর দ্বারা খুব প্রভাবিত মনে করেননি; তাঁর অনুপ্রেরণা ছিল ABC আর C-এর মতো imperative ভাষা।

তারপর এল list comprehension (Python 2.0, ২০০০) আর generator expression (Python 2.4, ২০০৪)। এরা map আর filter-এর প্রায় সব কাজ আরও পড়ার মতো করে করতে পারত। ২০০৫ সালের মার্চে Guido Artima-তে লিখলেন “The fate of reduce() in Python 3000” — তখনো Python 3-এর কাজ চলছিল, পুরোনো জিনিস সরানোর সুযোগ সেটাই। তাঁর প্রস্তাব ছিল চারটিকেই সরানো। reduce নিয়ে তিনি সবচেয়ে কড়া ছিলেন: তাঁর অভিজ্ঞতায় যোগ বা গুণের বাইরে প্রায় প্রতিটি reduce বুঝতে কাগজ-কলম নিয়ে বসতে হয়, আর সাধারণ লুপে লিখলে সেটা বেশি পরিষ্কার হয়। সেই সময়েই any() আর all()-এর প্রস্তাবও আসে, যা reduce-এর দুটি সাধারণ ব্যবহারের নামওয়ালা বিকল্প।

সম্প্রদায়ের প্রতিক্রিয়া ছিল তীব্র, বিশেষ করে lambda-কে ঘিরে — callback আর key-র জন্য এটা অনেকের প্রতিদিনের হাতিয়ার। দীর্ঘ আলোচনার পর সমঝোতা হলো: lambda, map আর filter থাকল (map আর filter Python 3-এ অলস iterator হলো); শুধু reduce built-in থেকে সরে গেল functools module-এ (Python 3.0, ২০০৮)। functools.reduce Python 2.6 থেকেই ছিল, তাই পুরোনো কোড সহজে বদলানো যেত।

কেন শুধু reduce? map আর filter-এর অর্থ নামেই পরিষ্কার — “প্রতিটির ওপর চালাও”, “ছাঁকো”. reduce-এর অর্থ পুরোপুরি নির্ভর করে ভেতরের ফাংশনের ওপর, আর তার সাধারণ ব্যবহারগুলোর জন্য (sum, max, min, any, all, পরে math.prod) আলাদা নামওয়ালা built-in ছিল বা এল। সরিয়ে না ফেলে import-এর পেছনে রাখা মানে: ব্যবহার করা যাবে, কিন্তু প্রথম পছন্দ হিসেবে নয়।

মূল উপলব্ধি: ভাষা-নকশায় “কোনো সুবিধা আছে কি না” প্রশ্নের চেয়ে “কোনটি প্রথমে হাতের কাছে থাকবে” প্রশ্নটি বেশি গুরুত্বপূর্ণ। একটি টুলকে module-এ সরিয়ে দেওয়া হলো একটি নরম সংকেত — সম্প্রদায়কে না ভেঙে অভ্যাস বদলানোর উপায়।

প্র ০৩Python-এর অফিশিয়াল ডকুমেন্টেশন বলে, itertools-এর টুলগুলো APL, Haskell আর SML থেকে অনুপ্রাণিত। এই ভাষাগুলো থেকে কী নেওয়া হয়েছে, আর কেন একটি ছোট “iterator-এর বীজগণিত” এত কাজের?

itertools যোগ হয় Python 2.3-এ (২০০৩), মূলত Raymond Hettinger-এর কাজ — যিনি পরে collections, functools আর sorting-সহ standard library-র বহু অংশের উন্নয়নে বড় ভূমিকা রাখেন। module-এর ডকুমেন্টেশনের প্রথম অনুচ্ছেদেই লেখা আছে, এর টুলগুলো APL, Haskell আর SML-এর গঠন থেকে অনুপ্রাণিত, আর একসাথে এরা একটি “iterator algebra” তৈরি করে।

APL (Kenneth Iverson, ১৯৬০-এর দশক) ছিল পুরো array-র ওপর একবারে কাজ করার ভাষা, যেখানে লুপ প্রায় লিখতেই হতো না। এর “scan” অপারেটর প্রতিটি ধাপের মাঝের ফল দেয় — যা Python-এ accumulate. Iverson এই কাজের জন্য ১৯৭৯ সালে Turing Award পান। Haskell (১৯৯০) আর SML (Standard ML, ১৯৮০-এর দশক) হলো ফাংশনাল ভাষা, যেখানে ধারা (list বা stream) নিয়ে কাজ করার জন্য takeWhile, dropWhile, cycle, repeat, iterate, zipWith-এর মতো ছোট ফাংশন খুব প্রচলিত। Haskell সম্পূর্ণ অলস (lazy) ভাষা, তাই সেখানে অসীম list স্বাভাবিক — Python-এ সেই ধারণা এল iterator হিসেবে। নামগুলো মিলিয়ে দেখুন: Python-এর takewhile, dropwhile, cycle, repeat প্রায় হুবহু।

“বীজগণিত” কেন? গণিতে বীজগণিত মানে কিছু উপাদান আর কিছু নিয়ম, যা জুড়ে যেকোনো জটিল জিনিস বানানো যায়। itertools-এর প্রতিটি টুল একটি iterator নিয়ে একটি iterator দেয় — তাই যেকোনো টুলের আউটপুট যেকোনো টুলের ইনপুটে বসে। islice(accumulate(pairwise(...)), 10) — প্রতিটি অংশ ছোট, আলাদাভাবে বোঝা যায়, আর জোড়া লাগালে নতুন ক্ষমতা আসে। অলসতার কারণে কোনো ধাপে পুরো ডেটা মেমরিতে আসে না, আর শেষ ভোক্তা থামলে পুরো শিকল থামে। টুলগুলো C-তে লেখা, তাই একটি Python লুপের চেয়ে প্রায়ই দ্রুত।

Hettinger ডকুমেন্টেশনে একটি “recipes” অংশও রাখেন — টুল জুড়ে বানানো ছোট ছোট ফাংশন (যেমন grouper, pairwise, sliding_window). এগুলোর কয়েকটি এত জনপ্রিয় হয় যে পরে module-এ সরাসরি যোগ করা হয়: pairwise Python 3.10-এ, আর grouper-এর কাজ batched নামে Python 3.12-এ। third-party more-itertools প্যাকেজে আরও বহু রেসিপি তৈরি অবস্থায় আছে।

মূল উপলব্ধি: একটি ভালো library বড় বড় সমাধান দেয় না, ছোট ছোট জোড়া লাগানোর মতো টুকরো দেয়, যাদের একটি সাধারণ “ভাষা” আছে (এখানে: iterator ঢোকে, iterator বের হয়)। এই নকশা-ভাবনা আপনার নিজের ফাংশন লেখাতেও কাজে লাগে।

প্র ০৪itertools-এর প্রায় সব টুল অলস। এই অলসতা কোন সমস্যা সমাধান করে, আর কোন নতুন সমস্যা তৈরি করে? groupby আর tee কেন অন্য টুলের চেয়ে বেশি সতর্কতা চায়?

অলসতার প্রথম লাভ মেমরি। product(range(1000), repeat=3)-এ ১০০ কোটি মিশ্রণ — list হলে কোনো সাধারণ কম্পিউটারে আঁটবে না, কিন্তু iterator হিসেবে একসাথে একটিই মিশ্রণ থাকে। দ্বিতীয় লাভ আগেভাগে থামা: সব মিশ্রণের মধ্যে প্রথম যেটি শর্ত মানে, সেটি পেলেই next() থেমে যায়, বাকিগুলো কখনো তৈরিই হয় না। তৃতীয় লাভ অসীম ধারা: count(), cycle() — list দিয়ে যা অসম্ভব।

কিন্তু অলসতার দাম আছে। প্রথমত, কাজ কখন ঘটে তা আর লেখার জায়গা দেখে বোঝা যায় না — ঘটে যখন কেউ টানে। error-এর traceback তাই আসে টানার জায়গা থেকে, যা কোড লেখার জায়গা থেকে অনেক দূরে হতে পারে। দ্বিতীয়ত, একবারই হাঁটা যায় (পর্ব ৩৪) — ডিবাগ করতে গিয়ে print(list(it)) লিখলেই ডেটা শেষ। তৃতীয়ত, ভেতরের ফাংশনে side effect থাকলে সেটা কখন, কতবার চলবে তা অনিশ্চিত।

groupby বিশেষ, কারণ এটা অলস থেকেও “দল” দেয়। পুরো ডেটা না দেখে সে জানতে পারে না সামনে আবার একই key আসবে কি না — তাই সে শুধু পাশাপাশি থাকা জিনিস মেলায়। এটা অলসতারই স্বাভাবিক ফল: SQL-এর GROUP BY সব সারি দেখে (প্রয়োজনে ভেতরে sort বা hash table ব্যবহার করে), groupby সেটা করে না, আর তার বদলে স্থির মেমরিতে চলে। sort-এর দায়িত্ব আপনার। আর প্রতিটি দলের iterator মূল ধারার সাথে ভাগ করা, তাই পরের দলে গেলে আগেরটি হারিয়ে যায়।

tee-এর সমস্যা উল্টো দিক থেকে। একটি iterator থেকে দুটি স্বাধীন কপি বানানো মানে, যে মান একটি কপি পড়ে ফেলেছে কিন্তু অন্যটি এখনো পড়েনি, সেটা কোথাও জমা রাখতে হবে। দুটি কপি কাছাকাছি চললে খরচ সামান্য; একটি শেষ পর্যন্ত গিয়ে অন্যটি শুরুতে থাকলে tee কার্যত পুরো ডেটা মেমরিতে রাখে — তখন সরাসরি list বানানোই সৎ সমাধান। আর tee-র পরে মূল iterator সরাসরি ব্যবহার করলে কপিগুলো কিছু মান হারায়।

মূল উপলব্ধি: অলসতা একটি বিনিময়: মেমরি আর আগেভাগে থামার সুবিধার বদলে আপনি “কখন কী ঘটে” তার সরলতা ছেড়ে দেন। যে টুল এই বিনিময় লুকাতে পারে না (groupby, tee), তার নিয়মগুলো জেনে রাখাই ভালো প্রোগ্রামারের চিহ্ন।

প্র ০৫Zen of Python বলে, “There should be one — and preferably only one — obvious way to do it.” অথচ একই কাজ লুপ, comprehension, map/filter আর reduce — চারভাবে লেখা যায়। ফাংশনাল আর imperative ধাঁচের এই টানাপোড়েন Python-এর সংস্কৃতিতে কীভাবে মীমাংসা হয়েছে?

Zen of Python (PEP 20, Tim Peters, ১৯৯৯ সালে লেখা, পরে import this-এ রাখা) Python-এর নকশা-দর্শনের ১৯টি সূত্র। “একটি স্পষ্ট উপায়” সূত্রটি অনেকটা Perl-এর মূলমন্ত্র “There's more than one way to do it”-এর জবাব হিসেবে পড়া হয়। এর মানে এই নয় যে অন্য উপায় অসম্ভব — মানে হলো, প্রতিটি সাধারণ কাজের জন্য একটি প্রথম পছন্দ থাকা উচিত, যা অভিজ্ঞ প্রোগ্রামাররা প্রায় একমত হয়ে বেছে নেন।

ফাংশনাল ধাঁচ নিয়ে Python সম্প্রদায় সময়ের সাথে একটি মোটামুটি স্পষ্ট মীমাংসায় পৌঁছেছে। নতুন list বানাতে: comprehension. যোগফল, সর্বোচ্চ, “কোনোটি কি সত্য”: sum, max, any. key: lambda বা operator. ধারার জটিল রূপান্তর: itertools. side effect: সাধারণ লুপ। এই বিভাজনের পেছনে আছে Guido-র ধারাবাহিক অবস্থান — তিনি ২০০৯ সালে tail recursion elimination (ফাংশনাল ভাষায় লুপের বদলে recursion চালানোর একটি মূল কৌশল) Python-এ যোগ করতে অস্বীকার করেন, যুক্তি দিয়ে যে এতে traceback হারায় আর recursion-কে প্রধান ধাঁচ বানানো Python-এর স্বভাব নয়।

অন্যদিকে, ফাংশনাল ভাবনা Python-কে চুপচাপ অনেক বদলেও দিয়েছে। comprehension নিজেই Haskell থেকে আসা; generator আর itertools অলস ধারার ধারণা এনেছে; decorator হলো higher-order function; dataclass-এর frozen=True আর tuple-এর জনপ্রিয়তা immutable ডেটার দিকে ঝোঁক দেখায়; match statement-এর pattern matching ML পরিবারের ভাষার প্রিয় সুবিধা। অর্থাৎ Python ফাংশনাল ভাষা হয়নি, কিন্তু তার ভালো ধারণাগুলো নিজের ভাষায়, নিজের syntax-এ গ্রহণ করেছে।

এই টানাপোড়েন শুধু Python-এর নয়। ১৯৭৭ সালে John Backus — যিনি FORTRAN-এর প্রধান নকশাকার — Turing Award বক্তৃতায় প্রশ্ন তোলেন, প্রোগ্রামিং কি “von Neumann ধাঁচ” (মেমরি বদলে বদলে চলা) থেকে মুক্ত হতে পারে, আর ফাংশনাল ধাঁচের পক্ষে যুক্তি দেন। আজ Java 8-এর stream, JavaScript-এর array method, C++20-এর ranges, Rust-এর iterator — প্রায় সব মূলধারার ভাষা imperative ভিত্তির ওপর ফাংশনাল টুল বসিয়েছে। পার্থক্য হলো মাত্রায়, আর Python ইচ্ছা করে মাঝামাঝি থেকেছে।

মূল উপলব্ধি: “একটি স্পষ্ট উপায়” কোনো আইন নয়, একটি সাংস্কৃতিক চুক্তি — যাতে একজনের লেখা কোড আরেকজন কম চেষ্টায় পড়তে পারেন। ফাংশনাল টুল তখনই Pythonic, যখন সেটা সেই চুক্তি মেনে পাঠকের কাজ সহজ করে।

অনুশীলন

এগুলোর বেশিরভাগে নতুন কোড লিখতে হবে না — অনুমান করা, তুলনা করা আর যাচাই করাই আসল কাজ।

  1. হাতে চালান: চালানোর আগে খাতায় লিখুন প্রতিটি কী দেবে — (ক) sorted(["bb", "a", "ccc", "dd"], key=len); (খ) sorted(["bb", "a", "ccc", "dd"], key=lambda s: (-len(s), s)); (গ) m = map(str.upper, "ab")-এর পর list(m), list(m); (ঘ) list(accumulate([3, 1, 4], max)). তারপর REPL-এ মিলিয়ে নিন।

    (ক) ['a', 'bb', 'dd', 'ccc'] — দৈর্ঘ্য অনুযায়ী; "bb" আর "dd" দুটিরই দৈর্ঘ্য 2, আর sort stable বলে আগের ক্রম (bb আগে) থাকে। (খ) ['ccc', 'bb', 'dd', 'a'] — -len দৈর্ঘ্যের ক্রম উল্টে দেয় (বড় আগে), আর সমান দৈর্ঘ্যে নাম a-z. (গ) (['A', 'B'], []) — map একটি iterator, প্রথম list() তাকে ফুরিয়ে ফেলেছে। (ঘ) [3, 3, 4] — “এ পর্যন্ত সর্বোচ্চ”: 3, max(3, 1) = 3, max(3, 4) = 4.

  2. reduce সরান: নিচের প্রতিটি reduce-কে আরও পরিষ্কার রূপে লিখুন, আর খালি list দিলে দুই রূপের আচরণ তুলনা করুন — (ক) reduce(lambda a, b: a + b, prices); (খ) reduce(lambda a, b: a and b, checks, True); (গ) reduce(lambda a, b: a if len(a) >= len(b) else b, words).

    (ক) sum(prices) — খালি list-এ sum দেয় 0, অথচ initial ছাড়া reduce দেয় TypeError. (খ) all(checks) — খালি list-এ দুটোই True, কিন্তু all প্রথম মিথ্যা পেলেই থামে। (গ) max(words, key=len) — সমান দৈর্ঘ্যে দুটোই প্রথমটি রাখে (reduce-এ >= থাকায় আগেরটি জেতে, max-ও প্রথম সর্বোচ্চটি দেয়)। খালি list-এ দুটোই error দেয় — max-এ default="" দিয়ে সেটা এড়ানো যায়। লক্ষ করুন, তিনটির ক্ষেত্রেই নামওয়ালা ফাংশনটি পড়লেই উদ্দেশ্য বোঝা যায়।

  3. টুর্নামেন্টের সময়সূচি (একমাত্র কোড-লেখার কাজ): একটি পাড়ার ক্রিকেট টুর্নামেন্টে ৬টি দল, প্রতিটি দল প্রতিটির সাথে একবার খেলবে। combinations দিয়ে সব ম্যাচ বানান, তারপর batched দিয়ে প্রতিদিন ৩টি করে ম্যাচের দিন ভাগ করে ছাপান (“দিন ১: ...”)। কতটি ম্যাচ আর কত দিন লাগবে?
    from itertools import batched, combinations
    
    teams = ["Tigers", "Eagles", "Lions", "Sharks", "Falcons", "Rhinos"]
    matches = list(combinations(teams, 2))
    print(len(matches), "ম্যাচ")                 # 15 ম্যাচ
    for day, games in enumerate(batched(matches, 3), start=1):
        print(f"দিন {day}:", ", ".join(f"{a} বনাম {b}" for a, b in games))

    ৬টি দল থেকে ২টির জোড়া 6C2 = ১৫টি, তাই ৩টি করে ৫ দিন। batched Python 3.12 থেকে আছে; পুরোনো version-এ পর্ব ৩৪-এর islice-ভিত্তিক chunks ফাংশন ব্যবহার করুন। লক্ষ করুন, এই সহজ ভাগে একটি দল একই দিনে দুটি ম্যাচ খেলতে পারে (প্রথম দিনেই Tigers তিনটি!) — বাস্তব সময়সূচিতে “round-robin scheduling” নামে আলাদা অ্যালগরিদম লাগে। টুল সঠিক জোড়া দেয়, কিন্তু সমস্যার সব শর্ত আপনাকেই ভাবতে হয়।

  4. AI অডিট: একটি AI বিক্রির রিপোর্টের জন্য এই কোড দিয়েছে: get_m = lambda r: r["date"][:7], তারপর report = {m: reduce(lambda a, b: a + b, map(lambda r: r["amt"], g)) for m, g in groupby(rows, get_m)}. ছোট পরীক্ষায় ঠিক ফল দিয়েছে। কী কী সমস্যা, আর আসল ডেটায় কোনটি নিঃশব্দে ভুল ফল দেবে?

    সবচেয়ে বিপজ্জনক: groupby-এর আগে sort নেই। পরীক্ষার ডেটা হয়তো তারিখের ক্রমে ছিল, কিন্তু আসল ডেটা অসাজানো হলে একই মাস একাধিক দলে ভাগ হবে — আর dict comprehension-এ পরের দলটি আগেরটিকে মুছে লিখবে, তাই প্রতিটি মাসে শুধু শেষ খণ্ডের যোগফল থাকবে। কোনো error নেই, শুধু কম টাকা। দ্বিতীয়: lambda নামে বসানো (get_m) — def month_of(r) বা অন্তত স্পষ্ট নাম দিন। তৃতীয়: reduce(lambda a, b: a + b, map(lambda ...)) — দুটি lambda আর একটি reduce, যেখানে sum(r["amt"] for r in g) যথেষ্ট। ঠিক রূপ: rows.sort(key=month_of), তারপর {m: sum(r["amt"] for r in g) for m, g in groupby(rows, key=month_of)} — অথবা sort ছাড়াই defaultdict(int) দিয়ে এক লুপে। শিক্ষা: সাজানো নমুনা ডেটা groupby-এর বাগ লুকিয়ে রাখে; পরীক্ষায় সবসময় এলোমেলো ক্রমের ডেটা দিন।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমPython-এ itertools.groupby দিয়ে মাসভিত্তিক মোট বিক্রি বের করার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. 🔑 প্রতিটি রেকর্ড থেকে মাস বের করার একটি key ঠিক করা (যেমন itemgetter বা def)
  2. 📊 সেই একই key দিয়ে পুরো ডেটা sort করা
  3. 🧺 sort-করা ডেটার ওপর একই key দিয়ে groupby চালানো
  4. ➕ প্রতিটি দলের iterator সাথে সাথে sum দিয়ে যোগ করা
  5. 🖨️ মাস আর মোট একটি for লুপে ছাপানো

Python-এ (lambda x, y=2: x ** y)(3) expression-টির মান কী?

  • 6
  • TypeError, কারণ দুটি argument লাগে
  • 9
  • একটি function object

Python-এ lambda-র body-তে নিচের কোনটি লেখা যায় না?

  • "পাস" if m >= 33 else "ফেল"
  • return x * 2
  • len(s) + 1
  • (p.price, p.name)

Python-এ [k for k, g in groupby([1, 1, 2, 1, 1])]-এর মান কী?

  • [1, 2]
  • [1, 1, 2, 1, 1]
  • [1, 1, 2]
  • [1, 2, 1]

Python-এ list(accumulate([100, -30, 50]))-এর মান কী?

  • [100, 70, 120]
  • [120]
  • [0, 100, 70, 120]
  • [-130, 80]

৪ জন খেলোয়াড়ের প্রত্যেকে প্রত্যেকের সাথে একবার করে খেলবে। Python-এ সব ম্যাচ পেতে কোনটি সঠিক, আর কয়টি ম্যাচ হবে?

  • permutations(players, 2) — ১২টি
  • product(players, repeat=2) — ১৬টি
  • combinations(players, 2) — ৬টি
  • pairwise(players) — ৩টি

Python-এ p = functools.partial(pow, 2) লেখার পর p(5)-এর মান কী?

  • 25
  • 32
  • TypeError
  • আরেকটি partial object

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. ফাংশন একটি মান — পাঠানো যায়, রাখা যায়। key=len-এ বন্ধনী নেই: ফাংশন পাঠানো হচ্ছে, ডাকা নয়।
  2. lambda = এক expression, নাম নেই। সেরা জায়গা key=; একাধিক key-তে tuple (p.price, p.name). নাম দিতে চাইলে def — PEP 8.
  3. map/filter অলস iterator দেয়; lambda লাগলে comprehension সাধারণত পরিষ্কার। reduce-এর আগে ভাবুন sum, max, any, all, math.prod চলে কি না।
  4. partial argument আগেই বসায়; itemgetter/attrgetter lambda ছাড়াই key দেয়; cmp_to_key পুরোনো তুলনা ফাংশনকে key বানায়।
  5. itertools: accumulate = চলতি যোগফল; groupby-এর আগে একই key-তে sort; combinations (ক্রম গুরুত্বহীন), permutations (গুরুত্বপূর্ণ), product (সব মিশ্রণ); pairwise (3.10), batched (3.12). পড়ার যোগ্যতা সবার আগে।

পরের পর্বে: এই পর্বে দেখলেন, pure function-কে ইনপুট দিয়ে আউটপুট মিলিয়ে নেওয়া সবচেয়ে সহজ। পরের পর্বে সেই মিলিয়ে দেখাকে স্বয়ংক্রিয় করবেন — pytest দিয়ে টেস্ট লেখা, যাতে আপনার (বা AI-এর) প্রতিটি পরিবর্তন কিছু ভাঙল কি না, এক কমান্ডে জানা যায়।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান