В настоящее время я создаю сценарий, который будет сравнивать список из примерно 90 адресов друг с другом. Результатом скрипта должен быть список, содержащий время, затраченное на то, чтобы добраться до каждого адреса друг от друга.
Я столкнулся с рядом проблем, пытаясь решить эту проблему. Основная проблема заключается в том, что в результирующей матрице расстояний будет 8100 элементов. Максимальное время выполнения сценария Google составляет 30 минут, и поэтому время ожидания сценария истекает.
Какими способами я могу улучшить скрипт, чтобы он работал быстрее?
Цель этого скрипта - создать список с StartID, EndID и Time. Тогда я смогу отфильтровать список, чтобы найти адреса в пределах часа друг от друга.
Благодарность!
function maps(origin, destination) {
var driving = Maps.DirectionFinder.Mode.DRIVING
var transit = Maps.DirectionFinder.Mode.TRANSIT
var modeSet = driving
var directions = Maps.newDirectionFinder()
.setOrigin(origin)
.setDestination(destination)
.setMode(modeSet)
.setOptimizeWaypoints(true)
.getDirections()
var result = directions
return result;
}
function GoogleMaps() {
//get distance
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ABC");
var outputSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("EFG");
var lastrow = sheet.getLastRow();
var lastcolumn = sheet.getLastColumn();
var range = sheet.getRange(2, 3, lastrow-1, 3);
//var range = sheet.getRange(2, 3, 3, 3);
//Origin is in row 2, column 3
var values = range.getValues();
var output = []
for (var i = 0; i < values.length; ++i)
{
var loop1 = values[i]
var start = values[i][1]
var startId = values[i][0]
for (var j = 0; j < values.length; j++) {
var loop2 = values[j]
var end = values[j][1]
var endId = values[j][0]
var result = maps(start, end)
var status = result.status
try{
var time = result.routes[0].legs[0].duration.value / 60;
var row = [startId, endId, time]
output.push(row)
} catch(err){
Logger.log(err);
}
}
}
var outputLength = output.length
var outputRange = outputSheet.getRange(1,1,outputLength,3);
outputRange.setValues(output);
}
РЕДАКТИРОВАТЬ: обновленное количество элементов в списке
2 ответа
Первое, что вам нужно сделать, это уменьшить количество операций, выполняемых в циклах for
. Итак, давайте начнем с анализа этого, но с алгоритмической точки зрения.
В вашей текущей реализации вы в основном вычисляете декартово произведение для набора из 90 значений, чтобы произвести новый набор, состоящий из 8100 значений.
Однако в этом наборе результатов есть несколько избыточных значений, например:
Набор результатов включает вычисления, в которых один и тот же адрес используется как в качестве начального, так и конечного местоположения.
Расстояние между двумя адресами рассчитывается дважды; таким образом, что адрес A является начальным адресом, а адрес B является конечным адресом, а в другой итерации адрес A является конечным адресом, а адрес B является начальным адресом.
ПРЕДОСТЕРЕЖЕНИЕ: я предполагаю, что вы преодолеваете одинаковое расстояние во время транзита между двумя адресами независимо от транзита одного из них. направление (например, от A к B или от B к A). Это может быть не так в вашем сценарий.
Вы можете устранить эту избыточность, используя область дискретной математики, называемую комбинаторикой; более конкретно, используя эту прекрасную формулу:
Если мы допустим n = 90 и r = 2 , мы получим следующее:
Это означает, что в наиболее оптимальном варианте нам нужен алгоритм, который генерирует не более 4005 пар адресов.
С этой нашей целью [щелкает пальцами] пора написать более оптимальный алгоритм! Но в иллюстративных целях и в интересах краткости позволяет использовать меньший размер выборки из 4 адресов, составленных из одной буквы. Следующего массива должно хватить:
var addresses = ['a', 'b', 'c', 'd'];
Используя вышеупомянутую формулу, мы делаем вывод, что существует 6 уникальных пар адресов, которые мы можем представить следующим образом:
ab bc cd
ac bd
ad
Так как же создать эти пары?
Если вы посмотрите на изображение выше, вы заметите несколько вещей:
- Количество столбцов на единицу меньше количества адресов в массиве
- С каждым последующим столбцом (слева направо) количество пар адресов в столбце уменьшается на 1; т.е. есть 3 пары, которые начинаются с буквы «a», 2 пары начинаются с буквы «b», 1 пара начинается с буквы «c».
- Также обратите внимание, что при переходе от одного столбца к следующему в последующих столбцах нет пар с начальным символом предыдущих столбцов; т.е. во 2-м столбце нет пар, начинающихся с 'a', а в 3-м столбце нет пар, начинающихся с 'a' или 'b'
Обобщим эти наблюдения. Учитывая массив адресов n , мы можем сгенерировать n - 1 столбцов. Длина каждого столбца уменьшается на 1, так что первый столбец содержит n - 1 пар, а второй столбец - n - 2 . пары, третий столбец - n - 3 пары и т. д., где каждый столбец состоит из пар комбинаций, в которых пропущены адреса из предыдущих столбцов.
Основываясь на этих правилах, мы можем настроить цикл for
следующим образом (запустите скрипт, и он сгенерирует коллекцию объектов, свойства "start" и "end" которых представляют уникальные пары адресов):
var addresses = ['a', 'b', 'c', 'd'];
var pairs = [];
var numColumns = addresses.length - 1;
var columnHeight;
var columnIndex;
var rowIndex;
for (columnIndex = 0; columnIndex < numColumns; columnIndex++) {
columnHeight = numColumns - columnIndex;
for (rowIndex = 0; rowIndex < columnHeight; rowIndex++) {
pairs.push({
"start":addresses[columnIndex],
"end":addresses[columnIndex + rowIndex + 1]
});
}
}
console.log(pairs);
Таким образом, приведенное выше обрабатывает алгоритмическую оптимизацию, вам нужно настроить его для использования с вашей реализацией, но это должно служить хорошей отправной точкой. Однако, хотя создание пар адресов 4005 происходит относительно быстро, обработка этих пар адресов для определения пройденного расстояния через API карты, вероятно, займет много времени.
Если вам все же удастся исчерпать 30-минутную квоту на выполнение скрипта, вы можете рассмотреть возможность использования методов пакетной обработки, когда вы настраиваете свое приложение для выполнения вычислений на меньших пакетах пар адресов, по одному пакету за раз в течение заданного времени. период. Возможно, вы даже сможете обрабатывать несколько пакетов одновременно, если правильно настроите приложение. Но это пост в другой раз.
Возможно, это не лучше того, что у вас есть для производительности, но попробуйте здесь разбить его на более модульное решение, тогда вы можете решить, какую часть оптимизировать, возможно, выполняя это в некотором подмножестве за раз;
function getValuesArray(values) {
let valueArray = [];
for (let i = 0; i < values.length; ++i) {
valueArray.push({
id: values[i][0],
value: values[i][1]
});
}
return valueArray;
}
function GoogleMaps() {
//get distance
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ABC");
var outputSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("EFG");
var lastrow = sheet.getLastRow();
var lastcolumn = sheet.getLastColumn();
var range = sheet.getRange(2, 3, lastrow - 1, 3);
//var range = sheet.getRange(2, 3, 3, 3);
//Origin is in row 2, column 3
var values = range.getValues();
var output = [];
let list1 = getValuesArray(values);
// deep clone
const clone = (items) => items.map(item => Array.isArray(item) ? clone(item) : { ...item
});
// might only need list1 but usin two for clarity here
const list2 = clone(list1);
const listWork = [];
for (var a = 0; a < list1.length; a++) {
for (var j = 0; j < list2.length; j++) {
listWork.push({
dest: list2[j].value,
destId: list2[j].id,
origin: list1[a].value,
originId: list1[a].id
}
}
}
}
let results = [];
for (let w = 0; w < listWork.length; w++) {
results.push(startId: listWork.originId, endId: listWork.destId, map: maps(listWork.origin, listWork.dest));
}
for (let r = 0; r < results.length; r++) {
let result = results[r];
// seems to not be used
//var status = result.map.status;
let route = !!result.map.routes && result.map.routes[0] ? result.map.routes[0] : null;
if (route !== null &&
route.legs &&
route.legs[0] &&
route.legs[0].duration &&
route.legs[0].duration.value) {
let time = route.legs[0].duration.value / 60;
let row = [result.startId, result.endId, time];
output.push(row);
}
}
let outputLength = output.length;
let outputRange = outputSheet.getRange(1, 1, outputLength, 3);
outputRange.setValues(output);
}
Похожие вопросы
Новые вопросы
javascript
По вопросам программирования на ECMAScript (JavaScript/JS) и его различных диалектах/реализациях (кроме ActionScript). Обратите внимание, что JavaScript — это НЕ Java. Включите все теги, относящиеся к вашему вопросу: например, [node.js], [jQuery], [JSON], [ReactJS], [angular], [ember.js], [vue.js], [typescript], [стройный] и т. д.
output
? Является ли медленная часть созданиемouptutRange
?, возникли ли какие-либо ошибки в try / catch, И если да, то можете ли вы вместо этого логически определить это условие, которое будет быстрее, чем try / catch.result.routes[0].legs[0].duration.value
скрипта. «Ноги» выдавали неопределенную ошибку.result.routes[0].legs === undefined
, а также почему он НЕ определен?result.routes[0].legs[0].duration.value
на трех строках и получил желаемые результаты. Это заставляет меня думать, что, возможно, проблема в том, что ноги не могут работать против такого большого количества ценностей. Я не знаю, как решить эту проблему.