Разница между массивом nx1 и массивом n-элементов в Julia

В Julia, если я определяю массив с 1 столбцом и n строками, создается экземпляр "массива n-элементов", я не понимаю, как это отличается от массива nx1:

julia> a = [1 2 3]
1x3 Array{Int64,2}:
 1  2  3

julia> b = [1;2;3]
3-element Array{Int64,1}:
 1
 2
 3

Смутно, если я беру транспонирование дважды массива n-элементов, результатом возврата является массив nx1:

julia> transpose(transpose(b))
3x1 Array{Int64,2}:
 1
 2
 3

Это приводит к неожиданному (мне) поведению, например:

julia> size(b) == size(transpose(transpose(b)))
false

Мои вопросы:

  • В чем разница между массивом nx1 и массивом n-элементов?
  • Как создать массив nx1, не делая что-то вроде примера с двумя транспозициями, которые я дал.

Ответ 1

Быстрый ответ:

  • Массив n x1 или 1x n представляет собой двумерную матрицу (которая имеет место только с одной строкой или столбцом), тогда как ваш массив n-элементов представляет собой одномерный вектор столбца.
  • Я думаю, что самый простой способ создания литерала массива n x1 берет транспонирование вектора строки: [1 2 3]'. Идя в другую сторону, вы можете сгладить любой n-мерный массив до 1-го вектора, используя vec.

Это гораздо более поучительно, однако, думать о том, почему это имеет значение. Система типа Julia предназначена для полностью основанных на типах, а не на значениях. Размерность массива включена в его информацию о типе, но количество строк и столбцов не является. Таким образом, разница между матрицей nx1 и вектором n-элементов заключается в том, что они имеют разные типы... и механизм вывода типа не может видеть, что матрица имеет только один столбец.

Чтобы получить лучшую производительность от Julia, вы (и особенно разработчики основного языка и библиотеки) хотите написать функции, которые стабилизируются. То есть, функции должны иметь возможность выводить, какой тип они будут возвращать, основываясь только на типах аргументов. Это позволяет компилятору следить за вашими переменными через функции, не теряя следа типа..., что, в свою очередь, позволяет генерировать очень высоко оптимизированный код для этого конкретного типа.

Теперь подумайте о переносах снова. Если вам нужна стабильная функция транспонирования типа, она должна вернуть хотя бы двумерный массив. Он просто не может сделать что-то сложное, если один из размеров равен 1 и по-прежнему сохраняет хорошую производительность.

Все, что было сказано, все еще много обсуждается, что вектор переносится как в почтовых списках, так и в проблемах GitHub. Здесь большое место для начала: Проблема № 2686: ones(3) != ones(3)''. Или для более глубокой дискуссии по смежным вопросам: Проблема № 3262: встраивать тензорные объекты в качестве объектов с более высоким размером с конечными размерами синглтона. Оба были в конечном счете заменены и перевернуты в Проблема № 4774: перенос вектора серьезно переносится.


Обновление для Julia 0.6: Джулия теперь переносит вектор очень серьезно! Теперь векторная транспонирование возвращает специальный тип RowVector, который действует как матрица с 1 строкой, но с дополнительным знанием о том, что существует ровно одна строка. Это также ленивый "взгляд" в исходный вектор. С примерами в вопросе это означает, что не только size(b) == size(transpose(transpose(b))) true, но и b'' === b.

Также немного проще указать операции изменения, которые изменяют размерность в Julia 0.6. Хороший ответ на вопрос 2 выше (создание массива nx1) - с reshape([1,2,3], :, 1). Размер, указанный :, вычисляется в соответствии с длиной исходного массива.