Двумерные массивы, работа с файлами, исключения

Содержание лекции:

1. Двумерные массивы

2. Работа с файлами

3. Исключения

Запись четвертого занятия вы можете посмотреть по этой ссылке.

1. Двумерные массивы

Обработка и вывод вложенных списков

Часто в задачах приходится хранить прямоугольные таблицы с данными. Такие таблицы называются матрицами или двумерными массивами. В языке программирования Питон таблицу можно представить в виде списка строк, каждый элемент которого является в свою очередь списком, например, чисел. Например, приведём программу, в которой создаётся числовая таблица из двух строк и трех столбцов, с которой производятся различные действия.

a = [[1, 2, 3], [4, 5, 6]]

print(a[0])

print(a[1])

b = a[0]

print(b)

print(a[0][2])

a[0][1] = 7

print(a)

print(b)

b[2] = 9

print(a[0])

print(b)

Здесь первая строка списка a[0] является списком из чисел [1, 2, 3]. То есть a[0][0] == 1, значение a[0][1] == 2, a[0][2] == 3, a[1][0] == 4, a[1][1] == 5, a[1][2] == 6.

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

a = [[1, 2, 3, 4], [5, 6], [7, 8, 9]]

for i in range(len(a)):

    for j in range(len(a[i])):

        print(a[i][j], end=' ')

    print()

Помним, что переменная цикла for в Питоне может перебирать не только диапазон, создаваемый с помощью функции range(), но и вообще перебирать любые элементы любой последовательности. Последовательностями в Питоне являются списки, строки, а также некоторые другие объекты, с которыми мы пока не встречались. Продемонстрируем, как выводить двумерный массив, используя это удобное свойство цикла for:

a = [[1, 2, 3, 4], [5, 6], [7, 8, 9]]

for row in a:

    for elem in row:

        print(elem, end=' ')

    print()

Естественно, для вывода одной строки можно воспользоваться методом join():

for row in a:

    print(' '.join([str(elem) for elem in row]))

Используем два вложенных цикла для подсчета суммы всех чисел в списке:

a = [[1, 2, 3, 4], [5, 6], [7, 8, 9]]

s = 0

for i in range(len(a)):

    for j in range(len(a[i])):

        s += a[i][j]

print(s)

Или то же самое с циклом не по индексу, а по значениям строк:

a = [[1, 2, 3, 4], [5, 6], [7, 8, 9]]

s = 0

for row in a:

    for elem in row:

        s += elem

print(s)

Создание вложенных списков

Пусть даны два числа: количество строк n и количество столбцов m. Необходимо создать список размером n×m, заполненный нулями.

Очевидное решение оказывается неверным:

a = [[0] * m] * n

В этом легко убедиться, если присвоить элементу a[0][0] значение 5, а потом вывести значение другого элемента a[1][0] — оно тоже будет равно 5. Дело в том, что [0] * m возвращает ccылку на список из m нулей. Но последующее повторение этого элемента создает список из n элементов, которые являются ссылкой на один и тот же список (точно так же, как выполнение операции b = a для списков не создает новый список), поэтому все строки результирующего списка на самом деле являются одной и той же строкой.

Обратите внимание на номер id у списков. Если у двух списков id совпадает, то это на самом деле один и тот же список в памяти.

n = 3

m = 4

a = [[0] * m] * n

a[0][0] = 5

print(a[1][0])

­ Таким образом, двумерный список нельзя создавать при помощи операции повторения одной строки. Что же делать?

Первый способ: сначала создадим список из n элементов (для начала просто из n нулей). Затем сделаем каждый элемент списка ссылкой на другой одномерный список из m элементов:

n = 3

m = 4

a = [0] * n

for i in range(n):

   a[i] = [0] * m

Второй способ: создать пустой список, потом n раз добавить в него новый элемент, являющийся списком-строкой:

n = 3

m = 4

a = []

for i in range(n):

   a.append([0] * m)

Третий способ: воспользоваться генератором: создать список из n элементов, каждый из которых будет списком, состоящих из m нулей:

n = 3

m = 4

a = [[0] * m for i in range(n)]

В этом случае каждый элемент создается независимо от остальных (заново конструируется список [0] * m для заполнения очередного элемента списка), а не копируются ссылки на один и тот же список.

Ввод двумерного массива

Способ 1 Пусть программа получает на вход двумерный массив в виде n строк, каждая из которых содержит m чисел, разделенных пробелами. Как их считать? Например, так:

# в первой строке ввода идёт количество строк массива

n = int(input())

a = []

for i in range(n):

   a.append([int(j) for j in input().split()])

Способ 2 без использования сложных вложенных вызовов функций:

# в первой строке ввода идёт количество строк массива

n = int(input())

a = []

for i in range(n):

   row = input().split()

   for i in range(len(row)):

       row[i] = int(row[i])

   a.append(row)

Способ 3 при помощи генератора:

# в первой строке ввода идёт количество строк массива

n = int(input())

a = [[int(j) for j in input().split()] for i in range(n)]

Пример обработки двумерного массива

Задача: Пусть дан квадратный массив из n строк и n столбцов. Необходимо элементам, находящимся на главной диагонали, проходящей из левого верхнего угла в правый нижний (то есть тем элементам a[i][j], для которых i==j) присвоить значение 1, элементам, находящимся выше главной диагонали – значение 0, элементам, находящимся ниже главной диагонали – значение 2. То есть необходимо получить такой массив (пример для n==4):

1 0 0 0

2 1 0 0

2 2 1 0

2 2 2 1

Решение 1: Элементы, которые лежат выше главной диагонали – это элементы a[i][j], для которых i<j, а для элементов ниже главной диагонали i>j. Таким образом, мы можем сравнивать значения i и j и по ним определять значение A[i][j]. Получаем следующий алгоритм:

n = 4

a = [[0] * n for i in range(n)]

for i in range(n):

   for j in range(n):

       if i < j:

           a[i][j] = 0

       elif i > j:

           a[i][j] = 2

       else:

           a[i][j] = 1

for row in a:

   print(' '.join([str(elem) for elem in row]))

Данный алгоритм плох, поскольку выполняет одну или две инструкции if для обработки каждого элемента. Если мы усложним алгоритм, то мы сможем обойтись вообще без условных инструкций.

Решение 2: Сначала заполним главную диагональ, для чего нам понадобится один цикл:

for i in range(n):

   a[i][i] = 1

Затем заполним значением 0 все элементы выше главной диагонали, для чего нам понадобится в каждой из строк с номером i присвоить значение элементам a[i][j] для j=i+1, ..., n-1. Здесь нам понадобятся вложенные циклы:

for i in range(n):

   for j in range(i + 1, n):

       a[i][j] = 0

Аналогично присваиваем значение 2 элементам a[i][j] для j=0, ..., i-1:

for i in range(n):

   for j in range(0, i):

       a[i][j] = 2

Решение 3: внешние циклы объединить в один - более компактное решение

n = 4

a = [[0] * n for i in range(n)]

for i in range(n):

   for j in range(0, i):

       a[i][j] = 2

   a[i][i] = 1

   for j in range(i + 1, n):

       a[i][j] = 0

for row in a:

   print(' '.join([str(elem) for elem in row]))

Решение 4 использует операцию повторения списков для построения очередной строки списка. i-я строка списка состоит из i чисел 2, затем идет одно число 1, затем идет n-i-1 число 0:

n = 4

a = [0] * n

for i in range(n):

   a[i] = [2] * i + [1] + [0] * (n - i - 1)

for row in a:

   print(' '.join([str(elem) for elem in row]))

Решение 5: заменить цикл на генератор:

n = 4

a = [0] * n

a = [[2] * i + [1] + [0] * (n - i - 1) for i in range(n)]

for row in a:

   print(' '.join([str(elem) for elem in row]))

Вложенные генераторы двумерных массивов

Для создания двумерных массивов можно использовать вложенные генераторы, разместив генератор списка, являющегося строкой, внутри генератора всех строк. Напомним, что сделать список из n строк и m столбцов можно при помощи генератора, создающего список из n элементов, каждый элемент которого является списком из m нулей:

[[0] * m for i in range(n)]

Но при этом внутренний список также можно создать при помощи, например, такого генератора: [0 for j in range(m)]. Вложив один генератор в другой, получим вложенные генераторы:

[[0 for j in range(m)] for i in range(n)]

Но если число 0 заменить на некоторое выражение, зависящее от i (номер строки) и j (номер столбца), то можно получить список, заполненный по некоторой формуле.

Например, пусть нужно задать следующий массив (для удобства добавлены дополнительные пробелы между элементами):

0 0 0 0 0 0

0 1 2 3 4 5

0 2 4 6 8 10

0 3 6 9 12 15

0 4 8 12 16 20

В этом массиве n = 5 строк, m = 6 столбцов, и элемент в строке i и столбце j вычисляется по формуле: a[i][j] = i * j.

Для создания такого массива можно использовать генератор:

[[i * j for j in range(m)] for i in range(n)]

2. Работа с файлами

Python позволяет работать с файлами. В качестве примера будем работать с текстовым файлом.

Прежде чем начать работать с файлом необходимо его открыть, для этого есть метод open():

 f = open('test.txt', 'r')

Мы присваиваем переменной f результат выполнения команды открытия файла, параметры этой команды - адрес файла, путь до места где он расположен на компьютере с названием файла(test) и его расширением(.txt), говорящим что он текстовый. Второй параметр 'r' означает, что файл открыт для чтения, то есть мы не сможем вносить изменения в этот файл до тех пор, пока он открыт только для чтения.

У метода open() существует много аргументов:

●    'r' - открытие на чтение(значение по умолчанию)

●    'w' - открытие на запись, содержимое файла удаляется и перезаписывается заново, если файла не существует, создается новый

●    'x' - открытие на запись, только если файла не существует

●    'a' - открытие на дозапись, информация добавляется в конец файла

●    'b' - открытие файла в двоичном виде

●    't' - открытие в текстовом режиме(значение по умолчанию)

●    '+' - открытие на чтение и запись

Возможно сочетание режимов, например 'rb', чтение в бинарном виде, по умолчанию установлен 'rt'.

После открытия файла можно прочитать из него информацию, благодаря методу read():

>>> f = open('test.txt')

>>> f.read()

'Python is\nAwesome!.\n\n'

Также можно прочитать и вывести файл построчно:

>>> f = open('test.txt')

>>> for line in f:

      print(line)

'Python is\n'

'\n'

'Awesome\n'

'\n'

Записать информацию в файл можно открыв файл на запись:

>>> f = open('test.txt', 'w')

>>> for i in range(1, 4):

       f.write(i + '\n')

1

2

3

По окончании работы с файлом его необходимо закрыть, используя метод close():

>>> f.close()

3. Исключения

Исключения - это объекты, которые выводятся на экран как строка с текстом сообщения. Обычно они вызываются функциями и методами при возникновении ошибок или других важных событий при выполнении программы. Существует множество видов исключений, вот только некоторые из них:

●    BaseException - базовое исключение, порождающее все остальные

●    SystemExit - системное исключение, порождаемое функцией sys.exit при выходе из программы

●    KeyboardInterrupt - системное исключение, порождаемое пользовательским выходом из программы с помощью сочетания клавиш

●    ArithmeticError - арифметическая ошибка

●    AssertionError - выражение assert ложно

●    ImportError - ошибка импорта модуля или его атрибута

●    IndexError - индекс не входит в диапазон элементов

●    NameError - не найдено переменных с таким именем

●    SyntaxError - ошибка синтаксиса

●    TypeError - операция к объекту несоответствующего типа

●    и т.д.

Исключения позволяют понять какого рода ошибки возникают при выполнении программ. Иногда необходимо отловить ошибку конкретного вида или пропустить ее, не обращая внимания. Для этого существуют обработчики исключений.

Конструкция try...except

Конструкция try ... except позволяет перехватывать исключения, полный синтаксис выглядит следующим образом:

try:

  try_suite

except exception_group1 as variable1:

  except_suite1

...

except exception_groupN as variableN:

   except_suiteN

else:

  else_suite

finally:

  finally_suite

Минимально необходимая конструкция должна состоять из операторов try и except. Все остальные операторы являются необязательными.

После оператора try записывается вложенная инструкция того, что должна выполнить программа. В случае успешного выполнения, выполнится инструкция после оператора else, если такой есть в программе. Если присутствует оператор finally, тогда вложенная в него инструкция выполняется всегда и в последнюю очередь. Если во время выполнения инструкции try_suite возникает исключение, то оно проверяется на соответствие операторами except. Exception_group может быть как единственным видом исключений, так и кортежем нескольких. Приставка as variable1 является необязательной и служит для записи исключения в переменную variable1, чтобы затем к нему можно было обратиться в инструкции except_suite1. Инструкция except_suite1 будет выполняться, когда при выполнении try_suite возникнет исключение, соответствующее exception_group1.

При работе с исключениями необходимо помнить, что существует определенная иерархия в структуре исключений: 

Фрагмент структуры исключений

При отлове конкретных ошибок необходимо указывать их вид в первую очередь:

try:

   try_suite

except IndexError:

   except_suite1

except Exception:

   except_suite2

В данном примере нам необходимо отловить ошибку индекса и выполнить соответствующее действие, а при возникновении любой другой - иное действие. В таком случае, исходя из структуры исключений, мы должны прописать конкретный вид исключения выше, чем более общий. Потому, что при возникновении IndexError, инструкция except_suite2 будет также исполнена, так как IndexError является частью вида Exception. И, если, нам необходимо, чтобы except_suite1 выполнился раньше except_suite2, то мы указываем более частный вид исключения.

Исключения могут быть полезными, однако есть и несколько недостатков применения исключений. Например, они используются, когда природа ошибки заранее известна, а это частный случай. Гораздо важнее, если бы программа могла сигнализировать о заранее неизвестной ошибке. Помимо этого, бывает невозможно определить какая строка кода будет выполняться после перехвата исключения. В любом случае, перехват исключений не является необходимым инструментом при написании кода, каждый может применять его на свое усмотрение.

Практические задания:

Задание 1:

Считать матрицу целых чисел размером 5*10 из файла input.txt

Транспонировать матрицу.

Записать полученную новую матрицу в новый файл

Задание 2:

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

Программа получает на вход размеры массива n и m, затем n строк по m чисел в каждой.

В результате выводится индексы первого вхождения максимального элемента

Задание 3:

Дано число n. Создайте массив размером n×n и заполните его по следующему правилу: 

На главной диагонали должны быть записаны числа 0. На двух диагоналях, прилегающих к главной, числа 1. На следующих двух диагоналях числа 2, и т. д.

Задание 4:

В теории игр существуют антагонистические игры двух лиц, характеризующиеся множеством X стратегий первого игрока, множеством Y стратегий второго игрока и функцией выигрыша K(x, y) (x из X, y из Y). Если множества стратегий X и Y конечны, то игра называется матричной, так как функцию выигрыша K в этом случае удобно задавать матрицей.

Рассмотрим матричную игру, в которой X = {1,…,n}, Y = {1,…,m}. Матрицу выигрышей обозначим символом K. Нижним значением игры назовем число maxi=1..n minj=1..m Kij . Верхним значением игры назовем число minj=1..m maxi=1..n Kij. Игра, у которой нижнее и верхнее значение совпадают, называется игрой с седловой точкой.

Задана матрица выигрышей K для некоторой матричной игры. Найдите ее верхнее и нижнее значение.

Входные данные. Первая строка входного файла INPUT.TXT содержит целые числа n и m (1 ≤ n,m ≤ 100). Далее следуют n строк по m чисел в каждой. j-ое число i-ой строки равно Kij . (-100 ≤ Kij ≤ 100) 

Выходные данные. В выходной файл OUTPUT.TXT выведите нижнее и верхнее значение игры через пробел.

Задание 5:

На днях Иван у себя в прихожей (в форме квадрата 4х4) выложил кафель, состоящий из квадратных черных и белых плиток. Иван переживает, что узор из плиток, который у него получился не симпатичный. Симпатичным узором считается тот, который не содержит в себе квадрата 2х2, состоящего из плиток одного цвета.

Примеры возможных узоров:

По заданному расположению плиток в прихожей Ивана требуется определить: является ли узор симпатичным.

Входной файл INPUT.TXT содержит 4 строки по 4 символа «W» или «B» в каждой, описывающие узор из плиток. Где «W» - белая плитка, «B» - черная.

В выходной файл OUTPUT.TXT выведите «Yes», если узор является симпатичным и «No» в противном случае.

Задание 6:

Судоку размера n называется квадрат со стороной n2, разделенный на n2 средних квадратов со стороной n, каждый из которых разделен на n2 маленьких квадратов. В каждом маленьком квадрате записано число от 1 до n2.

Судоку называется правильным, если в каждом столбце, каждой строке и каждом среднем квадрате встречаются все числа от 1 до n2.

Недавно Вася нарисовал Судоку размера n. Ваша задача – помочь ему определить правильный ли он.

Входные данные

В первой строке входного файла INPUT.TXT содержится число n (1 ≤ n ≤ 10). В следующих n2 строках содержится по n2 чисел, задающих нарисованный Васей Судоку.

Все числа во входном файле натуральные и не превосходят 100 по модулю.

Выходные данные

Если Судоку правильный, то выведите в выходной файл OUTPUT.TXT слово «Correct», иначе выведите «Incorrect».

Домашнее задание=контрольная работа

Создайте простую консольную игру 

Примеры:

  1. Камень-ножницы-бумага
  2. Крестики-нолики
  3. Текстовый квест

Критерии:

  • Есть оператор условия (1б)
  • Есть цикл (1б)
  • Есть функция (1б)
  • Есть коллекция (список/кортеж/словарь) (1б)
  • Есть работа с файлом (запись/чтение) (1б)
  • Есть исключение (1б)
  • Пользователь выполняет минимум 3 действия (1б)
  • Есть инструкция по запуску (можно в консоли, а не отдельным файлом) (1б)
  • Оригинальность (2б)

Завершить и продолжить  
Обсуждение

0 Комментарии