অধ্যায় 13

লিস্ট সাজানো আর তার মেথডগুলো

sorted() আর .sort()-এর পার্থক্য, max/min/sum/count/index, key= দিয়ে সাজানোর নিয়ম বদলানো, আর join() দিয়ে লিস্টকে পড়ার মতো লেখা বানানো।

30 মিনিটPython 3.12
  1. 1সমস্যা
  2. 2বোঝা
  3. 3উদাহরণ
  4. 4অনুমান
  5. 5নিজে করা
  6. 6কঠিন করা

যে সমস্যাটা আমরা সমাধান করছি

সাতজনের নম্বর একটি লিস্টে আছে। তিনটি প্রশ্নের উত্তর দিতে হবে: সবচেয়ে বেশি কত, গড় কত, আর সেরা তিনজন কারা?

গত অধ্যায়ের হাতিয়ার দিয়ে প্রথম দুটোর উত্তর লুপ লিখে বের করা যায় — কষ্টসাধ্য, কিন্তু সম্ভব। তৃতীয়টা আলাদা। «সেরা তিনজন» মানে আগে সাজাতে হবে, আর সাজানোর কোনো উপায় আমাদের এখনো নেই।

এই কাজগুলো এত ঘন ঘন লাগে যে পাইথন সেগুলো নিজেই দিয়ে রেখেছে। আজকের অধ্যায় সেই তালিকাটা — আর তার সাথে একটি ফাঁদ যা প্রায় সবাই অন্তত একবার পড়ে: একই কাজের দুটি রূপ আছে, একটি লিস্টটাকে বদলায় আর অন্যটি নতুন একটি ফেরত দেয়, আর ভুলটা বেছে নিলে কোনো ত্রুটি হয় না।

এই অধ্যায় শেষে আপনি পারবেন

  • sorted() আর .sort()-এর পার্থক্য বলতে, আর কোনটি কখন লাগে জানতে
  • max, min, sum, count, index দিয়ে লিস্ট থেকে উত্তর বের করতে
  • key= দিয়ে সাজানোর নিয়ম বদলাতে
  • join() দিয়ে একটি লিস্টকে পড়ার মতো লেখায় পরিণত করতে
  • মেশানো ধরনের লিস্ট সাজাতে গেলে কেন TypeError হয়, বলতে

আগে যা জানা লাগবে: লিস্ট — একই নামে অনেকগুলো মান।


sorted() আর .sort() — এই অধ্যায়ের মূল কথা

দুটোই সাজায়। পার্থক্য হলো কী নিয়ে ফিরে আসে।

python
marks = [72, 45, 90, 61]

ordered = sorted(marks)
print(ordered)
print(marks)

marks.sort()
print(marks)
text
[45, 61, 72, 90]
[72, 45, 90, 61]
[45, 61, 72, 90]

তিনটি লাইন মন দিয়ে পড়ুন।

sorted(marks) একটি নতুন সাজানো লিস্ট বানিয়ে ফেরত দিয়েছে। মূল marks অবিকল আগের মতোই রয়ে গেছে — দ্বিতীয় লাইনটাই তার প্রমাণ।

marks.sort() কোনো নতুন লিস্ট বানায়নি। সে মূল লিস্টটাকেই সাজিয়ে দিয়েছে, আর পুরোনো ক্রমটা চিরতরে হারিয়ে গেছে।

গত অধ্যায়ের নিয়মটাই এখানে ফিরে এসেছে: যে মেথড লিস্টটাকে বদলায়, সে কিছু ফেরত দেয় না।

python
marks = [72, 45, 90, 61]
marks = marks.sort()
print(marks)
text
None

append-এর মতোই। .sort() লিখলে ফলটা ধরতে নেই; sorted() লিখলে ফলটা না ধরলে কাজটাই বৃথা।

কোনটা কখন? মূল ক্রমটা আর দরকার না হলে .sort() সহজ ও সস্তা। মূল লিস্টটা অন্য কোথাও ব্যবহৃত হচ্ছে, বা আপনি নিশ্চিত নন — তখন sorted(), কারণ সে কারও কিছু কেড়ে নেয় না। সন্দেহ হলে sorted()।

উল্টো ক্রম আর সংখ্যার উত্তর

python
marks = [72, 45, 90, 61]

print(sorted(marks, reverse=True))
print(max(marks), min(marks), sum(marks))
print(round(sum(marks) / len(marks), 2))
text
[90, 72, 61, 45]
90 45 268
67.0

max, min, sum — তিনটিই ফাংশন, মেথড নয়, তাই marks.max() নয় বরং max(marks)। গড়ের জন্য আলাদা কিছু নেই: যোগফলকে সংখ্যা দিয়ে ভাগ করাই গড়, আর round(..., 2) দেখানোর জন্য দুই ঘরে নামিয়ে আনে।

খেয়াল করুন দশম অধ্যায়ের লুপ দিয়ে জমানোর কাজটা এখানে এক লাইনে হয়ে গেল। লুপটা শেখা বৃথা যায়নি — sum যে কাজটা করছে সেটা ঠিক ওই লুপই, আর কোনো দিন এমন কিছু জমাতে হবে যার জন্য পাইথনের তৈরি ফাংশন নেই।

গোনা আর খোঁজা

python
marks = [72, 45, 90, 45, 61]

print(marks.count(45))
print(marks.index(45))
print(marks.index(90))
text
2
1
2

count(x) বলে x কতবার আছে। index(x) বলে প্রথম কোথায় আছে — দুটো 45 থাকলেও সে প্রথমটির সূচক 1 দেয়, দ্বিতীয়টির কথা বলে না।

আর নেই এমন কিছু খুঁজলে index থেমে যায়:

python
marks = [72, 45, 90]
print(marks.index(50))
text
ValueError: 50 is not in list

remove-এর মতোই। আগে if 50 in marks: দিয়ে দেখে নেওয়া অভ্যাস করুন।

লেখা সাজানো, আর key=

python
names = ["rafi", "Bilal", "ahmed"]

print(sorted(names))
print(sorted(names, key=str.lower))
text
['Bilal', 'ahmed', 'rafi']
['ahmed', 'Bilal', 'rafi']

প্রথম ফলটা দেখে অনেকে ভাবে পাইথন ভুল করেছে। করেনি — সে অক্ষরের সংকেত ধরে সাজায়, আর সেখানে সব বড় হাতের অক্ষর সব ছোট হাতের অক্ষরের আগে আসে। তাই Bilal চলে গেছে সবার আগে।

key=str.lower বলছে: সাজানোর সময় প্রতিটি নামকে ছোট হাতের ভেবে নাও। নামগুলো বদলায় না — ছাপা ফলে Bilal-এ বড় হাতের B ঠিকই আছে — শুধু তুলনা করার সময় ছোট হাতের রূপটা ব্যবহৃত হয়।

key যেকোনো নিয়ম হতে পারে:

python
words = ["bag", "pen", "notebook", "ink"]
print(sorted(words, key=len))
text
['bag', 'pen', 'ink', 'notebook']

দৈর্ঘ্য ধরে সাজানো। খেয়াল করুন bag, pen, ink তিনটিরই দৈর্ঘ্য তিন, আর তারা যে ক্রমে ছিল সেই ক্রমেই রয়ে গেছে — সমান হলে পাইথন ক্রম নষ্ট করে না। এই বৈশিষ্ট্যটির নাম stable sort, আর একাধিক শর্তে সাজানোর সময় এটিই কাজে লাগে।

মেশানো ধরন সাজানো যায় না

python
values = [3, "1", 2]
print(sorted(values))
text
TypeError: '<' not supported between instances of 'str' and 'int'

সাজাতে হলে জোড়ায় জোড়ায় তুলনা করতে হয়, আর অষ্টম অধ্যায়ে দেখেছিলাম একটি সংখ্যা আর একটি লেখা < দিয়ে তুলনা করা যায় না। বার্তাটি তাই হুবহু সেই একই বার্তা। CSV ফাইল থেকে পড়া লিস্টে এটি নিয়মিত ঘটে — সেখানে সব সংখ্যাই আসলে লেখা।

লিস্ট থেকে লেখা — join

লিস্ট সরাসরি ছাপলে বর্গবন্ধনী আর কোটেশনসহ আসে, যা মানুষকে দেখানোর জন্য নয়:

python
names = ["rafi", "ahmed", "bilal"]

print(", ".join(names))
print(" | ".join(names))
text
rafi, ahmed, bilal
rafi | ahmed | bilal

লাইনটা প্রথমবার উল্টো লাগে: জোড়া লাগানোর চিহ্নটা আগে বসে, আর লিস্টটা বন্ধনীর ভেতরে। মনে রাখার উপায় — «এই চিহ্ন দিয়ে এদের জুড়ে দাও»।

একটাই শর্ত, আর সেটি কড়া: সব মান লেখা হতে হবে।

python
marks = [72, 45, 90]
print(", ".join(marks))
text
TypeError: sequence item 0: expected str instance, int found

সংখ্যার লিস্ট জোড়া লাগাতে হলে আগে প্রতিটিকে লেখায় বদলে নিতে হয়:

python
marks = [72, 45, 90]
print(", ".join(str(m) for m in marks))
text
72, 45, 90

str(m) for m in marks অংশটি অষ্টাদশ অধ্যায়ের বিষয় — আপাতত এটিকে «প্রতিটি মানকে str() করে নাও» হিসেবেই পড়ুন।


একটা সম্পূর্ণ উদাহরণ

report.py:

python
# A marks report: order them, summarise them, and show the top three
marks = [72, 45, 90, 61, 88, 45, 33]

ordered = sorted(marks, reverse=True)

print("Marks    :", marks)
print("Ordered  :", ordered)
print("Count    :", len(marks))
print("Highest  :", max(marks))
print("Lowest   :", min(marks))
print("Total    :", sum(marks))
print("Average  :", round(sum(marks) / len(marks), 2))
print("How many 45s:", marks.count(45))

top_three = ordered[:3]
print("Top three:", top_three)
print("As text  :", ", ".join(str(m) for m in top_three))

passed = []
for mark in marks:
    if mark >= 40:
        passed.append(mark)

print("Passed   :", len(passed), "of", len(marks))
text
Marks    : [72, 45, 90, 61, 88, 45, 33]
Ordered  : [90, 88, 72, 61, 45, 45, 33]
Count    : 7
Highest  : 90
Lowest   : 33
Total    : 434
Average  : 62.0
How many 45s: 2
Top three: [90, 88, 72]
As text  : 90, 88, 72
Passed   : 6 of 7

দুটো নকশার সিদ্ধান্ত খেয়াল করুন।

sorted() বেছে নেওয়া হয়েছে, .sort() নয় — কারণ প্রথম লাইনেই মূল marks আবার ছাপা হচ্ছে। .sort() লিখলে «Marks» আর «Ordered» দুটো লাইনই একই দেখাত, আর কেউ ত্রুটি দেখাত না। এটাই সেই নীরব ভুল যা এই অধ্যায়ের শুরুতে বলা হয়েছিল।

সেরা তিনজন বের হয়েছে স্লাইস দিয়ে — ordered[:3]। আলাদা কোনো «শীর্ষ তিন» ফাংশন লাগেনি: উল্টো ক্রমে সাজানো লিস্টের প্রথম তিনটিই সেরা তিনটি। সাজানোর পর বহু প্রশ্নের উত্তর এভাবে একটি স্লাইস হয়ে যায়।


কিছু ভাঙা অবস্থা ও তার সমাধান

.sort() করার পর লিস্টটা None হয়ে গেছে marks = marks.sort() লেখা হয়েছে। .sort() লিস্টটাকেই সাজায় আর None ফেরত দেয় — শুধু marks.sort() লিখুন। নতুন একটি লিস্ট চাইলে ordered = sorted(marks)।

সাজানোর পর মূল লিস্টটাও বদলে গেছে .sort() ব্যবহার করা হয়েছে, যা মূল লিস্টকেই সাজায়। মূলটা অক্ষত রাখতে sorted() ব্যবহার করুন।

TypeError: '<' not supported between instances of 'str' and 'int' লিস্টে সংখ্যা আর লেখা মেশানো। print(marks) করে দেখুন কোনগুলো কোটেশনের ভেতরে; ফাইল থেকে পড়া হলে সাধারণত সবই লেখা, আর int() দিয়ে বদলে নিতে হয়।

ValueError: 50 is not in list index() বা remove()-কে এমন একটি মান দেওয়া হয়েছে যা লিস্টে নেই। আগে in দিয়ে দেখে নিন।

TypeError: sequence item 0: expected str instance, int found join()-কে সংখ্যার লিস্ট দেওয়া হয়েছে। প্রতিটিকে str() করে নিন।

নাম সাজানোর পর বড় হাতের নামগুলো সব আগে চলে এসেছে এটাই স্বাভাবিক আচরণ, ত্রুটি নয়। মানুষের প্রত্যাশা মতো সাজাতে sorted(names, key=str.lower)।