Учитесь писать код без for

0
39160

Зачем писать код с for, если можно этого не делать? Аргументируем, почему.


Да, это продолжение той самой горячей статьи про написание кода без if!

О чём это

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

Под циклами мы имеем в виду императивные циклы наподобие for, for … in, for … of, while, do … while. Все они работают по одному принципу — императивный стиль выполнения операций. Альтернативой ему является декларативный стиль.

Императивность vs декларативность

Это весьма обширная тема, но в двух словах разницу можно описать так:

  • Императивный стиль говорит, “как” ...
  • Декларативный стиль показывает, “что”

Какая между ними разница?

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

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

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

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

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

Immutability — неизменяемость

Избегать циклы стоит не только для того, чтобы приобщиться к декларативному стилю. Важный момент здесь — относиться к данным как к immutable.

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

Вместо того, чтобы хранить состояние в переменных для выполнения простых операций, сделайте их неизменяемыми: это безопаснее и чище. Код с неизменяемыми переменными намного проще для работы и расширения.

Читаемость и производительность кода могут быть лучше при таком подходе. Как находить баланс — отдельная тема, достойная обсуждения.

Рекурсия

Ещё один способ избежать цикла — это использовать рекурсию.

Рекурсия проста в исполнении. Создаёте функцию, которая вызывает саму себя (создавая тем самым цикл), добавляете условие выхода.

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

Иногда рекурсия — лучший способ решить задачу, и мы можем обойти её, воспользовавшись стеком (это несложно).

Задачи

В любом случае, написать что-то без циклов — это просто интересный челлендж!

Здесь приведено несколько задач с решениями, использующими императивные циклы и не использующими. Все примеры написаны на JavaScript.

Какие решения вам нравятся больше, какие легче читаются, как по-вашему?

Задача №1: вычислить сумму чисел в массиве

Предположим, у нас есть массив чисел наподобие следующего:

const arrayOfNumbers = [17, -4, 3.2, 8.9, -1.3, 0, Math.PI];

Решение с циклом:

let sum = 0;
arrayOfNumbers.forEach((number) => {
sum += number;
});
console.log(sum);

Здесь мы для достижения результата постоянно изменяем переменную sum.

Вот решение, использующее прекрасную функцию reduce:

const sum = arrayOfNumbers.reduce((acc, number) =>
 acc + number
);
console.log(sum);

Промежуточные состояния нигде не перезаписываются. Вместо этого, мы сделали множество вызовов функции, а состояние передавалось между этими вызовами до конечного присваивания к sum.

А вот решение с применением рекурсии:

const sum = ([number, ...rest]) => {
if (rest.length === 0) { 
return number;
}
return number + sum(rest);
};
console.log(sum(arrayOfNumbers))

Функция sum вызывает саму себя и использует оператор rest, чтобы уменьшить суммируемый массив. И останавливается, когда массив пуст.

Кому-то может показаться это решение хорошим, но как минимум, оно хуже читается, нежели решение с reduce.

Задача №2: составить предложение из смешанных данных

Допустим, дан массив из string и объектов прочих типов, и нам необходимо склеить все string, игнорируя остальные элементы.

Пример для тестирования:

const dataArray = [0, 'H', {}, 'e', Math.PI, 'l', 'l', 2/9, 'o!'];

Ожидаемый вывод — “Hello!”. Для проверки на тип string, стоит использовать оператор typeof.

Решение с применением простого цикла:

let string = '', i = 0;
while (dataArray[i] !== undefined) {
if (typeof dataArray[i] === 'string') {
string += dataArray[i];
}
i += 1;
}
console.log(string);

А вот решение, использующее функцию filter в комбинации с join:

const string = dataArray.filter(e => typeof e === 'string')
.join('');
console.log(string);

Берите эти функции на заметку! filter изящно скрывает за собой условные операции, и мы получаем новый уровень абстракции.

Задача №3: сделать список объектов из списка значений

Допустим, у нас есть массив из названий книг. Нам необходимо сделать из каждого объект и дать ему уникальный идентификатор.

Пример данных:

const booksArray = [
'Clean Code',
'Code Complete',
'Introduction to Algorithms',
];
// Ожидаемый результат
newArray = [
{ id: 1, title: 'Clean Code' },
{ id: 2, title: 'Code Complete' },
{ id: 3, title: 'Introduction to Algorithms' },
];

Решение с применением обычного цикла:

const newArray = [];
let counter = 1;
for (let title of booksArray) {
 newArray.push({
 id: counter,
 title,
 });
 counter += 1;
}
console.log(newArray);

Решение с простым использованием функции map:

const newArray = booksArray.map((title, index) => ({ 
id: index + 1, 
title 
}));
console.log(newArray);

В итоге

Все решения, предложенные здесь, основаны на функциях map, filter и reduce. Они позволяют совершать очень много крутых приёмов! Читайте о них больше, пробуйте применять на практике.

РУБРИКИ В СТАТЬЕ

МЕРОПРИЯТИЯ

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

ВАКАНСИИ

Programmer UE4
Краснодар, по итогам собеседования
Tableau developer
по итогам собеседования
Unreal Engine Developer
по итогам собеседования
Middle\Senior .Net разработчик
от 120000 RUB до 165000 RUB

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ

BUG