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

Обзор задач

Учитывая массив значений, найдите самое длинное (строковое) значение в массиве.

Дан массив: [1, 10, 'longest string', false, ['this', 'may', 'be', 'long', 'enough;], { prop: 'value' }, 'short string', null, undefined] .

Ожидаемый результат: longest string .

Теория

документация MDN Object.prototype.toString()

Существует два способа преобразования любого объекта в строку. Давайте изучим их.

«Все есть объект»

Ну, на самом деле, нет.

Есть 7 примитивов: строка, число, bigint, логическое значение, неопределенный, ноль, символ. Каждый примитив не является объектом, хотя каждый примитив имеет оболочку объекта. Мы не можем преобразовывать непримитивные объекты, скажем, в настоящую строку, означающую значения, которые мы, люди, можем рассматривать как строки.

Какие данные считать возможными для преобразования в строки

Я бы сказал, что такие задачи могут нуждаться в дополнительных деталях для рассмотрения, но для данной задачи допустим, что мы хотим сравнить только примитивы. Но поскольку null или undefined означают, что переменная не имеет значения, мы будем рассматривать только значения string, number, bigint и boolean. Каждый symbol уникален и не является счетным, поэтому эти значения тоже рассматривать не будем (подробнее о примитиве Symbol здесь).

Все примитивы, которые мы рассматриваем как «настоящие» строки, имеют собственный метод toString, который интерпретирует значение как строку.

Как преобразовать в строку?

Вызов метода toString

Просто как тот. Просто вызовите variable.toString() и получите строковое представление.

Вызов конструктора String()

Напишите String(variable) и получите строковое представление. Использование конструктора String примерно так же, как вызов метода toString. По крайней мере, на избранных примитивах.

Конкатенация строк с помощью оператора присваивания «+»

Несмотря на то, что существует метод String.prorotype.concat(), документы MDN говорят использовать операторы + или += для объединения строк из-за соображений производительности.

Вызов '' + variable даст тот же результат, что и вызов ''.concat(variable), который преобразует variable в String, а затем объединяет его с пустой строкой.

Итерация по массиву

Существует 5 способов перебора массива:

  1. for петля [МДН]
  2. for _ of петля [МДН]
  3. foreach() метод [MDN]
  4. map() метод [MDN]
  5. reduce() метод [MDN]

Давайте попробуем их и посмотрим, как они работают.

Поиск самого длинного (строкового) значения в массиве

Для наших операций мы рассмотрим:

const arr = [1, 10, 'longest string', false, ['this', 'may', 'be', 'long', 'enough;], { prop: 'value' }, 'short string', null, undefine];
const arrLength = arr.length;
const primitives = [ 'string', 'number', 'bigint', 'boolean' ];
let longestLength = 0;
let longestValue = null;

Для цикла

for (let i = 0; i < arrLength; i++)
{
    const value = arr[i];
    if (primitives.includes(typeof value))
    {
        const valueLength = value.toString().length;
        if (valueLength > longestLength)
        {
            longestLength = valueLength;
            longestValue = value;
        }
    }
}

Для _ цикла

for (const value of arr)
{
    if (primitives.includes(typeof value))
    {
        const valueLength = value.toString().length;
        if (valueLength > longestLength)
        {
            longestLength = valueLength;
            longestValue = value;
        }
    }
}

метод foreach()

arr.forEach(value => {
    
    if (primitives.includes(typeof value))
    {
        const valueLength = value.toString().length;
        if (valueLength > longestLength)
        {
            longestLength = valueLength;
            longestValue = value;
        }
    }
});

метод карты()

arr.map(value => {
    
    if (primitives.includes(typeof value))
    {
        const valueLength = value.toString().length;
        if (valueLength > longestLength)
        {
            longestLength = valueLength;
            longestValue = value;
        }
    }
});

метод уменьшения()

arr.reduce((previousValue, currentValue) => {
   
    if (primitives.includes(typeof currentValue))
    {
        const valueLength = currentValue.toString().length;
        if (valueLength > longestLength)
        {
            longestLength = valueLength;
            previousValue = currentValue;
        }
    }
}, longestLength);

Анализ

Как мы видим, все циклы в основном выполняют одну и ту же операцию при условии. Кроме того, просто взглянув на код, становится ясно, что последние 2 метода (map() и reduce()) являются накладными. Но мы все равно будем учитывать их в наших тестах.

Производительность

Давайте перейдем на https://jsben.ch/ и создадим несколько тестов.

1к записей

Как и ожидалось, простой цикл for работает лучше, чем другие методы, причем map является медленным.

Бенчмарк доступен здесь: https://jsben.ch/APBhX



1 млн записей

Для этого теста цикл for по-прежнему является лучшим выбором, а методы reduce и forEach занимают второе место.

Бенчмарк доступен здесь: https://jsben.ch/Cu9H2

Заключение

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

Спасибо за прочтение,
kyoshie

Читать далее: