DSA Material

Turlangan massivlar (Typed Arrays — array moduli)

Share to

Python'ning o'rnatilgan (built-in) list turi Python obyektlariga havolalarni saqlaydi: har bir element list'ning ichki massividagi ko'rsatkich uchun ~8 bayt, shuningdek havola qilingan obyektning o'zi uchun ham xotira talab qiladi — kichik int obyekti 64-bitli CPython'da ~28 bayt. Aynan shu har bir obyekt uchun qo'shimcha xarajatdan xom C massivi qochadi. Standart kutubxonadagi array moduli esa C-ga asoslangan, turi qat'iy belgilangan massiv taqdim etadi: u xom qiymatlarni uzluksiz (contiguous) xotirada saqlaydi — xuddi C yoki Cython dasturi ishlatadigan joylashuvning o'zi.

Kitob bilan moslik

Bu sahifa Necaise kitobining massiv/vektor boblari yonidagi amaliy kengaytmadir.
U xuddi shu massiv ADT mantig'ini saqlab qoladi, ammo Python standart kutubxonasida mavjud bo'lgan quyi darajadagi xotira joylashuvini ko'rsatadi.

Turlangan massiv xotira joylashuvi

Shu sababli array quyidagi holatlarda eng yaxshi tanlovdir:

Tur kodlari (Type codes)

Har bir array tur kodi (type code) bilan yaratiladi, bu kod massivning butun yashash davri uchun element turini qat'iy belgilab qo'yadi.

Tur kodi C turi Python turi Minimal hajm
'b' signed char int 1 bayt
'B' unsigned char int 1 bayt
'h' signed short int 2 bayt
'H' unsigned short int 2 bayt
'i' signed int int 2 bayt
'I' unsigned int int 2 bayt
'l' signed long int 4 bayt
'L' unsigned long int 4 bayt
'q' signed long long int 8 bayt
'Q' unsigned long long int 8 bayt
'f' float float 4 bayt
'd' double float 8 bayt
Tur kodlarining hajmi platforma va kompilyatorga qarab farq qilishi mumkin (ayniqsa `l`/`L` uchun). Agar binar moslik muhim bo'lsa, `i`/`I` yoki `q`/`Q` kabi kengligi aniq belgilangan kodlardan foydalaning.

Massiv yaratish

import array

# Ishorali (signed) butun sonlardan iborat bo'sh massiv
ints = array.array('i')

# iterable'dan boshlang'ich qiymat berib yaratish
floats = array.array('f', [1.0, 2.5, 3.14])

# range'dan yaratish
counts = array.array('l', range(1_000_000))

Amallar

append va extend

append va extend xuddi list'dagidek ishlaydi. Elementlar tur kodiga mos kelishi shart.

a = array.array('i', [10, 20, 30])
a.append(40)          # [10, 20, 30, 40]
a.extend([50, 60])    # [10, 20, 30, 40, 50, 60]

Indeks orqali murojaat va kesim olish (slicing)

Indeks orqali tasodifiy murojaat O(1) — xuddi list'dagidek. Kesim (slice) esa o'sha turdagi yangi array qaytaradi.

a = array.array('d', [1.1, 2.2, 3.3, 4.4])
print(a[1])       # 2.2
print(a[-1])      # 4.4
print(a[1:3])     # array('d', [2.2, 3.3])

Qo'shish (insert) va o'chirish (delete)

a = array.array('i', [1, 2, 3, 4])
a.insert(2, 99)   # [1, 2, 99, 3, 4]  — O(n) siljish
a.pop(2)          # 99 ni o'chiradi    — O(n) siljish
a.remove(3)       # birinchi 3 ni o'chiradi — O(n) skan + siljish

Qidirish

a = array.array('i', [10, 20, 30, 20])
print(a.index(20))   # 1  — birinchi uchrash, O(n)
print(a.count(20))   # 2  — uchrashlar sonini sanaydi, O(n)

Teskari aylantirish

a = array.array('i', [1, 2, 3])
a.reverse()
print(a)   # array('i', [3, 2, 1])

Ko'p hajmli bayt I/O

list'dan asosiy ustunligi: butun array xom baytlarga (yoki xom baytlardan) har bir element uchun emas, balki har bir bayt uchun doimiy vaqtda serializatsiya qilinadi — chunki xotira allaqachon uzluksiz va turlangan.

import array

a = array.array('i', [1, 2, 3, 4])

# Baytlarga serializatsiya qilish
raw = a.tobytes()
print(len(raw))   # 16  (4 ta int × har biri 4 bayt)

# Deserializatsiya qilish
b = array.array('i')
b.frombytes(raw)
print(b)          # array('i', [1, 2, 3, 4])

Fayl I/O

import array

a = array.array('d', [3.14, 2.71, 1.41])

with open('numbers.bin', 'wb') as f:
    a.tofile(f)

loaded = array.array('d')
with open('numbers.bin', 'rb') as f:
    loaded.fromfile(f, 3)   # aniq 3 ta elementni o'qiydi

print(loaded)   # array('d', [3.14, 2.71, 1.41])

Xotira solishtiruvi: list va array

import sys
import array

n = 1_000_000

py_list = list(range(n))
c_array = array.array('l', range(n))

print(f"list  : {sys.getsizeof(py_list):,} bytes")
print(f"array : {sys.getsizeof(c_array):,} bytes")
# 64-bitli CPython 3.13'da odatiy chiqish:
# list  : 8,000,056 bytes
# array : 8,183,816 bytes  (l = 8 baytli signed long — list ko'rsatkichlari
#                           kabi har bir element uchun 8 bayt, shu sababli
#                           bu yerda tejamkorlik yo'q; ortiqcha ajratish
#                           (over-allocation) tufayli massiv hatto kattaroq)
#
# Xotirani aslida tor tur kodi tejaydi. 32-bitli butun sonlar uchun
# ('i', har biri 4 bayt):
# array : 4,091,948 bytes  — list'ning taxminan yarmicha xotira
#
# Aniq bayt qiymatlari platforma, CPython build'i va massivning ortiqcha
# ajratilishiga bog'liq, shu sababli bularni taxminiy deb qabul qiling.

'l' massivi 64-bitli build'da list'ga nisbatan hech qanday xotira tejamkorligini bermaydi — ikkalasi ham har bir element uchun 8 bayt sarflaydi (list uchun ko'rsatkich, massiv uchun long), va ortiqcha ajratish (over-allocation) tufayli massiv hatto biroz kattaroq bo'lishi mumkin. Tejamkorlik faqat tur kodi ko'rsatkichdan tor (64-bitda 8 bayt) bo'lganda paydo bo'ladi: ishorali int ('i', 4 bayt) xotirani taxminan ikki barobar qisqartiradi; unsigned char ('B', 1 bayt) esa uni taxminan sakkizdan biriga tushiradi.

O'rovchi (wrapper) sinf

array.array'ni tur kodini majburiy qiladigan va sohaga xos metodlar qo'shadigan sinf ichiga o'rab olishingiz mumkin:

import array


class IntArray:
    """Ishorali 64-bitli butun sonlardan iborat o'lcham o'zgaruvchan massiv."""

    TYPECODE = 'q'  # signed long long

    def __init__(self, iterable=()):
        self._data = array.array(self.TYPECODE, iterable)

    def append(self, value):
        self._data.append(value)

    def get(self, index):
        if index < 0 or index >= len(self._data):
            raise IndexError("index out of range")
        return self._data[index]

    def set(self, index, value):
        if index < 0 or index >= len(self._data):
            raise IndexError("index out of range")
        self._data[index] = value

    def insert(self, index, value):
        self._data.insert(index, value)

    def delete(self, index):
        if index < 0 or index >= len(self._data):
            raise IndexError("index out of range")
        return self._data.pop(index)

    def tobytes(self):
        return self._data.tobytes()

    @classmethod
    def frombytes(cls, raw):
        obj = cls()
        obj._data.frombytes(raw)
        return obj

    def __len__(self):
        return len(self._data)

    def __iter__(self):
        return iter(self._data)

    def __repr__(self):
        return f"IntArray({list(self._data)})"

Cython va NumPy bilan bog'liqlik

Python'ning array moduli Cython va NumPy tayanadigan xuddi shu **bufer protokoli (buffer protocol)**dan foydalanadi. Bu shuni anglatadiki:

# non-runnable: requires numpy
import array
import numpy as np

a = array.array('d', [1.0, 2.0, 3.0, 4.0])

# Nusxasiz ko'rinish (zero-copy) — NumPy o'sha xotira blokini o'qiydi
arr = np.frombuffer(a, dtype=np.float64)
print(arr)          # [1. 2. 3. 4.]
print(arr.base)     # <memory at 0x...>  — nusxa emas

Qachon NumPy'ni afzal ko'rish kerak

array.array'dan quyidagi holatlarda foydalaning:

NumPy'dan quyidagi holatlarda foydalaning:

Qachon array.array'dan foydalanmaslik kerak

Murakkablik xulosasi

Amal Vaqt Izoh
Indeks orqali o'qish / yozish O(1) To'g'ridan-to'g'ri ko'rsatkich siljishi, boxing yo'q
Append O(1) amortizatsiyalangan list bilan bir xil o'sish strategiyasi
O'rtaga qo'shish O(n) Elementlar xom xotirada siljiydi
O'rtadan o'chirish O(n) Elementlar xom xotirada siljiydi
tobytes O(n) Uzluksiz blokning xotira nusxasi (memcopy)
frombytes O(n) Uzluksiz blokka xotira nusxasi (memcopy)
index / count O(n) Ketma-ket skan

Tipik xatolar