কনটেন্টে যান

010 — Number Pattern Printing

এতদিন আমরা একটা সংখ্যা ভেঙে digit নিয়ে খেলছিলাম। আজ একদম আলাদা শাখা — nested loop-এর হাতেখড়ি। সংখ্যার ভেতরে ঢোকা নয়, বরং সারি-সারি করে সংখ্যা সাজিয়ে ছাপা। দেখতে নিরীহ ত্রিভুজ, কিন্তু এই "বাইরের loop সারি, ভেতরের loop কলাম" কাঠামোটাই পরে matrix, grid, 2D DP — সব জায়গায় ফিরবে। ধীরে যাও, প্রতিটা pattern নিজে হাতে খাতায় এঁকে দেখো।

Problem link ও উৎস

  • Original source: Classic beginner exercise (HackerRank-style warmup)
  • Platform: Classic exercise
  • Topic: Math-based programming fundamentals → Level 0: Absolute Basics
  • Difficulty: Easy
  • Pattern: nested loops
  • Basic idea inherited from: — (এটা আলাদা শাখার শুরু, আগের digit-problem থেকে আসেনি)

প্রশ্ন ও উদাহরণ

সহজ করে প্রশ্ন

একটা সংখ্যা n দেওয়া আছে। তোমাকে nটা সারি (row) ছাপতে হবে, যেখানে সংখ্যাগুলো একটা সুন্দর pattern বানায়। সবচেয়ে চেনা pattern — একটা ত্রিভুজ:

উদাহরণ
1
1 2
1 2 3
1 2 3 4

মানে: ১ম সারিতে 1টা সংখ্যা, ২য় সারিতে 2টা, ... i-তম সারিতে iটা সংখ্যা। ব্যস, এই pattern-টা তৈরি করাই কাজ।

(আরও কয়েকটা variation আছে — একই সংখ্যা বারবার, উল্টো ত্রিভুজ — নিচে দেখব। মূল কৌশল সবগুলোতেই এক।)

Input ও output

উদাহরণ
input n = 4   ->   output:
1
1 2
1 2 3
1 2 3 4

input n = 1   ->   output:
1

input n = 0   ->   output:
(কিছুই না — খালি, কোনো সারি নেই)

Edge case দুটো: n = 1 → শুধু একটা সারি 1; n = 0 (বা negative) → একটাও সারি নয়, output সম্পূর্ণ খালি। loop ঠিকভাবে লিখলে এগুলো এমনিই সামলে যায়।

চেনা উদাহরণে বোঝা

ভাবো একটা বইয়ের তাক (bookshelf) সাজাচ্ছ — কয়েকটা থাক (shelf), প্রতিটা থাকে কিছু বই।

  • বাইরের loop = "এখন কোন থাকে কাজ করছি" (থাক 1, থাক 2, ...) → সারি
  • ভেতরের loop = "এই থাকে এক-একটা করে বই বসাচ্ছি" → ওই সারির সংখ্যাগুলো
  • এক থাক ভরা শেষ হলে পরের থাকে যাও = সারি শেষে newline

ত্রিভুজে নিয়মটা শুধু এই — প্রথম থাকে 1টা বই, দ্বিতীয় থাকে 2টা, ... যত নিচে নামো তত বেশি বই। থাক (সারি) সামলায় বাইরের loop, বই (সংখ্যা) সামলায় ভেতরের loop।

ভিত্তির ধারণা

আগের কোন ধারণাটি লাগবে

এই problem-টা digit-extraction পরিবারের নয় — তাই "inherited from: —"। এটা Level 0-এর আলাদা একটা শাখার শিকড়: loop-এর ভেতরে loop।

আগের problem-গুলোতে (002-009) একটাই loop দিয়ে একটা সংখ্যার digit ধরে হাঁটতাম। এখানে উল্টো — সংখ্যা ভাঙছি না, বরং দুটো loop একসাথে চালিয়ে একটা দ্বিমাত্রিক (2D) আকৃতি বানাচ্ছি। তাই এটা নতুন একটা হাতিয়ার, যেটা থেকে পরে গজাবে: matrix ছাপা, grid-এ হাঁটা, multiplication table, এমনকি 2D dynamic programming-এর ভিত।

ভেতরের অঙ্ক

এখানে গভীর কোনো গণিত-সূত্র নেই; মূল idea হলো nested loop — একটা loop-এর ভেতরে আরেকটা loop:

  • বাইরের loop ঠিক করে কোন সারি-তে আছি (i = 1 থেকে n)
  • ভেতরের loop ঠিক করে ওই সারিতে কোন কোন সংখ্যা/কলাম ছাপব (j = 1 থেকে i)
উদাহরণ
বাইরের loop i: 1 .. n          # প্রতিটা সারি
    ভেতরের loop j: 1 .. i      # ওই সারির ভেতরের সংখ্যাগুলো
        ছাপো j
    সারি শেষ -> newline

ছোট গাণিতিক সত্য পাশে: মোট কতগুলো সংখ্যা ছাপা হলো? 1 + 2 + 3 + ... + n = n(n+1)/2 — এটাই বলে দেয় কাজের পরিমাণ (Section 15-এ লাগবে)।

ধাপে ধাপে সমাধান

প্রথম সরল উপায়

এখানে "brute force বনাম optimal" ব্যাপারটা একটু আলাদা — কারণ pattern ছাপতে হলে প্রতিটা সংখ্যা ছাপতেই হবে, তাকানোর শর্টকাট নেই। তবে প্রথম, সবচেয়ে সরল ভাবনা — প্রতি সারিতে ভেতরের loop দিয়ে এক-একটা সংখ্যা আলাদা করে print করা:

Python
def print_triangle_brute(n):
    for i in range(1, n + 1):       # প্রতিটা সারি
        for j in range(1, i + 1):   # সারির ভেতরের সংখ্যা
            print(j, end=" ")       # একটা একটা ছাপো, পাশে ফাঁকা
        print()                     # সারি শেষ -> পরের লাইনে যাও

এটা একদম সরাসরি pattern-টাকে code-এ অনুবাদ করে — দুটো loop, ভেতরে print। কাজ করে, পড়তেও সহজ।

কোথায় বেশি কাজ হতে পারে

এই version "ধীর" নয় algorithm-এর অর্থে — মোট n(n+1)/2টা সংখ্যা ছাপতে হলে, সেটুকু কাজ যেকোনো পদ্ধতিতেই লাগবে। কিন্তু কীভাবে ছাপছি, তাতে একটা ব্যবহারিক পার্থক্য আছে:

  • বারবার print() ডাকা ব্যয়বহুল: প্রতিটা সংখ্যার জন্য আলাদা print(j, end=" ") ডাকা — output operation প্রতিবার একটু overhead নেয়। সংখ্যা অনেক হলে (বড় n) এটা টের পাওয়া যায়।
  • পরিচ্ছন্নতা: প্রতি সারি এক-এক করে না ছেপে, পুরো সারিটা একটা string বানিয়ে একবারে ছাপলে code পরিষ্কার হয় আর print-call কমে।

মূল কথা ধীরগতি নয় — ভেতরের loop-এ কাজটা সরাসরি print না করে আগে জমিয়ে নেওয়া একটা ভালো অভ্যাস, যেটা পরে বড় output-এ কাজে দেয়।

আরও ভালোভাবে ভাবা

ভেতরের loop-এ সরাসরি না ছেপে, প্রতি সারির সংখ্যাগুলো আগে একটা string-এ জমাই, তারপর সারিপিছু একবার করে print:

Python
def print_triangle(n):
    for i in range(1, n + 1):
        row = " ".join(str(j) for j in range(1, i + 1))  # পুরো সারি একসাথে
        print(row)                                        # সারিপিছু একবার print

" ".join(...) সংখ্যাগুলোর মাঝে ফাঁকা বসিয়ে এক টুকরো string বানায় (যেমন "1 2 3"), তারপর সেটা একবারে ছাপি। print-call কমল (সারিপিছু একটা), trailing space-ও থাকে না — পরিষ্কার।

ভাবনার পরিবর্তন

মূল মোচড় এক বাক্যে: বাইরের loop "কতগুলো সারি", ভেতরের loop "এই সারিতে কতদূর" — আর ভেতরের সীমা বাইরের চলক i-এর উপর নির্ভর করলেই ত্রিভুজ জন্ম নেয়।

এই নির্ভরতাই (ভেতরের loop range(1, i+1)) আসল জাদু:

  • ভেতরের loop যদি 1..i হয় → সোজা ত্রিভুজ (নিচে বাড়ে)
  • যদি i..n বা 1..(n-i+1) হয় → উল্টো ত্রিভুজ (নিচে কমে)
  • যদি j-এর বদলে i ছাপো → প্রতি সারিতে একই সংখ্যা (1, 2 2, 3 3 3...)

মানে কাঠামো এক — দুটো nested loop — শুধু ভেতরের loop-এর সীমা আর কী ছাপছি বদলেই হাজারটা pattern। মুখস্থ নয়, এই নির্ভরতাটা বোঝো।

ছবিতে বোঝা

n = 4-এর ত্রিভুজ — কোন সারিতে i কত, ভেতরের loop j কতদূর যায়, পাশে দেখো:

উদাহরণ
i = 1 :  j: 1               ->  "1"          (1টা সংখ্যা)
i = 2 :  j: 1 2             ->  "1 2"        (2টা সংখ্যা)
i = 3 :  j: 1 2 3           ->  "1 2 3"      (3টা সংখ্যা)
i = 4 :  j: 1 2 3 4         ->  "1 2 3 4"    (4টা সংখ্যা)

লক্ষ করো: ভেতরের loop j সবসময় 1 থেকে শুরু,
          কিন্তু থামে i-তে -> তাই সারি যত নিচে, তত লম্বা

আরেকভাবে দেখো — কাজটা আসলে একটা ত্রিভুজাকার grid ভরা:

উদাহরণ
       j=1  j=2  j=3  j=4
i=1 :  [1]
i=2 :  [1] [2]
i=3 :  [1] [2] [3]
i=4 :  [1] [2] [3] [4]
       ^ ভেতরের loop ডানে বাড়ে, বাইরের loop নিচে নামে

ছোট input-এ ধাপে ধাপে

n = 3-এ Section 9-এর version হাতে চালাই। বাইরের loop i-এর প্রতিটা মানে ভেতরে কী হয়, দেখো:

i (বাইরের) ভেতরের range(1, i+1) j গুলো row = " ".join print
1 range(1, 2) 1 "1" 1
2 range(1, 3) 1, 2 "1 2" 1 2
3 range(1, 4) 1, 2, 3 "1 2 3" 1 2 3

তিনটা সারি ছাপা শেষ, বাইরের loop থামল। চূড়ান্ত output:

উদাহরণ
1
1 2
1 2 3

এবার মনে মনে n = 0 চালাও: range(1, 1) খালি, বাইরের loop একবারও চলে না → কিছুই ছাপে না। ঠিক, n = 0-এ output খালি — আলাদা if লাগে না।

কোড ও ব্যাখ্যা

Python

Python
def triangle_rows(n):
    """ত্রিভুজ pattern-টা list of string হিসেবে বানায় (print না করে)।
    এতে test করা সহজ — প্রতিটা সারি যাচাই করা যায়।"""
    rows = []
    for i in range(1, n + 1):
        row = " ".join(str(j) for j in range(1, i + 1))
        rows.append(row)
    return rows


def print_triangle(n):
    """ত্রিভুজটা সরাসরি ছাপে।"""
    for row in triangle_rows(n):
        print(row)


def same_number_rows(n):
    """variation: প্রতি সারিতে একই সংখ্যা (1 / 2 2 / 3 3 3 ...)।"""
    return [" ".join([str(i)] * i) for i in range(1, n + 1)]


# --- ছোট test গুলো (সব pass করা উচিত) ---
assert triangle_rows(4) == ["1", "1 2", "1 2 3", "1 2 3 4"]
assert triangle_rows(1) == ["1"]
assert triangle_rows(0) == []          # n = 0 -> খালি
assert triangle_rows(3) == ["1", "1 2", "1 2 3"]
assert same_number_rows(3) == ["1", "2 2", "3 3 3"]
assert same_number_rows(0) == []

# মোট সংখ্যা = n(n+1)/2 — সারিগুলোর শব্দ গুনে যাচাই
total = sum(len(r.split()) for r in triangle_rows(5))
assert total == 5 * 6 // 2            # 15

print("n = 4 ত্রিভুজ:")
print_triangle(4)
print("সব test pass!")

কোডের প্রতিটি অংশ

উদাহরণ
def triangle_rows(n):
    rows = []
    for i in range(1, n + 1):

প্রতিটা সারিকে আলাদা করে print না করে একটা list-এ জমাই — এতে পরে test-এ মিলিয়ে দেখা যায়। বাইরের loop i চলে 1 থেকে n পর্যন্ত (range(1, n+1) মানে n-ও ধরা)।

উদাহরণ
        row = " ".join(str(j) for j in range(1, i + 1))

এই problem-এর প্রাণ। ভেতরের অংশ range(1, i+1) দেয় 1 থেকে i পর্যন্ত সংখ্যা — খেয়াল করো সীমাটা i-এর উপর নির্ভর করছে, এজন্যই সারি যত নিচে তত লম্বা। str(j) প্রতিটাকে string বানায়, " ".join(...) মাঝে ফাঁকা বসিয়ে এক সারির string গাঁথে।

উদাহরণ
        rows.append(row)
    return rows

বানানো সারিটা list-এ জমা, সব শেষে list ফেরত।

উদাহরণ
def print_triangle(n):
    for row in triangle_rows(n):
        print(row)

জমানো সারিগুলো এক-এক করে ছাপি — সারিপিছু একটাই print, পরিষ্কার।

উদাহরণ
def same_number_rows(n):
    return [str(i)] * i ...

variation — এখানে j-এর বদলে i ছাপি, আর [str(i)] * i মানে "i লেখাটা i বার" (যেমন ["3","3","3"])। তাই 3 3 3। কাঠামো একই, শুধু কী ছাপছি বদলালো।

বাকি assert গুলো — সারির তালিকা, edge case (n = 0), আর মোট সংখ্যার সূত্র n(n+1)/2 — সব যাচাই করে; ঠিক থাকলে শেষে সব test pass!।

Output মিলিয়ে দেখা

চালালে ছাপবে:

উদাহরণ
n = 4 ত্রিভুজ:
1
1 2
1 2 3
1 2 3 4
সব test pass!

প্রথম লাইনটা শিরোনাম। তারপর print_triangle(4) চারটে সারি ছাপে — 1; 1 2; 1 2 3; 1 2 3 4 (i বাড়ার সাথে সারি লম্বা)। তার আগের সব assert (সারির তালিকা, n = 0 খালি, variation, যোগফল 15) চুপচাপ pass করেছে। শেষ লাইন সব test pass! — সব ঠিক।

খরচ ও সাধারণ ভুল

সময়

O(n²) — বাইরের loop n বার, ভেতরের loop গড়ে n/2 বার, মোট কাজ 1 + 2 + ... + n = n(n+1)/2 ≈ n²/2টা সংখ্যা ছাপা। n দ্বিগুণ হলে কাজ প্রায় চারগুণ। এটা pattern-এর স্বভাবগত — মোট এতগুলো সংখ্যা ছাপতে হলে এর কমে হয় না।

অতিরিক্ত জায়গা

  • শুধু ছাপলে (print_triangle সরাসরি): O(n) — এক সময়ে একটা সারির string (সর্বোচ্চ n-টা সংখ্যা) মেমরিতে থাকে।
  • পুরো তালিকা ফেরালে (triangle_rows): O(n²) — সব সারি একসাথে রাখলে মোট n(n+1)/2টা সংখ্যার string জমে।

(শুধু console-এ ছাপা হলে অনেকে একে O(1) auxiliary-ও বলে, কারণ ছাপার পর কিছু রাখি না।)

যে ভুলগুলো এড়াতে হবে

  1. range সীমায় off-by-one — range(1, n+1) লিখতে হয় n-কে ধরতে; range(1, n) লিখলে শেষ সারিটা বাদ পড়ে। আর ভেতরে range(1, i+1), range(1, i) নয়। এটাই এক নম্বর ভুল।
  2. print() দিয়ে newline ভুলে যাওয়া — ভেতরের loop শেষে সারি ভাঙতে newline দরকার; না দিলে সব সংখ্যা এক লাইনে লেপ্টে যায়। (string-জমানো version-এ print(row) নিজেই newline দেয়, তাই এ ভুল হয় না।)
  3. ভেতরের সীমা i-এর বদলে n দেওয়া — range(1, n+1) ভেতরে দিলে ত্রিভুজ না হয়ে আয়তক্ষেত্র (সব সারি সমান লম্বা) হয়ে যাবে। ভেতরের loop-কে i-এর উপর নির্ভর করতেই হবে।
  4. i আর j গুলিয়ে ফেলা — কোন সংখ্যা ছাপছি (j) আর কোন সারিতে আছি (i) — গুলিয়ে ফেললে pattern বদলে যায়।
  5. n = 0 / negative নিয়ে দুশ্চিন্তা — আলাদা if লাগে না; range(1, 0+1) = range(1, 1) খালি, loop এমনিই চলে না।

শেখা ও পরের অনুশীলন

এই ধারণার পরের অনুশীলন

  • HackerRank — Staircase (https://www.hackerrank.com/challenges/staircase/problem) — একই nested-loop ত্রিভুজ, শুধু সংখ্যার বদলে #, আর ডানে সারিবদ্ধ (আগে কিছু space, পরে star)।
  • LeetCode — Pascal's Triangle (https://leetcode.com/problems/pascals-triangle/) — ত্রিভুজ কাঠামো এক, কিন্তু প্রতি ঘরের মান উপরের দুই ঘরের যোগ — nested loop-এর উপর গণিত চাপানো।
  • LeetCode — Spiral Matrix (https://leetcode.com/problems/spiral-matrix/) — 2D grid-এ nested-loop চলাচলের আরও জটিল রূপ; এই "সারি-কলাম" চিন্তা সেখানেই ভিত্তি।

কৌশলটি মনে রাখুন

Nested loops — বাইরের loop সারি, ভেতরের loop কলাম। বড় শিক্ষা: ভেতরের loop-এর সীমা বাইরের চলকের উপর নির্ভর করালেই (যেমন range(1, i+1)) ত্রিভুজ-জাতীয় আকৃতি তৈরি হয়; কাঠামো বদলায় না, শুধু সীমা আর "কী ছাপছি" বদলে নানা pattern। এই 2D চিন্তা পরে matrix, grid, 2D DP — সবখানে ফিরবে।

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

ভবিষ্যতের আমাকে এক লাইনে: "pattern printing = nested loop; বাইরের i সারি (1..n), ভেতরের j কলাম (1..i — সীমা i-এর উপর নির্ভর বলেই ত্রিভুজ)। range(1, n+1) দিয়ে off-by-one এড়াও; সারি শেষে newline। কাঠামো এক, সীমা বদলেই সব pattern।"

এই note-টা digit-শাখার নয় — এটা nested loop শাখার শুরু। সম্পর্কিত পরের ধাপ Level 1-এ: factor/prime খোঁজার i*i <= n loop, আর তারও পরে sieve-এর nested loop।

ফিরে দেখা: এই level-এর map → ../README.md · এই note-টা যে ছকে লেখা → ../../../templates/math-problem-note-template.md।


মনে রাখার নিয়ম (legal): সব নিজের ভাষায় লেখা; problem statement copy করা হয়নি; official link শুধু attribution-এর জন্য রাখা; "asked by Google/Amazon" এমন দাবি করা হয়নি — বরং "common interview pattern" হিসেবে লেখা।