পাইথন দিয়ে সর্টিং – ২য় পর্ব

পাইথন প্রোগ্রামিং ভাষায় সর্ট করার পদ্ধতি।

আগের পর্বে আমরা দেখেছি কীভাবে পাইথনের বিল্টইন ফাংশন ব্যবহার করে সর্টিং করা যায়। এই লেখায় আমরা আরেকটু জটিল সর্টিংয়ের কাজ করব, তবে বিল্টইন ফাংশন ব্যবহার করেই কাজগুলো করা হবে।

ধরা যাক, একটি লিস্টে বিভিন্ন ফলের নাম এবং সেই ফল কতগুলো করে আছে, সেটি দেওয়া আছে – fruits = [(‘orange’, 3), (‘apple’, 3), (‘banana’, 2), (‘mango’, 10), (‘guava’, 5)]

এখন এই লিস্টকে আমরা যদি সর্ট করি, তাহলে ফলের নাম অনুসারে সর্ট হয়ে যাবে –

>>> fruits = [('orange', 3), ('apple', 3), ('banana', 2), ('mango', 10), ('guava', 5)]
>>> print(sorted(fruits))
[('apple', 3), ('banana', 2), ('guava', 5), ('mango', 10), ('orange', 3)]

ফলের নাম অনুসারে সর্ট হওয়ার কারণ কী? ফলের সংখ্যা অনুসারেও তো সর্ট হতে পারত। এখানে প্রতিটি টাপলের প্রথম উপাদান সেই টাপলের প্রতিনিধিত্ব করে। যেমন, টাপলে আমরা যদি আগে সংখ্যা লিখতাম, তারপরে ফলের নাম লিখতাম, তাহলে সংখ্যা অনুযায়ী সর্ট হতো।

কিন্তু আমরা যদি চাই, আমাদেরকে যেই লিস্ট দেওয়া আছে সেটি ফলের নাম নয়, বরং সংখ্যা অনুসারে সর্ট করা হবে, তখন কী করতে হবে? প্রতিটি টাপলের দ্বিতীয় উপাদানটি যদি সেই টাপলের প্রতিনিধিত্ব করত, তাহলে কিন্তু আমরা কাঙ্ক্ষিত উপায়ে সর্ট করতে পারতাম। sorted() ফাংশনে key নামে একটি প্যারামিটার আছে, যার মাধ্যমে আমরা বলে দিতে পারি, কোন উপাদানটির ওপর ভিত্তি করে সর্ট করার কাজটি হবে। key-তে আসলে একটি ফাংশন দেওয়া হয়, আর যেই লিস্ট সর্ট করতে হবে, তার প্রতিটি উপাদান সেই ফাংশনের মধ্যে পাঠানো হয়। ফাংশনটি একটি উপাদান রিটার্ন করবে, যার ওপর ভিত্তি করে সর্টিং হবে। তাহলে আমরা এখানে যেই কাজটি করতে চাচ্ছি, সেখানে এমন একটি ফাংশন লিখতে হবে, যা (‘apple’, 3) ইনপুট নিবে আর 3 রিটার্ন করবে।

def compare_fnc(item):
    return item[1]

fruits = [('orange', 3), ('apple', 3), ('banana', 2), ('mango', 10), ('guava', 5)]
print(sorted(fruits, key=compare_fnc))

ওপরের কোড রান করলে দেখা যাবে ফলের সংখ্যা অনুযায়ী ছোট থেকে বড় ক্রমে সর্ট করা হয়ে গিয়েছে।

[('banana', 2), ('orange', 3), ('apple', 3), ('guava', 5), ('mango', 10)]

বড় থেকে ছোট ক্রমে সর্ট করতে চাইলে লিখতে হবে sorted(fruits, key=compare_fnc, reverse=True).

পাইথনে operator মডিউলে একটি ফাংশন আছে itemgetter, যেটি ব্যবহার করে আমরা ওপরের কাজটি আরো সহজে করতে পারি, আমাদের নিজেদের কষ্ট করে ফাংশন তৈরি করতে হবে না।

from operator import itemgetter

fruits = [('orange', 3), ('apple', 3), ('banana', 2), ('mango', 10), ('guava', 5)]
print(sorted(fruits, key=itemgetter(1)))

ওপরের কোডে itemgetter(1) এর বদলে itemgetter(0) লিখলে ফলের নাম অনুযায়ী সর্ট হয়ে যাবে। এখন আমরা যদি চাই, প্রথমে ফলের সংখ্যা অনুযায়ী সর্ট হবে, তারপরে যেসব ফলের সংখ্যা সমান, তাদের মধ্যে নাম অনুযায়ী সর্ট হবে, তাহলে কী করতে হবে? মানে আমাদের আউটপুট (‘orange’, 3′), (‘apple’, 3) ক্রমে না এসে (‘apple’, 3), (‘orange’, 3) ক্রমে আসবে। কাজটি সহজেই করা যায় এভাবে –

>>> fruits = [('orange', 3), ('apple', 3), ('banana', 2), ('mango', 10), ('guava', 5)]
>>> print(sorted(fruits, key=itemgetter(1, 0)))
[('banana', 2), ('apple', 3), ('orange', 3), ('guava', 5), ('mango', 10)]

এখান আমরা itemgetter(1, 0) ব্যবহার করেছি। কিন্তু এখন আমরা যদি চাই, ফলের সংখ্যার বড় থেকে ছোট ক্রমে সর্ট হবে আর যেসব ফলের সংখ্যা সমান, তারা নাম অনুযায়ী ছোট থেকে বড় ক্রমে সর্ট হবে, তখন কী করতে হবে? তাহলে দুইবার সর্ট করার কাজটি করতে হবে –

>>> fruits = [('orange', 3), ('apple', 3), ('banana', 2), ('mango', 10), ('guava', 5)]
>>> print(fruits)
[('orange', 3), ('apple', 3), ('banana', 2), ('mango', 10), ('guava', 5)]
>>> fruits = sorted(fruits, key=itemgetter(0))
>>> print(fruits)
[('apple', 3), ('banana', 2), ('guava', 5), ('mango', 10), ('orange', 3)]
>>> fruits = sorted(fruits, key=itemgetter(1), reverse=True)
>>> print(fruits)
[('mango', 10), ('guava', 5), ('apple', 3), ('orange', 3), ('banana', 2)]

ওপরের পদ্ধতি কাজ করে, কারণ পাইথনের sorted() ফাংশন stable সর্ট করে। sort()-এর ক্ষেত্রেও একই কথা প্রযোজ্য।

পাইথন দিয়ে সর্টিং – ১ম পর্ব

এই টিউটোরিয়ালে পাইথন দিয়ে কিভাবে সর্ট করতে হয়, সেটি আলোচনা করা হবে। প্রোগ্রামিংয়ে সর্ট (sort) করা মানে হচ্ছে কোনো নির্দিষ্ট ক্রমে সাজানো। পাইথনে কোনো লিস্টকে সর্ট করার জন্য একটি মেথড দেওয়া আছে, যার নাম হচ্ছে sort()। নিচের কোড দেখলেই sort() কী করে, সেটি বুঝা যাবে –

>>> li = [1, 3, 4, 5, 6, 2, 3]
>>> li.sort()
>>> print(li)
[1, 2, 3, 3, 4, 5, 6]

প্রোগ্রামটি রান করলে দেখা যাচ্ছ যে, লিস্টের সংখ্যাগুলো ছোট থেকে বড় ক্রমে সাজানো হয়ে গিয়েছে। এখন, কেউ যদি চায় যে, সে বড় থেকে ছোট ক্রমে সাজাবে, তাহলে sort() মেথডের ভেতরে reverse নামে একটি প্যারামিটার আছে, সেখানে True পাঠাতে হবে –

>>> li = [1, 3, 4, 5, 6, 2, 3]
>>> li.sort(reverse=True)
>>> print(li)
[6, 5, 4, 3, 3, 2, 1]

একটি স্ট্রিংয়ের লিস্টকেও একইভাবে সর্ট করা যায়। যেমন –

>>> countries = ["Bangladesh", "Japan", "Australia", "Canada", "Singapore"]
>>> countries.sort()
>>> print(countries)
['Australia', 'Bangladesh', 'Canada', 'Japan', 'Singapore']

আমরা দেখতে পাচ্ছি, লিস্টকে সর্ট করা হলে লিস্টের ভেতরের উপাদানগুলো নির্দিষ্ট ক্রমে সাজানো হয়ে যায়, আগের লিস্টটি আর পাওয়া যায় না। কিন্তু আমরা যদি সেটি পেতে চাই, তাহলে কী করতে হবে? পাইথনে আরেকটি ফাংশন আছে, যার নাম sorted(). এটি সর্ট করার পরে একটি নতুন লিস্ট রিটার্ন করে। যেই লিস্টটি সর্ট করা হচ্ছে, সেটি পরিবর্তন হয় না। নিচের কোড দেখলেই এটি বুঝতে পারা যাবে –

>>> li = [1, 3, 4, 5, 6, 2, 3]
>>> nums = sorted(li)
>>> print(nums)
[1, 2, 3, 3, 4, 5, 6]
>>> print(li)
[1, 3, 4, 5, 6, 2, 3]

sorted() ফাংশনটি কেবল লিস্ট না, অন্য ইটারেবলের (iterable) ওপরও কাজ করে। যেমন, আমরা চাইলে একটি টাপলকে সর্ট করতে পারি। ফলাফল হিসেবে একটি লিস্ট রিটার্ন করা হবে।

>>> tpl = (3, 8, 1, 4, 6, 2)
>>> result = sorted(tpl)
>>> result
[1, 2, 3, 4, 6, 8]
>>> tpl
(3, 8, 1, 4, 6, 2)

sorted() ফাংশনেও উল্টো ক্রমে সর্ট করতে চাইলে reverse প্যারামিটার ব্যবহার করা যাবে।

>>> li = [1, 3, 4, 5, 6, 2, 3]
>>> nums = sorted(li, reverse=True)
>>> print(nums)
[6, 5, 4, 3, 3, 2, 1]
>>> print(li)
[1, 3, 4, 5, 6, 2, 3]

পাইথনের এই বিল্টইন সর্ট করার ফাংশনটিতে আসলে Timsort নামক একটি অ্যালগরিদম ব্যবহার করা হয়।

পাইথনের সর্টিং নিয়ে পরবর্তী লেখায় আমরা আরেকটু জটিল ডেটা স্ট্রাকচার কীভাবে সর্ট করতে হয়, সেটি দেখব।

একটি লিস্টে দ্বিতীয় লিস্টের সকল উপাদানের উপস্থিতি – প্রোগ্রামিং ইন্টারভিউ সমস্যা ১৫

সহজ প্রোগ্রামিং ইন্টারভিউ সমস্যা।

সমস্যা – একটি ফাংশন লিখতে হবে, যেখানে দুটি লিস্ট ইনপুট দেওয়া হবে। প্রথম লিস্টে যদি দ্বিতীয় লিস্টের সকল উপাদান থাকে তাহলে True রিটার্ন করতে হবে, আর নইলে False রিটার্ন করতে হবে।

সমাধান – সমাধান খুবই সহজ। দ্বিতীয় লিস্টের প্রতিটি উপাদান প্রথম লিস্টে আছে কী না, সেটি পরীক্ষা করতে হবে। যদি না থাকে, তাহলে False রিটার্ন করতে হবে, আর যদি সব উপাদানই প্রথম লিস্টে পাওয়া যায়, তাহলে True রিটার্ন করতে হবে।

def is_sublist(listA, listB):
    for item in listB:
        if item not in listA:
            return False
    return True

এখন প্রশ্ন হচ্ছে, ওপরের কোডের কমপ্লেক্সিটি কত? অতিরিক্ত কোনো মেমোরি ব্যবহার করা হচ্ছে না, তাই স্পেস কমপ্লেক্সিটি হচ্ছে O(1)। আর টাইম কমপ্লেক্সিটি হচ্ছে O(n * m), যেখানে n হচ্ছে listA-এর উপাদান সংখ্যা আর m হচ্ছে listB-এর উপাদান সংখ্যা। অনেকেই এখানে ভুল করে টাইম কমপ্লেক্সিটি বলবে O(n), কিন্তু item not in listA – এখানে কিন্তু লিস্টের সকল উপাদান পরীক্ষা করতে হতে পারে, তাই শুধু এই কাজটির টাইম কমপ্লেক্সিটি হচ্ছে O(m)।

পরবর্তী প্রশ্ন হচ্ছে, ওপরের কোডটি কীভাবে আরো ইফিশিয়েন্ট করা যায়? এজন্য সেট (set) ব্যবহার করা যেতে পারে। প্রথমে listA-কে সেটে রূপান্তর করতে হবে। তারপরে কোড আগের মতোই। আমি আর এখানে কোড লিখে দেখালাম না, এটি নিজে লিখতে হবে।

লিস্টে যদি n সংখ্যক উপাদান থাকে, তাহলে এটিকে সেটে রূপান্তর করার কমপ্লেক্সিটি হচ্ছে O(n)। আর তারপরে যেই হোড হবে, সেটি ঠিকঠাকমতো লিখলে তার কমপ্লেক্সিটি হবে O(m)। কারণ একটি উপাদান সেটে আছে কী না, সেটি পরীক্ষা করার জন্য O(1) সময় লাগে। তাহলে প্রোগ্রামটির টাইম কমপ্লেক্সিটি হবে O(n) + O(m)। তবে সেট ব্যবহার করায় কিন্তু এই প্রোগ্রামের স্পেস কমপ্লেক্সিটি বেড়ে যাচ্ছে, স্পেস কমপ্লেক্সিটি কত সেটি পাঠকের জন্য কুইজ (কমেন্টে উত্তর দেওয়া যাবে)।

দুটি বিষয় লক্ষ রাখতে হবে। ইন্টারভিউতে স্পেস কমপ্লেক্সিটি না জিজ্ঞাসা করলেও বলা উচিত। মানে কমপ্লেক্সিটি জিজ্ঞাসা করলে টাইম ও স্পেস কমপ্লেক্সিটি দুটোই বলা উচিত। আর দ্বিতীয়ত, ফাংশন লেখার পরে তার জন্য ইউনিট টেস্ট লিখতে পারলে ভালো হয়, এতে ইন্টারভিউয়ারকে একটু খুশি করা যায় আর কী।

সকল সাবসেট তৈরি – প্রোগ্রামিং ইন্টারভিউ সমস্যা ১৪

সমস্যাঃ একটি সেট দেওয়া আছে, সেই সেটের সকল সাবসেট তৈরি করার ফাংশন লিখতে হবে।

সমাধানঃ ইন্টারভিউতে এই প্রশ্ন করা হলে শুরুতেই দুটি বিষয় পরিষ্কার হয়ে নিতে হবে। সেটি হচ্ছে, ফাঁকা সেট এবং ওই সেটটি নিজে তার সাবসেট কী না। আমাদের এই আলোচনায়, দুটি উত্তরই হচ্ছে, হ্যাঁ।

সমাধান দেখার আগে নিজে চেষ্টা করা উচিত। এখানে গিয়ে সমস্যা দেখা যাবে ও সমাধান জমা দেওয়া যাবে – https://leetcode.com/problems/subsets/

তাহলে, ইনপুট যদি হয় [1, 2, 3], আউটপুট হবে, [[], [1], [2], [3], [1, 2], [2, 3], [1, 3], [1, 2, 3]]। আউটপুট লিস্টের উপাদানগুলোর ক্রম ভিন্ন হলেও সমস্যা নেই, কিন্তু একই জিনিস দুইবার থাকতে পারবে না।

আমরা রিকার্শন ব্যবহার করে খুব সহজেই সমস্যাটির সমাধান করতে পারি। আমরা প্রতিবার সেট থেকে একটি উপাদান নেওয়ার চেষ্টা করব। তবে আমাদেরকে খেয়াল রাখতে হবে যেন, একই উপাদান একাধিকবার নেওয়া না হয়। সেজন্য একটি বুলিয়ান অ্যারে বা লিস্ট ব্যবহার করতে পারি (visited)। বিস্তারিত ব্যাখ্যা না করে, বরং কোড লিখে দিচ্ছি। তবে কোড বুঝার জন্য খাতা-কলম নিয়ে বসতে হবে। আর যারা ডিএফএস (dfs)-এর সঙ্গে পরিচিত, তারা সহজেই বুঝতে পারবে। আর যারা পরিচিত নও, তাদেরও খুব একটা সমস্যা হবে না।

def subsets1(S):
    if S == []:
        return [[]]
    
    result = [[]]
    n = len(S)
    visited = [False] * n
    
    def recurse(i, n, li):
        if i == n:
            return
        
        if len(li) > 0:
            result.append([x for x in li])
        
        for j in range(i, n):
            if visited[j]:
                continue
            visited[j] = True
            li.append(S[j])
            recurse(j, n, li)
            li.pop()
            visited[j] = False
            
    recurse(0, n, [])
    return result

এখন কথা হচ্ছে, ইন্টারভিউতে সমাধান করাই শেষ কথা নয়। কেউ এই কোড লেখা পরে ইন্টারভিউয়ার যদি বলেন, এবারে রিকার্শন ছাড়া সমাধান করে দেখান, তখন রিকার্শন ছাড়া সমাধান করতে হবে। যেসব সমস্যা রিকার্শন ব্যবহার করে সহজে করা যায়, সেগুলো রিকার্শন ছাড়া সমাধান করতে গেলে স্ট্যাক ব্যবহারের প্রয়োজন হতে পারে। আবার না বুঝে কোড লিখলেও হবে না। কারণ ইন্টারভিউতে পুরো কোড ব্যাখ্যাও করতে হবে। সুতরাং ভালো প্রস্তুতির প্রয়োজন।

সম্পূর্ণ কোড এখানে – https://gist.github.com/tamim/87d3b79ff15d3375a98e9e0cd73b7c73

 

LCS-Zero – প্রোগ্রামিং ইন্টারভিউ সমস্যা ১২

সমস্যাঃ একটা ইন্টিজারের অ্যারে দেওয়া আছে। সেখানে যদি পরপর কতগুলো সংখ্যা পাওয়া যায়, যাদের সমষ্টি শূন্য, সেই সংখ্যাগুলোর মধ্যে সবচেয়ে বেশিসংখ্যক ক্রমিক (অ্যারেতে ক্রমানুসারে সাজানো) সংখ্যাগুলো বের করতে হবে।

উদাহরণঃ 1, 2, -2, 4, -4 – এই সংখ্যাগুলোর মধ্যে, 2, -2 -এর যোগফল 0; 4, -4 – এর যোগফল 0; আবার 2, -2, 4, -4 সংখ্যাগুলোর যোগফলও 0। এখন সবচেয়ে বেশি সংখ্যক ক্রমিক সংখ্যা হচ্ছে 2, -2, 4, -4। তাই আমাদের উত্তর হবে 2, -2, 4, -4.

সমস্যাটি কেউ নিজে সমাধান করতে চাইলে এখানে চেষ্টা করতে হবে – https://www.interviewbit.com/problems/largest-continuous-sequence-zero-sum/

সমাধানঃ আমরা প্রথমে সবচেয়ে সহজ বুদ্ধি প্রয়োগ করতে পারি। অ্যারেতে যদি n সংখ্যক উপাদান থাকে, তাহলে সবগুলো উপাদানের সমষ্টি 0 কী না, সেটি পরীক্ষা করি। তারপরে n-1 সংখ্যক ক্রমিক সংখ্যার সমষ্টি 0 কী না পরীক্ষা করি। তারপর n-2 সংখ্যক ক্রমিক সংখ্যার সমষ্টি পরীক্ষা করি… এভাবে যতক্ষণ না আমরা সমষ্টি 0 পাচ্ছি, ততক্ষণ পরীক্ষা করবো আর n-এর মান এক কমাতে থাকবো 1 পর্যন্ত। যেমন, আমাদের যদি 1, 2, 3, -4, 5 – এই পাঁচটি সংখ্যা দেওয়া হয়, তাহলে প্রথমে পরীক্ষা করবো –

1 + 2 + 3 + -4 + 5 = 7

তারপরে চারটি করে ক্রমিক সংখ্যা নেব –
1 + 2 + 3 + -4 = 2
2 + 3 + -4 + 5 = 6

এবারে তিনটি করে ক্রমিক সংখ্যা –
1 + 2 + 3 = 6
2 + 3 + -4 = 1
3 + -4 + 5 = 4

এখন দুটি করে ক্রমিক সংখ্যা নেই –
1 + 2 = 3
2 + 3 = 5
3 + -4 = -1
-4 + 5 = 1

এখন একটি করে সংখ্যা নিয়ে দেখবো তাদের সমষ্টি (অর্থাৎ সেই সংখ্যাটির মান) 0 কী না। এই উদাহরণে এরকম সংখ্যা নাই।

যাই হোক, আশা করি, আমি কী করতে চাচ্ছি, তা বুঝাতে পেরেছি। এখন নিজে কোড লেখার চেষ্টা করতে হবে।

আমরা এরকম কোড লিখতে পারি –

def lszero1(A):
    n = len(A)
    
    for window_size in range(n, 0, -1):
        for start in range(0, n):
            if start+window_size > n:
                    break
            if 0 == sum(A[start:start+window_size]):
                return A[start:start+window_size]
                
    return []

এই কোডের কমপ্লেক্সিটি কত? sum() ফাংশনের কমপ্লেক্সিটি হচ্ছে O(n)। তাহলে আমাদের ফাংশনের কমপ্লেক্সিটি হবে O(n^3). প্রতিবার কিন্তু আসলে sum() ফাংশন ব্যবহার না করলেও চলে। আমরা যদি জানি, 1 + 2 + 3 = 6, তাহলে 2 + 3 + 4 হবে, আগের যোগফল থেকে 1 বিয়োগ ও 4 যোগ। বিষয়টি একটু ঠান্ডা মাথায় চিন্তা করলেই বুঝে ফেলার কথা। এই কাজটি ঠিকভাবে করতে পারলে আমরা আমাদের কোডের কমপ্লেক্সিটি O(n^2)-এ নামিয়ে আনতে পারবো।

for window_size in range(n, 0, -1):
    w_sum = sum(A[0:window_size])
    if w_sum == 0:
        return A[0:window_size]
    for start in range(1, n):
        if start + window_size > n:
            break    
        w_sum = w_sum - A[start-1] + A[start+window_size-1]
        if w_sum == 0:
            return A[start:start+window_size]                

এখন, আরো গভীরভাবে চিন্তা করলে, এই সমস্যাটির সমাধান O(n) কমপ্লেক্সিটিতেও করা সম্ভব। এবং 90% ক্ষেত্রে ইন্টারভিউয়ার হয়ত সেটিই আশা করবেন, কিংবা বলে দিবেন যে O(n) কমপ্লেক্সিটিতে সমাধান করতে। সেটি নিজে করার চেষ্টা করতে হবে। আমি কেবল একট হিন্ট দিয়ে দিচ্ছি –

A = [1, 2, -2, 4, -4], এর শুরু থেকে ক্রমিক সংখ্যাগুলোর যোগফল 1, 3 (1+2), 1 (1+2+-2), 5 , (1+2+-2+4), 1 (1+2+-2+4+-4)। এগুলো একটি অ্যারেতে রাখি –
S = [1, 3, 1, 5, 1]. এখন এই ক্ষেত্রে, প্রথম সংখ্যাটি 1, আবার পঞ্চম সংখ্যাটিও 1। অর্থাৎ প্রথম সংখ্যার পরে পরপর চারটি সংখ্যা যোগ করে যোগফলের কোনো পরিবর্তন হলো না। এতে আমরা কী বুঝলাম?

১->০, ০->১ – প্রোগ্রামিং ইন্টারভিউ সমস্যা ৮

সমস্যাঃ সম্প্রতি আমি একটা ইন্টারভিউতে এই সমস্যাটি দেখেছি – একটা ফাংশন লিখতে হবে, যেখানে ইনপুট 0 হলে আউটপুট হবে 1 আর ইনপুট 1 হলে আউটপুট হবে 0। ফাংশনটা বিভিন্নভাবে লিখতে হবে। ফাংশনের ইনপুট কেবল 0 আর 1 হবে, অন্য কোনো সংখ্যা নয়।

সমাধানঃ আমার সমাধান দেখার আগে নিজে নিজে চেষ্টা করা উচিত।

এটা আসলে ঠিক কতভাবে করা যায়, আমার জানা নেই। তবুও পাইথন ব্যবহার করে আমি বেশ কয়েকভাবে ফাংশনটি লিখতে পারি।

প্রথমে অবশ্যই if-else ব্যবহার করে –

def fnc1(n):
    if n == 1:
        return 0
    else:
        return 1

ওপরের প্রোগ্রামটা পাইথনে আরেকটু সুন্দরভাবে লেখা যায় –

def fnc2(n):
    return 0 if n == 1 else 1

এবারে কিছু গাণিতিক বুদ্ধিশুদ্ধি ব্যবহার করি –

def fnc3(n):
    return (n + 1) % 2

এবারে লিস্ট ডেটা স্ট্রাকচার ব্যবহার করি –

def fnc4(n):
    li = [1, 0]
    return li[n]

একইভাবে চাইলে টাপলও ব্যবহার করা যায়। ডিকশনারি ব্যবহার করেও করা যায় –

def fnc5(n):
    dt = {1: 0, 0: 1}
    return dt[n]

এবারে বিট লেভেলে চিন্তা করা যাক।  আমরা যদি 1 এর সঙ্গে 0-কে এক্সর (XOR) করি, তাহলে 1 পাবো, আর 1-কে এক্সর করলে 0 পাবো –

def fnc6(n):
    return 1 ^ n

আমার মাথায় এগুলোই এসেছে। আর কোনোভাবে ফাংশনটি লেখা যায়?

স্ট্রিং-এর ভেতর স্ট্রিং (সাবস্ট্রিং) – প্রোগ্রামিং ইন্টারভিউ সমস্যা ৭

সমস্যাঃ দুটি স্ট্রিং দেওয়া আছে – A ও s. একটি ফাংশন লিখতে হবে, যার কাজ হচ্ছে, s যদি A-এর সাবস্ট্রিং হয়, তাহলে A-এর যেই ইনডেক্স থেকে s শুরু হয়েছে, সেই ইনডেক্স রিটার্ন করতে হবে। আর s যদি A-তে না থাকে, তাহলে -1 রিটার্ন করতে হবে।

সমাধানঃ প্রশ্নটা খুবই সহজ, তাই অনেকেই সরাসরি কোড লেখা শুরু করে দিবে। ইন্টারভিউতে এমন করলে কিন্তু হবে না। কারণ রিকোয়ারমেন্ট আরো পরিষ্কার করে নিতে হবে। যেমন, s যদি খালি স্ট্রিং হয়, তখন কী হবে? s যদি A-তে একাধিকবার থাকে, তখন কী হবে? – এসব প্রশ্নের উত্তর কোড লেখার আগেই জেনে নিতে হবে।

যারা পাইথন ব্যবহার করে অভ্যস্ত, তারা সহজেই কোড লিখে ফেলতে পারবে –

def string_search(A, s):
  if s in A:
    return A.index(s)
  else:
    return -1

খুবই সহজ কোড। তবে কেউ যদি বলে যে সে পাইথনে অভিজ্ঞ, তাহলে হয়ত তাকে ইন্টারভিউয়ার বলবেন যে, ফাংশনের ভেতরে এক লাইনে কোড লিখে ফেলতে। তখন কোড হবে এরকম –

def string_search(A, s):
  return A.index(s) if s in A else -1

এরপর ইন্টারভিউয়ার হয়ত বলবেন যে, index ফাংশন ব্যবহার করা যাবে না, আমি আসলে দেখতে চাইছি, আপনি এই ফাংশনটা নিজে তৈরি করতে পারেন কী না। তখন একটু চিন্তাভাবনা করে কোড লিখে ফেলতে হবে। আমার ধারণা আমার ব্লগের ৮০% পাঠকেরই এটা লিখতে দশ মিনিটের কম সময় লাগবে। আমি কোড লিখে দিচ্ছি –

if s == "" or A == "" or len(s) > len(A):
  return -1

lenA, lenS = len(A), len(s)

for i in range(lenA):
  if A[i] == s[0]:
    j, k = i, 0
    
    while j < lenA and k < lenS and A[j] == s[k]:
      j += 1
      k += 1

    if k == lenS:
      return i
    
return -1

এখন প্রশ্ন হচ্ছে এই যে কোড লিখলাম, এর টাইম কমপ্লেক্সিটি কত? আর কোনো কোনো ইন্টারভিউয়ার এটাও বলবেন যে, এখন ইউনিট টেস্ট লিখে দেখাতে হবে। আবার কেউ কেউ হয়ত শুরুতেই ইউনিট টেস্ট লিখতে বলতে পারেন। সেজন্য প্রস্তুত থাকতে হবে।

বি.দ্র. ইউনিট টেস্ট নিয়ে আমি আগে একটি আর্টিকেল লিখেছি।

নোটঃ কোনো কোনো ইন্টারভিউয়ার এই প্রশ্নের উত্তরে আরো ইফিশিয়েন্ট অ্যালগরিদম ব্যবহার করতে পরামর্শ দেবেন। সেক্ষেত্রে রবিন-কার্প স্ট্রিং ম্যাচিং, অথবা কেএমপি (নুথ-মরিস-প্র্যাট) অ্যালগরিদম ব্যবহার করতে হবে।

পাইথন-এ ডেটা অ্যানালাইটিক্স

পাইথন নিয়ে চারপাশে অনেক কথা বার্তা – অনেকেই এই ল্যাঙ্গুয়েজটা শেখার চেষ্টা করছে – এটা একটা ভাল দিক । আমার নিজের প্রোগ্রামিং এর হাতেখড়ি সি ল্যাঙ্গুয়েজ দিয়ে – তাই নিজে যখন পাইথন এর বেসিক শেখা শুরু করলাম আশ্চর্যজনক ভাবে আবিষ্কার করলাম এর সাবলীলতা (Simplicity)  – অনেক কাজই ঠিক আমরা যেভাবে চিন্তা করি সেভাবে লিখে ফেলা যায় । শুধু বারবার খচখচ লাগছিল সেমিকোলন আর ট্যাব দিয়ে এলাইনমেন্ট এর ব্যাপারটা – যা অবশ্য দু একদিন পরই অভ্যাস হয়ে যায়।

পাইথন এর প্রাথমিক ব্যাপারগুলো নিয়ে কিছু বলার আর তেমন দরকার আছে বলে আমার মনে হয় না। কারো প্রয়োজন হলে বিস্তারিত দেখে নেয়া যাবে এখান থেকে –

১। পাইথন পরিচিতি   

২। প্রাথমিক ধারনা  

আমার এই লিখাটা মূলত তিনটি বা চারটি পর্বে ভাগ করার ইচ্ছে। এই পর্বে আলোচনা করব NumPy এর একেবারের শুরুর কিছু ব্যাপার। সামনের পর্ব গুলোতে  থাকবে NumPy আর Pandas নিয়ে বিস্তারিত কিছু যা মূলত ডাটা এনালাইটিকস এর কাজে ব্যাবহৃত হয়।

তার আগে কিন্তু আমাদের পাইথন এর বেসিক কিছু ডাটা স্ট্রাকচার নিয়ে হাতেকলমে কাজ করে আসতে হবে ,যেমন list, dict, tuples, sets, strings । আমি নিশ্চিত তোমরা এই জিনিসগুলো পড়েই ডাটা এনালাইটিক্স এর কাজে হাত দিবে –
চটপট একটা পুরনো পড়া পড়ে ফেলতে পারি আমরা –
List এ  append আর  extend এর পার্থক্য কি ?

NumPy (সচরাচর উচ্চারন “নামপাই”) একটি লাইব্রেরি এবং শব্দটা এসেছে Numerical Python থেকে । এই লাইব্রেরি এন-ডাইমেনশনাল Array , বিভিন্ন জটিল গাণিতিক ফাংশন এর কাজ ( যেমন  Fourier transforms )-সহ আরো অনেক ধরনের গাণিতিক কাজ করার ক্ষেত্রে ওস্তাদ ।

ঢাকায় এখন বিভিন্ন এপভিত্তিক পরিবহন খুব জনপ্রিয় – আমরা সবাই উবার, পাঠাও, মুভ বা স্যাম নামে এইসব সেবা ব্যাবহার করছি আর সেইরকম একটি সার্ভিস-এর তিনজন ব্যবহারকারীর কিছু ডেটা আছে, সেইরকম তিনটা ফাইল নিয়ে আমরা কাজ শুরু করতে পারি।
আমরা সেখান থেকে তিনটা জিনিস বের করতে চাইব :
১। অফিস থেকে কে কখন রাইড নিয়ে বের হয়ে গিয়েছে ?
২। তাদের ভাড়ার Standard Deviation দেখতে কেমন ?
আর
৩। চেষ্টা করব বের করতে এই তিনজনের মাঝে কোন বাইক ড্রাইভার কমন ছিলেন কিনা, সম্ভব হলে সেটা গ্রাফে দেখা (যদিও গ্রাফে দেখার ব্যাপারটা এর সাথে তেমন একটা সম্পর্কিত না – কিন্তু দেখতে সুন্দর লাগে)।

এখান থেকে ফাইলগুলো নামিয়ে নেয়া যাবে (আপাতত আমি একটা ফাইল রেখেছি …পরবর্তী পর্বে বাকীগুলো পাওয়া যাবে)। 

প্রথমেই RidersData1 ফাইলটি পড়ে নিয়ে পাইথন-এ এর ডাটা দেখে নেই – 

import csv
with open("C:\Users\Asus\Desktop\RidersData1.csv", 'r') as f:
riders_data = list(csv.reader(f, delimiter=","))
print(riders_data[:2])

দেখে নেই যাত্রীর ভাড়ার পরিমান কত ছিল তার ভ্রমণের দিনগুলোতে

fare = [item[10] for item in riders_data[1:]]
print(fare)

তার মোট পরিমান কত ছিল ?

fare = [int(item[10]) for item in riders_data[1:]]
print(sum(fare))

>> ৫১৮৫ টাকা

এবার আমরা Numpy ইন্সটল করে কাজটা শুরু করি-
( Numpy ইন্সটল করার ধাপগুলো সুন্দর করে বলে দেয়া আছে এখানে

import numpy as np
riders_data = np.genfromtxt("D:\RidersData1.csv", delimiter=",", skip_header=1)

 

fare = [int(item[10]) for item in riders_data[0:]]
print(sum(fare))

এখন যদি Numpy ব্যাবহার করে Array তৈরী করি যা আসলে Array এর Array তৈরী করবে ।

riders_data_array = np.array(riders_data[1:], dtype=np.int)

এখানে dtype=np.int  আসলে নিশ্চিত করবে যেন ডাটা গুলোকে  String থেকে int এ রুপান্তর(convert) করে নেয়।

এখন আমরা যদি Array টা দেখতে চাই?

ইনপুট
 print(riders_data_array)
আউটপুট
 [[4445115     143    3180   15536       5]
  [4341383     167    3067   15704       5]
  [4153972     163    2383   15692       5]
  [4052253     167    3055   15703       5]
  [3935184     191    1520   12760       5]
  [3896665     202    2251   13224       5]
  [3807622     141    2611   15722       5]
  [3723194     236    2063   16165       5]
  [3650954     233    2411   15695       5]
  [3592384     262    5845   15721       5]
  [3538266     204    4802   11583       5]
  [3496106     242    3462   15698       5]
  [3455465     235    2588   15695       5]
  [3361305     229    1883   15724       5]
  [3353856     230    2173   15574       5]
  [3342150     234    2483   15700       5]
  [3302407     217    2461   15716       5]
  [3263818     163    2393   15688       5]
  [3227817     163    2270   15691       5]
  [3189625     166    2976   15635       5]
  [3118401     244    3155   16076       5]
  [3056517     245    3161   12712       5]
  [2966371     191    1882   12546       5]
  [2904590     242    2844   16122       5]
  [2872271     135    2652   15900       5]]

আমরা এখন যেকোন রকমের Array তৈরি করতে পারি

ইনপুট
 any_array = np.ones((5,6))
 print(any_array)
আউটপুট
 [[ 1.  1.  1.  1.  1.  1.]
  [ 1.  1.  1.  1.  1.  1.]
  [ 1.  1.  1.  1.  1.  1.]
  [ 1.  1.  1.  1.  1.  1.]
  [ 1.  1.  1.  1.  1.  1.]]

এখানে np.ones((5,6)) তে 5 এবং 6  যথাক্রমে row আর column বুঝাচ্ছে ।ঠিক একই রকম ভাবে একটা array তৈরি করতে পারি যেটার সবগুলো ভ্যালু হবে zero।

ইনপুট
 any_array = np.zeros((5,6))
 print(any_array)
আউটপুট
 [[ 0.  0.  0.  0.  0.  0.]
  [ 0.  0.  0.  0.  0.  0.]
  [ 0.  0.  0.  0.  0.  0.]
  [ 0.  0.  0.  0.  0.  0.]
  [ 0.  0.  0.  0.  0.  0.]]

আর এখন যদি চিন্তা করি একটা array তৈরি করব যার ভ্যালুগুলো হবে যে কোন নাম্বার বা Random ।

ইনপুট
 any_array = np.random.rand(2,3)
 print(any_array)
আউটপুট
 [[ 0.58870582  0.75596673  0.48148491]
  [ 0.39392526  0.16046594  0.34446376]]

এই পর্যায়ে এসে আমি দুটো বাড়ির কাজ দিতে চাই – 

১। Random নাম্বারের একটা array তৈরি করতে হবে যেখানে সংখ্যাগুলো হবে পূর্ণমান বা integer
২। any_array = np.eye(4)  – এই কোডটি run করলে যেই আউটপুট আসল তার মানে কি ?

স্লাইসিং (Slicing) :

শব্দটা শুনেই বুঝা যাচ্ছে – যে কোন বড় কোন Array থেকে কিছু অংশ কেটে নেয়াই slicing । যেমন আমাদের মূল ডাটা এর array ( riders_data_array)  যা দেখতে এমন

[[4445115     143    3180   15536       5]
  [4341383     167    3067   15704       5]
  [4153972     163    2383   15692       5]
  [4052253     167    3055   15703       5]
  [3935184     191    1520   12760       5]
  [3896665     202    2251   13224       5]
  [3807622     141    2611   15722       5]
  [3723194     236    2063   16165       5]
  [3650954     233    2411   15695       5]
  [3592384     262    5845   15721       5]
  [3538266     204    4802   11583       5]
  [3496106     242    3462   15698       5]
  [3455465     235    2588   15695       5]
  [3361305     229    1883   15724       5]
  [3353856     230    2173   15574       5]
  [3342150     234    2483   15700       5]
  [3302407     217    2461   15716       5]
  [3263818     163    2393   15688       5]
  [3227817     163    2270   15691       5]
  [3189625     166    2976   15635       5]
  [3118401     244    3155   16076       5]
  [3056517     245    3161   12712       5]
  [2966371     191    1882   12546       5]
  [2904590     242    2844   16122       5]
  [2872271     135    2652   15900       5]]

আমি যদি এখন এখান থেকে ২য় কলাম এর প্রথম ৫ টা ডাটা দেখতে চাই –

ইনপুট
 print(riders_data_array[0:5,1])
আউটপুট
 [143  167   163   167   191]

এখানে [0:5,1] অংশ টা বুঝাচ্ছে – প্রথম ৫ টি row এর ডাটা এবং তা ২য় column এর। এখানে যদি 0 এর পরিবর্তে আমি 1 লিখি – তারমানে তখন প্রথম row বাদ দিয়ে চারটি ডাটা নিবে এবং আউটপুট হবে এরকম

[167   163    167     191]

NumPy নিয়ে পরবর্তী পর্বটি কিছুদিন পর প্রকাশিত হবে। ধন্যবাদ।

 

 

 

 

গড়, মধ্যক ও প্রচুরক

গড় শব্দটির সঙ্গে তোমরা যারা স্কুলে বিষয়টি পড়ে ফেলেছ, তারা যেমন পরিচিত, তেমনি যারা ক্রিকেট খেলা দেখো, তারাও পরিচিত। এই বইটি যখন লেখা হচ্ছে, তখন তামিম ইকবালের টেস্ট ক্রিকেটে ব্যাটিং গড় হচ্ছ 40.34 আর সাকিব আল হাসানের 40.93। এই গড় কিভাবে হিসেব করা হলো আর এর মানেই বা কী?

আমাকে যদি দুটি সংখ্যা দিয়ে এদের গড় বের করতে বলা হয়, তখন আমি সংখ্যা দুটি যোগ করে দুই দিয়ে ভাগ করবো। যেমন : 5 ও 6, এই দুটি সংখ্যার গড় হচ্ছে (5 + 6) / 2 বা 11 / 2 বা 5.5। তিনটি সংখ্যা 6, 7, 8-এর গড় হচ্ছে (6 + 7 + 8) / 3 বা 21 / 3 বা 7। তাহলে আমাকে যদি n সংখ্যক সংখ্যা দেওয়া হয়, তাহলে তাদের গড় বের করতে হলে সবগুলো সংখ্যা যোগ করে n দিয়ে ভাগ করবো। তাহলেই হয়ে গেলো।

এখন আমরা পাইথন ব্যবহার করে একটি প্রোগ্রাম লিখবো, যার কাজ হচ্ছে অনেকগুলো সংখ্যার গড় বের করা।

def average(li):
   s = sum(li)
   n = len(li)
   return s / n

li = [1, 2, 3]
print("Average:", average(li))
li = [10, 20, 30, 40, 50, 60, 70, 80]
print("Average:", average(li))
li = [-1, 0, 1]
print("Average:", average(li))

প্রোগ্রামটি রান করলে আমরা আউটপুট পাবো এরকম :

Average: 2.0
Average: 45.0
Average: 0.0

ক্রিকেট খেলায় ব্যাটিং গড় কিভাবে বের করে? মোট রানকে ইনিংস দিয়ে ভাগ করতে হয়। তবে একটি মজার বিষয় হচ্ছে, কোনো ইনিংসে অপরাজিত থাকলে, অর্থাৎ, আউট না হলে, সেই ইনিংসকে ভাগ করার সময় গণনা করা হয় না। ধরা যাক, কোনো ব্যাটসম্যান প্রথম খেলায় করলো 50 রান, দ্বিতীয় খেলায় 100 রান (অপরাজিত), তৃতীয় খেলায় আবারো 50 রান করে আউট হলো। তাহলে তার ব্যাটিং গড় হবে, (50 + 100 + 50) / 2, বা 200 / 2 বা 100। এখানে 3 এর বদলে 2 দিয়ে ভাগ করার কারণ হচ্ছে, দ্বিতীয় খেলায় সে অপরাজিত ছিল।

এখন গড় আমাদের কী কাজে লাগে? ধরা যাক, আন্তর্জাতিক ক্রিকেট খেলায় নতুন একটি দেশের আগমন ঘটলো এবং বাংলাদেশের সঙ্গে ওই দলের খেলা। টসে হেরে ওই দল প্রথমে ব্যাটিং পেল। এখন ওদের যেই দুজন ব্যাটসম্যান ইনিংস ওপেন করতে এসেছে, ঘরোয়া লিগে একজনের ব্যাটিং গড় হচ্ছে 26 আরেকজনের হচ্ছে 41। এই তথ্য থেকে তুমি দুজন ব্যাটসম্যানের মধ্যে তুলনা করতে পারো যে, কে তুলনামূলক ভালো ব্যাটসম্যান। তবে আজকের ম্যাচে কে কত রান করবে, এটি কিন্তু ব্যাটিং গড়ের ওপর নির্ভর করে না। কারও ব্যাটিং গড় 26 মানে এই নয় যে, সে প্রতি ইনিংসে 26 রান করে। তাহলে গড় হচ্ছে কোনো কিছুর মান সম্পর্কে ধারনা করার জন্য একটি টুল মাত্র। ইংরেজিতে একে average বলে, তবে গণিতের ক্ষেত্রে mean শব্দটিই বেশি ব্যবহার করা হয়।

এখন আরেকটি উদাহরণ দেই। কোনো দেশের মানুষ কেমন ধনী বা গরিব, তা বোঝার জন্য অনেকসময় মাথাপিছু আয় ব্যবহার করা হয়। মাথাপিছু আয় মানে হচ্ছে গড় আয়। সেটি ব্যবহার করে সেই দেশের মানুষের অর্থনৈতিক অবস্থা সম্পর্কে ধারণা পাওয়া যায়। কিন্তু সেখানে যদি মানুষের আয়ের মধ্যে বৈষম্য অনেক বেশি হয়, তাহলে কিন্তু গড় ব্যবহার করে প্রকৃত ধারণা পাওয়া যাবে না। একটি উদহারণ দিয়ে বোঝাই। ধরা যাক, কোনো দেশে 10 জন মানুষ আছে। তাদের মধ্যে 2 জন প্রতি মাসে 10 হাজার টাকা আয় করে। 5 জন প্রতিমাসে 20 হাজার টাকা আয় করে। আর একজন আয় করে প্রতিমাসে 30 হাজার টাকা। বাকী দুইজন প্রতি মাসে 5 লক্ষ টাকা আয় করে। তাহলে প্রতিমাসে তাদের গড় আয় কত?

গড় আয় = (10000 + 10000 + 20000 + 20000 + 20000 + 20000 + 20000 + 30000 + 500000 + 500000) / 10 = 115000।
তার মানে গড় আয় এক লক্ষ পনের হাজার টাকা! তাহলে শুধু গড় আয় জানলে যেকেউ সেই দেশের মানুষকে ধনী ভাববে। তাই গড় ব্যবহার করে সবসময় প্রকৃত চিত্র পাওয়া যায় না। তবে এতে হতাশ হওয়ার কিছু নেই, কারণ আমাদের হাতে রয়েছে মধ্যক ও প্রচুরক।

মধ্যক

এখন ধরা যাক, তুমি কোনো ক্রিকেট দলের ম্যানেজার। তোমার দল তৈরির সময় দুই জন ব্যাটসম্যান – রবিন ও সমিত-এর মধ্যে একজনকে বেছে নিতে হবে। দুজনের মধ্যে যার ব্যাটিং গড় বেশি, তুমি তাকে দলে নিতে পারো। কিন্তু তুমি যদি আরেকটু সচেতন হও, তখন হয়ত তুমি জানতে চাইতে পারো যে, কে কতগুলো ম্যাচ খেলেছে। ধরা যাক, রবিন 50 টি ম্যাচ খেলেছে এবং তার ব্যাটিং গড় 30। আর সমিত খেলেছে 5টি ম্যাচ এবং তার ব্যাটিং গড় 38। তুমি কিন্তু বেশিরভাগ ক্ষেত্রেই রবিনকে দলে নেবে, যেহেতু সে সমিতের তুলনায় অনেক বেশি অভিজ্ঞ। কিন্তু দুইজন যদি সমান সংখ্যক ম্যাচ খেলে, তখন কি কেবল গড় হিসেব করবে? তুমি চাইলে তখন আরেক ধরনের টুল ব্যবহার করতে পারো, যার নাম মধ্যক (ইংরেজিতে বলে median)। ধরা যাক, রবিন ও সমিত – দুজনেই 10টি করে ম্যাচ খেলেছে। 10টি ম্যাচে রবিনের রান হচ্ছে 95, 88, 47, 0, 10, 1, 5, 12, 0, 3। আর সমিতের রান হচ্ছে 10, 40, 20, 37, 0, 1, 25, 35, 30, 33। রবিনের গড় রান সমিতের গড় রানের চেয়ে বেশি। তবে এখানে আমরা দেখতে পাচ্ছি, রবিন মাঝে-মধ্যে অনেক বেশি রান করে, তবে বেশিরভাগ সময়ই সে খুব একটা ভালো খেলে না। আর সমিত দুয়েকটা বাদের বাকি খেলাগুলোয় মোটামুটি রান করতে পারে। তাই শুধু গড়ের ওপর ভরসা করা আমাদের ঠিক হবে না। আমরা মধ্যক বের করবো। প্রথমে আমরা তাদের প্রতি ম্যাচের রান ছোট থেকে বড় ক্রমানুসারে সাজাবো। তাহলে রবিনের রান হবে 0, 0, 1, 3, 5, 10, 12, 47, 88, 95 আর সমিতের রান হবে 0, 1, 10, 20, 25, 30, 33, 35, 37, 40। মধ্যক বের করতে গেলে আমাদেরকে তালিকার মাঝামাঝি সংখ্যাটি নিতে হবে। মোট সংখ্যা যদি বিজোড় হয়, তাহলে মাঝামাঝি সংখ্যা হবে একটি। যেমন 11-এর ক্ষেত্রে 6 নম্বর সংখ্যাটি হচ্ছে মাঝামাঝি সংখ্যা। কারণ ওই সংখ্যার চেয়ে ছোট 5টি সংখ্যা আছে। আবার বড় সংখ্যাও আছে 5টি। কিন্তু জোড় সংখ্যার বেলায় একটি মাঝামাঝি সংখ্যা বের করা যায় না। যেমন 10টি সংখ্যার ক্ষেত্রে আমরা যদি 5 নম্বর সংখ্যাটিকে মাঝামাঝি সংখ্যা ধরি, তাহলে তার ছোটি 4টি আর তার বড় 5টি সংখ্যা থাকবে। আবার 6 নম্বর সংখ্যাকে মাঝামাঝি সংখ্যা ধরলে, তার ছোট 5টি আর বড় 4টি সংখ্যা থাকবে।

যেহেতু আমাদের 10 টি সংখ্যা, তাই আমরা 5 ও 6 নম্বর সংখ্যা দুটি নিয়ে তাদের গড় বের করবো, মানে সংখ্যা দুটি যোগ করে দুই দিয়ে ভাগ করবো। তাহলে রবিনের রানের মিডিয়ান হবে 7.5 আর সমিতের রানের মিডিয়ান হবে 27.5। এখানে আমরা মিডিয়ান ব্যবহার করে সিদ্ধান্ত নিতে পারি যে কাকে দলে নেব। কাজটি তোমরা খাতা কলমে করে ফেল, তবে আমি পাইথন ব্যবহার করে একটি প্রোগ্রাম লিখে দেখাবো।

def median(li):
   li.sort()
   count = len(li)
   if count == 0:
      return None
   if count % 2 == 1:
      mid = count // 2
      return li[mid]
   else:
      mid2 = count // 2
      mid1 = mid2 - 1
      return (li[mid1]+li[mid2])/2

robin_run = [95, 88, 47, 0, 10, 1, 5, 12, 0, 3]
shomit_run = [10, 40, 20, 37, 0, 1, 25, 35, 30, 33]

median_robin = median(robin_run)
median_shomit = median(shomit_run)

print("Median run for Robin", median_robin)
print("Median run for Shomit", median_shomit)

আমরা এখন আমাদের মাথাপিছু আয়ের হিসেবে ফেরত যাই। তোমরা যদি সেই উদাহরণ থেকে মধ্যক বের করো, তাহলে সেটি হবে 20000। তোমরা হাতে-কলমে কিংবা একটি প্রোগ্রাম লিখে সেটি বের করতে পারো। এখানে কিন্তু মধ্যক ব্যবহার করেই বাস্তব চিত্রের কাছাকাছি চিত্র পাওয়া যাচ্ছে।

প্রচুরক

কোনো একটি ওয়ানডে ম্যাচের আগে তোমার বন্ধুরা মিলে আলোচনা করছ, আজকের খেলায় মোস্তাফিজ কয়টি উইকেট পাবে। একেকজন একেক সংখ্যা বলছে। কিন্তু তুমি যদি গড় ও মধ্যক ঠিকভাবে বুঝে থাক, তাহলে তুমি মোস্তাফিজের সব খেলার তথ্য ইন্টারনেট থেকে যোগাড় করে গড় ও মধ্যক বের করে আজকে সে কয়টি উইকেট পাবে, তা অনুমান করে ফেলতে পারবে। যদিও সেই অনুমান সঠিক নাও হতে পারে। তবে আমি তোমাদেরকে এখন আরেকটি টুলের সঙ্গে পরিচয় করিয়ে দেব, যার নাম হচ্ছে প্রচুরক (ইংরেজিতে mode)।

একটি ওয়ানডে ম্যাচে একজন বোলারের পক্ষে সর্বোচ্চ ও সর্বনিম্ন কয়টি উইকেট পাওয়া সম্ভব? উত্তর হবে, যথাক্রমে 10টি ও 0টি। 0-এর চেয়ে কম কিংবা 10-এর চেয়ে বেশি উইকেট পাওয়া সম্ভব নয়। এখন ধরা যাক, মোস্তাফিজ এখন পর্যন্ত 20টি ওয়ানডে ম্যাচে বোলিং করেছে। সেই খেলাগুলোতে সে প্রতি খেলায় যতগুলো উইকেট পেয়েছে, তা হচ্ছে : 6, 5, 6, 4, 3, 1, 3, 2, 1, 0, 5, 3, 3, 2, 2, 1, 3, 4, 3, 3। এখন আমি একটি তালিকা তৈরি করবো, যে মোস্তাফিজ 0 উইকেট পেয়েছে কতবার, 1 উইকেট পেয়েছে কতবার … এরকম।

উইকেট ম্যাচের সংখ্যা
0 1
1 3
2 3
3 7
4 2
5 2
6 2
7 0
8 0
9 0
10 0

তাহলে আমরা দেখতে পাচ্ছি, মোস্তাফিজ সবচেয়ে বেশি পেয়েছে 3 উইকেট। 20টি খেলার মধ্যে 7 টি খেলাতেই সে 3 উইকেট করে পেয়েছে। তাহলে তুমি ধরে নিতে পারো যে, আজকের খেলাতে মোস্তাফিজের 3 উইকেট পাওয়ার সম্ভাবনাই সবচেয়ে বেশি। এখানে প্রচুরক হচ্ছে 3। কারণ উইকেটের লিস্টে 3 সবচেয়ে বেশি বার আছে। আমরা এখন ওপরের হিসেবটা একটি পাইথন প্রোগ্রাম লিখে করবো।

wkts_list = [6, 5, 6, 4, 3, 1, 3, 2, 1, 0, 5, 3, 3, 2, 2, 1, 3, 4, 3, 3]

for item in range(11):
   print("Wicket:", item, "Count:", wkts_list.count(item))

প্রোগ্রামটি রান করলে আউটপুট আসবে এরকম :

Wicket: 0 Count: 1
Wicket: 1 Count: 3
Wicket: 2 Count: 3
Wicket: 3 Count: 7
Wicket: 4 Count: 2
Wicket: 5 Count: 2
Wicket: 6 Count: 2
Wicket: 7 Count: 0
Wicket: 8 Count: 0
Wicket: 9 Count: 0
Wicket: 10 Count: 0

যে ঘটনাটি সবচেয়ে বেশি সংখ্যক বার ঘটে, সেটিই হচ্ছে প্রচুরক। বিভিন্ন রকম মতামত জরিপ করতে প্রচুরক ব্যবহার করা হয়। যেমন ধরো, তুমি একটি নতুন মোবাইল ফোন কিনতে চাও। কিন্তু কোন ব্র্যান্ডের ফোন ভালো সেটা বুঝতে পারছ না। তখন তুমি তোমার বন্ধুদের জিজ্ঞাসা করতে পারো এবং সবচেয়ে বেশি সংখ্যক বন্ধু যেই ব্র্যান্ডের মোবাইল ফোন ব্যবহার করে বা ভালো বলে, সেই ব্র্যান্ডের ফোন কিনতে পারো।

গড়, মধ্যক ও প্রচুরক হচ্ছে পরিসংখ্যানের একেবারে মৌলিক জিনিস। এগুলো ছাড়াও আরো অনেক ধরনের টুল আছে, যেগুলো ব্যবহার করে বিভিন্ন তথ্য-উপাত্ত বিশ্লেষণ করা যায়। এগুলো তোমরা ভবিষ্যতে পরিসংখ্যান পড়লে জানতে পারবে।

এই লেখাটি সহ গণিতের আরো কিছু মৌলিক বিষয় নিয়ে প্রকাশিত হয়েছে গণিত করবো জয়

FAQ – পাইথন দিয়ে প্রোগ্রামিং শেখা

পাইথন দিয়ে প্রোগ্রামিং শেখা (লেখক তামিম শাহরিয়ার সুবিন) বই সম্পর্কে কিছু সচরাচর জিজ্ঞাসিত প্রশ্নের উত্তর –

১) কোন প্রকাশনী থেকে বইটি প্রকাশিত হয়েছে?

উত্তরঃ দ্বিমিক প্রকাশনী (ওয়েবসাইট http://dimik.pub )

২) বইয়ের দাম কত?

উত্তরঃ গায়ের দাম ২০০ টাকা (দোকানে একটু কম রাখার কথা)।

৩) বইতে পাইথন ২ নাকি পাইথন ৩ ব্যবহার করা হয়েছে?

উত্তরঃ পাইথন ৩।

৪) আমি (তামিম শাহরিয়ার সুবিন-এর) কম্পিউটার প্রোগ্রামিং ১ম ও ২য় খণ্ড বইটি পড়েছি, এখন কি পাইথন বইটি পড়ব?

উত্তরঃ পাইথন শেখার কোনো দরকার থাকলে পড়া যেতে পারে, নইলে পড়ার দরকার নাই।

৫) বইটা কাদের জন্য উপযোগি?

উত্তরঃ যারা প্রোগ্রামিংয়ে একেবারে নতুন, প্রোগ্রামিং শেখা শুরু করবে, তাদের জন্য উপযোগি। এছাড়া যারা আগে প্রোগ্রামিং শিখতে গিয়ে শিখতে পারে নাই, এখন আরেকবার চেষ্টা করবে, বইটি তাদেরও কাজে লাগতে পারে। যারা অভিজ্ঞ প্রোগ্রামার, বইটি তাদের জন্য নয়।

৬) “পাইথন পরিচিতি” ও “পাইথন দিয়ে প্রোগ্রামিং শেখা” বই দুটির মধ্য পার্থক্য কী?

পাইথন পরিচিতি বইতে পাইথন 2.x ব্যবহার করা হয়েছে, আর পাইথন দিয়ে প্রোগ্রামিং শেখা বইটিতে 3.x। পাইথন পরিচিতি বইটি অভিজ্ঞ প্রোগ্রামার যারা পাইথন শিখতে চায়, তাদের জন্য, আর পাইথন দিয়ে প্রোগ্রামিং শেখা বইটি যারা নতুন প্রোগ্রামিং শিখতে চায়, তাদের জন্য।

৭) বইটি কোথায় পাওয়া যাবে?

নীলক্ষেতের হক লাইব্রেরি, মানিক লাইব্রেরি ও রানা বুক পাবলিশার্স-এ (ফোন নাম্বার দ্বিমিকের ওয়েবসাইটে দেওয়া আছে)। এছাড়া ঘরে বসে অনলাইনে অর্ডার করা যাবে রকমারি ডট কম-এ।

৮) ঢাকার বাইরে থেকে কিভাবে কিনব?

উত্তরঃ উপরে উল্লেখিত (নীলক্ষেতের) তিনটি বইয়ের দোকানে যোগাযোগ করতে হবে (ঠিকানা দ্বিমিকের ওয়েবসাইটে দেওয়া আছে)। এছাড়া rokomari.com বাংলাদেশের যেকোনো জায়গায় বই পৌঁছে দেয়।

৯) বাংলাদেশের বাইরে থেকে কিভাবে কিনব?

উত্তরঃ জানি না।

১০) বইতে কী কী বিষয় আলোচনা করা হয়েছে?

উত্তরঃ বইয়ের ওয়েবসাইটে বিস্তারিত আছে ঃ http://dimik.pub/book/155/

১১) পাইথন সম্পর্কিত কিছু প্রশ্ন ছিল, কোথায় জিজ্ঞাসা করবো?

উত্তরঃ পাইথন নিয়ে জিজ্ঞাসা থাকলে নিচের দুটি গ্রুপে কিংবা প্রোগ্রামাবাদে প্রশ্ন করতে হবে:

১২) আচ্ছা, পাইথন কী?

উত্তরঃ বিস্তারিত লিখেছি এই লেখায় : পাইথন কী?