بررسی لیست
از لیست (list) در برنامه نویسی برای نگهداری عناصری از نوع یکسان استفاده می کنیم؛ به لیست (list) آرایه دینامیکی نیز گفته میشه، یعنی یک آرایه با طول متغیر (dynamic) که میتونیم عناصر رو ازش حذف یا بهش اضافه کنیم و اندازه ی (size) آن با حذف و اضافه کردن عنصر ها تغییر میکنه.
list در زبان های مختلف با نام های مختلفی پیادهسازی شده مثلا در جاوا و کاتلین به عنوان ArrayList پیادهسازی شده و در api قدیمی جاوا به نام vector پیادهسازی شده است.
بعد از آشنایی با عملکرد لیست در ادامه به پیادهسازی لیست با جاوا، کاتلین، جاوااسکریپت و پایتون خواهیم پرداخت.
ساختار list
در تصویر زیر n تا عنصر داخل لیست (list) نگهداری شده:
ساختار لیست مشابه ساختار آرایه است هر عنصر داخل یک خونه نگهداری میشه و به شماره خونه ی هر عنصر index میگیم.
شماره خونه ی اولین عنصر 0 و شماره خونه ی n امین عنصر n-1 است.
با افزودن یک عنصر به list اندازه ی list یک واحد افزایش پیدا میکنه و با حذف یک عنصر، size (اندازه) list یک واحد کاهش پیدا میکنه.
به تعداد خونه هایی که عناصر داخلشون نگهداری میشن ظرفیت (capacity) لیست میگیم، هنگامی که ظرفیت پر بشه، با افزودن عنصر جدید به list ظرفیت به طور خودکار دوبرابر (و در بعضی از زبان ها 1.5 برابر) میشه.
به تعداد عناصری که list داخل خودش نگهداری میکنه، اندازه (size) list میگیم.
اندازه ی list برابر با تعداد عناصریه که نگهداری میکنه در تصویر اندازه ی list برابر با n است.
به طور معمول اندازه (size) یک list کمتر یا مساوی ظرفیت (capacity) آن است.
در تصویر ظرفیت (capacity) برابر n+k است ولی اندازه (size) آن n است.
اگه تعداد عناصری که میخوایم استفاده کنیم ثابت باشه، استفاده از آرایه بهینه تره اما اگه تعداد عناصر متغیر باشه یعنی قصد حذف یا اضافه کردن عناصر رو داشته باشیم استفاده از لیست (list) بهینه تره.
افزودن عنصر به لیست
عناصر در لیست (List) مانند آرایه ها نگهداری میشن با این تفاوت، هر عنصری که اضافه می کنیم، اندازه ی List افزایش پیدا میکنه؛ پیچیدگی زمان (time complexity) در list برای افزودن عنصر O(n) است.
اگه بخوایم عناصر رو به لیست اضافه کنیم با افزودن هر عنصر، یک واحد به اندازه ی List اضافه میشه:
- اولین عنصری که اضافه میکنیم، داخل خونه ی شماره 0 نگهداری میشه و اندازه ی List میشه 1.
- دومین عنصری که اضافه می کنیم، داخل خونه ی شماره 1 نگهداری میشه و اندازه ی List میشه 2.
- سومین عنصری که اضافه می کنیم داخل خونه ی شماره 2 نگهداری میشه و اندازه ی List میشه 3.
و به همین ترتیب اگه n عنصر به یک List خالی اضافه کنیم، n امین عنصر داخل خونه ی n-1 نگهداری میشه و اندازه ی list میشه n.
فرض کنید ظرفیت (capacity) یک list سه است و عناصر e1, e2, e3 رو داخل خودش نگهداری میکنه، در این وضعیت ظرفیت list پر شده و با افزودن عنصر e4 ظرفیت list دو برابر میشه و اندازه (size) آن نیز یک واحد افزایش پیدا میکنه؛ و هنگام افزودن عنصر e5 چون ظرفیت (capacity) جدید پر نشده تنها یک واحد دیگه به اندازه (size) آن اضافه میشه.
به طور عمومی در زبان های برنامه نویسی با تابعی مشابه add(e: E) یک عنصر رو به List اضافه می کنیم.
function add(element: E){
if size = capacity
array := new E[(capacity *= 2)]
for 0 <= i < elements.length i++
array[i] := elements[i]
array[size++] = element
elements = array
else
elements[size++] = element
}
صدا زدن عنصر از لیست
در لیست (List) مانند آرایه میتونیم هر عنصری رو که نیاز داشتیم، بدون رعایت ترتیب و به صورت تصادفی با وارد کردن شماره index عنصر صداش کنیم.
به طور عمومی در اکثر زبونا برای صدا زدن عناصر داخل list از تابعی مانند get(int: index) استفاده میکنیم، که index شماره ی خونه ی عنصر مورد نظر است؛ پیچیدگی زمان (time complexity) برای list هنگام صدا زدن عناصر، O(1) است.
فرض کنید در مثال قبل میخوایم e2 و e3 رو صدا بزنیم.
function get(index: int): E {
if index < 0 or index >= size
throw IndexOutOfBoundsException
if size = 0
throw NoSuchElementException
return elements[index];
}
میتونیم تمام عناصر رو با استفاده از حلقه ها صدا بزنیم.
for 0 <= i < list.size i++
e := list.get(i);
print(e);
حذف عنصر از لیست
با حذف هر عنصر از لیست، اندازه ی لیست یک واحد کاهش پیدا می کنه؛ هنگامی که تمامی عناصر از لیست (List) حذف بشن اندازه ی list صفر میشه و اصطلاحا میگیم لیست خالی شده؛ پیچیدگی زمان (time complexity) در list برای حذف کردن یک عنصر O(n) است.
هنگامی که یک عنصر رو در لیست حذف میکنیم، برای جایگزین کردن جای خالی عنصر حذف شده، عناصر رو موجود در آرایه فعلی رو به یک آرایه جدید کپی میکنیم و از آرایه جدید استفاده میکنیم، به همین دلیل پیچیدگی زمان حذف عناصر از لیست O(n) است (بجز آخرین عنصر که با حذف اخرین عنصر نیاز به مرتب سازی دوباره نیست).
هنگامی که یک عنصر رو از لیست حذف میکنیم، یک واحد از index یک واحد از اندازه ی list کم میشه.
در زیر به صورت تصویری شرح داده شده هنگام حذف عناصر e2 و e3 از یک list به اندازه ی n، چه اتفاقی برای خونه های بعد و اندازه ی لیست می افته.
به طور کلی در زبان های برنامه نویسی با استفاده از تابعی مانند remove(index: int) می تونیم یک عنصر رو از لیست حذف کنیم و بجای index شماره ی خونه ی عنصر مورد نظر رو می نویسیم.
function remove(index: int): E {
if index < 0 or index >= size
throw IndexOutOfBoundsException
if size = 0
throw NoSuchElementException
removedElement := elements[index]
elements[index] := null
if index < size - 1
array := E[capacity]
count := 0
for 0 <= i < size i++
if elements[i] != null
array[count++] := elements[i]
elements = array
size--
}
پیادهسازی لیست در زبان های برنامه نویسی
میخوایم یک لیست رو به صورت عملی در زبان های برنامه نویسی جاوا، کاتلین، جاوااسکریپت و پایتون پیادهسازی کنیم.
در این پیادهسازی ظرفیت اولیه (INITIAL_CAPACITY) موقع ایجاد یک نمونه از لیست رو برابر 3 قرار دادیم.
برای راحتی یک دسترسی سریع به پیادهسازی با هر زبان رو قرار داده ایم.
پیادهسازی لیست با زبان برنامه نویسی جاوا
در زیر یک لیست رو با زبان برنامه نویسی جاوا به صورت جنریک پیادهسازی کرده ایم.
public class MyList<E> {
private static final int INITIAL_CAPACITY = 3;
private int capacity;
private int size;
private E[] elements;
public MyList(int capacity){
this.capacity = capacity;
elements = (E[]) new Object[capacity];
}
public MyList(){
this(INITIAL_CAPACITY);
}
public int getSize() {
return size;
}
public void add(E element){
if (size == capacity){
E[] array = (E[]) new Object[(capacity *= 2)];
for (int i=0; i<elements.length; i++){
array[i] = elements[i];
}
array[size++] = element;
elements = array;
}else {
elements[size++] = element;
}
}
public E get(int index){
if (isEmpty())
throw new NoSuchElementException("The list is empty");
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("index should be between 0 to " + (size - 1));
return elements[index];
}
public E remove(int index){
if (isEmpty())
throw new NoSuchElementException("The list is empty");
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("index should be between 0 to " + (size - 1));
E removedElement = elements[index];
elements[index] = null;
if (index < size -1){
E[] array = (E[]) new Object[capacity];
int count =0;
for (int i=0; i<elements.length; i++){
if (elements[i] != null)
array[count++] = elements[i];
}
elements = array;
}
size--;
return removedElement;
}
public boolean isEmpty(){
return size == 0;
}
}
پیادهسازی لیست با زبان برنامه نویسی کاتلین
در زیر یک لیست رو با زبان برنامه نویسی کاتلین به صورت جنریک پیادهسازی کردیم.
class MyList<E>(private var capacity: Int = INITIAL_CAPACITY) {
var size: Int = 0
private set
val isEmpty: Boolean
get() = size == 0
private var elements: Array<E?>
init {
elements = arrayOfNulls<Any>(capacity) as Array<E?>
}
fun add(element: E?) {
if (size == capacity) {
val array = arrayOfNulls<Any>((2.let { capacity *= it; capacity })) as Array<E?>
for (i in elements.indices) {
array[i] = elements[i]
}
array[size++] = element
elements = array
} else {
elements[size++] = element
}
}
fun get(index: Int): E? {
if (this.isEmpty) throw NoSuchElementException("The list is empty")
if (index !in 0..<size) throw IndexOutOfBoundsException("index should be between 0 to " + (size - 1))
return elements[index]
}
fun remove(index: Int): E? {
if (this.isEmpty) throw NoSuchElementException("The list is empty")
if (index !in 0..<size) throw IndexOutOfBoundsException("index should be between 0 to " + (size - 1))
val removedElement = elements[index]
elements[index] = null
if (index < size - 1) {
val array = arrayOfNulls<Any>(capacity) as Array<E?>
var count = 0
for (i in elements.indices) {
if (elements[i] != null) array[count++] = elements[i]
}
elements = array
}
size--
return removedElement
}
companion object {
private const val INITIAL_CAPACITY = 3
}
}
پیادهسازی لیست در زبان برنامه نویسی جاوااسکریپت
آرایه ها در جاوااسکریپت به طور پیشفرض دینامیکی هستن، یعنی طول آرایه ها در جاوااسکریپت قابل تغییر است، ولی برای اشنایی با عملکرد آرایه دینامیکی (list)، در زیر یک لیست رو با جاوااسکریپت پیادهسازی کردیم.
زبان برنامه نویسی جاوااسکریپت type-free یعنی نوع متغیر تا زمان مقداردهیش معلوم نیست بنابراین مفهومی به نام جنریک در جاوااسکریپت وجود نداره.
class MyList {
// Static constant for initial capacity
static INITIAL_CAPACITY = 3;
constructor(capacity = MyList.INITIAL_CAPACITY) {
this.capacity = capacity;
this.size = 0;
this.elements = new Array(capacity).fill(null);
}
getSize() {
return this.size;
}
add(element) {
if (this.size === this.capacity) {
this.capacity *= 2;
const newArray = new Array(this.capacity).fill(null);
for (let i = 0; i < this.elements.length; i++) {
newArray[i] = this.elements[i];
}
newArray[this.size++] = element;
this.elements = newArray;
} else {
this.elements[this.size++] = element;
}
}
get(index) {
if (this.isEmpty()) {
throw new Error("The list is empty"); // Approximate for NoSuchElementException
}
if (index < 0 || index >= this.size) {
throw new RangeError("index should be between 0 to " + (this.size - 1));
}
return this.elements[index];
}
remove(index) {
if (this.isEmpty()) {
throw new Error("The list is empty");
}
if (index < 0 || index >= this.size) {
throw new RangeError("index should be between 0 to " + (this.size - 1));
}
const removedElement = this.elements[index];
this.elements[index] = null;
if (index < this.size - 1) {
const newArray = new Array(this.capacity).fill(null);
let count = 0;
for (let i = 0; i < this.elements.length; i++) {
if (this.elements[i] !== null) {
newArray[count++] = this.elements[i];
}
}
this.elements = newArray;
}
this.size--;
return removedElement;
}
isEmpty() {
return this.size === 0;
}
}
پیادهسازی لیست با زبان برنامه نویسی پایتون
در زیر یک لیست رو با زبان برنامه نویسی پایتون پیاده کرده ایم؛ زبان برنامه نویسی پایتون type-free است یعنی نوع متغیر تا زمان مقداردهیش مشخص نیست، بنابراین مفهومی به نام جنریک در پایتون وجود نداره.
class MyList:
INITIAL_CAPACITY = 3
def __init__(self, capacity=INITIAL_CAPACITY):
self.capacity = capacity
self._size = 0
self.elements = [None] * capacity
def get_size(self):
return self._size
def add(self, element):
if self._size == self.capacity:
self.capacity *= 2
new_array = [None] * self.capacity
for i in range(len(self.elements)):
new_array[i] = self.elements[i]
new_array[self._size] = element
self._size += 1
self.elements = new_array
else:
self.elements[self._size] = element
self._size += 1
def get(self, index):
if self.is_empty():
raise ValueError("The list is empty")
if index < 0 or index >= self._size:
raise IndexError("index should be between 0 to " + str(self._size - 1))
return self.elements[index]
def remove(self, index):
if self.is_empty():
raise ValueError("The list is empty")
if index < 0 or index >= self._size:
raise IndexError("index should be between 0 to " + str(self._size - 1))
removed_element = self.elements[index]
self.elements[index] = None
if index < self._size - 1:
new_array = [None] * self.capacity
count = 0
for i in range(len(self.elements)):
if self.elements[i] is not None:
new_array[count] = self.elements[i]
count += 1
self.elements = new_array
self._size -= 1
return removed_element
def is_empty(self):
return self._size == 0
استفاده از لیست در زبان های برنامه نویسی
در این قسمت میخوایم دوتا مثال بیاریم، اولین مثال مربوط به استفاده از list است مثل افزودن، صدا زدن و حذف کردن عناصر است؛ در مثال دوم میخوایم Stack رو با استفاده از list پیادهسازی کنیم.
میخوایم با استفاده از لیست که در قسمت قبل پیاده کردیم، یک list از نوع String ایجاد کنیم و چندتا اسم بهش اضافه کنیم؛ اسامی رو صدا زده و چاپ کنیم و در اخر چندتا اسم رو ازش حذف کنیم.
MyList<String> list = new MyList();
list.add("Lee");
list.add("Rose");
list.add("Joe");
list.add("Billy");
list.add("Susan");
list.add("Emilly");
list.add("Nick");
System.out.println("Size of list after adding elements is " + list.size());
for(int i=0; i<list.size(); i++){
String name = list.get(i);
System.out.print(name + " ");
}
String trash = null;
removedName = list.remove(0);
System.out.println("Name " + removedName + " has been removed");
removedName = list.remove(0);
System.out.println("Name " + removedName + " has been removed");
removedName = list.remove(4);
System.out.println("Name " + removedName + " has been removed");
println("The size of list after removing 3 elements is " + list.size())
val list: MyList<String> = MyList();
list.add("Lee")
list.add("Rose")
list.add("Joe")
list.add("Billy")
list.add("Susan")
list.add("Emilly")
list.add("Nick")
println("Size of list after adding elements is ${list.size}")
for(i in 0 until list.size ){
val name = list.get(i)
print("$name ");
}
var removedName: String? = null;
removedName = list.remove(0);
println("Name $removedName has been removed");
removedName = list.remove(0);
println("Name $removedName has been removed");
removedName = list.remove(4);
println("Name $removedName has been removed");
println("The size of list after removing 3 elements is ${list.size}")
let list = new MyList();
list.add("Lee");
list.add("Rose");
list.add("Joe");
list.add("Billy");
list.add("Susan");
list.add("Emilly");
list.add("Nick");
console.log("Size of list after adding elements is " + list.getSize());
for(let i=0; i<list.getSize(); i++){
let name = list.get(i);
process.stdout.write(name + " ");
}
console.log(); // New line
let trash = null;
let removedName = list.remove(0);
console.log("Name " + removedName + " has been removed");
removedName = list.remove(0);
console.log("Name " + removedName + " has been removed");
removedName = list.remove(4);
console.log("Name " + removedName + " has been removed");
console.log("The size of list after removing 3 elements is " + list.getSize());
list = MyList()
list.add("Lee")
list.add("Rose")
list.add("Joe")
list.add("Billy")
list.add("Susan")
list.add("Emilly")
list.add("Nick")
print("Size of list after adding elements is " + str(list.get_size()))
for i in range(0, list.get_size()):
name = list.get(i)
print(name + " ", end="")
print() # New line
trash = None
removed_name = list.remove(0)
print("Name " + removed_name + " has been removed")
removed_name = list.remove(0)
print("Name " + removed_name + " has been removed")
removed_name = list.remove(4)
print("Name " + removed_name + " has been removed")
print("The size of list after removing 3 elements is " + str(list.get_size()))
پیادهسازی Stack با کمک List
میخوایم با کمک list یک Stack پیادهسازی کنیم؛ Stack مانند List یک بخشی از ساختمان داده است؛ در Stack اخرین عنصری که اضافه میشه، اولین عنصریه که میتونیم صدا کنیم یا از Stack حذفش کنیم؛ بهش میگن last-in-first-out.
توجه
به طور عمومی در اکثر زبان های برنامه نویسی، Stack با کمک LinkedList پیادهسازی میشه و در زبان های شی گرا مانند جاوا Stack یک کلاسه که از LinkedList ارث بری میکنه و علت این کار بهینه تر بودن LinkedList در حذف کردن عناصر اول و اخر است. اما در اینجا میخوایم با استفاده از List یک Stack پیاده کنیم تا با Stack اشنا بشید.
در پیادهسازیمون یک رابطه ی aggregation با List ایجاد میکنیم، سپس سه کانستراکتور و چهار تابع push(e: E), pop():E و isEmpty() رو برای MyStack تعریف میکنیم.
در Stack برای افزودن یک عنصر از تابع push, برای حذف اخرین عنصر از pop و برای صدا زدن عنصر (یعنی اخرین عنصری که با push اضافه شده) از تابع peek استفاده میکنیم و با تابع isEmpty بررسی میکنیم آیا Stack خالی است یا خیر.
نمودار uml پیادهسازی که میخوایم با list انجام بدیم در زیر اورده شده است.
public class MyStack<E> {
private final MyList<E> list = new MyList<>();
public MyStack(E[] elements){
for (int i=0; i<elements.length; i++){
list.add(elements[i]);
}
}
public MyStack(){
}
public boolean push(E e){
return list.add(e);
}
public E peek(){
if (list.isEmpty()) return null;
return list.get(list.size() - 1);
}
public E pop(){
if (list.isEmpty()) return null;
return list.remove(list.size() - 1);
}
public boolean isEmpty(){
return list.size() == 0;
}
}
class MyStack<E> {
private val list: MyList<E?> = MyList<E?>()
val isEmpty: Boolean
get() = list.size == 0
constructor(elements: Array<E?>) {
for (i in elements.indices) {
list.add(elements[i])
}
}
constructor()
fun push(e: E): Boolean {
return list.add(e)
}
fun peek(): E? {
if (list.isEmpty()) return null
return list.get(list.size - 1)
}
fun pop(): E? {
if (list.isEmpty()) return null
return list.removeAt(list.size - 1)
}
}
class MyStack {
// Custom private-like list instance belonging to this stack
list = new MyList();
constructor(elements = null) {
if (elements !== null) {
for (let i = 0; i < elements.length; i++) {
this.list.add(elements[i]);
}
}
}
push(e) {
this.list.add(e);
return true; // Returns a boolean matching the signature pattern
}
peek() {
if (this.list.isEmpty()) return null;
return this.list.get(this.list.getSize() - 1);
}
pop() {
if (this.list.isEmpty()) return null;
return this.list.remove(this.list.getSize() - 1);
}
isEmpty() {
return this.list.getSize() === 0;
}
}
class MyStack:
def __init__(self, elements=None):
self.list = MyList()
if elements is not None:
for i in range(len(elements)):
self.list.add(elements[i])
def push(self, e):
self.list.add(e)
return True
def peek(self):
if self.list.is_empty():
return None
return self.list.get(self.list.get_size() - 1)
def pop(self):
if self.list.isEmpty():
return None
return self.list.remove(self.list.get_size() - 1)
def is_empty(self):
return self.list.get_size() == 0
حالا نوبت به استفاده از Stack پیادهسازی شده میرسه، چندتا عدد بهش اضافه میکنیم و اعداد رو با pop حذف کرده و نمایش میدیم.
MyStack<Integer> stack = new MyStack();
stack.add(23);
stack.add(12);
stack.add(42);
stack.add(50);
stack.add(126);
while(!stack.isEmpty()){
int number = stack.pop();
System.out.println(number);
}
val stack: MyStack<Int> = MyStack()
stack.add(23)
stack.add(12)
stack.add(42)
stack.add(50)
stack.add(126)
while(!stack.isEmpty){
val number = stack.pop();
println(number);
}
let stack = new MyStack();
stack.push(23);
stack.push(12);
stack.push(42);
stack.push(50);
stack.push(126);
while (!stack.isEmpty()) {
let number = stack.pop();
console.log(number);
}
stack = MyStack()
stack.push(23)
stack.push(12)
stack.push(42)
stack.push(50)
stack.push(126)
while not stack.is_empty():
number = stack.pop()
print(number)
خلاصه
- برای نگهداری تعداد نامعلومی از عناصر هم نوع، از list استفاده میکنیم.
- میتونیم هر عنصر داخل list رو که خواستیم به صورت تصادفی صدا بزنیم.
- بر خلاف آرایه ها طول list ثابت نیست و بعد از ایجاد یک لیست میتونیم عناصر رو بهش اضافه کنیم یا ازش حذف کنیم.
- ظرفیت (capacity) یک list تعداد خونه هاییه که عناصر رو داخلشون نگهداری میکنه و اندازه (size) یک list تعداد عناصریه که نگهداری میکنه.
- اندازه ی list کمتر یا برابر با ظرفیت (capacity) آن است.
- پیچیدگی زمان برای حذف یا اضافه کردن یک عنصر در لیست O(n) است و برای صدا زدن عناصر با index برابر O(1) است.
بازخورد و دیدگاه ها
پیادهسازی کامل لیست