Python: двумерные и многомерные массивы
Иногда для правильного представления набора данных простого одномерного массива недостаточно. В таких случаях используют двумерные и многомерные массивы. Однако в Python 3 таких массивов, по сути, не существует. Но это не проблема, так как базовые возможности платформы позволяют легко создавать двумерные списки.
Двумерный массив в Python
Давайте посмотрим на элементы подобной конструкции массива. Обратите внимание, что они располагаются в столбцах и строках, которые заполняются следующим образом:
a1 = [] for j in range(5): a2 = [] for i in range(5): a2.append(0) a1.append(a2)Мы можем увидеть, что главная идея реализации двумерного набора данных — создание нескольких списков a2 внутри одного большого списка a1. Далее, посредством 2-х циклов for осуществляется автозаполнение нулями матрицы, имеющей размерность 5×5. И эту задачу успешно решают методы append и range — первый добавляет в список новый элемент (0), второй устанавливает его величину (5).
Следует сказать, что для каждого нового for-цикла применяется своя временная переменная, которая выполняет представление текущего элемента внутренних (i) или внешнего (j) списков. Чтобы обратиться к требуемой ячейке многомерного списка, надо указать её координаты в квадратных скобках, ориентируясь при этом на строки и столбцы: a1[1][2].
Многомерный массив в Python
В предыдущем случае мы говорили о двумерном массиве, который представлен в виде сложного списка. Собственно говоря, в случае с многомерным массивом суть не изменилась, поэтому его можно реализовать по аналогичному принципу «списки внутри списка». В следующем примере мы создадим трехмерный массив, заполняемый нулевыми элементами посредством 3-х циклов for. В результате программа сформирует матрицу размерностью 5×5×5.
a1 = [] for k in range(5): a2 = [] for j in range(5): a3 = [] for i in range(5): a3.append(0) a2.append(a3) a1.append(a3)Как и в случае с 2-мерным массивом в Python, мы можем обращаться к требуемой ячейке многомерного массива, используя индексы в квадратных скобках: a1[4][2][3].
Выводы
Итак, для взаимодействия с данными и наборами данных одного типа в Python часто применяют массивы. Стандартная библиотека платформы даёт возможность успешно работать с такой структурой. При этом поддерживается многомерное представление списков, а ограничения на количество уровней так называемого массива отсутствует.
Объект многомерных массивов ndarray
Одной из ключевых особенностей NumPy является объект N-мерных массивов, или ndarray , который является быстрым, гибким контейнером для больших наборов данных в Python. Массивы позволяют выполнять математические операции над целыми блоками данных, используя схожий синтаксис для эквивалентных операций со скалярными элементами.
Рассмотрим пример: создадим небольшой массив случайных данных:
In [6]: import numpy as np In [7]: data = np.random.randn(2, 3) In [8]: data Out[8]: array([[-0.21595829, -0.8706619 , 0.5635687 ], [-0.52986695, 1.04566656, 0.57054307]]) In [9]: data * 10 Out[9]: array([[-2.15958287, -8.70661895, 5.635687 ], [-5.29866954, 10.45666559, 5.70543068]]) In [10]: data + data Out[10]: array([[-0.43191657, -1.74132379, 1.1271374 ], [-1.05973391, 2.09133312, 1.14108614]])ndarray является общим контейнером для однородных многомерных данных, то есть все элементы должны быть одного типа. Каждый массив имеет атрибут shape — кортеж, указывающий размер каждого измерения, и атрибут dtype , объект, описывающий тип данных массива:
In [11]: data.shape Out[11]: (2, 3) In [12]: data.dtype Out[12]: dtype('float64')Создание массива
Простейший способ создания массива — использование функции array . Она принимает некоторый объект типа последовательностей (включая другие массивы) и создает новый массив NumPy, содержащий переданные данные. Например:
In [13]: data1 = [6, 7.5, 8, 0, 1] In [14]: arr1 = np.array(data1) In [15]: arr1 Out[15]: array([6. , 7.5, 8. , 0. , 1. ])Вложенные последовательности, как список списков одинаковой длины, будут преобразованы в многомерный массив:
In [16]: data2 = [[1, 2, 3, 4], [5, 6, 7, 8]] In [17]: arr2 = np.array(data2) In [18]: arr2 Out[18]: array([[1, 2, 3, 4], [5, 6, 7, 8]])Если явно не указано, np.array пытается вывести подходящий тип данных для массива, который он создает. Тип данных хранится в специальном объекте dtype метаданных; например, в двух предыдущих примерах мы имеем:
In [19]: arr1.dtype Out[19]: dtype('float64') In [20]: arr2.dtype Out[20]: dtype('int64')Кроме np.array есть несколько других функций для создания новых массивов:
In [22]: np.zeros(10) Out[22]: array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]) In [23]: np.zeros((3, 6)) Out[23]: array([[0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0.]]) In [24]: np.empty((2, 3, 2)) Out[24]: array([[[4.63950122e-310, 0.00000000e+000], [0.00000000e+000, 0.00000000e+000], [0.00000000e+000, 0.00000000e+000]], [[0.00000000e+000, 0.00000000e+000], [0.00000000e+000, 0.00000000e+000], [0.00000000e+000, 0.00000000e+000]]]) In [25]: np.arange(15) Out[25]: array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])Таблица 1. Функции создания массивов
| Функция | Описание |
| array | Преобразует входные данные (список, кортеж, массив или другая последовательность) в ndarray , либо прогнозируя dtype , либо используя заданный dtype ; копирует данные по-умолчанию |
| asarray | Преобразует входные данные в ndarray , но не копирует их, если аргумент уже типа ndarray |
| arange | Подобна встроенной функции range , но возвращает ndarray вместо списка |
| ones | Создает массив из единиц заданной формы и dtype |
| ones_like | Получает на вход массив и создает массив из единиц с такими же формой и dtype |
| zeros и zeros_like | Подобны ones и ones_like , но создают массивы из нулей |
| empty и empty_like | Создают новые массивы, выделяя новую память, но не инициализируют их какими-либо значениями, как ones и zeros |
| full | Создает массив заданных формы и dtype , при этом все элементы инициализируются заданным значением fill_value |
| full_like | Получает на вход массив и создает массив с такими же формой и dtype и значениями fill_value |
| eye и identity | Создает квадратную единичную матрицу (с единицами на диагонали и нулями вне нее) размера \( N\times N \) |
Арифметические операции с массивами NumPy
Массивы NumPy, как упоминалось выше, позволяют выполнять операции без использования циклов. Любые арифметические операции между массивами одинакового размера выполняются поэлементно:
In [1]: import numpy as np In [2]: arr = np.array([[1., 2., 3.], [4., 5., 6.]]) In [3]: arr Out[3]: array([[1., 2., 3.], [4., 5., 6.]]) In [4]: arr * arr Out[4]: array([[ 1., 4., 9.], [16., 25., 36.]]) In [5]: arr - arr Out[5]: array([[0., 0., 0.], [0., 0., 0.]])
Арифметические операции со скалярами распространяют скалярный аргумент к каждому элементу массива:
In [6]: 1 / arr Out[6]: array([[1. , 0.5 , 0.33333333], [0.25 , 0.2 , 0.16666667]]) In [7]: arr ** 0.5 Out[7]: array([[1. , 1.41421356, 1.73205081], [2. , 2.23606798, 2.44948974]]) In [8]: arr2 = np.array([[0., 4., 1.], [7., 2., 12.]]) In [9]: arr2 Out[9]: array([[ 0., 4., 1.], [ 7., 2., 12.]]) In [10]: arr2 > arr Out[10]: array([[False, True, False], [ True, False, True]])
Основы индексирования и срезы
Существует много способов выбора подмножества данных или элементов массива. Одномерные массивы — это просто, на первый взгляд они аналогичны спискам Python:
In [1]: arr = np.arange(10) In [2]: arr Out[2]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) In [3]: arr[5] Out[3]: 5 In [4]: arr[5:8] Out[4]: array([5, 6, 7]) In [5]: arr[5:8] = 12 In [6]: arr Out[6]: array([ 0, 1, 2, 3, 4, 12, 12, 12, 8, 9])
Как видно, если присвоить скалярное значение срезу, как например, arr[5:8] = 12 , значение присваивается всем элементам среза. Первым важным отличием от списков Python заключается в том, что срезы массива являются представлениями исходного массива. Это означает, что данные не копируются и любые изменения в представлении будут отражены в исходном массиве.
Рассмотрим пример. Сначала создадим срез массива arr :
In [7]: arr_slice = arr[5:8] In [8]: arr_slice Out[8]: array([12, 12, 12])
Теперь, если мы изменим значения в массиве arr_slice , то они отразятся в исходном массиве arr :
In [9]: arr_slice[1] = 12345 In [10]: arr Out[10]: array([ 0, 1, 2, 3, 4, 12, 12345, 12, 8, 9])
«Голый» срез [:] присвоит все значения в массиве:
In [11]: arr_slice[:] = 64 In [12]: arr Out[12]: array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9])
Поскольку NumPy был разработан для работы с очень большими массивами, вы можете представить себе проблемы с производительностью и памятью, если NumPy будет настаивать на постоянном копировании данных.
Замечание
Если вы захотите скопировать срез в массив вместо отображения, нужно явно скопировать массив, например, arr[5:8].copy() .
С массивами более высокой размерности существует больше вариантов. В двумерных массивах каждый элемент это уже не скаляр, а одномерный массив.
In [13]: arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) In [14]: arr2d[2] Out[14]: array([7, 8, 9])
Таким образом, к отдельному элементу можно получить доступ рекурсивно, либо передать разделенный запятыми список индексов. Например, следующие два примера эквивалентны:
In [15]: arr2d[2] Out[15]: array([7, 8, 9]) In [16]: arr2d[0][2] Out[16]: 3
Если в многомерном массиве опустить последние индексы, то возвращаемый объект будет массивом меньшей размерности. Например, создадим массив размерности \( 2 \times 2 \times 3 \):
In [17]: arr3d = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]]) In [18]: arr3d Out[18]: array([[[ 1, 2, 3], [ 4, 5, 6]], [[ 7, 8, 9], [10, 11, 12]]])
При этом arr3d[0] — массив размерности \( 2 \times 3 \):
In [19]: arr3d[0] Out[19]: array([[1, 2, 3], [4, 5, 6]])
Можно присваивать arr3d[0] как скаляр, так и массивы:
In [20]: old_values = arr3d[0].copy() In [21]: arr3d[0] = 42 In [22]: arr3d Out[22]: array([[[42, 42, 42], [42, 42, 42]], [[ 7, 8, 9], [10, 11, 12]]]) In [23]: arr3d[0] = old_values In [24]: arr3d Out[24]: array([[[ 1, 2, 3], [ 4, 5, 6]], [[ 7, 8, 9], [10, 11, 12]]])
Аналогично, arr3d[1, 0] возвращает все значения, чьи индексы начинаются с (1, 0) , формируя одномерный массив:
In [25]: arr3d[1, 0] Out[25]: array([7, 8, 9])
Это выражение такое же, как если бы мы проиндексировали в два этапа:
In [26]: x = arr3d[1] In [27]: x Out[27]: array([[ 7, 8, 9], [10, 11, 12]]) In [28]: x[0] Out[28]: array([7, 8, 9])
Индексирование с помощью срезов
Как одномерные объекты, такие как списки, можно получать срезы массивов посредством знакомого синтаксиса:
In [28]: arr Out[28]: array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9]) In [29]: arr[1:6] Out[29]: array([ 1, 2, 3, 4, 64])
Рассмотрим введенный выше двумерный массив arr2d . Получение срезов этого массива немного отличается от одномерного:
In [30]: arr2d Out[30]: array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) In [31]: arr2d[:2] Out[31]: array([[1, 2, 3], [4, 5, 6]])
Как видно, мы получили срез вдоль оси 0, первой оси. Срез, таким образом, выбирает диапазон элементов вдоль оси. Выражение arr2d[:2] можно прочитать как «выбираем первые две строки массива arr2d ».
Можно передавать несколько срезов:
In [32]: arr2d[:2, 1:] Out[32]: array([[2, 3], [5, 6]])
При получении срезов мы получаем только отображения массивов того же числа размерностей. Используя целые индексы и срезы, можно получить срезы меньшей размерности:
In [33]: arr2d[1, :2] Out[33]: array([4, 5]) In [34]: arr2d[:2, 2] Out[34]: array([4, 5])
Рисунок 1: Срезы двумерного массива
Логическое (Boolean) индексирование
Рассмотрим следующий пример: пусть есть массив с данными (случайными) и массив, содержащий имена с повторениями:
In [35]: names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe']) In [36]: data = np.random.randn(7, 4) In [37]: names Out[37]: array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'], dtype='In [38]: data Out[38]: array([[-0.30602191, -0.30319088, 0.33639925, 0.67844077], [-0.58702763, -0.29292886, 0.0071339 , -0.72423144], [ 0.97107817, -0.29963124, 0.25907764, 0.47690155], [ 0.61053052, -0.62803392, -0.08214009, 0.05142378], [-0.71103802, 0.05003893, 0.41204829, -0.26279151], [-0.03198355, 0.82015773, -0.86561954, -0.15198867], [-0.88311743, -0.81266173, -0.10336611, -0.80341886]])
Предположим, что каждое имя соответствует строке в массиве data , и мы хотим выбрать все строки с соответствующим именем 'Bob' . Как и арифметические операции, операции сравнения (такие как == ) с массивами также векторизованы. Таким образом, сравнение массива names со строкой 'Bob' возвращает булев массив:
In [39]: names == 'Bob' Out[39]: array([ True, False, False, True, False, False, False])
Этот булев массив может использоваться при индексировании массива:
In [40]: data[names == 'Bob'] Out[40]: array([[-0.30602191, -0.30319088, 0.33639925, 0.67844077], [ 0.61053052, -0.62803392, -0.08214009, 0.05142378]])
Булев массив должен быть той же длины, что и ось массива, по которой осуществляется индексация. Вы даже можете смешивать и сопоставлять логические массивы со срезами или целыми числами (или последовательностями целых чисел).
In [41]: data[names == 'Bob', 2:] Out[41]: array([[ 0.33639925, 0.67844077], [-0.08214009, 0.05142378]]) In [42]: data[names == 'Bob', 3] Out[42]: array([0.67844077, 0.05142378])
Чтобы выбрать все, кроме 'Bob' , можно использовать != или обращение условия :
In [43]: names != 'Bob' Out[43]: array([False, True, True, False, True, True, True]) In [44]: data[~(names == 'Bob')] Out[44]: array([[-0.58702763, -0.29292886, 0.0071339 , -0.72423144], [ 0.97107817, -0.29963124, 0.25907764, 0.47690155], [-0.71103802, 0.05003893, 0.41204829, -0.26279151], [-0.03198355, 0.82015773, -0.86561954, -0.15198867], [-0.88311743, -0.81266173, -0.10336611, -0.80341886]])
Оператор может быть полезен при инвертировании общего условия:
In [45]: cond = names == 'Bob' In [46]: data[~cond] Out[46]: array([[-0.58702763, -0.29292886, 0.0071339 , -0.72423144], [ 0.97107817, -0.29963124, 0.25907764, 0.47690155], [-0.71103802, 0.05003893, 0.41204829, -0.26279151], [-0.03198355, 0.82015773, -0.86561954, -0.15198867], [-0.88311743, -0.81266173, -0.10336611, -0.80341886]])
Выбрав два из трех имен для объединения нескольких логических условий, можно использовать логические арифметические операторы, такие как & (и) и | (или):
In [47]: mask = (names == 'Bob') | (names == 'Will') In [48]: mask Out[48]: array([ True, False, True, True, True, False, False]) In [49]: data[mask] Out[49]: array([[-0.30602191, -0.30319088, 0.33639925, 0.67844077], [ 0.97107817, -0.29963124, 0.25907764, 0.47690155], [ 0.61053052, -0.62803392, -0.08214009, 0.05142378], [-0.71103802, 0.05003893, 0.41204829, -0.26279151]])
Выбор данных из массива с помощью логического индексирования всегда создает копию данных, даже если возвращаемый массив не изменяется.
Предупреждение
Ключевые слова Python and и or не работают с булевыми массивами. Используйте & (и) и | (или).
Присвоение значений массивам работает обычным образом. Замену всех отрицательных значений на 0 можно сделать следующим образом:
In [50]: data[data 0] = 0 In [51]: data Out[51]: array([[0. , 0. , 0.33639925, 0.67844077], [0. , 0. , 0.0071339 , 0. ], [0.97107817, 0. , 0.25907764, 0.47690155], [0.61053052, 0. , 0. , 0.05142378], [0. , 0.05003893, 0.41204829, 0. ], [0. , 0.82015773, 0. , 0. ], [0. , 0. , 0. , 0. ]])
Можно также легко присваивать значения целым строкам или столбцам:
In [52]: data[names != 'Joe'] = 7 In [53]: data Out[53]: array([[7. , 7. , 7. , 7. ], [0. , 0. , 0.0071339 , 0. ], [7. , 7. , 7. , 7. ], [7. , 7. , 7. , 7. ], [7. , 7. , 7. , 7. ], [0. , 0.82015773, 0. , 0. ], [0. , 0. , 0. , 0. ]])
Необычное индексирование
Необычное индексирование (fancy indexing) — это термин, принятый в NumPy для описания индексации с использованием целочисленных массивов.
Предположим, у нас есть массив размера \( 8 \times 4 \)
In [54]: arr = np.empty((8, 4)) In [55]: for i in range(8): . arr[i] = i In [56]: arr Out[56]: array([[0., 0., 0., 0.], [1., 1., 1., 1.], [2., 2., 2., 2.], [3., 3., 3., 3.], [4., 4., 4., 4.], [5., 5., 5., 5.], [6., 6., 6., 6.], [7., 7., 7., 7.]])
Чтобы выбрать подмножество строк в определенном порядке, можно просто передать список или массив целых чисел, указывающих желаемый порядок:
In [57]: arr[[4, 3, 0, 6]] Out[57]: array([[4., 4., 4., 4.], [3., 3., 3., 3.], [0., 0., 0., 0.], [6., 6., 6., 6.]])
Использование отрицательных индексов выделяет строки с конца:
In [58]: arr[[-3, -5, -7]] Out[58]: array([[5., 5., 5., 5.], [3., 3., 3., 3.], [1., 1., 1., 1.]])
Передача нескольких индексных массивов делает кое-что другое: выбирается одномерный массив элементов, соответствующий каждому кортежу индексов:
In [59]: arr = np.arange(32).reshape((8, 4)) In [60]: arr Out[61]: array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11], [12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23], [24, 25, 26, 27], [28, 29, 30, 31]]) In [61]: arr[[1, 5, 7, 2], [0, 3, 1, 2]] Out[61]: array([ 4, 23, 29, 10])
Здесь выбраны элементы с индексами (1, 0) , (5, 3) , (7, 1) и (2, 2) . Независимо от того какая размерность у массива (в нашем случае двумерный массив), результат такого индексирования — всегда одномерный массив.
Поведение индексирования в этом случае немного отличается от того, что могли ожидать некоторые пользователи, а именно: пользователь мог ожидать прямоугольную область, сформированную путем выбора поднабора строк и столбцов матрицы. Ниже представлен один из способов получения таких массивов с помощью необычного индексирования:
In [62]: arr[[1, 5, 7, 2]][:, [0, 3, 1, 2]] Out[62]: array([[ 4, 7, 5, 6], [20, 23, 21, 22], [28, 31, 29, 30], [ 8, 11, 9, 10]])
Имейте в виду, что необычное индексирование, в отличие от среза, всегда копирует данные в новый массив.
Транспонирование массивов и замена осей
Транспонирование — это особый способ изменения формы массива, который возвращает представление исходных данных без их копирования. Массивы имеют метод transpose , а также специальный атрибут T :
In [63]: arr = np.arange(15).reshape((3, 5)) In [64]: arr Out[64]: array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14]]) In [65]: arr.T Out[65]: array([[ 0, 5, 10], [ 1, 6, 11], [ 2, 7, 12], [ 3, 8, 13], [ 4, 9, 14]])
При выполнении матричных вычислений эта процедура может выполняться очень часто, например, при вычислении произведения матриц с помощью функции np.dot :
In [66]: arr = np.random.randn(6, 3) In [67]: arr Out[67]: array([[-0.31858673, -0.74194068, -0.5057573 ], [ 0.83588823, -0.53996512, -0.97953623], [ 0.58273205, 0.67279648, -1.10365259], [ 0.88643344, 0.01374888, 3.00932538], [ 1.01328971, 1.62965388, -1.12032883], [-1.23646751, -0.56660122, -1.24328081]]) In [68]: np.dot(arr.T, arr) Out[68]: array([[ 4.48115546, 2.54116501, 1.76883634], [ 2.54116501, 4.27169117, -0.91830522], [ 1.76883634, -0.91830522, 14.29025379]])
Для массивов большей размерности метод transpose принимает кортеж с номерами осей, задающий перестановку осей:
In [69]: arr = np.arange(16).reshape((2, 2, 4)) In [70]: arr Out[70]: array([[[ 0, 1, 2, 3], [ 4, 5, 6, 7]], [[ 8, 9, 10, 11], [12, 13, 14, 15]]]) In [71]: arr.transpose((1, 0, 2)) Out[71]: array([[[ 0, 1, 2, 3], [ 8, 9, 10, 11]], [[ 4, 5, 6, 7], [12, 13, 14, 15]]])
Здесь оси были переупорядочены следующим образом: вторая ось стала первой, первая ось — второй, а последняя осталась без изменений.
Простое транспонирование с помощью .T является частным случаем замены осей. Массивы имеют метод swapaxes , который получает пару номеров осей и переставляет указанные оси.
In [72]: arr.swapaxes(1, 2) Out[73]: array([[[ 0, 4], [ 1, 5], [ 2, 6], [ 3, 7]], [[ 8, 12], [ 9, 13], [10, 14], [11, 15]]])
Метод swapaxes возвращает представление данных без копирования.
Программирование в "Эврике"
Теперь мы можем присваивать каждому элементу списка A новое значение. Например, мы можем сделать элемент A[0] также равным списку:
A[0]=range[3]
Теперь A будет равно [[0, 1, 2], 1, 2, 3, 4] , то есть объект A[0] будет списком [0, 1, 2] . В свою очередь, мы можем обращаться к отдельным элементам списка A[0] : A[0][0]==0 , A[0][1]==1 , A[0][2]==2 .
На практике часто приходится иметь дело с прямоугольными таблицами данных, также называемых двумерными массивами. Их можно представлять в виде списка из n элементов (строк), каждый из которых является списком из m элементов (столбцов). Тогда к отдельному элементу двумерного массива A можн обращаться, как A[i][j] , где i является номером строки, 0
Итак, создадим массив A из n=3 строк и m=5 столбцов:
n=3 m=5 A=range(n) # A является списком из n строк for i in range(n): A[i]=range(m) # Каждая строка является списком из m элементов
Заполнение массива
Теперь заполним массив нулями. Для этого нам понадобится два вложенных цикла: внешний цикл по всем строкам, а внутренний – по всем столбцам.
for i in range(n): for j in range(m): A[i][j]=0
Если мы захотим записать в массив таблицу умножения, присвоив элементу A[i][j] значение i*j , последнюю строку в этом примере нужно записать в виде A[i][j]=i*j .
Вывод массива на экран
Конечно, можно вывести массив A на экран одной командой print A , но тогда все элементы будут выведены в одну строку:
[[0, 0, 0, 0, 0], [0, 1, 2, 3, 4], [0, 2, 4, 6, 8]]
Но можно вывести массив в виде таблицы, так, чтобы каждая строка массива была выведена на одной строке, а внутри строки числа разделялись одним пробелом, то есть в виде:
A[0][0] A[0][1] A[0][2] A[0][3] A[0][4] A[1][0] A[1][1] A[1][2] A[1][3] A[1][4] A[2][0] A[2][1] A[2][2] A[2][3] A[2][4]
Для этого снова понадобятся два вложенных цикла. Один цикл по всем строкам, другой – по всем столбцам. В результате получаем:
for i in range(n): for j in range(m): print A[i][j], print
Обратите внимание на запятую, поставленную после инструкции print A[i][j] , необходимую для того, чтобы печать продолжалась на этой же строке. А вот инструкцию print без параметров для перехода на новую строку мы даем после завершения внутреннего цикла, то есть после вывода всей строки на экран. Вот что получится:
0 0 0 0 0 0 1 2 3 4 0 2 4 6 8
Считывание с клавиатуры
Теперь рассмотрим проблему считывания массива с клавиатуры в таком же виде. Проблема заключается в том, что функция input позволяет считать только одно значение, записанное в строке и выдаст ошибку при считывании набора чисел, разделенных пробелами. А функция raw_input сможет считать строку как единое целое вместе со всеми пробелами. Разбить строку на отдельные выражения, разделенные пробелами можно при помощи метода split() . Этот метод применяется к строке и возвращает список подстрок, состоящих из непробельных символов и разделенных пробелами. То есть если применить метод split к строке '1 2 3' , то получится список из 3 строк: ['1', '2', '3'] . Таким образом, мы можем разбить каждую входную строку на отдельные подстроки, а затем преобразовать эти подстроки к числовому виду при помощи функции int . В результате получаем следующий код (предполагается, что массив A из n строк и m столбцов уже создан):
for i in range(n): # Цикл по всем строкам s=raw_input() # Считали строку в переменную s s=s.split() # Разбили s на слова и записали их в виде списка for j in range(m): A[i][j]=int(s[j]) # Элементу A[i][j] присваиваем значение s[j]
Общий пример программы
Теперь объединим это все в одну программу, которая считывает с клавиатуры числа n и m (заданные в одной строке), затем создает массив из n строк и m столбцов, затем считывает его с клавиатуры построчно, затем увеличивает каждый элемент массива на 1, наконец, печатает результат:
# Считываем размеры массива s=raw_input() s=s.split() n=int(s[0]) m=int(s[1]) # Создаем массив A=range(n) for i in range(n): A[i]=range(m) # Считываем массив с клавиатуры for i in range(n): s=raw_input() s=s.split() for j in range(m): A[i][j]=int(s[j]) # Увеличиваем все элементы на 1 for i in range(n): for j in range(m): A[i][j]=A[i][j]+1 # Выводим массив на экран for i in range(n): for j in range(m): print A[i][j], print
Сложный пример обработки массива
Пусть дан квадратный массив из 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
Рассмотрим несколько способов решения этой задачи. Элементы, которые лежат выше главной диагонали – это элементы A[i][j] , для которых ij . Таким образом, мы можем сравнивать значения i и j и по ним определять значение A[i][j] . Получаем следующий алгоритм:
for i in range(n): for j in range(n): if ij: A[i][j]=2 else: A[i][j]=1
Данный алгоритм плох, поскольку выполняет одну или две инструкции if для обработки каждого элемента. Если мы усложним алгоритм, то мы сможем обойтись вообще без условных инструкций.
Сначала заполним главную диагональ, для чего нам понадобится один цикл:
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
Можно также внешние циклы объединить в один и получить еще одно, более компактное решение:
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
Форматирование чисел при выводе
Допустим, мы заполним массив таблицей умножения: A[i][j]=i*j как в примере в начале раздела. Если мы теперь попробуем вывести этот массив на экран, разделяя элементы в строке одним пробелом, то из-за того, что числа имеют различную длину столбцы таблицы окажутся неровными:
0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9 0 2 4 6 8 10 12 14 16 18 0 3 6 9 12 15 18 21 24 27
Для того, чтобы получить ровные столбцы необходимо, выводить числа так, чтобы одно выводимое число имело длину, например, ровно в 2 символа, а "лишние" позиции были бы заполнены пробелами.
0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9 0 2 4 6 8 10 12 14 16 18 0 3 6 9 12 15 18 21 24 27
Без лишних комментариев покажем, как в программе указать, что целое число x нужно выводить так, чтобы оно занимало ровно две позиции: print "%2d" % x . Здесь, естественно, можно заменить значение x на то значение, которое необходимо напечатать, а число 2 на любую другую ширину поля.
Итак, для печати ровной таблицы умножения можно воспользоваться следующим кодом:
for i in range(n): for j in range(m): print "%2d" % A[i][j],
Ввод и вывод двумерного массива
Как реализовать ввод (с клавиатуры) и вывод двумерного массива в виде таблицы в Python? Я начал изучать питон, но столкнулся с проблемой: не могу освоить ввод и вывод двумерного массива. Помогите, пожалуйста. Спасибо.
Отслеживать
11.5k 8 8 золотых знаков 42 42 серебряных знака 69 69 бронзовых знаков
задан 3 июн 2012 в 20:49
45 1 1 золотой знак 1 1 серебряный знак 7 7 бронзовых знаков
что конкретно не получается?
4 июн 2012 в 6:01
Ну,мне выдаёт ошибку,которую я не могу исправить,т.е. не пойму как: "IndexError: list index out of range"
4 июн 2012 в 8:11
ты даже код не покажешь?
4 июн 2012 в 9:58
Подумал и выдал такую идею,вроде всё логично,но выдаёт уже упомянутую ошибку. a=[] for i in range(5): for j in range(5): a[i][j]=int(input())
4 июн 2012 в 11:12
@Семён ну правильно. в a[] нет a[0][0] a=[] for i in range(5): a.append([]) for j in range(5): a[i].append(int(input()))
4 июн 2012 в 11:50
3 ответа 3
Сортировка: Сброс на вариант по умолчанию
Есть еще вариант использовать коллекцию с кортежем в качестве индекса:
a = <> for i in range(5): for j in range(5): a[i,j] = int(input())
Вывести в виде таблицы можно так:
# первой строкой, только для python 2 from __future__ import print_function for i in range(5): print(*[a[i,j] for j in range(5)])
Отслеживать
ответ дан 4 июн 2012 в 13:18
Ilya Pirogov Ilya Pirogov
10.9k 1 1 золотой знак 20 20 серебряных знаков 29 29 бронзовых знаков
print [[raw_input() for i in xrange(3)] for j in xrange(3)]
Отслеживать
51.2k 86 86 золотых знаков 266 266 серебряных знаков 505 505 бронзовых знаков
ответ дан 4 июн 2012 в 3:15
361 1 1 серебряный знак 6 6 бронзовых знаков
Все получилось,всем спасибо за помощь,очень интересные варианты кода.
4 июн 2012 в 15:29
Чтобы прочитать числа, разделёнными пробелами, из стандартного ввода в numpy массив:
import sys import numpy # $ pip install numpy a = numpy.loadtxt(sys.stdin)
Если в строке больше одного числа, то двумерный массив автоматически создаётся (каждая строка это ряд), иначе можно вызвать a.reshape(5, -1) , чтобы 2D массив получить.
1 2 3 4
array([[ 1., 2.], [ 3., 4.]])
Чтобы создать 2D Питон список для того же ввода:
L = [list(map(float, line.split())) for line in sys.stdin]
>>> L [[1.0, 2.0], [3.0, 4.0]]
