একটি list হলো ক্রমে সাজানো আইটেমের একটি মিউটেবল সংগ্রহ।
বেশিরভাগ কালেকশনের মতো (বিল্ট-ইন tuple, dict এবং set দেখুন), অ্যারে যেকোনো (বা একাধিক) ডেটা টাইপের রেফারেন্স ধারণ করতে পারে, এমনকি অন্য অ্যারেরও।
যেকোনো সিকোয়েন্স-এর মতোই, বাম দিক থেকে 0-based index এবং ডান দিক থেকে -1-based index দিয়ে আইটেম অ্যাক্সেস করা যায়।
অ্যারে সম্পূর্ণ বা আংশিকভাবে স্লাইস নোটেশন বা <list>.copy() দিয়ে কপি করা যায়।
অ্যারে কমন এবং মিউটেবল উভয় ধরনের সিকোয়েন্স অপারেশন সাপোর্ট করে, যেমন min()/max(), <list>.index(), .append() এবং .reverse()।
for item in <list> কনস্ট্রাক্ট ব্যবহার করে অ্যারের এলিমেন্টগুলোর ওপর ইটারেট করা যায়। এলিমেন্টের ইনডেক্স এবং এলিমেন্টের মান দুটোই দরকার হলে for index, item in enumerate(<list>) ব্যবহার করা যায়।
অ্যারে ডায়নামিক অ্যারে হিসেবে ইমপ্লিমেন্ট করা হয়, যা Java-র Arraylist টাইপের মতো, এবং সাধারণত অজানা দৈর্ঘ্যের একগুচ্ছ একই ধরনের ডেটা (স্ট্রিং, সংখ্যা, সেট ইত্যাদি) সংরক্ষণে ব্যবহৃত হয় (এন্ট্রির সংখ্যা যখন-তখন বাড়তে বা কমতে পারে)।
এলিমেন্ট অ্যাক্সেস করা, in দিয়ে সদস্যতা যাচাই করা, কিংবা অ্যারের "ডান দিকের" প্রান্তে আইটেম অ্যাপেন্ড করা, সবই বেশ দক্ষ।
প্রিপেন্ড করা (অ্যারের "বাম দিকের" প্রান্তে অ্যাপেন্ড করা) বা অ্যারের মাঝখানে ইনসার্ট করা অনেক কম দক্ষ, কারণ এই অপারেশনগুলোতে এলিমেন্টগুলোকে ক্রমে রাখতে সেগুলো শিফট করতে হয়।
দুই দিক থেকেই মেমোরি-দক্ষ appends/pops সাপোর্ট করে এমন একই ধরনের ডেটা স্ট্রাকচারের জন্য collections.deque দেখুন, যার দুই দিকেই প্রায় একই O(1) পারফরম্যান্স আছে।
যেহেতু অ্যারে মিউটেবল এবং যেকোনো Python অবজেক্টের রেফারেন্স ধারণ করতে পারে, তাই আপাতদৃষ্টিতে একই দৈর্ঘ্যের একটি array.array বা একটি tuple (যা ইমিউটেবল) থেকে এরা মেমোরিতে বেশি জায়গা নেয়।
তা সত্ত্বেও, অ্যারে অত্যন্ত নমনীয় ও উপযোগী একটি ডেটা স্ট্রাকচার, এবং Python-এর অনেক বিল্ট-ইন মেথড ও অপারেশন আউটপুট হিসেবে অ্যারে তৈরি করে।
বর্গাকার [] বন্ধনী এবং এলিমেন্টের মাঝে কমা দিয়ে একটি list-কে লিটারাল হিসেবে ডিক্লেয়ার করা যায়:
>>> no_elements = []
>>> no_elements
[]
>>> one_element = ["Guava"]
>>> one_element
['Guava']
>>> elements_separated_with_commas = ["Parrot", "Bird", 334782]
>>> elements_separated_with_commas
['Parrot', 'Bird', 334782]
পাঠযোগ্যতার জন্য, একটি অ্যারেতে অনেক এলিমেন্ট বা নেস্টেড ডেটা স্ট্রাকচার থাকলে লাইন ব্রেক ব্যবহার করা যায়:
>>> lots_of_entries = [
... "Rose",
... "Sunflower",
... "Poppy",
... "Pansy",
... "Tulip",
... "Fuchsia",
... "Cyclamen",
... "Lavender"
... ]
>>> lots_of_entries
['Rose', 'Sunflower', 'Poppy', 'Pansy', 'Tulip', 'Fuchsia', 'Cyclamen', 'Lavender']
# Each data structure is on its own line to help clarify what they are.
>>> nested_data_structures = [
... {"fish": "gold", "monkey": "brown", "parrot": "grey"},
... ("fish", "mammal", "bird"),
... ['water', 'jungle', 'sky']
... ]
>>> nested_data_structures
[{'fish': 'gold', 'monkey': 'brown', 'parrot': 'grey'}, ('fish', 'mammal', 'bird'), ['water', 'jungle', 'sky']]
list() কনস্ট্রাক্টরটি খালি অবস্থায় বা আর্গুমেন্ট হিসেবে একটি ইটারেবল দিয়ে ব্যবহার করা যায়।
কনস্ট্রাক্টরটি ইটারেবলের এলিমেন্টগুলোর মধ্য দিয়ে ঘুরে ঘুরে সেগুলো ক্রমানুসারে অ্যারেতে যোগ করে:
>>> no_elements = list()
>>> no_elements
[]
# The tuple is unpacked and each element is added.
>>> multiple_elements_from_tuple = list(("Parrot", "Bird", 334782))
>>> multiple_elements_from_tuple
['Parrot', 'Bird', 334782]
# The set is unpacked and each element is added.
>>> multiple_elements_from_set = list({2, 3, 5, 7, 11})
>>> multiple_elements_from_set
[2, 3, 5, 7, 11]
কোনো স্ট্রিং বা dict দিয়ে অ্যারে কনস্ট্রাক্টর ব্যবহার করলে ফলাফল আশ্চর্যজনক হতে পারে:
# String elements (Unicode code points) are iterated through and added *individually*.
>>> multiple_elements_string = list("Timbuktu")
>>> multiple_elements_string
['T', 'i', 'm', 'b', 'u', 'k', 't', 'u']
# Unicode separators and positioning code points are also added *individually*.
>>> multiple_code_points_string = list('अभ्यास')
>>> multiple_code_points_string
['अ', 'भ', '्', 'य', 'ा', 'स']
# The iteration default for dictionaries is over the keys, so only key data is inserted into the list.
>>> source_data = {"fish": "gold", "monkey": "brown"}
>>> list(source_data)
['fish', 'monkey']
যেহেতু list() কনস্ট্রাক্টর আর্গুমেন্ট হিসেবে শুধু ইটারেবল (বা কিছুই না) নেয়, তাই যেসব অবজেক্ট ইটারেবল নয় সেগুলো একটি TypeError রেইজ করে। ফলস্বরূপ, লিটারাল পদ্ধতিতে একটি এক-আইটেমের অ্যারে তৈরি করা অনেক সহজ।
# Numbers are not iterable, and so attempting to create a list with a number passed to the constructor fails.
>>> one_element = list(16)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable
# Tuples *are* iterable, so passing a one-element tuple to the constructor does work, but it's awkward
>>> one_element_from_iterable = list((16,))
>>> one_element_from_iterable
[16]
অ্যারের ভেতরের আইটেমগুলো (পাশাপাশি str ও tuple-এর মতো অন্য সিকোয়েন্স টাইপের এলিমেন্টগুলোও) বন্ধনী নোটেশন দিয়ে অ্যাক্সেস করা যায়।
ইনডেক্স left --> right (শূন্য থেকে শুরু) বা right --> left (-1 থেকে শুরু) হতে পারে।
| বাম থেকে ইনডেক্স ⟹ |
|
⟸ ডান থেকে ইনডেক্স |
>>> breakfast_foods = ["Oatmeal", "Fruit Salad", "Eggs", "Toast"]
# Oatmeal is at index 0 or index -4.
>>> breakfast_foods[0]
'Oatmeal'
>>> breakfast_foods[-4]
'Oatmeal'
# Eggs are at index -2 or 2
>>> breakfast_foods[-2]
'Eggs'
>>> breakfast_foods[2]
'Eggs'
# Toast is at -1
>>> breakfast_foods[-1]
'Toast'
একটি অ্যারের একটি অংশ স্লাইস নোটেশন (<list>[<start>:<stop>]) দিয়ে অ্যাক্সেস করা যায়।
একটি স্লাইস হলো index অবস্থানে থাকা এলিমেন্টের একটি সিকোয়েন্স, যেখানে start <= index < stop।
স্লাইসিং "স্লাইস করা" আইটেমগুলোর একটি কপি রিটার্ন করে এবং মূল list পরিবর্তন করে না।
স্লাইসে একটি step প্যারামিটারও ব্যবহার করা যায় (<list>[<start>:<stop>:<step>]), যা রিটার্ন করা এলিমেন্টগুলোকে "স্কিপ" বা ফিল্টার করে (যেমন, step 2 হলে অংশটির প্রতি বাদে একটি করে এলিমেন্ট বেছে নেবে):
>>> colors = ["Red", "Purple", "Green", "Yellow", "Orange", "Pink", "Blue", "Grey"]
# If there is no step parameter, the step is assumed to be 1.
>>> middle_colors = colors[2:6]
>>> middle_colors
['Green', 'Yellow', 'Orange', 'Pink']
# If the start or stop parameters are omitted, the slice will
# start at index zero, and will stop at the end of the list.
>>> primary_colors = colors[::3]
>>> primary_colors
['Red', 'Yellow', 'Blue']
অ্যারে একটি ইটারেটর প্রদান করে, এবং অন্য সিকোয়েন্স টাইপের মতোই for item in <list> বা for index, item in enumerate(<list>) ব্যবহার করে এর ওপর লুপ করা যায়:
# Make a list, and then loop through it to print out the elements
>>> colors = ["Orange", "Green", "Grey", "Blue"]
>>> for item in colors:
... print(item)
Orange
Green
Grey
Blue
# Print the same list, but with the indexes of the colors included
>>> colors = ["Orange", "Green", "Grey", "Blue"]
>>> for index, item in enumerate(colors):
... print(item, ":", index)
Orange : 0
Green : 1
Grey : 2
Blue : 3
# Start with a list of numbers and then loop through and print out their cubes.
>>> numbers_to_cube = [5, 13, 12, 16]
>>> for number in numbers_to_cube:
... print(number**3)
125
2197
1728
4096
মানের একটি অ্যারে তৈরি করার একটি কমন উপায় হলো লুপের ভেতরে <list>.append() ব্যবহার করা:
>>> cubes_to_1000 = []
>>> for number in range(11):
... cubes_to_1000.append(number**3)
>>> cubes_to_1000
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
বিভিন্ন কৌশলে অ্যারেগুলোকে একসাথে মেলানোও যায়:
# Using the plus + operator unpacks each list and creates a new list, but it is not efficient.
>>> new_via_concatenate = ["George", 5] + ["cat", "Tabby"]
>>> new_via_concatenate
['George', 5, 'cat', 'Tabby']
# Likewise, using the multiplication operator * is the equivalent of using + n times.
>>> first_group = ["cat", "dog", "elephant"]
>>> multiplied_group = first_group * 3
>>> multiplied_group
['cat', 'dog', 'elephant', 'cat', 'dog', 'elephant', 'cat', 'dog', 'elephant']
# Another method for combining 2 lists is to use slice assignment or a loop-append.
# This assigns the second list to index 0 in the first list.
>>> first_one = ["cat", "Tabby"]
>>> second_one = ["George", 5]
>>> first_one[0:0] = second_one
>>> first_one
['George', 5, 'cat', 'Tabby']
# This loops through the first list and appends its items to the end of the second list.
>>> first_one = ["cat", "Tabby"]
>>> second_one = ["George", 5]
>>> for item in first_one:
... second_one.append(item)
>>> second_one
['George', 5, 'cat', 'Tabby']
মনে রাখুন, Python-এ ভ্যারিয়েবল হলো লেবেল, যা অন্তর্নিহিত অবজেক্ট-কে পয়েন্ট করে।
lists কনটেইনার অবজেক্ট হিসেবে আরও একটি স্তর যোগ করে, এরা তাদের সংগৃহীত আইটেমের জন্য অবজেক্টের রেফারেন্স ধারণ করে।
সঠিকভাবে হ্যান্ডেল না করলে অ্যারে নিয়ে কাজ করার সময় এটি একাধিক সম্ভাব্য সমস্যার সৃষ্টি করতে পারে।
একটি list অবজেক্টকে নতুন ভ্যারিয়েবল নামে অ্যাসাইন করলে list অবজেক্ট বা তার এলিমেন্টগুলো কপি হয় না।
নতুন নামের অধীনে list-এর এলিমেন্টে করা যেকোনো পরিবর্তন মূলটিকে প্রভাবিত করে।
list.copy() বা স্লাইস দিয়ে একটি shallow_copy তৈরি করলে এই প্রথম-স্তরের রেফারেন্সিং জটিলতা এড়ানো যায়।
একটি shallow_copy নতুন একটি list অবজেক্ট তৈরি করবে, কিন্তু ধারণ করা list এলিমেন্টগুলোর জন্য নতুন অবজেক্ট তৈরি করবে না। এই ধরনের কপি সাধারণত আপনার জন্য যথেষ্ট হবে যাতে আপনি দুটি list অবজেক্ট থেকে স্বাধীনভাবে আইটেম যোগ বা মুছে ফেলতে পারেন এবং কার্যত দুটি "আলাদা" অ্যারে পেতে পারেন।
>>> actual_names = ["Tony", "Natasha", "Thor", "Bruce"]
# Assigning a new variable name does not make a copy of the container or its data.
>>> same_list = actual_names
# Altering the list via the new name is the same as altering the list via the old name.
>>> same_list.append("Clarke")
["Tony", "Natasha", "Thor", "Bruce", "Clarke"]
>>> actual_names
["Tony", "Natasha", "Thor", "Bruce", "Clarke"]
# Likewise, altering the data in the list via the original name will also alter the data under the new name.
>>> actual_names[0] = "Wanda"
['Wanda', 'Natasha', 'Thor', 'Bruce', 'Clarke']
# If you copy the list, there will be two separate list objects which can be changed independently.
>>> copied_list = actual_names.copy()
>>> copied_list[0] = "Tony"
>>> actual_names
['Wanda', 'Natasha', 'Thor', 'Bruce', 'Clarke']
>>> copied_list
["Tony", "Natasha", "Thor", "Bruce", "Clarke"]
নেস্টেড বা গুণ করা অ্যারে নিয়ে কাজ করলে এই রেফারেন্স জটিলতা আরও বেড়ে যায় (নিচের উদাহরণগুলো 2013 সালের চমৎকার Ned Batchelder ব্লগ পোস্ট Names and values: making a game board থেকে নেওয়া):
from pprint import pprint
# This will produce a game grid that is 8x8, pre-populated with zeros.
>>> game_grid = [[0]*8]*8
>>> pprint(game_grid)
[[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, 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, 0, 0, 0, 0]]
# An attempt to put a "X" in the bottom right corner.
>>> game_grid[7][7] = "X"
# This attempt doesn't work because all the rows are referencing the same underlying list object.
>>> pprint(game_grid)
[[0, 0, 0, 0, 0, 0, 0, 'X'],
[0, 0, 0, 0, 0, 0, 0, 'X'],
[0, 0, 0, 0, 0, 0, 0, 'X'],
[0, 0, 0, 0, 0, 0, 0, 'X'],
[0, 0, 0, 0, 0, 0, 0, 'X'],
[0, 0, 0, 0, 0, 0, 0, 'X'],
[0, 0, 0, 0, 0, 0, 0, 'X'],
[0, 0, 0, 0, 0, 0, 0, 'X']]
কিন্তু এই পরিস্থিতিতে, আমরা যে আচরণ চাই তা পাওয়ার জন্য একটি shallow_copy-ই যথেষ্ট:
from pprint import pprint
# This loop will safely produce a game grid that is 8x8, pre-populated with zeros
>>> game_grid = []
>>> filled_row = [0] * 8
>>> for row in range(8):
... game_grid.append(filled_row.copy()) # This is making a new shallow copy of the inner list object each iteration.
>>> pprint(game_grid)
[[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, 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, 0, 0, 0, 0]]
# An attempt to put a "X" in the bottom right corner.
>>> game_grid[7][7] = "X"
# The game grid now works the way we expect it to!
>>> pprint(game_grid)
[[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, 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, 0, 0, 0, 'X']]
আগেই বলা হয়েছে, অ্যারে হলো রেফারেন্সের কনটেইনার, তাই এখানে সম্ভাব্য জটিলতার একটি দ্বিতীয় স্তর আছে।
কোনো অ্যারেতে ভ্যারিয়েবল, অবজেক্ট বা নেস্টেড ডেটা স্ট্রাকচার থাকলে, shallow_copy বা স্লাইসের মাধ্যমে সেই দ্বিতীয়-স্তরের রেফারেন্সগুলো কপি হয় না।
তখন অন্তর্নিহিত অবজেক্ট মিউটেট করলে প্রতিটি কপি প্রভাবিত হবে, কারণ প্রতিটি list অবজেক্টে কেবল ধারণ করা এলিমেন্টগুলোর দিকে পয়েন্ট করা রেফারেন্স থাকে।
from pprint import pprint
>>> pprint(game_grid)
[[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, 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, 0, 0, 0, 'X']]
# We'd like a new board, so we make a shallow copy.
>>> new_game_grid = game_grid.copy()
# But a shallow copy doesn't copy the contained references or objects.
>>> new_game_grid[0][0] = 'X'
# So changing the items in the copy also changes the originals items.
>>> pprint(game_grid)
[['X', 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, 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, 0, 0, 'X']]
অ্যারে প্রায়ই স্ট্যাক ও কিউ হিসেবে ব্যবহৃত হয়, যদিও তাদের অন্তর্নিহিত ইমপ্লিমেন্টেশনের কারণে প্রিপেন্ড করা ও ইনসার্ট করা ধীর হয়।
collections মডিউল একটি deque ভ্যারিয়েন্ট দেয়, যা দুই প্রান্ত থেকেই দ্রুত অ্যাপেন্ড ও পপের জন্য অপটিমাইজ করা এবং ডাবলি লিংকড লিস্ট হিসেবে ইমপ্লিমেন্ট করা।
ছোট ম্যাট্রিক্স মডেল করতেও নেস্টেড অ্যারে ব্যবহৃত হয়, যদিও দক্ষ ম্যাট্রিক্স ও ট্যাবুলার ডেটা ম্যানিপুলেশনের জন্য Numpy ও Pandas লাইব্রেরি অনেক বেশি শক্তিশালী।
collections মডিউল একটি UserList টাইপও দেয়, যা বিশেষায়িত অ্যারের প্রয়োজন অনুযায়ী কাস্টমাইজ করা যায়।