27. Списки и еще раз списки в Python: отличия числового списка от списка объектов и приемы работы

Сегодня еще один урок, в котором будем работать со списками.
Подготовим два списка, один из которых будет заполнен числами, а другой — объектами.

from random import randrange as rnd
class ball():
        def __init__(self):
                self.x = 10
                self.y = 20
                self.r = rnd(10,30)
 
balls = [ball() for x in range(6)]
for b in balls:
        print(b.x, b.y, b.r)
print()

a = [rnd(10) for x in range(6)]
print(a) 

Найдем самое большое значение. Начинающие всегда пытаются сравнивать соседние значения, чтобы найти самое большое. Но это тупиковый путь. Представьте что вы находитесь возле 1000 людей, которые стоят в ряд и на вид примерно одинаково весят. Ваша задача — найти самого тяжелого, но у вас нет бумаги и ручки, чтобы записать все значения. Вы можете помнить одно-два значения и у вас есть весы. Какими будут ваши действия?
Прежде, чем читать дальше — подумайте.
Вот, вы подошли к первому… (подумали?)
Взвесили его, запомнили, сколько он весит.
Подошли ко второму, взвесили и его. Если он весит больше, то запомнили его вес, если нет, то, продолжая помнить весь первого, идете дальше. И так обходите всех.
Ключевой момент в том, чтобы в каждый момент времени работать только с одним элементом списка. И еще с одним значением, в котором храниться самое большое из уже просмотренных.

m = a[0]
for x in a:
        if x > m:
                m = x
print(m)

Сделаем тоже самое для списка шариков, найдем самый большой:

m = balls[0].r
for b in balls:
        if b.r > m:
                m = b.r
print(m)

Чтобы найти самый маленький, нужно просто поменять знак.

Посмотрим на встроенную функцию max, которая позволит найти самый большой элемент последовательности:

print(max(a))

Просто и легко, не так ли?
Но что делать с шариками?

На помощь придет прием, который в переводе называется «списковое включение», а иногда «генераторы списков». Термин «генераторы» занят, поэтому я буду использовать «списковое включение», которое я увидел в Р.Сузи в далеком 2006 году. Как я уже говорил, цикл for в Python, вместе со богатыми возможностями по работе со списками, позволяет писать в одну строку вещи, которые потребовали бы 5-6 строк в «обычных языках».

print(max([b.r for b in balls ]))

А теперь узнаем его номер: где стоит самый большой?

im = 0
for i in range(len(a)):
        if a[i] > a[im]:
                im = i
print(im)               

Или так:

print(a.index(max(a)))

В интернете вы можете найти и такой вариант, но зачем нам еще одна переменная?

m = a[0]
im = 0
for i in range(len(a)):
        if a[i] > m:
                m = a[i]
                im = i
print(im)               

А теперь сделаем тоже самое с шариками. На этот раз мы можем перебирать не по номерам, а по значениям:

bm = b
for b in balls:
        if b.r > bm.r:
                bm = b
balls.index(bm)

На самом деле — номер максимального это не самоцель. Обычно с ним нужно что-то делать: изменять, удалять, копировать. В этом случае в числовом списке нужен номер (иначе к элементу мы не обратимся),а в списке объектов достаточно ссылки на объект. Т.е. строка для получения номера, по сути, не нужна, достаточно значения bm, чтобы можно было что-то сделать с шариком, размер которого максимален.

А теперь работа с соседними значениями.
Удалить положительные, которые стоят после отрицательных. Напомню, что удалять проще, перебирая элементы с конца:

a = [rnd(-10,10) for x in range(12)]
print(a)
old_x = a[-1]
for i in range(len(a)-1,0,-1):
        if a[i] > 0 and a[i-1] < 0:
                a.pop(i)
print(a)

Аналогичная задача для списка шариков (привожу полный код, потому что немного изменил создание списка):

from random import randrange as rnd
class ball():
        def __init__(self):
                self.x = 10
                self.y = 20
                self.r = rnd(-10,10)
 
balls = [ball() for x in range(12)]
for b in balls:
        print(b.r, end = ' ')
print()

b = balls[-1]
for prev_b in balls[::-1]:
    if prev_b.r < 0 and b.r > 0:
        balls.remove(b)
    b = prev_b

for b in balls:
        print(b.r, end = ' ')
print()
        

Никто не мешает работать со списком объектов так же, как мы работаем с числовыми списками — с использованием индекса. Но другой подход, с использованием ссылки на объект дает больше гибкости, потому что индекс может измениться, а ссылка — нет.
Вот, пример: нужно удалить посчитать количество отрицательных и, если их больше, чем положительных, то удалить положительные, иначе — удалить отрицательные.
Мы можем сделать это двумя проходами без особых трудностей:

from random import randrange as rnd

class ball():
        def __init__(self):
                self.x = 10
                self.y = 20
                self.r = rnd(-10,11)
 
balls = [ball() for x in range(12)]
for b in balls:
        print(b.r, end = ' ')
print()

k_neg = 0
for b in balls:
    if b.r < 0:
        k_neg += 1
if k_neg < len(balls) / 2:
    for b in balls[::-1]:
        if b.r < 0: 
            balls.remove(b)
else:
    for b in balls[::-1]:
        if b.r > 0:
            balls.remove(b)

for b in balls:
        print(b.r, end = ' ')
print()

Посмотрим на другое решение:

neg = []
pos = []
for b in balls:
    if b.r < 0:
        neg += [b]
    else:
        pos += [b]
        
if len(neg) < len(balls) / 2:
    for killed_b in neg:
        balls.remove(killed_b)
else:
    for killed_b in pos:
        balls.remove(killed_b)

Не правда ли, это быстрее читается и больше понятно на человеческий язык, чем на код?
Можем ли мы еще упростить это решение?
Жду ваших предложений в комментариях.

А пока задачи:

В числовом списке:
1. Найти количество максимальных
2. Удалить все максимальные
3. Удалить все, которые стоят перед максимальнми
4.* Максимальные переставить в начало списка
5. Поменять максимальный и первый
6. Удалить все нули (да-да, опять)
7. Удалить элементы, которые стоят между нулями
8. Все четные удвоить, нечетные — разделить

С списке, заполненном шариками:

class ball():
        def __init__(self):
                self.x = rnd(100,200)
                self.r = rnd(-10,11)
 
balls = [ball() for x in range(12)]

9. Найти x,y самого большого
10. Самый большой шарик и те,которые ему равны, переместить вправо (x += 10)
11. Самый большой шарик переместить на первое место
12. Самый большой шарик поменять поменять с первым

На следующем занятии будем работать со списком «шариков», после чего — двумерные списки и где-то там будет игра «Сапер» и «Блоки».
Жду комментариев.

2 thoughts on “27. Списки и еще раз списки в Python: отличия числового списка от списка объектов и приемы работы

  1. Уведомление: Аноним
  2. Писал карточную игру (выигрывал то у кого сумма номиналов карт на руках, больше) и уперся в проблему определения победителя. Имелся список игроков у каждого по два атрибута: имя, набор карт и по свойству — сумма номиналов, и как вытащить именно номиналы из списка и сравнить их не мог понять. Пробовал создавать для номиналов отдельный список, сортировать их но тогда терялась привязка к владельцу данного набора карт. Прочитав данную главу, нашел решение, даже несколько. Первое похоже на то, как необходимую для сравнения величину мы выдергивали из списка, как в математике общий множитель за скобки. А второй способ когда одной строкой находишь максимальное значение необходимой величины ( вообще красиво). В общем СПАСИБО за красивые и интересные решения! Код не выкладываю так как использовались свои модули.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *