[Перевод] Вы правда знаете о том, что такое массивы?

Там, где я тружусь, от веб-разработчиков ожидают знания PHP и JavaScript. Я, проводя собеседования, обнаружил, что достаточно задать всего один простой вопрос для того чтобы узнать о том, насколько глубоко разработчик понимает инструменты, которыми пользуется каждый день. Вот этот вопрос:

Каковы сходства и различия массивов в JavaScript и в PHP?

Одно дело — умение писать код. И совершенно другое — понимание внутренних механизмов используемых языков.

a9zmg89eeq46pzczwzrbxyavfd4.jpeg

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

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

Массивы и их родной язык — C


Язык C — это не первый в истории язык программирования, но это — язык, который сильнее других повлиял на IT-индустрию. Многие разработчики учили в институтах C в качестве первого языка. И PHP, и JavaScript что-то взяли от C. В результате можно наблюдать некоторое сходство между этими языками и C, и именно анализ массивов в C позволит показать то, как далеко эти структуры данных продвинулись с 1972 года.

В C массивы строго типизированы и имеют фиксированную длину.

int myArray[10];
int fibonacci[10] = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34};


Выше показана пара объявлений массивов. Они могут хранить только целые числа, количество которых не превышает 10.

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

int i, sum;
for (i = 0; i < 9; i++) {
  sum += fibonacci[i];
}


Подобная конструкция не выглядит дикой ни в JavaScript, ни в PHP. Но именно здесь и кроется опасность.

Массивы в JavaScript


Можно представить себе, что массивы в JavaScript очень похожи на массивы в C. И правда — в JS совершенно нормально смотрятся следующие конструкции:

let myArray = [];
let fibonacci = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34];


Однако массивы в JavaScript и в C — это разные вещи. Например, следующее, совершенно очевидно, в C невозможно:

myArray[0] = 5;
myArray[1] = 5.5;
myArray[2] = 'cat';
myArray[3] = [1,2,3];
myArray[4] = (a,b) => {a+b};
myArray[1000] = 'mind blown';
// myArray = [5, 5.5, 'cat', [1,2,3], (a,b) => {a+b}];


В JavaScript массивы имеют переменную длину. Тип их содержимого не контролируется — точно так же, как и тип обычных переменных. Язык берёт на себя управление памятью, в результате длина массива способна увеличиваться или уменьшаться, а разработчик может об этом не задумываться. JavaScript-массивы, на самом деле, очень похожи на списки.

Перебор массива можно организовать, пользуясь неудачным способом, позаимствованным из C:

let sum = 0;
for (i = 0; i < fibonacci.length; i++) {
  sum += fibonacci[i];
}


Однако у нас нет необходимости в использовании такого подхода к перебору JS-массивов. Например, тут имеются ненужные промежуточные переменные. В такой конструкции вполне могут возникать ошибки, причиной которых являются неопределённые или некорректные значения. Есть ли какое-то значение в элементе массива fibonacci[10]? А если значение там есть — является ли оно целым числом?

Но в JavaScript имеются гораздо более совершенные механизмы для работы с массивами. Массивы в JS — это не просто некие простейшие структуры данных. Они, как и функции, являются объектами первого класса. У них есть методы, позволяющие адекватно решать различные задачи:

let sum = fibonacci
   .filter(Number.isInteger)
   .reduce(
      (x,y) => {return x+y}, 0
    );


Это гораздо лучше, чем перебирать массив с помощью цикла for.

ef682efd601f8617bea0343b882e81ab.png


Некоторые методы массивов

Кроме того, как уже говорилось, длина массивов в JS, в отличие от длины С-массивов, не фиксирована. Это позволяет оказывать на массивы довольно интересные воздействия, влияющие на их длину. Так, можно, пользуясь методом pop, извлечь из массива последний элемент. А метод push позволяет добавить новый элемент в конец массива. Метод unshift позволяет добавить элемент в начало массива. А метод shift — извлечь первый элемент массива. Используя разные комбинации этих методов, с массивами можно работать как со стеками или очередями. Тут всё зависит от потребностей программиста.

Массивы в PHP


Массивы в PHP почти похожи на JavaScript-массивы.

Они, как и JS-массивы, отличаются переменной длиной и слабой типизацией. Поэтому может возникнуть соблазн решить, что массивы в PHP и в JS — это одно и то же.

$myArray = [];
$fibonacci = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34];
$myArray[0] = 5;
$myArray[1] = 5.5;
$myArray[2] = 'cat';
$myArray[3] = [1,2,3];
$myArray[4] = function($a, $b) { return $a + $b; };


Лямбда-функции в PHP не так красивы, как похожие функции в JS (в ES6), но этот пример, написанный на PHP, функционально эквивалентен ранее рассмотренному JS-примеру.

Здесь можно использовать и аналоги вышеописанных функций для добавления элементов в массив и извлечения их из него (array_push, array_pop, array_shift, array_unshift).

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

$myArray['banana'] = 'yellow fruit';
$myArray[5] = 'is alive';
$myArray[0.02] = 'the 2%';


В PHP, с технической точки зрения, массивы — это хэш-таблицы или словари. В них используются пары вида ключ/значение. Ключи могут быть любыми примитивными значениями: целыми числами, числами с плавающей запятой, строками. Так как в основе PHP-массивов лежат словари, поиск значений по ключу в этих массивах отличается чрезвычайной эффективностью. А именно, временная сложность поиска составляет O(1).

Это означает, что PHP-массивы могут с успехом выполнять роль простых поисковых таблиц:

$colours = [
  'red' => '#FF0000',
  'green' => '#00FF00',
  'blue' => '#0000FF',
  'orange' => '#FF6600',
];


PHP-массивы дают разработчику множество гибких возможностей. Эти массивы можно сортировать по ключу и по значению. Можно, например, «перевернуть» массив с помощью array_flip, поменяв местами ключи и значения, что даёт возможность весьма эффективно организовывать поиск в массиве нужных данных.

Поиск конкретного значения в обычном массиве имеет временную сложность O(n), так как в ходе поиска нужно проверить каждое значение, хранящееся в массиве. А в PHP легко сделать так, чтобы временная сложность такой же операции составила бы O(1):

$users = [
  1 => 'Andi',
  2 => 'Benny',
  3 => 'Cara',
  4 => 'Danny',
  5 => 'Emily',
];
$lookupTable = array_flip($users);
return $lookupTable['Benny'];


Конечно, что-то подобное доступно и в JavaScript, хотя тут уже надо будет прибегнуть к возможностям объектов. Но из-за этого придётся пойти на некоторые компромиссы. А именно, при работе с объектами в распоряжении разработчика не будет методов массивов вроде тех, о которых мы говорили выше.

Если продолжить разговор о PHP-массивах, то можно сказать, что их перебор организован просто и безопасно. Здесь есть возможность применить цикл for, напоминающий такой же цикл из C, но, прежде чем это сделать, стоит как следует подумать о том, зачем поступать именно так. PHP, благодаря циклам foreach, позволяет решать проблемы, характерные для массивов переменной длины, способных содержать значения разных типов:

$sum = 0;
foreach ($myArray as $key => $value) {
  $sum += is_numeric($value) ? $value : 0;
}


В цикле даётся доступ и к ключам, и к значениям, что позволяет программисту работать и с тем, и с другим.

Стоит отметить, что PHP-массивы отличаются от JS-массивов тем, что в PHP для выполнения некоторых операций с массивами приходится пользоваться внешними по отношению к ним функциями:

$sum = 
  array_reduce(
    array_filter($fibonacci, 'is_numeric'),
    function ($x, $y) { return $x + $y; },
    0
  };


Это — функционально, но не так красиво, как в JavaScript. Если вы хотите писать код для работы с PHP-массивами, который напоминает код, используемый в JavaScript (существуют сильные аргументы в пользу такого подхода), то вам, возможно, стоит взглянуть на специализированное решение. Скажем — на класс Collection из фреймворка Laravel. Однако PHP позволяет создавать объекты, возможности которых напоминают возможности массивов (их, например, можно обрабатывать в циклах foreach).

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

PHP-массивы — это, в двух словах, самая недооценённая и самая незаметная возможность языка, которая, если ей правильно пользоваться, способна принести огромную пользу.

Итоги: вопрос и ответ


Вопрос: Каковы сходства и различия массивов в JavaScript и в PHP?

Ответ: в PHP и JavaScript массивы — это, по сути, слабо типизированные списки переменной длины. В JavaScript ключами элементов массивов являются упорядоченные целые числа. В PHP массивы можно сравнить и со списками, которые поддерживают сортировку, и со словарями, в которых удобно осуществлять поиск элементов по ключу. Ключи PHP-массивов могут быть любыми значениями примитивных типов, а сортировать такие массивы можно по ключам или по значениям.

Уважаемые читатели! Как вы думаете, каких стандартных возможностей больше всего не хватает JavaScript-массивам?

iqfib45pgphfrxv--zfemt0qnmw.jpeg

© Habrahabr.ru