Python: колекції, частина 2: індексування, слайсинг, сортування

imageЦя стаття є продовженням моєї статті "Python: колекції, частина 1: класифікація, загальні підходи і методи, конвертація".

У даній статті ми продовжимо вивчати загальні принципи роботи зі стандартними колекціями (модуль collections в ній не розглядається) Python.

Для кого: для вивчають Python і вже мають початкове уявлення про колекціях і роботі з ними, які бажають систематизувати і поглибити свої знання, скласти їх у цілісну картину.

ЗМІСТ:

  1. Індексування
  2. Слайсинг
  3. Сортування
1. Індексування
1.1 Індексовані колекції
Розглянемо індексовані колекції (їх ще називають послідовності — sequences) — список (list), кортеж (tuple), рядок (string).

Під индексированностью мається на увазі, що елементи колекції розташовуються у певному порядку, кожен елемент має свій індекс від 0 (тобто перший за рахунком елемент має індекс не 1, а 0) до індексу на одиницю меншого довжини колекції (тобто len(mycollection)-1).

Отримання 1.2 значення за індексом
Для всіх індексованих колекцій можна отримати значення елемента за його індексом у квадратних дужках. Причому, можна задавати від'ємний індекс, це означає, що будемо знаходити елемент з кінця вважаючи зворотному порядку.

При завданні негативного індексу, останній елемент має індекс -1, передостанній -2 і так далі до першого елементу, індекс якого дорівнює значенню довжини колекції з від'ємним знаком, тобто (-len(mycollection).
елементи a b c d e
індекси 0 (-5) 1 (-4) 2 (-3) 3 (-2) 4 (-1)
my_str = "abcde"
print(my_str[0]) # a - перший елемент
print(my_str[-1]) # e - останній елемент 
print(my_str[len(my_str)-1]) # e - так теж можна взяти останній елемент
print(my_str[-2]) # d - передостанній елемент

Наші колекції можуть мати кілька рівнів вкладеності, як список списків у прикладі нижче. Для переходу на рівень глибше ставиться друга пара квадратних дужок і так далі.

my_2lvl_list = [[1, 2, 3], ['a', 'b', 'c']]
print(my_2lvl_list[0]) # [1, 2, 3] - перший елемент — перший вкладений список
print(my_2lvl_list[0][0]) # 1 — перший елемент першого вкладеного списку
print(my_2lvl_list[1][-1]) # — останній елемент другого вкладеного списку

1.3 Зміна елемента списку за індексом
Оскільки кортежі і рядки у нас незмінні колекції, то за індексом ми можемо тільки брати, але не змінювати їх:

my_tuple = (1, 2, 3, 4, 5)
print(my_tuple[0]) # 1
my_tuple[0] = 100 # TypeError: 'tuple' object does not support item assignment

А ось для списку, якщо взяття елемента з індексом розташовується в лівій частині виразу, а далі йде оператор присвоювання =, то ми запитуємо нове значення елементу з цим індексом.

my_list = [1, 2, 3, [4, 5]]
my_list[0] = 10
my_list[-1][0] = 40
print(my_list) # [10, 2, 3, [40, 5]]


2 Слайсинг
2.1 Синтаксис слайсинга
Дуже часто, треба отримати не один якийсь елемент, а певний їх набір обмежений певними простими правилами — наприклад, перші 5 або останні три, або кожен другий елемент — в таких завданнях, замість перебору в циклі набагато зручніше використовувати так званий слайсинг («нарізку»).

Слід пам'ятати, що взявши елемент з індексом або слайсом ми не змінюємо вихідну колекцію, ми просто скопіювали її частину для подальшого використання (наприклад, додавання в іншу колекцію, виводу на друк, якихось обчислень). Оскільки сама колекція не змінюється — це застосовно як до змінюваним (список) так і незмінним (рядок кортеж) послідовності.

Синтаксис слайсинга схожий на такий для індексації, але в квадратних дужках замість одного значення вказується 2-3 через двоесточие:

my_collection[start:stop:step] # старт, стоп і крок

Особливості слайсинга:

  • Від'ємні значення старту і стопа означають, що рахувати треба не з початку, а з кінця колекції.
  • Від'ємне значення кроку — перебір ведемо в зворотному порядку справа наліво.
  • Якщо не вказано старт [:stop:step]— беремо з самого початку колекції, тобто start = 0
    Якщо не вказано стоп [start:: step] — йдемо до самого кінця колекції, тобто stop = 0
  • step = 1, тобто послідовний перебір зліва направо вказувати не обов'язково це значення кроку за замовчуванням. В такому випадку достатньо вказати [start:stop]
  • Можна зробити навіть так [:] — це означає взяти колекцію цілком
  • ВАЖЛИВО: При слайсинге, перший індекс входить у вибірку, а другою немає! Тобто від старту включно, до стопа, де стоп не включається в результат. Математично це можна було б записати як [start, stop)

Приклади слайсинга у вигляді таблиці:

image
Код прикладів з таблиці
col = 'abcdefg'
print(col[:]) # abcdefg
print(col[::-1]) # gfedcba
print(col[::2]) # aceg
print(col[1::2]) # bdf
print(col[:1]) # a
print(col[-1:]) # g
print(col[3:4]) # d
print(col[-3:]) # efg
print(col[-3:1:-1]) # edc
print(col[2:5]) # cde

2.2. Іменовані слайсы
Щоб позбавиться від «магічних констант», особливо у випадку, коли один і той же слайс треба застосовувати багаторазово, можна визначити константи з іменованими слайсами з користуванням спеціальної функції slice()()

Примітка: Nonе при опущеному відповідає значенню за замовчуванням. Тобто [:2] стає slice(None, 2), а [1::2] стає slice(1, None, 2).

person = ('Alex', 'Smith', "May", 10, 1980)
NAME, BIRTHDAY = slice(None, 2), slice(2, None) 
# задаємо константам іменовані слайсы
# дані константи в квадратних дужках заміняться відповідними слайсами
print(person[NAME]) # ('Alex', 'Smith')
print(person[BIRTHDAY]) # ('May', 10, 1980)

my_list = [1, 2, 3, 4, 5, 6, 7]
EVEN = slice(1, None, 2)
print(my_list[EVEN]) # [2, 4, 6]

2.3 Зміна списку слайсингом
Важливий момент, на якому не завжди загострюється увага — з допомогою слайсинга можна не тільки одержувати копію колекції, але у списку можна також змінювати значення елементів, видаляти і додавати нові.

Проілюструємо це на прикладах нижче:

  • Навіть якщо хочемо додати один елемент, необхідно передавати итерируемый об'єкт, інакше буде помилка TypeError: can only assign an iterable
    my_list = [1, 2, 3, 4, 5]
    # my_list[1:2] = 20 # TypeError: can only assign an iterable
    my_list[1:2] = [20] # Ось тепер все працює
    print(my_list) # [1, 20, 3, 4, 5]
    

  • Для вставки окремих елементів можна використовувати слайсинг, код прикладів є нижче, але робити так не рекомендую, так як такий синтаксис гірше читати. Краще використовувати методи списку .append ().insert():
    Слайсинг аналоги .append() та insert()
    my_list = [1, 2, 3, 4, 5]
    my_list[5:] = [6] # вставляємо в кінець — краще використовувати .append(6)
    print(my_list) # [1, 2, 3, 4, 5, 6]
    my_list[0:0] = [0] # вставляємо в початок — краще використовувати .insert(0, 0)
    print(my_list) # [0, 1, 2, 3, 4, 5, 6]
    my_list[3:3] = [25] # вставляємо між елементами — краще використовувати .insert(3, 25)
    print(my_list) # [0, 1, 2, 25, 3, 4, 5, 6]
    

  • Можна змінювати частини послідовності — це застосування виглядає найбільш цікавим, оскільки вирішує завдання просто і наочно.
    my_list = [1, 2, 3, 4, 5]
    my_list[1:3] = [20, 30]
    print(my_list) # [1, 20, 30, 4, 5]
    my_list[1:3] = [0] # немає проблем замінити два елемента на один
    print(my_list) # [1, 0, 4, 5]
    my_list[2:] = [40, 50, 60] # або два елемента на три
    print(my_list) # [1, 0, 40, 50, 60]
    

  • Можна просто видалити частину послідовності
    my_list = [1, 2, 3, 4, 5]
    my_list[:2] = []
    print(my_list) # [3, 4, 5]
    
2.4 Вихід за межі індексу
Хоча звернення за індексом по суті є приватним випадком слайсинга, коли ми звертаємося тільки до одного елемента, а не діапазону, є дуже важлива відмінність в обробці ситуації з відсутнім елементом з шуканим індексом.

Звернення до неіснуючого індексу колекції викликає помилку:

my_list = [1, 2, 3, 4, 5]
print(my_list[-10]) # IndexError: list index out of range
print(my_list[10]) # IndexError: list index out of range

А в разі виходу кордонів слайсинга за кордону колекції ніякої помилки не відбувається:

my_list = [1, 2, 3, 4, 5]
print(my_list[0:10]) # [1, 2, 3, 4, 5] — відпрацювали у межах колекції
print(my_list[10:100]) # [] - таких елементів немає — повернули порожню колекцію
print(my_list[10:11]) # [] - перевіряємо 1 відсутній елемент - порожня колекція, без помилки

3 Сортування елементів колекції
Сортування елементів колекції важлива і затребувана функція, постійно зустрічається в звичайних завданнях. Тут є кілька особливостей, на яких не завжди загострюється увага, але які дуже важливі.

3.1 Функція sorted()
Ми може використовувати функцію sorted() для виводу списку сортованих елементів будь-якої колекції для подальшого оброблення чи виводу.

  • функція не змінює вихідну колекцію, а повертає новий список з її елементів;
  • не залежно від типу вихідної колекції, повернеться список (list) її елементів;
  • оскільки вона не змінює вихідну колекцію, її можна застосовувати до сталий колекцій;
  • Оскільки при сортуванні елементів, що повертаються, нам не важливо, чи елемента якийсь індекс у вихідній колекції, можна застосовувати до індексуються колекцій;
  • не Має додаткові аргументи:
    reverse=True — сортування в зворотному порядку
    key=funcname — сортування за допомогою спеціальної функції funcname, вона може бути стандартною функцією Python, так і спеціально написаної вами для даної задачі
my_list = [2, 5, 1, 7, 3]
my_list_sorted = sorted(my_list)
print(my_list_sorted) # [1, 2, 3, 5, 7]

my_set = {2, 5, 1, 7, 3}
my_set_sorted = sorted(my_set, reverse=True)
print(my_set_sorted) # [7, 5, 3, 2, 1]

Приклад сортування рядків за довжиною len() кожного елемента:

my_files = ['somecat.jpg', 'pc.png', 'apple.bmp', 'mydog.gif']
my_files_sorted = sorted(my_files, key=len)
print(my_files_sorted) # ['pc.png', 'apple.bmp', 'mydog.gif', 'somecat.jpg']

3.2 Функція reversed()
Функція reversed() застосовується для послідовностей і працює по іншому:

  • повертає генератор списку, а не сам список;
  • якщо потрібно отримати не генератор, а готовий список, результат можна обернути в list() або ж замість reversed() скористатися слайсом [: :-1];
  • вона не сортує елементи, а повертає їх у зворотному порядку, тобто читає з кінця списку;
  • з попереднього пункту зрозуміло, що якщо у нас колекція неиндексированная — ми не можемо вивести її елементи в зворотному порядку, і ця функція до таких колекцій не застосовна — отримаємо «TypeError: argument to reversed() must be a sequence»;
  • не дозволяє використовувати додаткові аргументи — буде помилка «TypeError: reversed() does not take keyword arguments».
my_list = [2, 5, 1, 7, 3]
my_list_sorted = reversed(my_list)
print(my_list_sorted) # <listreverseiterator object at 0x7f8982121450>
print(list(my_list_sorted)) # [3, 7, 1, 5, 2]
print(my_list[::-1]) # [3, 7, 1, 5, 2] - той же результат з допомогою слайсинга

3.3 Методи списку .sort (). reverse()
У списку (і тільки він) є особливі методи .sort ().reverse() які роблять теж саме, що відповідні функції sorted() і reversed(), але при цьому:

  • Змінюють сам вихідний список, а не генерують новий;
  • Повертають None, а не новий список;
  • підтримують ті ж додаткові аргументи;
  • в них не треба передавати сам список першим параметром, більше того, якщо це зробити — буде помилка — не вірне кількість аргументів.
my_list = [2, 5, 1, 7, 3]
my_list.sort()
print(my_list) # [1, 2, 3, 5, 7]

Зверніть увагу: Часта помилка початківців, яка не є помилкою для інтерпретатора, але призводить не до того результату, який хочуть отримати.
my_list = [2, 5, 1, 7, 3]
my_list = my_list.sort()
print(my_list) # None

3.4 Особливості сортування словника
Сортування словника є свої особливості, викликані тим, що елемент словника — це пари ключ-значення.

  • sorted(my_dict) — коли ми передаємо в функцію сортування словник без виклику його додаткових методів — йде перебір тільки ключів, сортований список ключів нам і повертається;
  • sorted(my_dict.keys()) — той же результат, що в попередньому прикладі, але прописаний більш явно;
  • sorted(my_dict.items()) — повертається сортований список кортежів (ключ, значення), сортованих за ключем;
  • sorted(my_dict.values()) — повертається сортований список значень
my_dict = {'a': 1, 'c': 3, 'e': 5, 'f': 6, 'b': 2, 'd': 4}
mysorted = sorted(my_dict)
print(mysorted) # ['a', 'b', 'c', 'd', 'e', 'f']
mysorted = sorted(my_dict.items())
print(mysorted) # [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6)]
mysorted = sorted(my_dict.values())
print(mysorted) # [1, 2, 3, 4, 5, 6]

Окремі складнощі може викликати сортування словника не по ключам, а значення, якщо нам не просто потрібен список значень, і саме виводити пари в порядку сортування за значенням.

Для вирішення цього завдання можна як спеціальної функції сортування передавати lambda-функцію lambda x: x[1] яка з одержуваних на кожному етапі кортежів (ключ, значення) буде брати для сортування другий елемент кортежу.

population = {"Shanghai": 24256800, "Karachi": 23500000, "Beijing": 21516000, "Delhi": 16787941}
# відсортуємо за зростанням населення:
population_sorted = sorted(population.items(), key=lambda x: x[1])
print(population_sorted)
# [('Delhi', 16787941), ('Beijing', 21516000), ('Karachi', 23500000), ('Shanghai', 24256800)]




У наступних статтях планується продовження:

  • (скоро) Додавання і видалення елементів в колекції
  • (скоро) Конкатенація колекцій;
  • (пізніше) Тонкощі генерації колекцій.
  • (можливо в майбутньому) Модуль collections.

Запрошую до обговорення:

  • Якщо я десь допустив неточність або не врахував щось важливе — пишіть в коментарях, важливі коментарі будуть пізніше додано статтю з зазначенням вашого авторства.
  • Якщо якісь моменти не зрозумілі і потрібне уточнення — пишіть ваші запитання в коментарях — або я або інші читачі дадуть відповідь, а слушні запитання з відповідями будуть пізніше додано статтю.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.