Apps Script: Поиск элементов и индексов в двумерном массиве без indexOf

В мире Google Apps Script работа с данными из Google Таблиц является повседневной задачей. Часто эти данные представляются в виде двумерных массивов, где каждая строка таблицы соответствует внутреннему массиву. Когда возникает необходимость найти конкретный элемент или определить его позицию (индекс строки и столбца) в такой структуре, многие разработчики интуитивно обращаются к знакомому методу indexOf().

Однако, как мы вскоре убедимся, indexOf() не предназначен для прямого использования с двумерными массивами. Это создает определенные сложности и требует альтернативных подходов. В этой статье мы подробно рассмотрим, почему стандартный indexOf() не работает в контексте 2D массивов, и предложим ряд эффективных решений. Мы изучим как базовые методы с использованием циклов, так и более современные подходы JavaScript, а также разработаем универсальные функции для поиска и обработки данных в Google Таблицах, обеспечивая высокую производительность и гибкость.

Понимание двумерных массивов в Apps Script и ограничений метода indexOf

При работе с Google Apps Script и данными из Google Таблиц мы часто сталкиваемся с необходимостью обрабатывать информацию, представленную в виде двумерных массивов. Эти структуры данных являются естественным представлением табличных данных, где каждая строка таблицы соответствует внутреннему массиву, а каждый столбец — элементу в этом массиве. Понимание их устройства критически важно для эффективного манипулирования данными.

Хотя метод indexOf() является мощным инструментом для поиска элементов в одномерных массивах JavaScript, его прямое применение к двумерным структурам оказывается невозможным. Чтобы разработать эффективные стратегии поиска, необходимо сначала разобраться в фундаментальных принципах работы двумерных массивов в Apps Script и понять, почему стандартный indexOf() не может быть использован "как есть" для таких сложных структур.

Что такое двумерные массивы в Google Apps Script?

В контексте Google Apps Script, двумерный массив (или массив массивов) представляет собой структуру данных, где каждый элемент основного массива сам является массивом. Это идеальный способ для представления табличных данных, таких как те, что хранятся в Google Таблицах, где каждый внутренний массив соответствует строке, а его элементы — ячейкам в этой строке.

Представьте себе таблицу с данными. В Apps Script вы можете получить эти данные с помощью метода getValues() из объекта Range, который возвращает именно двумерный массив. Например:

const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const dataRange = sheet.getDataRange();
const values = dataRange.getValues();

/*
  values может выглядеть так:
  [
    ["Имя", "Возраст", "Город"],
    ["Иван", 30, "Москва"],
    ["Анна", 25, "Санкт-Петербург"]
  ]
*/

Доступ к элементам в таком массиве осуществляется по двум индексам: первый для строки, второй для столбца (например, values[1][0] вернет "Иван"). Понимание этой структуры критически важно для эффективной работы с данными в Apps Script.

Почему стандартный метод indexOf() неприменим к 2D массивам?

Стандартный метод indexOf() в JavaScript, и, соответственно, в Google Apps Script, разработан для работы с одномерными массивами. Его основная задача — найти первое вхождение указанного элемента и вернуть его индекс (позицию) в массиве. Если элемент не найден, метод возвращает -1.

Однако, когда мы применяем indexOf() к двумерному массиву, возникают ограничения:

  • Поиск на верхнем уровне: indexOf() ищет среди элементов верхнего уровня массива. В случае двумерного массива, элементами верхнего уровня являются другие массивы (строки). Метод не «заглядывает» внутрь этих вложенных массивов.

  • Сравнение по ссылке, а не по значению: Если вы пытаетесь найти примитивное значение (например, число или строку) в двумерном массиве с помощью indexOf(), он будет сравнивать это примитивное значение с объектами-массивами (строками). Очевидно, что строка "Apple" никогда не будет равна массиву ["Apple", "Banana"], поэтому indexOf() всегда вернет -1.

  • Поиск вложенного массива: Даже если вы ищете целый вложенный массив (например, [1, 2, 3]), indexOf() найдет его только в том случае, если это та же самая ссылка на объект массива, а не просто массив с идентичным содержимым. То есть, my2DArray.indexOf([1, 2, 3]) почти всегда вернет -1, если только [1, 2, 3] не является точно тем же объектом, который был помещен в my2DArray.

Базовые подходы к поиску: Вложенные циклы и их реализация

Поскольку стандартный метод indexOf() не может напрямую работать с двумерными массивами, нам необходимо обратиться к более фундаментальным подходам для поиска элементов и их позиций. Самым базовым и интуитивно понятным способом является использование вложенных циклов. Этот метод позволяет последовательно перебирать каждую «строку» (вложенный массив), а затем каждый «столбец» (элемент) внутри этой строки, обеспечивая полный контроль над процессом поиска.

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

Поиск первого вхождения элемента и его координат

Самый прямой и интуитивно понятный способ найти первое вхождение элемента в двумерном массиве — это использование вложенных циклов. Один цикл будет перебирать строки, а внутренний — элементы в каждой строке. Как только искомое значение найдено, мы можем немедленно зафиксировать его координаты (индексы строки и столбца) и прервать выполнение циклов, чтобы избежать лишних итераций.

Рассмотрим пример функции, которая принимает двумерный массив и искомое значение, возвращая массив из двух элементов: индекс строки и индекс столбца первого найденного вхождения. Если элемент не найден, функция вернет null.

function findFirstOccurrence(dataArray, searchValue) {
  for (let i = 0; i < dataArray.length; i++) {
    for (let j = 0; j < dataArray[i].length; j++) {
      if (dataArray[i][j] === searchValue) {
        return [i, j]; // Возвращаем индексы строки и столбца
      }
    }
  }
  return null; // Если элемент не найден
}

// Пример использования:
const my2DArray = [
  ['A', 'B', 'C'],
  ['D', 'E', 'F'],
  ['G', 'H', 'I']
];

const coords = findFirstOccurrence(my2DArray, 'E'); // [1, 1]
const notFound = findFirstOccurrence(my2DArray, 'Z'); // null
Logger.log(coords);
Logger.log(notFound);

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

Поиск всех вхождений элемента в 2D массиве

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

Рассмотрим функцию findAllOccurrences, которая возвращает массив всех найденных координат (индексов строки и столбца):

function findAllOccurrences(data, targetValue) {
  const occurrences = [];
  for (let i = 0; i < data.length; i++) {
    for (let j = 0; j < data[i].length; j++) {
      if (data[i][j] === targetValue) {
        occurrences.push({ row: i, col: j });
      }
    }
  }
  return occurrences;
}

// Пример использования:
const my2DArray = [
  ['A', 'B', 'C'],
  ['D', 'A', 'F'],
  ['G', 'H', 'A']
];
const allA = findAllOccurrences(my2DArray, 'A');
Logger.log(JSON.stringify(allA)); // Выведет: [{row:0,col:0},{row:1,col:1},{row:2,col:2}]

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

Современные методы JavaScript для эффективного поиска в 2D массивах

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

В этом разделе мы рассмотрим, как использовать такие мощные методы, как find(), findIndex(), filter() и some(), для эффективного поиска и обработки данных в двумерных массивах в Google Apps Script. Мы покажем, как адаптировать их для работы со строками или столбцами массива, имитируя функциональность, которую indexOf предоставляет для одномерных структур.

Использование методов find() и findIndex() для строк массива

Продолжая тему современных подходов, методы find() и findIndex() предоставляют элегантный способ поиска элементов в массивах. Хотя они напрямую не работают с двумерными массивами, их можно эффективно применять к отдельным строкам такого массива.

Реклама

Представьте, что вам нужно найти строку, содержащую определенное значение. Вы можете перебрать каждую строку двумерного массива и применить findIndex() к этой строке. Это позволит определить, присутствует ли искомое значение в текущей строке, и, если да, получить его индекс в этой строке (то есть номер столбца).

function findElementIn2DArrayRow(data, searchValue) {
  for (let rowIndex = 0; rowIndex < data.length; rowIndex++) {
    const row = data[rowIndex];
    const colIndex = row.findIndex(item => item === searchValue);
    if (colIndex !== -1) {
      return { rowIndex: rowIndex, colIndex: colIndex }; // Найдено первое вхождение
    }
  }
  return null; // Элемент не найден
}

const my2DArray = [
  ['A', 'B', 'C'],
  ['D', 'E', 'F'],
  ['G', 'H', 'I']
];

const result = findElementIn2DArrayRow(my2DArray, 'E');
Logger.log(result); // { rowIndex: 1, colIndex: 1 }

Этот подход позволяет найти первое вхождение элемента, возвращая его координаты (индекс строки и столбца).

Применение filter() и some() для комплексного поиска

В то время как find() и findIndex() отлично подходят для поиска первого вхождения в пределах одной строки, методы filter() и some() предлагают более гибкие возможности для комплексного поиска и проверки наличия элементов во всем двумерном массиве.

Использование filter() для поиска строк

Метод filter() позволяет получить все строки двумерного массива, которые соответствуют определенному условию. Это особенно полезно, когда нужно найти не просто элемент, а целые строки, содержащие этот элемент или удовлетворяющие более сложным критериям. Например, чтобы найти все строки, содержащие определенное значение:

function filterRowsByValue(data, value) {
  return data.filter(row => row.includes(value));
}

const data = [['Яблоко', 10], ['Банан', 20], ['Яблоко', 15]];
const applesRows = filterRowsByValue(data, 'Яблоко');
// applesRows будет [['Яблоко', 10], ['Яблоко', 15]]

Применение some() для проверки наличия

Метод some() идеально подходит для быстрой проверки, существует ли хотя бы один элемент во всем двумерном массиве, который удовлетворяет заданному условию. Он возвращает true или false и прекращает итерацию при первом же совпадении, что делает его очень эффективным для проверки наличия. Для проверки существования значения в любой точке 2D массива можно комбинировать some():

function hasValueIn2DArray(data, value) {
  return data.some(row => row.includes(value));
}

const data = [['A', 1], ['B', 2], ['C', 3]];
const hasTwo = hasValueIn2DArray(data, 2);
// hasTwo будет true

Эти методы предоставляют мощные инструменты для работы с двумерными массивами, позволяя выполнять как извлечение данных, так и быстрые проверки наличия.

Создание универсальных функций для работы с 2D массивами

Мы уже рассмотрели различные подходы к поиску элементов в двумерных массивах, от базовых вложенных циклов до современных методов JavaScript, таких как find(), findIndex(), filter() и some(). Каждый из них имеет свои преимущества для конкретных сценариев. Однако, для обеспечения максимальной эффективности, переиспользуемости и чистоты кода, особенно при работе с повторяющимися задачами поиска, целесообразно инкапсулировать эту логику в универсальные функции.

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

Разработка переиспользуемой функции для поиска значения и его индекса/координат

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

Рассмотрим пример такой функции:

function findElementIn2DArray(dataArray, searchValue) {
  for (let i = 0; i < dataArray.length; i++) {
    const rowIndex = dataArray[i].indexOf(searchValue);
    if (rowIndex !== -1) {
      return { row: i, col: rowIndex }; // Возвращаем объект с координатами
    }
  }
  return null; // Элемент не найден
}

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

Оптимизация производительности при поиске в больших массивах

При работе с большими двумерными массивами, особенно когда данные извлекаются из Google Таблиц, оптимизация производительности критически важна. Главный принцип — минимизация итераций. Если вы ищете первое вхождение, убедитесь, что функция немедленно прекращает поиск после обнаружения элемента, используя return или break.

Для сценариев многократного поиска по одному и тому же массиву рассмотрите предварительную обработку данных. Создание вспомогательной структуры, например Map, где ключами будут часто искомые значения, а значениями — их координаты, может значительно сократить время последующих поисков с O(n*m) до O(1) в среднем, компенсируя однократные затраты на построение индекса.

Практическое применение: Поиск и обработка данных в Google Таблицах

После того как мы подробно рассмотрели различные подходы к эффективному поиску элементов и их индексов в двумерных массивах, включая оптимизированные методы, пришло время применить эти знания на практике. Google Таблицы являются одним из наиболее распространенных источников двумерных массивов данных в экосистеме Google Apps Script. Данные, извлекаемые из листов с помощью getValues(), всегда представляют собой двумерный массив, что делает наши ранее изученные техники поиска чрезвычайно актуальными.

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

Примеры поиска данных, полученных из Google Таблиц

Переходя от теоретических основ к реальным сценариям, рассмотрим, как применять разработанные нами методы поиска непосредственно к данным, извлеченным из Google Таблиц. Метод getValues() объекта Range возвращает данные листа в виде двумерного массива, что делает его идеальным кандидатом для наших алгоритмов.

Предположим, нам нужно найти конкретный артикул товара в таблице и получить его координаты (строку и столбец):

function findArticleInSheet() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Каталог");
  const data = sheet.getDataRange().getValues(); // Получаем данные как 2D массив
  const targetArticle = "ART-005";

  for (let i = 0; i < data.length; i++) {
    for (let j = 0; j < data[i].length; j++) {
      if (data[i][j] === targetArticle) {
        // Возвращаем индексы, скорректированные для Google Таблиц (начинаются с 1)
        Logger.log(`Артикул "${targetArticle}" найден в строке ${i + 1}, столбце ${j + 1}.`);
        return { row: i + 1, column: j + 1 };
      }
    }
  }
  Logger.log(`Артикул "${targetArticle}" не найден.`);
  return null;
}

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

Модификация или извлечение строк/столбцов по найденным значениям

После определения координат элемента, например, rowIndex и colIndex, мы можем легко модифицировать данные. Чтобы изменить значение в найденной ячейке, достаточно обратиться к data[rowIndex][colIndex] = newValue;. Для модификации других ячеек в той же строке или столбце используются те же индексы.

Извлечение всей строки, содержащей найденное значение, выполняется просто: const foundRow = data[rowIndex];. Извлечение столбца потребует итерации по всем строкам массива для сбора значений по colIndex.

Заключение

В этом заключительном разделе мы подвели итоги нашего исследования методов поиска элементов и их индексов в двумерных массивах Google Apps Script. Мы убедились, что, несмотря на отсутствие прямого аналога indexOf для 2D структур, существует множество эффективных подходов. От базовых вложенных циклов до более современных методов JavaScript, таких как find(), findIndex(), filter() и some(), каждый инструмент предлагает свои преимущества в зависимости от конкретной задачи.

Освоение этих техник позволяет не только точно находить нужные данные, но и эффективно манипулировать ими в Google Таблицах, автоматизируя сложные процессы. Универсальные функции, разработанные на основе этих принципов, значительно повышают переиспользуемость кода и производительность ваших скриптов. Применяя полученные знания, вы сможете создавать более мощные и гибкие решения для работы с данными.


Добавить комментарий