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

ফাংশন — one-to-one, onto, inverse আর composition

Functions — Domain and Codomain, Injective, Surjective and Bijective Maps, Inverses, Composition, Floor and Ceiling, Counting Functions and Hashing

Taught in Bangla, this lesson defines functions by domain, codomain and image and gives templates for proving and disproving one-to-one and onto, with a full bijection proof and inverse for f(x) = 2x/(x − 1). It covers composition and inverses, floor and ceiling identities, partial functions, and counts all, one-to-one and onto functions. In-page Python brute-forces those counts against the formulas, tests floor identities, and shows why more keys than buckets forces a hash collision.

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

  • ফাংশনের নিখুঁত সংজ্ঞা: domain, codomain, image, preimage আর relation হিসেবে ফাংশন
  • One-to-one, onto আর bijection প্রমাণ ও খণ্ডনের নির্দিষ্ট ছক
  • Inverse কখন থাকে, composition-এর নিয়ম আর তাদের প্রমাণ
  • Floor আর ceiling-এর identity, প্রমাণ আর বাক্স, পাতা ও byte গোনায় প্রয়োগ
  • Partial function, সসীম সেটের মধ্যে ফাংশন গোনা, আর hash collision-এর গাণিতিক কারণ

১ · ফাংশন কী — domain, codomain আর image

বাজারঘরে প্রতিটি অর্ডারের একটি নম্বর আছে, আর প্রতিটি অর্ডার ঠিক একজন ক্রেতা দিয়েছেন। অর্ডার ১০১ রিনার, ১০২ সজীবের, ১০৩ আবার রিনার। “অর্ডার থেকে ক্রেতা” এই নিয়মটিই একটি ফাংশন। লক্ষ করুন দুটি জিনিস: কোনো অর্ডার ক্রেতাবিহীন নয়, আর কোনো অর্ডারের দুজন ক্রেতা নেই। অথচ একজন ক্রেতার একাধিক অর্ডার থাকতে পারে, আর কোনো নিবন্ধিত ক্রেতার একটিও অর্ডার না থাকতে পারে। ফাংশনের পুরো তত্ত্ব আসলে এই অসমতা নিয়েই: বাম দিকে নিয়ম কঠোর, ডান দিকে ঢিলা।

আগের দুটি পর্বে, পর্ব ১৪ “সেট — সংজ্ঞা, subset, power set আর Cartesian product” আর পর্ব ১৫ “সেটের অপারেশন — union, intersection, Venn diagram আর identity”, সেট আর তাদের অপারেশন এসেছে। ফাংশন হলো দুটি সেটের মধ্যে সংযোগ। Python-এ যে def দিয়ে ফাংশন লেখেন (Python কোর্সের পর্ব ১৩), তা এই গাণিতিক ধারণার একটি প্রয়োগ, তবে পুরোপুরি এক জিনিস নয়; পার্থক্যটা §৫-এ পরিষ্কার হবে।

সংজ্ঞা ১.১ (ফাংশন)

A আর B দুটি অশূন্য সেট। A থেকে B-তে একটি ফাংশন f হলো এমন একটি নিয়ম, যা A-এর প্রতিটি উপাদান a-কে B-এর ঠিক একটি উপাদানের সঙ্গে যুক্ত করে; সেই উপাদানকে লেখা হয় f(a). লেখা হয় f: A → B.

A হলো f-এর domain (সংজ্ঞার সেট), B হলো codomain (লক্ষ্যের সেট)। f(a) = b হলে b-কে বলে a-এর ছবি (image), আর a-কে b-এর একটি preimage (পূর্বছবি)। সব ছবির সেট f(A) = {f(a) : a ∈ A}-কে বলে f-এর image বা range.

সংজ্ঞাটি predicate logic-এ (পর্ব ০৪) লিখলে শর্ত দুটি স্পষ্ট হয়: ∀a ∈ A ∃b ∈ B (a-এর ছবি b), এবং সেই b অনন্য। এই “ঠিক একটি” হলো পর্ব ০৪-এর uniqueness quantifier ∃!. দুটি ফাংশন f, g সমান ধরা হয় ঠিক তখন, যখন তাদের domain এক, codomain এক, আর প্রতিটি a-তে f(a) = g(a). অর্থাৎ নিয়মের চেহারা আলাদা হলেও মান মিললে ফাংশন একই: ℤ-এর ওপর f(x) = (x + 1)² আর g(x) = x² + 2x + 1 একই ফাংশন।

Relation হিসেবে ফাংশন, আর ফাংশনের graph

“নিয়ম” শব্দটি একটু অস্পষ্ট। সেট তত্ত্বে একে নিখুঁত করা হয় ordered pair দিয়ে। Cartesian product A × B হলো সব (a, b) জোড়ার সেট; তার যেকোনো subset একটি relation (পর্ব ৫২ “Relation আর তার ধর্ম — reflexive, symmetric, transitive”-এ বিস্তারিত)। একটি ফাংশন হলো এমন relation R ⊆ A × B, যেখানে প্রতিটি a ∈ A-এর জন্য ঠিক একটি b আছে যাতে (a, b) ∈ R. এই জোড়াগুলোর সেটকেই বলে f-এর graph: {(a, f(a)) : a ∈ A}. স্কুলের xy-তলের লেখচিত্র আসলে এই সেটেরই ছবি, আর “উল্লম্ব রেখা পরীক্ষা” (প্রতিটি উল্লম্ব রেখা লেখচিত্রকে বড়জোর একবার ছোঁয়) সংজ্ঞার “ঠিক একটি”-রই চাক্ষুষ রূপ।

বাজারঘরের উদাহরণে: অর্ডার সেট O = {101, 102, 103, 104}, ক্রেতা সেট C = {রিনা, সজীব, তানিয়া, মাহিন}, আর f = {(101, রিনা), (102, সজীব), (103, রিনা), (104, তানিয়া)}. এখানে domain O, codomain C, কিন্তু image f(O) = {রিনা, সজীব, তানিয়া}: মাহিন codomain-এ আছেন, image-এ নেই। রিনার preimage দুটি: 101 আর 103. Codomain আর image-এর এই পার্থক্যই পরের অংশের onto ধারণার মূল।

পরীক্ষার হলে রোল নম্বর থেকে আসন নম্বরে যাওয়ার তালিকাটি ভাবুন। প্রতিটি রোলের ঠিক একটি আসন থাকতে হবে: কোনো পরীক্ষার্থী আসনবিহীন থাকলে বা একজনের দুটি আসন লেখা থাকলে তালিকাটি ভুল, ফাংশন নয়। কিন্তু হলে কিছু আসন খালি থাকতে পারে (image codomain-এর চেয়ে ছোট), আর দুজনকে ভুল করে একই আসন দেওয়া তালিকাটিকে অকেজো করে, যদিও তবু সেটি ফাংশন (one-to-one নয়)।

যা ফাংশন নয়, তার উদাহরণ থেকেও শেখার আছে। ℝ থেকে ℝ-এ “x-এর বর্গমূল” ফাংশন নয়, দুই কারণে: ঋণাত্মক x-এর কোনো বাস্তব বর্গমূল নেই (প্রতিটি উপাদানের ছবি থাকার শর্ত ভাঙে), আর ধনাত্মক x-এর দুটি বর্গমূল ±√x (ঠিক একটি ছবির শর্ত ভাঙে)। তাই √x লিখলে আমরা বুঝি f: [0, ∞) → ℝ, যেখানে f(x) হলো অঋণাত্মক বর্গমূলটি: domain ছোট করে আর একটি মান বেছে নিয়ে ফাংশন বানানো হয়েছে।

২ · One-to-one, onto আর bijection — প্রমাণ আর খণ্ডনের ছক

সংজ্ঞা ২.১

f: A → B একটি ফাংশন।

f one-to-one (injective), যদি ∀a1, a2 ∈ A (f(a1) = f(a2) → a1 = a2). Contrapositive-এ (পর্ব ০৮): আলাদা ইনপুট, আলাদা আউটপুট।

f onto (surjective), যদি ∀b ∈ B ∃a ∈ A (f(a) = b), অর্থাৎ image = codomain.

f একটি bijection (one-to-one correspondence), যদি f একই সঙ্গে one-to-one আর onto.

তিন ধরনের ফাংশন, arrow diagram-এ

বাম ডিম্বাকার domain A, ডান ডিম্বাকার codomain B; লাল তীর একই লক্ষ্যে মিলেছে, কমলা বিন্দু কোনো তীর পায়নি

Arrow diagrams: one-to-one, onto, bijection (ক) one-to-one, onto নয় A B 1 2 3 a b c d (খ) onto, one-to-one নয় A B 1 2 3 4 a b c (গ) bijection A B 1 2 3 a b c
ছবি ২.১ — one-to-one পরীক্ষা মানে B-তে কোনো বিন্দুতে দুটি তীর না মেলা (খ-তে a-তে দুটি লাল তীর); onto পরীক্ষা মানে B-তে কোনো বিন্দু তীরবিহীন না থাকা (ক-তে c). সসীম সেটে one-to-one হলে |A| ≤ |B|, onto হলে |A| ≥ |B|, আর bijection হলে |A| = |B|.

প্রমাণ আর খণ্ডনের ছক

সংজ্ঞাগুলো quantifier দিয়ে লেখা, তাই প্রমাণের কাঠামোও quantifier থেকেই আসে। এটি মুখস্থ রাখার মতো চারটি ছক:

লক্ষ্যছক
f one-to-one প্রমাণযেকোনো a1, a2 ∈ A নিন, ধরুন f(a1) = f(a2); বীজগণিত দিয়ে a1 = a2 দেখান
f one-to-one নয় দেখানোএকটি নির্দিষ্ট জোড়া a1 ≠ a2 দিন যাতে f(a1) = f(a2); একটি counterexample-ই যথেষ্ট
f onto প্রমাণযেকোনো b ∈ B নিন; f(a) = b সমাধান করে a বের করুন; a সত্যিই A-তে আছে কি না যাচাই করুন
f onto নয় দেখানোএকটি নির্দিষ্ট b ∈ B দিন, আর প্রমাণ করুন কোনো a ∈ A-তে f(a) = b হয় না

খেয়াল করুন onto-র সংজ্ঞায় quantifier-এর ক্রম ∀b ∃a: প্রতিটি b-এর জন্য a আলাদা হতে পারে। ক্রম উল্টে ∃a ∀b লিখলে দাঁড়ায় “একটি a সব b-তে যায়”, যা B-তে একাধিক উপাদান থাকলেই মিথ্যা; পর্ব ০৫-এর শিক্ষা এখানে সরাসরি খাটে। আর onto ছকের শেষ বাক্যটি (a সত্যিই A-তে আছে কি না) সবচেয়ে বেশি বাদ পড়ে: নিচের stepper-এর শেষ ধাপে দেখবেন, একই সূত্র ℝ-এ onto, ℤ-এ নয়।

উপপাদ্য ২.২

f: ℝ → ℝ, f(x) = 3x + 5 একটি bijection, আর তার inverse f−1(y) = (y − 5)/3. কিন্তু একই সূত্রে g: ℤ → ℤ, g(x) = 3x + 5 one-to-one, onto নয়।

1f: ℝ → ℝ, f(x) = 3x + 52one-to-one: f(a)=f(b) ⇒ a=b ?3onto: ∀y ∃x f(x) = y ?4inverse: (y − 5)/35g: ℤ → ℤ, একই সূত্র
যুক্তি আর নমুনা
এই ধাপের যুক্তি
নমুনা
  1. লক্ষ্য ভাগ করা। Bijection মানে দুটি আলাদা দাবি: one-to-one আর onto. প্রতিটির নিজস্ব ছক আছে। নমুনা হিসেবে f(2) = 3 · 2 + 5 = 11 আর f(−1) = 2.
  2. One-to-one. যেকোনো a, b ∈ ℝ নিই আর ধরি f(a) = f(b), অর্থাৎ 3a + 5 = 3b + 5. দুই পাশ থেকে 5 বাদ দিয়ে 3a = 3b, তারপর 3 দিয়ে ভাগ (3 ≠ 0) করে a = b. ছকের প্রথম সারি পূরণ হলো।
  3. Onto. যেকোনো y ∈ ℝ নিই। 3x + 5 = y সমাধান করে x = (y − 5)/3. এটি একটি বাস্তব সংখ্যা, তাই domain-এ আছে, আর f(x) = 3 · (y − 5)/3 + 5 = y. যেমন y = 11-এ x = 2, y = 0-এ x = −5/3. দুই দাবি মিলে f একটি bijection.
  4. Inverse. Onto ধাপে যে সূত্রটি পেলাম, সেটিই f−1(y) = (y − 5)/3. যাচাই দুই দিকেই: f−1(f(x)) = ((3x + 5) − 5)/3 = x, আর f(f−1(y)) = y. যেমন f−1(20) = 5, আর f(5) = 20. ∎
  5. একই সূত্র, ভিন্ন domain. g: ℤ → ℤ-এ one-to-one প্রমাণ হুবহু খাটে। কিন্তু onto ভাঙে: y = 0-এর জন্য x = −5/3, যা পূর্ণসংখ্যা নয়। g-এর image শুধু 3 দিয়ে ভাগ করলে ভাগশেষ 2 থাকে এমন সংখ্যা: …, −1, 2, 5, 8, …. Domain আর codomain ফাংশনের অংশ, শুধু সূত্র নয়।

খণ্ডনের উদাহরণ: x² কোথায় হোঁচট খায়

এবার উল্টো কাজ: একটি দাবি মিথ্যা দেখানো। f: ℤ → ℤ, f(x) = x². এখানে দুটি দাবিই ভাঙে, আর domain বা codomain বদলে তাদের কীভাবে সারানো যায়, তাও দেখব।

1f: ℤ → ℤ, f(x) = x²2x : −3 −2 −1 0 1 2 33x²:  9  4  1 0 1 4 94one-to-one? একটি জোড়া খুঁজি5onto? 2-এর preimage?6সারানো: ℕ → বর্গসংখ্যা
যা খুঁজছি আর যা পেলাম
খণ্ডনের লক্ষ্য
পাওয়া তথ্য
  1. কী দেখাতে হবে। “one-to-one” একটি ∀ দাবি, তাই তার অস্বীকার ∃: আলাদা দুটি a, b যাদের f(a) = f(b). “onto”-র অস্বীকার: একটি y, যার কোনো preimage নেই। দুটির জন্যই একটি নির্দিষ্ট উদাহরণ যথেষ্ট।
  2. ছোট মান দেখা। x = −3 থেকে 3 পর্যন্ত x²: 9, 4, 1, 0, 1, 4, 9. প্যাটার্ন চোখে পড়ে: x আর −x একই মান দেয়, শুধু 0 একা।
  3. Counterexample. a = −2, b = 2: a ≠ b, অথচ f(a) = f(b) = 4. তাই f one-to-one নয়। একটি জোড়াই পুরো ∀ দাবি ভেঙে দেয়।
  4. অপ্রাপ্য মান। y = 2 নিই। x ∈ {−1, 0, 1} হলে x² ∈ {0, 1}; আর |x| ≥ 2 হলে x² ≥ 4. তাই কোনো পূর্ণসংখ্যার বর্গ 2 নয়, f onto নয়। (ঋণাত্মক y, যেমন −1, আরও সহজ উদাহরণ।)
  5. সারানো। Domain ℕ = {0, 1, 2, …} করলে one-to-one: a² = b² থেকে (a − b)(a + b) = 0, তাই a = b অথবা a = −b; a, b ≥ 0 হলে দ্বিতীয়টি থেকেও a = b = 0. Codomain-কে বর্গসংখ্যার সেট {0, 1, 4, 9, …} করলে onto-ও, অর্থাৎ bijection. ∎

একটি সতর্কতা: ছোট মানের তালিকা দেখে খণ্ডন করা যায় (একটি উদাহরণই প্রমাণ), কিন্তু প্রমাণ করা যায় না। “x = −3 থেকে 3 পর্যন্ত 3x + 5-এর মানগুলো আলাদা, তাই one-to-one” ভুল যুক্তি; সসীম পরীক্ষা অসীম domain-এর জন্য কিছু প্রমাণ করে না। তবে domain নিজেই সসীম হলে সব মান দেখে নেওয়া পূর্ণ প্রমাণ (পর্ব ০৯-এর exhaustive proof). নিচের ব্লক ঠিক তাই করে: ছোট সসীম ফাংশনের পুরো টেবিল বানিয়ে image গুনে শ্রেণি ঠিক করে, আর one-to-one না হলে একটি সাক্ষী জোড়া, onto না হলে অপ্রাপ্য মানগুলো ছাপে। প্রথমবার চালালে ব্রাউজারে Python (Pyodide) নামতে কয়েক সেকেন্ড লাগে।

def classify(name, rule, domain, codomain):
    f = {x: rule(x) for x in domain}              # the function as a table
    assert all(y in codomain for y in f.values()), "value outside codomain"
    image = set(f.values())
    one_to_one = len(image) == len(f)             # no two inputs share an output
    onto = image == set(codomain)                 # every codomain element is hit
    kind = ("bijection" if one_to_one and onto else
            "one-to-one only" if one_to_one else
            "onto only" if onto else "neither")
    print(f"{name:13} image={sorted(image)}  -> {kind}")
    if not one_to_one:                            # show a witness pair
        seen = {}
        for x, y in f.items():
            if y in seen:
                print(f"{'':13} witness: f({seen[y]}) = f({x}) = {y}")
                break
            seen[y] = x
    if not onto:
        print(f"{'':13} missed: {sorted(set(codomain) - image)}")

Z7 = range(7)
classify("(3x+5) mod 7", lambda x: (3 * x + 5) % 7, Z7, Z7)
classify("x*x mod 7", lambda x: x * x % 7, Z7, Z7)
classify("x // 2", lambda x: x // 2, range(10), range(5))
classify("2x", lambda x: 2 * x, range(5), range(10))
classify("x*x on -3..3", lambda x: x * x, range(-3, 4), range(10))

আসল আউটপুট (ব্রাউজারের Python):

(3x+5) mod 7  image=[0, 1, 2, 3, 4, 5, 6]  -> bijection
x*x mod 7     image=[0, 1, 2, 4]  -> neither
              witness: f(3) = f(4) = 2
              missed: [3, 5, 6]
x // 2        image=[0, 1, 2, 3, 4]  -> onto only
              witness: f(0) = f(1) = 0
2x            image=[0, 2, 4, 6, 8]  -> one-to-one only
              missed: [1, 3, 5, 7, 9]
x*x on -3..3  image=[0, 1, 4, 9]  -> neither
              witness: f(-1) = f(1) = 1
              missed: [2, 3, 5, 6, 7, 8]

মূল কৌশল এক লাইনের: image-এর আকার domain-এর সমান হলে one-to-one, image codomain-এর সমান হলে onto. প্রথম সারিতে ঘড়ির মতো mod 7 গণিতে 3x + 5 সাতটি মানের প্রতিটি একবার করে দেয়, তাই bijection; পর্ব ২৭ “Classical cipher — shift, affine আর frequency analysis”-এর affine cipher ঠিক এই ধর্মের ওপর দাঁড়িয়ে, কারণ bijection না হলে গোপন বার্তা ফেরত পড়া যেত না। বর্গ mod 7-এ 3² = 9 ≡ 2 আর 4² = 16 ≡ 2, তাই সাক্ষী (3, 4); x² mod 7 মাত্র চারটি মান নেয়। x // 2 (দশটি অর্ডার, প্রতি ভ্যানে দুটি) onto কিন্তু one-to-one নয়; 2x উল্টোটা। শেষ সারি stepper-এর x² টেবিলই। এখানে domain সসীম আর পুরোটা দেখা হয়েছে, তাই প্রতিটি ফল একটি পূর্ণ প্রমাণ; কিন্তু “3x + 5 on ℝ”-এর মতো অসীম domain-এ এই কৌশল শুধু খণ্ডন করতে পারে।

৩ · Inverse আর composition — উল্টো পথ আর পরপর দুই ধাপ

বাজারঘরের ডেলিভারি ব্যবস্থায় দুটি ধাপ। প্রথমে অর্ডারের ব্যাচ (1, 2, 3, 4) একজন courier-কে দেওয়া হয় (a, b, c, d), তারপর প্রতিটি courier তাঁর জেলার hub-এ যান (DHK ঢাকা, CTG চট্টগ্রাম, KHL খুলনা, RAJ রাজশাহী)। “ব্যাচ থেকে hub” জানতে দুটি ফাংশন পরপর লাগাতে হয়। আর কোনো hub-এ একটি ভুল পার্সেল পাওয়া গেলে উল্টো পথে “কোন ব্যাচের?” জানতে চাই। এই দুই কাজ composition আর inverse.

সংজ্ঞা ৩.১

f: A → B আর g: B → C হলে তাদের composition g∘f: A → C, (g∘f)(a) = g(f(a)). “g∘f” পড়া হয় ডান থেকে বামে: আগে f, পরে g. শর্ত: f-এর image g-এর domain-এর ভেতরে থাকতে হবে।

A-এর ওপর identity function ιA(a) = a. f: A → B-এর inverse হলো এমন একটি ফাংশন f−1: B → A, যাতে f−1∘f = ιA আর f∘f−1 = ιB; অর্থাৎ f(a) = b ⇔ f−1(b) = a. এমন ফাংশন থাকলে f-কে invertible বলে।

সাবধান: f−1(b) লেখাটি দুই অর্থে চলে। inverse ফাংশন থাকলে এটি একটি উপাদান; কিন্তু অনেক বইয়ে যেকোনো f-এর জন্য f−1(S) = {a : f(a) ∈ S} লেখা হয়, যা একটি সেট (S-এর preimage). আর 1/f(x) একেবারেই অন্য জিনিস; sin−1 x মানে arcsin x, (sin x)−1 নয়। প্রসঙ্গ দেখে অর্থ বুঝতে হয়।

উপপাদ্য ৩.২

f: A → B-এর inverse আছে ⇔ f একটি bijection. থাকলে inverse অনন্য, আর f−1 নিজেও একটি bijection.

প্রমাণ:

  1. (⇐) ধরি f bijection. যেকোনো b ∈ B নিই। f onto, তাই কোনো a-তে f(a) = b; f one-to-one, তাই এমন a ঠিক একটি। এই a-কেই f−1(b) সংজ্ঞায়িত করি: প্রতিটি b-এর ঠিক একটি ছবি, তাই এটি সত্যিই একটি ফাংশন। — onto দেয় অস্তিত্ব, one-to-one দেয় অনন্যতা
  2. সংজ্ঞা থেকেই f(f−1(b)) = b, আর f−1(f(a)) = a, কারণ f(a)-এর অনন্য preimage a. — সংজ্ঞা
  3. (⇒) ধরি g: B → A এমন যে g∘f = ιA আর f∘g = ιB. One-to-one: f(a1) = f(a2) হলে দুই পাশে g লাগিয়ে a1 = g(f(a1)) = g(f(a2)) = a2. — g∘f = ιA
  4. Onto: যেকোনো b ∈ B-এর জন্য a = g(b) নিলে f(a) = f(g(b)) = b. — f∘g = ιB
  5. অনন্যতা: g আর h দুটিই inverse হলে প্রতিটি b-তে g(b) = g(f(h(b))) = h(b). আর f−1-এর inverse f নিজে, তাই f−1-ও bijection. ∎
উপপাদ্য ৩.৩

(ক) Associativity: f: A → B, g: B → C, h: C → D হলে h∘(g∘f) = (h∘g)∘f.
(খ) f আর g দুটিই one-to-one হলে g∘f one-to-one.
(গ) f আর g দুটিই onto হলে g∘f onto.
(ঘ) f আর g দুটিই bijection হলে g∘f bijection, আর (g∘f)−1 = f−1∘g−1.

প্রমাণ:

  1. (ক) দুই পাশের domain A, codomain D. যেকোনো a ∈ A-তে (h∘(g∘f))(a) = h((g∘f)(a)) = h(g(f(a))), আর ((h∘g)∘f)(a) = (h∘g)(f(a)) = h(g(f(a))). প্রতিটি a-তে মান সমান, তাই ফাংশন দুটি সমান। — composition-এর সংজ্ঞা, ফাংশনের সমতা
  2. (খ) ধরি (g∘f)(a1) = (g∘f)(a2), অর্থাৎ g(f(a1)) = g(f(a2)). g one-to-one, তাই f(a1) = f(a2). f one-to-one, তাই a1 = a2. — দুবার one-to-one-এর সংজ্ঞা
  3. (গ) যেকোনো c ∈ C নিই। g onto, তাই কোনো b ∈ B-তে g(b) = c. f onto, তাই কোনো a ∈ A-তে f(a) = b. তাহলে (g∘f)(a) = g(b) = c. — দুবার onto-র সংজ্ঞা
  4. (ঘ) (খ) আর (গ) থেকে g∘f bijection. এখন (f−1∘g−1)∘(g∘f) = f−1∘(g−1∘g)∘f = f−1∘ιB∘f = f−1∘f = ιA, associativity (ক) দিয়ে বন্ধনী সরিয়ে; অন্য দিকও একইভাবে। উপপাদ্য ৩.২-এর অনন্যতায় এটিই (g∘f)−1. ∎

(ঘ)-এর ক্রম উল্টে যাওয়া মনে রাখার একটি ঘরোয়া উপায়: আগে মোজা, পরে জুতা পরেন; খোলার সময় আগে জুতা, পরে মোজা। আর composition সাধারণত বিনিময়যোগ্য (commutative) নয়: ℝ-এ f(x) = x + 1, g(x) = 2x হলে (g∘f)(x) = 2x + 2, কিন্তু (f∘g)(x) = 2x + 1; x = 0-তেই 2 ≠ 1.

1f: 1→c  2→a  3→d  4→b2g: a→KHL b→DHK c→RAJ d→CTG3(g∘f)(x) = g(f(x))4(g∘f)⁻¹: সব তীর উল্টো5যাচাই: f⁻¹(g⁻¹(z))
x = ব্যাচ, z = hub
x
f(x)
g(f(x))
  1. দুটি ফাংশন। f: {1, 2, 3, 4} → {a, b, c, d} ব্যাচকে courier দেয়, g: {a, b, c, d} → {DHK, CTG, KHL, RAJ} courier-কে hub দেয়। দুটিতেই চারটি আলাদা আউটপুট, সব লক্ষ্য ছোঁয়া: দুটিই bijection.
  2. একটি মান ধরে। (g∘f)(1) = g(f(1)) = g(c) = RAJ. ডান থেকে বামে: আগে f, তারপর তার ফলের ওপর g. f(1)-এর মান না জেনে g লাগানো যায় না।
  3. পুরো composition. g∘f: 1 → RAJ, 2 → g(a) = KHL, 3 → g(d) = CTG, 4 → g(b) = DHK. চারটি আলাদা hub, সবগুলো ছোঁয়া; উপপাদ্য ৩.৩(ঘ) যা বলেছিল, g∘f bijection.
  4. Inverse. প্রতিটি তীর উল্টে দিই: (g∘f)−1: DHK → 4, CTG → 3, KHL → 2, RAJ → 1. টেবিলে z-কে DHK, CTG, KHL, RAJ ক্রমে সাজালে x-এর সারি 4, 3, 2, 1. RAJ hub-এ ভুল পার্সেল মানে ব্যাচ 1.
  5. মোজা-জুতা যাচাই। f−1(g−1(DHK)) = f−1(b) = 4; CTG → d → 3; KHL → a → 2; RAJ → c → 1. চারটিই আগের ধাপের সঙ্গে মেলে: (g∘f)−1 = f−1∘g−1, ক্রম উল্টো। ∎

উপপাদ্য ৩.৩(খ)-এর উল্টো সত্য নয়। g∘f one-to-one হলে f অবশ্যই one-to-one (অনুশীলন ১), কিন্তু g না-ও হতে পারে: f: {1} → {a, b}, f(1) = a, আর g: {a, b} → {x}, দুটিই x-এ। g∘f-এর domain-এ একটিই উপাদান, তাই সেটি one-to-one, অথচ g(a) = g(b). একইভাবে g∘f onto হলে g onto, কিন্তু f না-ও হতে পারে।

৪ · Floor আর ceiling — পূর্ণসংখ্যায় নামানো আর ওঠানো

বাজারঘরের গুদামে ২৫০টি সাবান, একটি কার্টনে ধরে ২৪টি। কয়টি কার্টন লাগবে? 250/24 ≈ 10.42, কিন্তু 10.42টি কার্টন কেনা যায় না; লাগবে 11টি। আর কয়টি কার্টন পুরো ভরবে? 10টি। প্রথমটি ওপরের দিকে গোল করা, দ্বিতীয়টি নিচের দিকে। এই দুই কাজের নাম ceiling আর floor, আর কম্পিউটার বিজ্ঞানে এরা সর্বত্র: byte, পাতা, ব্লক, অ্যারের মাঝের index, গাছের উচ্চতা।

সংজ্ঞা ৪.১

বাস্তব সংখ্যা x-এর floor ⌊x⌋ হলো x-এর সমান বা ছোট সবচেয়ে বড় পূর্ণসংখ্যা; ceiling ⌈x⌉ হলো x-এর সমান বা বড় সবচেয়ে ছোট পূর্ণসংখ্যা। দুটিই ℝ → ℤ ফাংশন। সমতুল্যভাবে, যেকোনো পূর্ণসংখ্যা n-এর জন্য:

⌊x⌋ = n ⇔ n ≤ x < n + 1     ⌈x⌉ = n ⇔ n − 1 < x ≤ n

উদাহরণ: ⌊3.7⌋ = 3, ⌈3.7⌉ = 4, ⌊5⌋ = ⌈5⌉ = 5. ঋণাত্মকে সাবধান: ⌊−3.5⌋ = −4 (−3 নয়, কারণ −3 > −3.5), আর ⌈−3.5⌉ = −3. দুটিই onto (প্রতিটি পূর্ণসংখ্যা n নিজের ছবি) কিন্তু one-to-one নয় (⌊0.2⌋ = ⌊0.7⌋ = 0). এদের graph দেখতে সিঁড়ির মতো:

y = ⌊x⌋-এর graph

ভরাট বিন্দু ধাপের অংশ, ফাঁপা বিন্দু অংশ নয়

Graph of the floor function −3 −2 −1 1 2 3 −2 −1 1 2 y = ⌊x⌋
ছবি ৪.১ — প্রতিটি ধাপ [n, n + 1) ব্যবধানে উচ্চতা n-এ: বাঁ প্রান্ত অন্তর্ভুক্ত, ডান প্রান্ত নয়। উল্লম্ব রেখা পরীক্ষা পাস করে (ফাংশন), কিন্তু অনুভূমিক রেখা y = 1 একটি পুরো ধাপ ছোঁয় (one-to-one নয়)।
উপপাদ্য ৪.২

যেকোনো বাস্তব x আর পূর্ণসংখ্যা n-এর জন্য (ক) ⌊x + n⌋ = ⌊x⌋ + n; (খ) ⌈x⌉ = −⌊−x⌋; (গ) n = ⌊n/2⌋ + ⌈n/2⌉.

প্রমাণ:

  1. (ক) m = ⌊x⌋ ধরি। সংজ্ঞা ৪.১ অনুযায়ী m ≤ x < m + 1. — floor-এর সংজ্ঞা
  2. সব পাশে n যোগ করি: m + n ≤ x + n < (m + n) + 1. m + n একটি পূর্ণসংখ্যা। — অসমতায় যোগ, পূর্ণসংখ্যার closure
  3. সংজ্ঞা ৪.১-এর “⇔” দিক থেকে ⌊x + n⌋ = m + n = ⌊x⌋ + n. — সংজ্ঞা
  4. (খ) m = ⌊−x⌋ ধরি: m ≤ −x < m + 1. −1 দিয়ে গুণ করলে চিহ্ন উল্টায়: −m − 1 < x ≤ −m. এটি ঠিক ⌈x⌉ = −m-এর সংজ্ঞা, তাই ⌈x⌉ = −⌊−x⌋. — ceiling-এর সংজ্ঞা
  5. (গ) দুই ক্ষেত্র (পর্ব ০৯-এর proof by cases). n = 2k জোড় হলে ⌊k⌋ + ⌈k⌉ = k + k = n. n = 2k + 1 বিজোড় হলে n/2 = k + 1/2, তাই ⌊n/2⌋ = k, ⌈n/2⌉ = k + 1, যোগফল 2k + 1 = n. ∎

(ক)-এর প্রমাণে n পূর্ণসংখ্যা হওয়াটাই আসল: n-এর জায়গায় বাস্তব y বসালে দাবিটি ভাঙে। x = y = 0.5 নিলে ⌊x + y⌋ = ⌊1⌋ = 1, কিন্তু ⌊x⌋ + ⌊y⌋ = 0 + 0 = 0. সঠিক কথা হলো ⌊x⌋ + ⌊y⌋ ≤ ⌊x + y⌋ ≤ ⌊x⌋ + ⌊y⌋ + 1 (অনুশীলন ৪ দেখুন)।

প্রয়োগ: বাক্স, পাতা আর byte

প্রতিটি বাক্সে k-টি করে n-টি জিনিস রাখতে সবচেয়ে কম ⌈n/k⌉টি বাক্স লাগে; কারণ b-টি বাক্সে বড়জোর bk-টি জিনিস ধরে, তাই bk ≥ n, অর্থাৎ b ≥ n/k, আর b পূর্ণসংখ্যা। এই এক নিয়ম থেকেই: ৪৭টি অর্ডার, প্রতি পাতায় ১০টি দেখালে ⌈47/10⌉ = 5 পাতা; 1001 bit রাখতে ⌈1001/8⌉ = 126 byte; একটি ধনাত্মক পূর্ণসংখ্যা n লিখতে ⌊log2 n⌋ + 1টি binary অঙ্ক (n = 1000 হলে 10টি)। পর্ব ১৩-এর দ্রুত ঘাতের ঘোরার সংখ্যাও ঠিক এই রাশি। প্রোগ্রামে float ছাড়া ⌈n/k⌉ পেতে, k > 0 আর n ≥ 0 হলে, ⌊(n + k − 1)/k⌋ লেখা হয়; Python-এ (n + k - 1) // k বা -(-n // k), দ্বিতীয়টি আসলে (খ)-এর সূত্র।

Python-এর // সত্যিই floor: -7 // 2 হলো −4. কিন্তু int(-3.5) শূন্যের দিকে কাটে, ফল −3; C, Java-র পূর্ণসংখ্যা ভাগও শূন্যের দিকে কাটে। ধনাত্মক সংখ্যায় পার্থক্য নেই, ঋণাত্মকে আছে। নিচের ব্লক Fraction (নির্ভুল ভগ্নাংশ, float-এর গোলমাল ছাড়া) দিয়ে উপরের identity-গুলো হাজার হাজার মানে পরীক্ষা করে, আর একটি মিথ্যা দাবির counterexample খুঁজে দেয়।

from fractions import Fraction
from math import floor, ceil

xs = [Fraction(k, 12) for k in range(-60, 61)]    # exact rationals -5 .. 5
ns = range(-5, 6)

def test(name, claim, pairs):
    bad = next((p for p in pairs if not claim(*p)), None)
    verdict = "holds" if bad is None else f"FAILS at {tuple(str(v) for v in bad)}"
    print(f"{name:30} {len(pairs):6} cases: {verdict}")

test("floor(x+n) = floor(x)+n", lambda x, n: floor(x + n) == floor(x) + n,
     [(x, n) for x in xs for n in ns])
test("ceil(x) = -floor(-x)", lambda x: ceil(x) == -floor(-x), [(x,) for x in xs])
test("floor(x+y) = floor(x)+floor(y)", lambda x, y: floor(x + y) == floor(x) + floor(y),
     [(x, y) for x in xs for y in xs])
test("floor(2x) = floor(x)+floor(x+1/2)",
     lambda x: floor(2 * x) == floor(x) + floor(x + Fraction(1, 2)), [(x,) for x in xs])
test("n//2 + ceil(n/2) = n", lambda n: n // 2 + ceil(Fraction(n, 2)) == n,
     [(n,) for n in range(-100, 101)])
test("ceil(n/k) = (n+k-1)//k, k>0", lambda n, k: ceil(Fraction(n, k)) == (n + k - 1) // k,
     [(n, k) for n in range(0, 201) for k in range(1, 21)])

print("orders 47, 10 per page   -> pages:", -(-47 // 10))
print("items 250, 24 per box    -> boxes:", -(-250 // 24))
print("1001 bits                -> bytes:", -(-1001 // 8))
print("-7 // 2 =", -7 // 2, "  int(-3.5) =", int(-3.5), "  floor(-3.5) =", floor(-3.5))

আসল আউটপুট (ব্রাউজারের Python):

floor(x+n) = floor(x)+n          1331 cases: holds
ceil(x) = -floor(-x)              121 cases: holds
floor(x+y) = floor(x)+floor(y)  14641 cases: FAILS at ('-59/12', '-49/12')
floor(2x) = floor(x)+floor(x+1/2)    121 cases: holds
n//2 + ceil(n/2) = n              201 cases: holds
ceil(n/k) = (n+k-1)//k, k>0      4020 cases: holds
orders 47, 10 per page   -> pages: 5
items 250, 24 per box    -> boxes: 11
1001 bits                -> bytes: 126
-7 // 2 = -4   int(-3.5) = -3   floor(-3.5) = -4

প্রমাণিত তিনটি identity আর ⌈n/k⌉-এর পূর্ণসংখ্যা সূত্র প্রতিটি পরীক্ষিত মানে টিকেছে। মিথ্যা দাবিটি প্রথম যে জোড়ায় ভাঙল তা পরীক্ষা করুন: x = −59/12 ≈ −4.92, y = −49/12 ≈ −4.08. ⌊x⌋ = ⌊y⌋ = −5, যোগফল −10; কিন্তু x + y = −108/12 = −9, তাই ⌊x + y⌋ = −9. দুই ভগ্নাংশ-অংশ (1/12 আর 11/12) মিলে ঠিক 1 হয়েছে, তাই একটি পূর্ণসংখ্যা “বাড়তি” এল। চতুর্থ সারিটি একটি কম পরিচিত সত্য identity (Hermite-এর identity-র n = 2 ক্ষেত্র); এখানে শুধু পরীক্ষিত, প্রমাণ নয়, তাই উপপাদ্য বলার আগে প্রমাণ চাই (চেষ্টা করে দেখুন: x-এর ভগ্নাংশ-অংশ 1/2-এর কম না বেশি, দুই ক্ষেত্র)। শেষ লাইনে float ছাড়া floor আর শূন্যের দিকে কাটার পার্থক্য।

৫ · Partial function — যে প্রোগ্রাম সব ইনপুটে উত্তর দেয় না

সংজ্ঞা ১.১ কঠোর: domain-এর প্রতিটি উপাদানের ছবি থাকতে হবে। কিন্তু প্রোগ্রামিংয়ের ফাংশন প্রায়ই এই শর্ত ভাঙে। 1 / x x = 0-তে exception দেয়; একটি লুপ কিছু ইনপুটে কখনো থামে না। এদের জন্য গণিতে আলাদা নাম আছে।

সংজ্ঞা ৫.১

A থেকে B-তে একটি partial function f হলো একটি subset D ⊆ A (f-এর সংজ্ঞার domain) আর একটি ফাংশন f: D → B. a ∈ A − D হলে বলা হয় f(a) অসংজ্ঞায়িত। D = A হলে f একটি total function, অর্থাৎ সাধারণ ফাংশন।

উদাহরণ: f: ℤ → ℝ, f(n) = 1/n হলো partial, D = ℤ − {0}. g: ℝ → ℝ, g(x) = √x হলো partial, D = [0, ∞). আর পর্ব ১৩-এর একটি প্রোগ্রাম মনে করুন: while x != 0: x = x - 2, যা জোড় অঋণাত্মক x-এ থামে, বিজোড়ে চিরকাল চলে। “ইনপুট থেকে শেষ অবস্থা” ফাংশনটি ℤ থেকে ℤ-এ partial: এর সংজ্ঞার domain জোড় অঋণাত্মক সংখ্যা।

প্রতিটি প্রোগ্রাম আসলে একটি partial function গণনা করে: যে ইনপুটে থামে না, সেখানে ফল অসংজ্ঞায়িত। আর কোন ইনপুটে থামবে, অর্থাৎ D ঠিক কোনটি, তা সাধারণভাবে কোনো algorithm দিয়ে নির্ধারণ করা যায় না: এটিই halting problem, যার প্রমাণ আসবে পর্ব ৭৫ “Turing machine-এর পরিচয় — computability আর halting problem”-এ, আর তার পেছনের গণনা-তত্ত্ব আসবে পর্ব ১৮-এ। পরে একটি আলাদা Theory of Computation কোর্সেও এ বিষয়ে বিস্তারিত থাকবে।

প্রোগ্রামিংয়ে partial function-কে total বানানোর দুটি প্রচলিত কৌশল আছে, দুটিই গণিতের ভাষায় পরিষ্কার। এক, domain ছোট করা: precondition লেখা (“n ≠ 0 হলে ডাকুন”), যা পর্ব ১৩-এর Hoare logic. দুই, codomain বড় করা: B-এর সঙ্গে একটি বিশেষ মান যোগ করা, যেমন Python-এর None বা dict.get(key), যা চাবি না থাকলে None দেয়। তখন f: A → B ∪ {None} একটি total function. d[key] আর d.get(key)-এর পার্থক্য ঠিক partial আর total-এর পার্থক্য।

Python-এর একটি dict নিজেই একটি সসীম ফাংশন: চাবির সেট domain, আর প্রতিটি চাবির ঠিক একটি মান (একই চাবিতে দ্বিতীয়বার লিখলে আগের মান মুছে যায়, ঠিক “ঠিক একটি ছবি” শর্তের মতো)। Python কোর্সের পর্ব ১৬ dictionary-র ব্যবহার শেখায়; §৭-এর ব্লকে dict দিয়ে one-to-one পরীক্ষা আর inverse-এর ফাঁদ দেখবেন।

৬ · ফাংশন গোনা — nm, n!/(n − m)! আর pigeonhole

বাজারঘরে ৩টি নতুন অর্ডার, আর ৪ জন courier. প্রতিটি অর্ডার একজনকে দিতে হবে। কত উপায়ে? আর যদি শর্ত থাকে একজন courier একটির বেশি অর্ডার পাবেন না? প্রথম প্রশ্নের উত্তর মোট ফাংশনের সংখ্যা, দ্বিতীয়টির one-to-one ফাংশনের সংখ্যা। গোনার নিয়মগুলো বিস্তারিত আসবে মডিউল ৬-এ (পর্ব ৩১ “গণনার মূল নিয়ম — যোগ, গুণ, বিয়োগ আর ভাগ”); এখানে শুধু গুণের নিয়ম লাগবে: পরপর স্বাধীন বাছাইয়ে মোট উপায় প্রতিটি ধাপের উপায়ের গুণফল।

উপপাদ্য ৬.১

|A| = m আর |B| = n (m, n ≥ 1) হলে:
(ক) A থেকে B-তে মোট ফাংশন nmটি।
(খ) one-to-one ফাংশন n(n − 1)(n − 2)⋯(n − m + 1) = n!/(n − m)!টি, যদি m ≤ n; আর m > n হলে একটিও নেই।
(গ) m = n হলে bijection n!টি; এরা A-এর permutation.

প্রমাণ: A = {a1, …, am} লিখি। একটি ফাংশন মানে ক্রমে f(a1), …, f(am) বেছে নেওয়া।

  1. (ক) প্রতিটি f(ai)-এর জন্য n-টি পছন্দ, আর একটির বাছাই অন্যটিকে সীমিত করে না। গুণের নিয়মে n · n ⋯ n (m বার) = nm. — গুণের নিয়ম
  2. (খ) One-to-one হলে f(a1)-এর n-টি পছন্দ, f(a2)-এর n − 1টি (f(a1) বাদ), …, f(am)-এর n − m + 1টি (আগের m − 1টি আলাদা মান বাদ)। গুণফল n!/(n − m)!. — গুণের নিয়ম
  3. m > n হলে ধরি একটি one-to-one f আছে। তাহলে f(a1), …, f(am) m-টি আলাদা উপাদান B-তে, অথচ B-তে মাত্র n < m-টি উপাদান: বিরোধ। — proof by contradiction
  4. (গ) m = n বসালে n!/0! = n!. সসীম সেটে |A| = |B| হলে one-to-one ফাংশনের image-এ n-টি আলাদা উপাদান, অর্থাৎ পুরো B: তাই প্রতিটি one-to-one ফাংশনই onto, অর্থাৎ bijection. ∎

ধাপ ৩-এর যুক্তিটির একটি নিজস্ব নাম আছে: pigeonhole principle (কবুতরের খোপের নীতি)। ফাংশনের ভাষায়: |A| > |B| হলে A থেকে B-তে কোনো ফাংশন one-to-one নয়, অর্থাৎ অন্তত দুটি উপাদান একই ছবি পায়। পর্ব ৩২ “Pigeonhole principle — সহজ থেকে generalized”-এ এর শক্তিশালী রূপ আসবে। ধাপ ৪-এর শেষ বাক্যটিও গুরুত্বপূর্ণ: সমান আকারের সসীম সেটে one-to-one ⇔ onto. অসীম সেটে এটি মিথ্যা: f: ℕ → ℕ, f(n) = n + 1 one-to-one, কিন্তু 0 কারো ছবি নয়। এই পার্থক্যই পর্ব ১৮ “Cardinality — countable, uncountable আর Cantor-এর diagonal”-এর শুরু।

Onto ফাংশন গোনা কঠিনতর: কোনো লক্ষ্য খালি না থাকার শর্ত সরাসরি গুণের নিয়মে আসে না। উত্তর হলো

onto(m, n) = ∑k=0n (−1)k C(n, k) (n − k)m

যেখানে C(n, k) হলো n-টি থেকে k-টি বাছাইয়ের উপায় (পর্ব ৩৩-এ)। সূত্রটির প্রমাণ inclusion–exclusion দিয়ে, পর্ব ৪৯ “Inclusion–exclusion আর derangement”-এ; এখানে আমরা শুধু brute force দিয়ে সংখ্যাগুলো মেলাব। নিচের ব্লক সব m-tuple বানিয়ে (প্রতিটি tuple একটি ফাংশন) image-এর আকার গোনে, আর তিনটি সূত্রের সঙ্গে তুলনা করে।

from itertools import product
from math import comb, factorial, perm
import time

def brute(m, n):                    # every function {0..m-1} -> {0..n-1} is an m-tuple
    total = inj = onto = 0
    for t in product(range(n), repeat=m):
        total += 1
        k = len(set(t))             # size of the image
        inj += (k == m)
        onto += (k == n)
    return total, inj, onto

def formulas(m, n):
    total = n ** m
    inj = perm(n, m)                # n!/(n-m)!, and 0 when m > n
    onto = sum((-1) ** k * comb(n, k) * (n - k) ** m for k in range(n + 1))
    return total, inj, onto

print(" m  n | brute (all, 1-1, onto) | formulas")
for m, n in [(2, 4), (3, 2), (3, 3), (3, 4), (4, 3), (4, 5), (5, 3)]:
    b, f = brute(m, n), formulas(m, n)
    print(f"{m:2} {n:2} | {str(b):22} | {f}  {'match' if b == f else 'MISMATCH'}")

t0 = time.perf_counter()
brute(7, 7)
print(f"brute(7, 7): 7**7 = {7**7} tuples in {time.perf_counter() - t0:.1f} s")
print("formulas(10, 10):", formulas(10, 10), " 10! =", factorial(10))

আসল আউটপুট (ব্রাউজারের Python):

 m  n | brute (all, 1-1, onto) | formulas
 2  4 | (16, 12, 0)            | (16, 12, 0)  match
 3  2 | (8, 0, 6)              | (8, 0, 6)  match
 3  3 | (27, 6, 6)             | (27, 6, 6)  match
 3  4 | (64, 24, 0)            | (64, 24, 0)  match
 4  3 | (81, 0, 36)            | (81, 0, 36)  match
 4  5 | (625, 120, 0)          | (625, 120, 0)  match
 5  3 | (243, 0, 150)          | (243, 0, 150)  match
brute(7, 7): 7**7 = 823543 tuples in 0.2 s
formulas(10, 10): (10000000000, 3628800, 3628800)  10! = 3628800

সাতটি জোড়ায় brute force আর সূত্র হুবহু মিলেছে। শুরুর প্রশ্নের উত্তর (3, 4) সারিতে: ৪ জন courier-কে ৩টি অর্ডার দেওয়ার 64টি উপায়, যার 24টিতে কেউ দুটি পান না (4 · 3 · 2); আর onto 0, কারণ ৩টি অর্ডারে ৪ জনকে কাজ দেওয়া অসম্ভব। (4, 3) সারিতে উল্টো: one-to-one 0 (pigeonhole), onto 36. (3, 3) সারিতে one-to-one আর onto দুটোই 6 = 3!, কারণ সমান আকারে দুটি একই শর্ত। শেষ দুই লাইন brute force-এর সীমা দেখায়: m = n = 7-এই আট লাখের বেশি tuple (সময় যন্ত্রভেদে বদলাবে; প্রতি ধাপে m বা n এক বাড়লে কাজ কয়েকগুণ বাড়ে), আর m = n = 10-এ 1010টি ফাংশন, যা এভাবে গোনা অবাস্তব; অথচ সূত্র মুহূর্তে উত্তর দেয়, আর m = n-এ onto-র সংখ্যা 10! = 3628800, ঠিক bijection-এর সংখ্যা। Brute force সূত্রে আস্থা আনে, প্রমাণ দেয় উপপাদ্য।

৭ · Hash function — collision মানে one-to-one নয়

বাজারঘরের লাখ লাখ ক্রেতার id দ্রুত খুঁজতে একটি hash function h ব্যবহার হয়: প্রতিটি id-কে একটি ছোট সংখ্যায় (বাকেট নম্বর) পাঠায়, যেমন h(id) = id mod 7. গাণিতিকভাবে h: (সব সম্ভাব্য id) → {0, 1, …, 6} একটি ফাংশন, যার domain বিশাল আর codomain ছোট। দুটি আলাদা id একই বাকেটে পড়লে বলে collision, অর্থাৎ ঠিক “h one-to-one নয়”-এর সাক্ষী। উপপাদ্য ৬.১(খ)-এর m > n অংশ বলে: domain codomain-এর চেয়ে বড় হলে collision এড়ানো অসম্ভব, h যত চালাকি করেই বানানো হোক। Hash table কীভাবে collision সামলায় (chaining, probing, load factor), তা DSA কোর্সের পর্ব ৩২-এ।

তাহলে ভালো hash function-এর লক্ষ্য কী? One-to-one হওয়া নয় (অসম্ভব), বরং বাকেটগুলোতে id-গুলো প্রায় সমানভাবে ছড়ানো, অর্থাৎ প্রতিটি বাকেটের preimage প্রায় সমান আকারের। কাঠামোবদ্ধ ডেটায় দুর্বল h কী করে, নিচের ব্লকে দেখুন। সঙ্গে একটি dict-কে ফাংশন হিসেবে দেখা, আর mod গণিতে কোন সূত্র one-to-one তার পরীক্ষা।

from collections import defaultdict

# a dict is a finite function: each key has exactly one value
city = {"C101": "Dhaka", "C102": "Khulna", "C103": "Dhaka", "C104": "Sylhet"}
print("one-to-one?", len(set(city.values())) == len(city))
back = {v: k for k, v in city.items()}      # "inverting" a non-injective dict
print("naive inverse:", back)               # C101 has silently disappeared

# hashing customer ids into 7 buckets: h(id) = id mod 7
ids = [1009, 1016, 1023, 1500, 2024, 2600, 3301, 4150, 5005, 7777]
buckets = defaultdict(list)
for c in ids:
    buckets[c % 7].append(c)
print(f"{len(ids)} ids, 7 buckets, used {len(buckets)}:")
for b in sorted(buckets):
    print(f"  bucket {b}: {buckets[b]}")

def is_injective(h, dom):
    return len({h(x) for x in dom}) == len(dom)

Z11 = range(11)
print("(3x + 2) mod 11 one-to-one on Z11:", is_injective(lambda x: (3 * x + 2) % 11, Z11))
print("(4x + 2) mod 12 one-to-one on Z12:", is_injective(lambda x: (4 * x + 2) % 12, range(12)))
print("x*x mod 11 one-to-one on Z11:", is_injective(lambda x: x * x % 11, Z11))

আসল আউটপুট (ব্রাউজারের Python):

one-to-one? False
naive inverse: {'Dhaka': 'C103', 'Khulna': 'C102', 'Sylhet': 'C104'}
10 ids, 7 buckets, used 6:
  bucket 0: [5005, 7777]
  bucket 1: [1009, 1016, 1023, 2024]
  bucket 2: [1500]
  bucket 3: [2600]
  bucket 4: [3301]
  bucket 6: [4150]
(3x + 2) mod 11 one-to-one on Z11: True
(4x + 2) mod 12 one-to-one on Z12: False
x*x mod 11 one-to-one on Z11: False

প্রথম অংশ উপপাদ্য ৩.২-এর একটি বাস্তব ফাঁদ: “ক্রেতা থেকে শহর” one-to-one নয়, তাই তার inverse নেই; তবু dict comprehension চুপচাপ একটি “inverse” বানিয়ে দিল, আর Dhaka-র দুই ক্রেতার একজন (C101) হারিয়ে গেল, কোনো error ছাড়াই। দ্বিতীয় অংশে দশটি id সাতটি বাকেটে: pigeonhole অনুযায়ী collision নিশ্চিত ছিল, কিন্তু বাকেট 1-এ চারটি জমেছে। কারণ 1009, 1016, 1023 পরপর 7 ব্যবধানে, আর 2024 = 7 · 289 + 1; mod 7 ঠিক এই কাঠামোকে মুছে ফেলতে পারে না। বাস্তবের id প্রায়ই এমন নিয়মিত ব্যবধানে আসে, তাই hash function বাছাইয়ে সতর্কতা লাগে। শেষ অংশ: 3 আর 11-এর সাধারণ গুণনীয়ক নেই, তাই (3x + 2) mod 11 একটি bijection; 4 আর 12-এর আছে (4), তাই (4x + 2) mod 12 one-to-one নয়; আর x² mod 11-এ x আর 11 − x একই মান দেয়। কোন a-তে ax + b mod n bijection, তার পূর্ণ উত্তর (gcd(a, n) = 1) প্রমাণসহ আসবে পর্ব ২২ “Modular arithmetic — ঘড়ির গণিত আর modular exponentiation”-এ।

Cryptography-র hash function (যেমন SHA-256) একই গাণিতিক বাধার মধ্যে কাজ করে: যেকোনো দৈর্ঘ্যের বার্তা থেকে 256-bit মান, তাই বিপুল সংখ্যক collision আছেই। তার নিরাপত্তার দাবি হলো collision খুঁজে বের করা কার্যত অসম্ভব কঠিন, collision নেই এমন নয়। এর বাস্তব ব্যবহার সাইবার সিকিউরিটি কোর্সের পর্ব ৯-এ, আর কত id-এ collision-এর সম্ভাবনা ৫০% ছাড়ায় (birthday paradox), তা পর্ব ৪২ “Birthday paradox, hash collision আর balls-into-bins”-এ।

৮ · পরীক্ষার অঙ্ক আর একটি ভুল প্রমাণ

এই অংশে বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর লিখিত পরীক্ষায় যে ধাঁচের অঙ্ক আসে, তার তিনটি পূর্ণ সমাধান, তারপর একটি ভুল প্রমাণ ধরার অনুশীলন।

অঙ্ক ১: প্রমাণ করুন, f: ℝ − {1} → ℝ − {2}, f(x) = 2x/(x − 1) একটি bijection, আর f−1 বের করুন

  1. ফাংশন কি না: x ≠ 1 হলে হর শূন্য নয়। আর f(x) = 2 হলে 2x = 2x − 2, অর্থাৎ 0 = −2, অসম্ভব; তাই প্রতিটি ছবি সত্যিই codomain ℝ − {2}-এ।
  2. One-to-one: 2a/(a − 1) = 2b/(b − 1) ⇒ 2a(b − 1) = 2b(a − 1) ⇒ 2ab − 2a = 2ab − 2b ⇒ a = b.
  3. Onto: y ≠ 2 নিই। 2x/(x − 1) = y ⇒ 2x = yx − y ⇒ x(y − 2) = y ⇒ x = y/(y − 2), যা y ≠ 2 বলে সংজ্ঞায়িত। এই x কি 1 হতে পারে? y/(y − 2) = 1 ⇒ y = y − 2, অসম্ভব; তাই x domain-এ আছে, আর f(x) = y.
  4. উত্তর: f একটি bijection, f−1(y) = y/(y − 2). যাচাই: f(3) = 6/2 = 3, আর f−1(3) = 3/1 = 3; f(−1) = −2/−2 = 1, আর f−1(1) = 1/(−1) = −1. ∎

লক্ষ করুন domain থেকে 1 আর codomain থেকে 2 বাদ দেওয়া কেন জরুরি: 1-এ f অসংজ্ঞায়িত (partial হয়ে যেত), আর 2 কারো ছবি নয় (onto হতো না)। পরীক্ষায় এই দুটি “বাদ” কোথা থেকে এল তা লিখলে পূর্ণ নম্বর।

অঙ্ক ২: A = {1, 2, 3, 4}, B = {a, b, c, d, e}

(ক) A থেকে B-তে কয়টি ফাংশন? 54 = 625. (খ) কয়টি one-to-one? 5 · 4 · 3 · 2 = 120. (গ) কয়টি onto? 0, কারণ চারটি উপাদানের ছবি বড়জোর চারটি, B-তে পাঁচটি। (ঘ) B থেকে A-তে কয়টি one-to-one? 0, pigeonhole. (ঙ) A থেকে A-তে কয়টি bijection? 4! = 24. (ক)–(গ)-এর সংখ্যা §৬-এর ব্লকের (4, 5) সারির সঙ্গে মেলে।

অঙ্ক ৩: floor আর ceiling

(ক) ⌊−2.5⌋ + ⌈−2.5⌉ + ⌊2.5⌋ + ⌈2.5⌉ = −3 + (−2) + 2 + 3 = 0. (খ) বাজারঘর ১,০০০টি পার্সেল পাঠাবে, একটি ভ্যানে ধরে ৪৫টি: ⌈1000/45⌉ = 23টি ভ্যান (45 · 22 = 990 < 1000). (গ) ⌊x⌋ = 3 আর ⌈x⌉ = 4 হলে x সম্পর্কে কী বলা যায়? 3 ≤ x < 4 আর 3 < x ≤ 4, একসঙ্গে 3 < x < 4, অর্থাৎ x পূর্ণসংখ্যা নয় এবং 3 আর 4-এর মাঝে।

প্রমাণের ভুল ধরুন: “x² one-to-one”

একজন লিখলেন: “দাবি: f: ℝ → ℝ, f(x) = x² one-to-one. প্রমাণ: ধরি f(a) = f(b), অর্থাৎ a² = b². দুই পাশের বর্গমূল নিয়ে a = b. ∎” পড়ে ভুলটি নিজে খুঁজুন।

ভুল কোথায়: √(a²) = |a|, a নয়। তাই সঠিক ধাপ হলো |a| = |b|, অর্থাৎ a = b অথবা a = −b; দ্বিতীয় সম্ভাবনাটি নিঃশব্দে বাদ পড়েছে। দাবিটিই মিথ্যা, a = −1, b = 1 counterexample. এটি একটি পরিচিত ধরন: এমন একটি ধাপ, যা সব সংখ্যায় উল্টো দিকে চলে না (a = b ⇒ a² = b² সত্য, কিন্তু উল্টোটা নয়)। প্রমাণে প্রতিটি “⇒” পড়ার সময় জিজ্ঞেস করুন, এটি কি আসলে “⇔”? domain [0, ∞) হলে |a| = a, আর তখন প্রমাণটি সঠিক হয়; তাই √ ফাংশনের domain [0, ∞)।

আরও গভীরে: f: A → B-এর একটি left inverse (g∘f = ιA) থাকে ঠিক তখন, যখন f one-to-one (A অশূন্য হলে); আর right inverse (f∘g = ιB) থাকে ঠিক তখন, যখন f onto. দ্বিতীয় দাবির প্রমাণে প্রতিটি b-এর preimage থেকে একটি বেছে নিতে হয়; অসীম সেটে এমন বাছাই সাধারণভাবে করা যায় এই নিশ্চয়তা দেয় axiom of choice, আর আসলে “প্রতিটি onto ফাংশনের right inverse আছে” দাবিটি axiom of choice-এর সমতুল্য। ফাংশনকে বস্তু হিসেবে নিয়ে composition-এর নিয়ম অধ্যয়ন করে গণিতের category theory, যা Haskell-এর মতো functional programming ভাষার নকশায় প্রভাব ফেলেছে।

পরীক্ষা ও ইন্টারভিউতে: বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর পরীক্ষায় এই ধরনের প্রশ্ন প্রায়ই আসে:

  • f: ℤ → ℤ, f(x) = 2x + 1 কি onto? না; image শুধু বিজোড় সংখ্যা, যেমন 0-এর preimage নেই। তবে one-to-one.
  • |A| = 3, |B| = 5 হলে A → B one-to-one ফাংশন কয়টি? 5 · 4 · 3 = 60.
  • কখন inverse থাকে? ঠিক bijection হলে; (g∘f)−1 = f−1∘g−1.
  • ⌈n/k⌉ পূর্ণসংখ্যা গণিতে কীভাবে লিখবেন? (n + k − 1) // k, n ≥ 0, k > 0.
  • hash table-এ collision কেন এড়ানো যায় না? key-র সেট বাকেটের চেয়ে বড়, তাই pigeonhole অনুযায়ী h one-to-one হতে পারে না।

৯ · AI-এর যুগে — ফাংশনের প্রমাণ আর গোনা যাচাই

AI সহকারীকে “f কি bijection?” জিজ্ঞেস করলে সাধারণত আত্মবিশ্বাসী, পরিপাটি উত্তর আসে। কিন্তু এই পর্বের প্রায় প্রতিটি সূক্ষ্মতায় AI নিয়মিত ভুল করে:

AI-এর ভুলকীভাবে ধরবেন
Domain আর codomain উপেক্ষা: “3x + 5 onto” বলা, ℤ-এর ক্ষেত্রেওপ্রথমে domain আর codomain লিখতে বলুন; onto ধাপে পাওয়া x domain-এ আছে কি না নিজে দেখুন
Onto প্রমাণে উল্টো দিক: f(x) কোন মান নেয় দেখিয়ে থেমে যাওয়া, যেকোনো y-এর preimage না দেখানোছক মিলিয়ে দেখুন: “যেকোনো y নিই” বাক্যটি আছে কি না
a² = b² ⇒ a = b ধরনের এক দিকের ধাপ, বা শূন্য হতে পারে এমন কিছু দিয়ে ভাগপ্রতিটি “⇒” উল্টো দিকে চলে কি না, আর ভাজক শূন্য হতে পারে কি না জিজ্ঞেস করুন
গোনায় off-by-one: n!/(n − m)!-এর বদলে n!/(n − m − 1)!, বা onto আর one-to-one গুলিয়ে ফেলাছোট m, n-এ brute force চালিয়ে সূত্রের সঙ্গে মেলান (§৬-এর ব্লক)
⌊x + y⌋ = ⌊x⌋ + ⌊y⌋ বা ঋণাত্মক সংখ্যায় floor আর truncation গুলিয়ে ফেলাFraction দিয়ে অনেক মানে পরীক্ষা করুন; ঋণাত্মক উদাহরণ আলাদা করে দিন
“g∘f one-to-one ⇒ g one-to-one” ধরনের ভুল উল্টো দাবি; উপপাদ্যের শর্ত বাদ (যেমন সসীম সেটে one-to-one ⇔ onto-কে অসীমে খাটানো)ছোট সসীম counterexample খুঁজুন; f(n) = n + 1-এর মতো অসীম উদাহরণ মনে রাখুন

AI-এর কাছে ফাংশন-সংক্রান্ত প্রমাণ চাইলে এমন কাঠামো দিন, যাতে প্রতিটি ধাপ আলাদা করে যাচাই করা যায়:

নিচের ফাংশনটি one-to-one, onto বা bijection কি না, প্রমাণসহ বলো: ফাংশন: [এখানে f, তার domain আর codomain] ১. প্রথমে domain আর codomain স্পষ্ট লেখো; সংজ্ঞাটি ঠিকমতো একটি ফাংশন কি না (প্রতিটি ইনপুটের ঠিক একটি ছবি, codomain-এর ভেতরে) দেখাও। ২. one-to-one: "f(a) = f(b) ধরি" দিয়ে শুরু করো; প্রতিটি ধাপে কোন নিয়ম লাগল লেখো। মিথ্যা হলে একটি নির্দিষ্ট জোড়া দাও। ৩. onto: "যেকোনো y নিই" দিয়ে শুরু করো, x বের করো, আর x যে domain-এ আছে তা আলাদা করে দেখাও। মিথ্যা হলে একটি অপ্রাপ্য y দাও আর কেন অপ্রাপ্য প্রমাণ করো। ৪. bijection হলে inverse লেখো আর দুই দিকেই f⁻¹(f(x)) = x, f(f⁻¹(y)) = y যাচাই করো। ৫. কোনো ধাপে ভাগ বা বর্গমূল থাকলে শূন্য বা চিহ্নের সমস্যা হতে পারে কি না বলো। ৬. শেষে ছোট মানে পরীক্ষার একটি Python কোড দাও, কিন্তু মনে রেখো সসীম পরীক্ষা অসীম domain-এর প্রমাণ নয়।

AI-এর উত্তর হাতে পেলে — যাচাইয়ের চেকলিস্ট

সবচেয়ে সস্তা প্রথম পরীক্ষা: AI যে ফাংশনকে bijection বলছে, তাকে একটি ছোট সসীম domain-এ (বা mod n-এ) চালিয়ে image গুনুন। একবার collision পেলেই দাবি বাতিল; না পেলে তখন প্রমাণের প্রতিটি ধাপ নিজে পড়ুন।

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

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।

প্র ০১“ফাংশন” মানে কি সবসময় একটি সূত্র? ধারণাটি ইতিহাসে কীভাবে বদলেছে?

না, আর এই উত্তরে পৌঁছাতে গণিতের প্রায় তিন শতাব্দী লেগেছে। “function” শব্দটি সাধারণত Leibniz-এর ১৬৯০-এর দশকের লেখার সঙ্গে যুক্ত করা হয়, তবে তখন এর অর্থ ছিল একটি বক্ররেখার সঙ্গে সম্পর্কিত রাশি। Leonhard Euler ১৭৪৮ সালের তাঁর বিখ্যাত পাঠ্যবই “Introductio in analysin infinitorum”-এ ফাংশনকে একটি “analytic expression” হিসেবে সংজ্ঞায়িত করেন: চলক আর ধ্রুবক দিয়ে গঠিত একটি সূত্র। এই ধারণায় 3x + 5 ফাংশন, কিন্তু “x মূলদ হলে 1, অমূলদ হলে 0” ধরনের নিয়ম ফাংশন কি না, প্রশ্ন থেকে যায়।

উনিশ শতকে Fourier series-এর গবেষণা বিষয়টিকে জরুরি করে তোলে, কারণ সূত্রবিহীন, লাফ দেওয়া বক্ররেখাও ত্রিকোণমিতিক সিরিজ দিয়ে প্রকাশ করা যাচ্ছিল। Peter Gustav Lejeune Dirichlet সাধারণত এই প্রসঙ্গে উল্লেখিত হন: ১৮২৯ সালের দিকে তিনি ঠিক “মূলদে এক মান, অমূলদে আরেক” ধরনের ফাংশনটি উদাহরণ হিসেবে দেন, আর ১৮৩০-এর দশকে একটি সাধারণ সংজ্ঞা দেন: প্রতিটি x-এর সঙ্গে একটি নির্দিষ্ট y যুক্ত থাকলেই হলো, কীভাবে যুক্ত তা গুরুত্বহীন। এই “যেকোনো নিয়ম” ধারণার কৃতিত্ব কতটা Dirichlet-এর আর কতটা অন্যদের (যেমন Lobachevsky), তা নিয়ে ইতিহাসবিদদের মধ্যে মতভেদ আছে।

বিশ শতকে সেট তত্ত্ব শেষ ধাপটি নেয়: “নিয়ম” শব্দটিও বাদ, ফাংশন মানে শুধু ordered pair-এর একটি সেট, যেখানে প্রতিটি প্রথম উপাদান ঠিক একবার আসে (§১-এর graph সংজ্ঞা)। এই রূপের প্রচারে Bourbaki গোষ্ঠীর পাঠ্যবইয়ের বড় ভূমিকা ছিল বলে প্রায়ই বলা হয়। এর সুবিধা হলো ফাংশন আর কোনো আলাদা মৌলিক ধারণা নয়, সেটের ভাষাতেই প্রকাশ করা যায়।

কম্পিউটার বিজ্ঞানে মজার একটি টান আছে: আমরা আবার “নিয়ম”-এ ফিরি, কারণ প্রোগ্রাম একটি নিয়ম, আর §৫ অনুযায়ী তা প্রায়ই partial. আবার দুটি ভিন্ন প্রোগ্রাম (যেমন সাধারণ আর দ্রুত ঘাত) একই গাণিতিক ফাংশন গণনা করতে পারে: গণিতে তারা সমান, প্রকৌশলে নয়।

মূল উপলব্ধি: আধুনিক সংজ্ঞায় ফাংশন হলো ইনপুট-আউটপুট জোড়ার একটি সেট, সূত্র নয়; সূত্র বা প্রোগ্রাম শুধু সেই সেটকে বর্ণনা বা গণনা করার একটি উপায়।

প্র ০২দুটি সেট “সমান আকারের” কি না জানতে কেন গোনার বদলে bijection খোঁজা হয়? অসীম সেটে কী ঘটে?

গোনা নিজেই একটি bijection. “৫টি আম” বলার মানে আমগুলোর সঙ্গে {1, 2, 3, 4, 5}-এর একটি one-to-one correspondence বানানো, আঙুল দিয়ে ছুঁয়ে ছুঁয়ে। সংখ্যার নাম জানার আগেও মানুষ দুটি সংগ্রহ সমান কি না বলতে পারত: প্রতিটি ভেড়ার জন্য একটি পাথর রাখলে, সন্ধ্যায় পাথর আর ভেড়া জোড়ায় জোড়ায় মিলিয়ে দেখা যায় কোনোটি হারাল কি না। তাই সংজ্ঞা হিসেবে “|A| = |B| ⇔ A থেকে B-তে একটি bijection আছে” স্বাভাবিক, আর এটি এমন ক্ষেত্রেও খাটে যেখানে গোনা শেষ হয় না।

সসীম সেটে এই সংজ্ঞা আর গোনা একই ফল দেয়, আর উপপাদ্য ৬.১ দেখায় সমান আকারে one-to-one ⇔ onto. কিন্তু অসীম সেটে বিস্ময় শুরু হয়। f(n) = 2n হলো ℕ থেকে জোড় সংখ্যার সেটে একটি bijection: তাই এই সংজ্ঞায় জোড় সংখ্যা আর সব স্বাভাবিক সংখ্যা “সমান সংখ্যক”, অথচ একটি অন্যটির প্রকৃত অংশ। Galileo ১৬৩৮ সালের একটি লেখায় বর্গসংখ্যা আর স্বাভাবিক সংখ্যার এই রকম মিল দেখে বিভ্রান্তিকর বলে মন্তব্য করেছিলেন বলে উল্লেখ আছে। Richard Dedekind ১৮৮৮ সালের দিকে উল্টো দিক থেকে ভাবেন: যে সেটের একটি প্রকৃত অংশের সঙ্গে নিজের bijection আছে, তাকেই অসীম বলা হোক।

Georg Cantor এই ধারণাকে একটি পূর্ণ তত্ত্বে পরিণত করেন, আর দেখান সব অসীম সমান নয়: বাস্তব সংখ্যার সেট ℕ-এর চেয়ে কঠোরভাবে বড়, তাঁর ১৮৯১ সালের diagonal যুক্তিতে। “Hilbert-এর হোটেল” নামে পরিচিত চিন্তা-পরীক্ষাটি (সব ঘর ভর্তি একটি অসীম হোটেলে প্রত্যেক অতিথিকে n থেকে n + 1 নম্বর ঘরে সরিয়ে একজন নতুন অতিথির জায়গা করা) আসলে §৬-এর f(n) = n + 1: one-to-one, কিন্তু onto নয়। এই সব আলোচনা পর্ব ১৮ “Cardinality — countable, uncountable আর Cantor-এর diagonal”-এ প্রমাণসহ আসবে।

কম্পিউটার বিজ্ঞানে এর একটি গভীর পরিণতি আছে: প্রোগ্রামের সেট আর সমস্যার সেট সমান আকারের নয়, তাই এমন সমস্যা থাকতেই হবে যার কোনো প্রোগ্রাম নেই। সেই যুক্তির মূল হাতিয়ার এই পর্বের bijection আর onto-র সংজ্ঞাই।

মূল উপলব্ধি: bijection হলো গোনার সারকথা; এটি সসীমে গোনার সঙ্গে মেলে, আর অসীমে এমন সত্য উন্মোচন করে যা গোনা দিয়ে কখনো পাওয়া যেত না।

প্র ০৩Pigeonhole বলে hash function one-to-one হতে পারে না। তাহলে cryptographic hash-কে “collision resistant” বলা হয় কোন অর্থে, আর সেই দাবি কি কখনো ভেঙেছে?

গাণিতিক সত্যটি নির্মম: SHA-256 প্রায় যেকোনো দৈর্ঘ্যের ইনপুট নেয় (মানে 264 bit-এর কম দৈর্ঘ্যের সব বার্তা) আর 256-bit আউটপুট দেয়, তাই domain বিশাল, codomain-এ মাত্র 2256টি মান। উপপাদ্য ৬.১ অনুযায়ী এটি one-to-one হতে পারে না; বরং বিপুল সংখ্যক collision আছে। তাই “collision resistant”-এর অর্থ গাণিতিক অস্তিত্ব নয়, গণনার কঠিনতা: কোনো কার্যকর algorithm দিয়ে বাস্তবসম্মত সময়ে একটি collision জোড়া খুঁজে বের করা যায় না, এই দাবি।

কতটা কঠিন হওয়া উচিত, তার একটি সাধারণ মাপকাঠি আছে। birthday paradox (পর্ব ৪২-এ) অনুযায়ী এলোমেলো ইনপুট চেষ্টা করলে মোটামুটি 2n/2টি চেষ্টায় n-bit hash-এ collision পাওয়ার ভালো সম্ভাবনা, শুধু pigeonhole-এর 2n + 1 নয়। তাই 256-bit hash-এর জন্য প্রায় 2128টি চেষ্টা লাগে, যা আজকের যেকোনো কম্পিউটারের নাগালের বহু বাইরে। কোনো hash-কে “ভাঙা” বলা হয়, যখন কেউ এর চেয়ে উল্লেখযোগ্যভাবে দ্রুত collision খোঁজার উপায় দেখান।

এমন ঘটনা ঘটেছে। ২০০৪ সালে Xiaoyun Wang ও সহকর্মীরা MD5 আর আরও কয়েকটি hash-এ collision খোঁজার দ্রুত পদ্ধতি প্রকাশ করেন বলে ব্যাপকভাবে উল্লেখিত। SHA-1-এর বিরুদ্ধে তাত্ত্বিক আক্রমণ এর পরপরই আসে, আর ২০১৭ সালে গবেষকদের একটি দল প্রথম বাস্তব SHA-1 collision (একই hash-এর দুটি আলাদা PDF ফাইল) প্রকাশ করেন। এর পর থেকে নিরাপত্তার কাজে MD5 আর SHA-1 বাদ দেওয়ার পরামর্শ দেওয়া হয়; বিস্তারিত সাইবার সিকিউরিটি কোর্সের পর্ব ৯-এ।

হ্যাশ টেবিলের hash আর cryptographic hash-এর লক্ষ্য তাই আলাদা। প্রথমটির দরকার শুধু সমান ছড়ানো আর দ্রুততা; collision স্বাভাবিক, সামলানোর ব্যবস্থা থাকে। দ্বিতীয়টির দরকার এমন আচরণ, যাতে কেউ ইচ্ছা করে collision বানাতে না পারে। id mod 7-এর মতো সরল hash-এ ইচ্ছা করে collision বানানো তুচ্ছ (সাত যোগ করলেই হলো), যা §৭-এর ব্লকে দেখা গেছে।

মূল উপলব্ধি: collision-এর অস্তিত্ব একটি গাণিতিক নিশ্চয়তা (pigeonhole); collision খুঁজে পাওয়ার কঠিনতাই cryptographic hash-এর নিরাপত্তা, আর সেটি গাণিতিক প্রমাণ নয়, বছরের পর বছর আক্রমণ টিকে থাকার অভিজ্ঞতা।

প্র ০৪প্রোগ্রামিং ভাষার “ফাংশন” আর গণিতের ফাংশন কোথায় মেলে, কোথায় মেলে না? কিছু ভাষা সব ফাংশনকে total রাখতে চায় কেন?

তিনটি জায়গায় পার্থক্য। প্রথমত, partiality (§৫): প্রোগ্রাম কিছু ইনপুটে থামে না বা exception দেয়। দ্বিতীয়ত, side effect: Python-এর একটি ফাংশন ফাইল লিখতে পারে, একটি global variable বদলাতে পারে, বা random() ডেকে একই ইনপুটে আলাদা উত্তর দিতে পারে; গণিতের ফাংশনে একই ইনপুটে সবসময় একই আউটপুট। যে প্রোগ্রাম-ফাংশন শুধু ইনপুট দেখে আউটপুট দেয় আর বাইরের কিছু বদলায় না, তাকে বলে pure function; শুধু সেগুলোই গাণিতিক ফাংশনের কাছাকাছি। তৃতীয়ত, সীমিত যন্ত্র: float-এ (x + y) + z আর x + (y + z) সবসময় সমান নয়, তাই “একই সূত্র” মানেই একই ফাংশন নয়।

Partiality-র মূল কারণ গভীর। Alan Turing ১৯৩৬ সালে দেখান, কোনো প্রোগ্রাম সব প্রোগ্রাম-ইনপুট জোড়ার জন্য বলতে পারে না সেটি থামবে কি না। এর অর্থ: সাধারণ উদ্দেশ্যের একটি প্রোগ্রামিং ভাষায় (যেখানে যেকোনো while লুপ লেখা যায়) এমন কোনো compiler থাকতে পারে না, যা প্রতিটি ফাংশন total কি না নির্ভুলভাবে বলে দেবে। প্রমাণটি পর্ব ৭৫-এ আসবে।

তবু কিছু ভাষা ও টুল উল্টো পথ নেয়: ভাষাটিকেই সীমিত করে, যাতে প্রতিটি ফাংশন প্রমাণযোগ্যভাবে total হয়। proof assistant-গুলোতে (যেমন Lean, Rocq/Coq, Agda) recursive ফাংশন সাধারণত গ্রহণ করা হয় শুধু তখন, যখন টুলটি দেখতে পায় প্রতিটি recursive ডাক একটি “ছোট” ইনপুটে হচ্ছে, অর্থাৎ পর্ব ১১ আর ১৩-এর well-ordering বা variant যুক্তি। কারণটি যুক্তিবিদ্যার: সেখানে একটি ফাংশন একটি প্রমাণের ভূমিকা নেয়, আর চিরকাল চলা একটি “প্রমাণ” যেকোনো মিথ্যা দাবিকে “প্রমাণ” করে ফেলতে পারত। তার দাম হলো কিছু সত্যিকারের total প্রোগ্রামও টুলকে বোঝাতে অতিরিক্ত প্রমাণ লিখতে হয়।

দৈনন্দিন প্রোগ্রামিংয়েও এই চিন্তা কাজে আসে: Optional বা Maybe ধরনের type, যা §৫-এর “codomain বড় করা”-র কৌশল, ফাংশনকে total করে আর কলকারীকে “উত্তর নেই” ক্ষেত্রটি সামলাতে বাধ্য করে।

মূল উপলব্ধি: pure, total প্রোগ্রাম-ফাংশন গণিতের ফাংশনের সবচেয়ে কাছে; partiality আর side effect হলো সেই দূরত্ব, যা প্রোগ্রামের প্রমাণকে কঠিন করে।

প্র ০৫⌊ ⌋ আর ⌈ ⌉ চিহ্ন কোথা থেকে এল? প্রোগ্রামিং ভাষাগুলো পূর্ণসংখ্যা ভাগে একমত নয় কেন, আর এতে কী ঝুঁকি?

ধারণাটি পুরোনো (“x-এর পূর্ণ অংশ”, অনেক বইয়ে [x] লেখা হতো), কিন্তু ⌊x⌋ আর ⌈x⌉ চিহ্ন দুটি এবং floor ও ceiling নাম Kenneth Iverson-এর বলে সাধারণত উল্লেখ করা হয়, তাঁর ১৯৬২ সালের বই “A Programming Language”-এ (যা থেকে APL ভাষা)। Donald Knuth তাঁর বইগুলোতে, বিশেষত Graham, Knuth ও Patashnik-এর “Concrete Mathematics” (১৯৮৯)-এ এই চিহ্ন জনপ্রিয় করেন। পুরোনো [x] চিহ্নের সমস্যা ছিল ঋণাত্মক সংখ্যায় অস্পষ্টতা: [−3.5] মানে −4 না −3, বই ভেদে আলাদা। দুটি আলাদা চিহ্ন সেই অস্পষ্টতা দূর করে।

প্রোগ্রামিং ভাষায় ঠিক সেই অস্পষ্টতা ফিরে এসেছে। C (১৯৯৯ সালের C99 মান থেকে নিশ্চিতভাবে) আর Java-তে পূর্ণসংখ্যা ভাগ শূন্যের দিকে কাটে: −7 / 2 = −3, আর −7 % 2 = −1. Python-এ // floor নেয়: −7 // 2 = −4, আর −7 % 2 = 1. দুই ভাষাতেই a = (a / b) · b + (a % b) সম্পর্কটি টিকে থাকে, কিন্তু ভাগফল আর ভাগশেষ আলাদা। ধনাত্মক সংখ্যায় পার্থক্য নেই, তাই বাগ সাধারণত লুকিয়ে থাকে যতক্ষণ না কোনো ঋণাত্মক মান আসে।

বাস্তব ঝুঁকির কিছু ধরন: একটি C প্রোগ্রাম Python-এ অনুবাদ করার সময় ঋণাত্মক স্থানাঙ্কের grid cell হিসাব বদলে যায়; ঋণাত্মক সংখ্যায় “x % 2 == 1” দিয়ে বিজোড় পরীক্ষা C-তে ব্যর্থ হয়, কারণ ভাগশেষ −1; সময় বা তারিখের হিসাবে একটি ঋণাত্মক পার্থক্য ভুল দিনে পড়ে। আবার ⌈n/k⌉ float দিয়ে (math.ceil(n / k)) হিসাব করলে খুব বড় n-এ float-এর সীমিত নির্ভুলতায় ভুল আসতে পারে, যেমনটা পর্ব ১৩-এর পূর্ণসংখ্যার বর্গমূলে দেখা গেছে; তাই পূর্ণসংখ্যা সূত্র (n + k − 1) // k নিরাপদ।

এখানে উপপাদ্য ৪.২ সরাসরি কাজে আসে: ⌈x⌉ = −⌊−x⌋ জানা থাকলে যেকোনো floor-ভিত্তিক ভাষায় ceiling পাওয়া যায়, আর ⌊x + n⌋ = ⌊x⌋ + n জানা থাকলে ঋণাত্মক সংখ্যাকে একটি বড় গুণিতক যোগ করে ধনাত্মকে এনে হিসাব করা যায়।

মূল উপলব্ধি: floor আর ceiling-এর নিখুঁত সংজ্ঞা ঠিক সেই অস্পষ্টতা দূর করে, যা প্রোগ্রামিং ভাষার পূর্ণসংখ্যা ভাগে এখনো আছে; নতুন ভাষায় কাজ করলে ঋণাত্মক উদাহরণে ভাগ পরীক্ষা করে নিন।

অনুশীলন

আগে খাতায় করুন, তারপর উত্তর খুলে মেলান।

  1. প্রমাণ: f: A → B আর g: B → C ফাংশন। প্রমাণ করুন, g∘f one-to-one হলে f one-to-one. তারপর একটি উদাহরণ দিন যেখানে g∘f one-to-one, কিন্তু g নয়।
    1. ধরি a1, a2 ∈ A আর f(a1) = f(a2). — one-to-one-এর ছক
    2. দুই পাশে g লাগাই: g(f(a1)) = g(f(a2)), অর্থাৎ (g∘f)(a1) = (g∘f)(a2). — g একটি ফাংশন, একই ইনপুটে একই আউটপুট
    3. g∘f one-to-one, তাই a1 = a2. অতএব f one-to-one. ∎

    উদাহরণ: A = {1}, B = {a, b}, C = {x}; f(1) = a, g(a) = g(b) = x. g∘f-এর domain-এ একটিই উপাদান, তাই সেটি one-to-one; কিন্তু g(a) = g(b) আর a ≠ b. লক্ষ করুন ধাপ ২-এ g-এর one-to-one হওয়া লাগেনি, শুধু g ফাংশন হওয়া লেগেছে।

  2. হিসাব: (ক) |A| = 4, |B| = 6 হলে A → B মোট ফাংশন, one-to-one ফাংশন আর bijection কয়টি? (খ) A = {1, 2, 3, 4} থেকে {x, y, z}-এ কয়টি onto ফাংশন? (গ) 2026 bit রাখতে কয়টি byte লাগে, আর ১,০০০টি অর্ডার প্রতি পাতায় ২৪টি করে দেখালে কয়টি পাতা?

    (ক) 64 = 1296টি ফাংশন; 6 · 5 · 4 · 3 = 360টি one-to-one; bijection 0, কারণ |A| ≠ |B|.

    (খ) সূত্রে 34 − 3 · 24 + 3 · 14 − 0 = 81 − 48 + 3 = 36; §৬-এর ব্লকের (4, 3) সারিও 36. অর্থ: 81টি ফাংশন থেকে যেগুলো কোনো একটি লক্ষ্য বাদ দেয় সেগুলো সরানো, দুবার বাদ পড়াগুলো ফেরত যোগ করা।

    (গ) ⌈2026/8⌉ = 254 byte (8 · 253 = 2024 < 2026); ⌈1000/24⌉ = 42 পাতা (24 · 41 = 984 < 1000). সব সংখ্যা scratch-এ Python দিয়ে যাচাই করা।

  3. আউটপুট অনুমান: কোড না চালিয়ে বলুন কী ছাপা হবে। তারপর যেকোনো Python-এ চালিয়ে মেলান।
    d = {1: "x", 2: "y", 3: "x"}
    inv = {v: k for k, v in d.items()}
    print(inv)
    print(len(set(d.values())) == len(d))
    print(-9 // 4, -(-9 // 4), 9 // 4)
    {'x': 3, 'y': 2}
    False
    -3 3 2

    d একটি ফাংশন {1, 2, 3} → {x, y}, one-to-one নয় (1 আর 3 দুটিই x-এ), তাই দ্বিতীয় লাইন False. “উল্টানো” dict-এ x-এর জন্য পরে আসা 3 আগের 1-কে মুছে দেয়: inverse নেই, আর কোড চুপচাপ তথ্য হারায়। শেষ লাইনে −9/4 = −2.25-এর floor −3; −(−9 // 4) = 3 হলো ⌈9/4⌉, উপপাদ্য ৪.২(খ); আর 9 // 4 = 2.

  4. প্রমাণের ভুল ধরুন: একজন লিখলেন: “দাবি: সব বাস্তব x, y-এর জন্য ⌊x + y⌋ = ⌊x⌋ + ⌊y⌋. প্রমাণ: x = ⌊x⌋ + a আর y = ⌊y⌋ + b লিখি, যেখানে 0 ≤ a, b < 1. তাহলে x + y = (⌊x⌋ + ⌊y⌋) + (a + b). a + b একটি ভগ্নাংশ-অংশ, তাই floor নিলে বাদ যায়: ⌊x + y⌋ = ⌊x⌋ + ⌊y⌋. ∎” ভুল কোথায়, আর সঠিক দাবি কী?
    • ভুল: “a + b একটি ভগ্নাংশ-অংশ” মিথ্যা। 0 ≤ a, b < 1 থেকে শুধু 0 ≤ a + b < 2 আসে; a + b ≥ 1 হতে পারে। যেমন x = y = 0.5: a + b = 1, আর ⌊x + y⌋ = 1 ≠ 0 = ⌊x⌋ + ⌊y⌋.
    • সঠিক দাবি: ⌊x + y⌋ = ⌊x⌋ + ⌊y⌋ যদি a + b < 1, আর ⌊x⌋ + ⌊y⌋ + 1 যদি a + b ≥ 1. প্রমাণ: m = ⌊x⌋ + ⌊y⌋ পূর্ণসংখ্যা, x + y = m + (a + b); প্রথম ক্ষেত্রে m ≤ x + y < m + 1, দ্বিতীয় ক্ষেত্রে m + 1 ≤ x + y < m + 2; সংজ্ঞা ৪.১ প্রয়োগ। ∎
    • শিক্ষা: প্রতিটি “তাই” পড়ার সময় অসমতার সীমা দুই প্রান্তেই পরীক্ষা করুন; §৪-এর ব্লক এই দাবির counterexample মুহূর্তে পেয়েছিল।

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

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

ক্রমএকটি ফাংশন bijection প্রমাণ করে তার inverse বের করার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Domain আর codomain স্পষ্ট লেখা, আর নিয়মটি সত্যিই ফাংশন কি না দেখা
  2. One-to-one: f(a) = f(b) ধরে a = b দেখানো
  3. Onto: যেকোনো y নিয়ে f(x) = y সমাধান করা
  4. পাওয়া x domain-এর ভেতরে আছে কি না যাচাই করা
  5. Inverse লিখে f⁻¹(f(x)) = x আর f(f⁻¹(y)) = y যাচাই করা

f: ℤ → ℤ, f(x) = 2x + 1 সম্পর্কে কোনটি সঠিক?

  • f একটি bijection
  • one-to-one, কিন্তু onto নয়
  • onto, কিন্তু one-to-one নয়
  • কোনোটিই নয়

|A| = 3 আর |B| = 5 হলে A থেকে B-তে কয়টি one-to-one ফাংশন আছে?

  • 125
  • 60
  • 10
  • 243

⌈−2.3⌉ + ⌊2.7⌋-এর মান কত?

  • 0
  • 1
  • −1
  • 5

f: A → B আর g: B → C ফাংশন, আর g∘f onto. তাহলে কোনটি অবশ্যই সত্য?

  • f onto
  • g onto
  • f আর g দুটিই onto
  • f one-to-one

f: {1, 2, 3} → {1, 2, 3}, যেখানে f(1) = 2, f(2) = 3, f(3) = 1. f−1(1) কত?

  • 2
  • 1
  • 3
  • সংজ্ঞায়িত নয়, কারণ f-এর inverse নেই

১৩টি আলাদা ক্রেতা-id একটি hash function দিয়ে ১২টি বাকেটে পাঠানো হলো। কোন দাবিটি hash function যেমনই হোক, নিশ্চিতভাবে সত্য?

  • অন্তত একটি বাকেট খালি থাকবে
  • প্রতিটি বাকেটে ঠিক একটি id থাকবে
  • hash function-টি one-to-one হতে পারে
  • অন্তত একটি বাকেটে দুটি বা তার বেশি id থাকবে

শব্দকোষ

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

মনে রাখুন

  1. ফাংশন f: A → B: প্রতিটি a-এর ঠিক একটি ছবি। Domain, codomain আর image আলাদা; domain বা codomain বদলালে ফাংশনও বদলায়।
  2. ছক: one-to-one প্রমাণে f(a) = f(b) ধরে a = b; onto প্রমাণে যেকোনো y নিয়ে x বের করে x ∈ domain যাচাই; খণ্ডনে একটি নির্দিষ্ট উদাহরণই যথেষ্ট।
  3. Inverse ⇔ bijection. Composition associative, commutative নয়; one-to-one∘one-to-one = one-to-one, onto∘onto = onto, (g∘f)−1 = f−1∘g−1.
  4. Floor/ceiling: ⌊x⌋ = n ⇔ n ≤ x < n + 1; ⌊x + n⌋ = ⌊x⌋ + n, ⌈x⌉ = −⌊−x⌋, কিন্তু ⌊x + y⌋ ≠ ⌊x⌋ + ⌊y⌋ সাধারণভাবে। বাক্স গুনতে ⌈n/k⌉.
  5. গোনা: nmটি ফাংশন, n!/(n − m)!টি one-to-one; m > n হলে collision নিশ্চিত (pigeonhole). প্রোগ্রাম সাধারণত partial function গণনা করে।

পরের পর্বে: পর্ব ১৭ “Sequence আর summation — closed form আর বিখ্যাত যোগফল”: sequence আসলে ℕ থেকে একটি ফাংশন, আর তার যোগফলের closed form বের করা ও প্রমাণ করা।

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

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