Блог

Бесконечные Связанные Списки: Личный Финансовый План (пример #1)

Данная статья является продолжением статьи Бесконечные Зависимые Выпадающие Списки в Google Sheets (часть 3).

Здесь рассмотрен пример практического применения Бесконечных связанных списков при составлении Личного Финансового Плана.

Помимо добавления нескольких удобных для пользователя функций (автоматическая вставка даты внесения платежа и формулы для ведения баланса приходов-расходов) скрипт, приведённый содержит целый ряд существенных доработок и улучшений:

// global variable
const excludedSheets = ['Summary', 'Category'];
const dataSheet = 'Category';

var colShift = 3;
var rowMin = 4;

function onEdit(e) {

  let row = e.range.getRow();
  let col = e.range.getColumn();
  let sheetName = e.source.getActiveSheet().getName();
  if (excludedSheets.includes(sheetName)) return;
  let name = e.value;
  let oldName = e.oldValue;
  let sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
  let mask = JSON.stringify(sh.getRange(row, colShift+1, 1, col-colShift).getValues()[0]);
  let colMax = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(dataSheet).getLastColumn();
  
  if(name !== oldName && row >= rowMin && col < colMax+colShift) {
    insertDateFormulasAndDataValidation(row, col, sheetName)
    fillColumn(row, col, mask, sheetName);
  }
}

function insertDateFormulasAndDataValidation(row, col, sheetName) {

  if (col == colShift+1) {
    let sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
    let dateValue = sh.getRange(row, 2).getValue();
    if (dateValue == '') {
      let today = new Date();
      let timeZone = Session.getScriptTimeZone();
      let todayStr = Utilities.formatDate(today, timeZone, 'dd.MM.yyyy');
      sh.getRange(row, 2).setValue(todayStr);
    };
    let formulaStr = '=I' + (row-1) + '+H' + row + '-C' + row; 
    sh.getRange(row, 9).setFormula(formulaStr);
    let conN = String.fromCharCode(65+colShift) 
    let adress = sheetName + '!' + conN + (row+1) + ':' + conN + (row+3);
    sh.getRange(adress).setDataValidation(SpreadsheetApp.newDataValidation()
      .setAllowInvalid(false)
      .requireValueInRange(sh.getRange(dataSheet+'!$A$2:$A'), true)
      .build());    
  }
}

function tmp() {
  insertDateFormulasAndDataValidation(7, 4, 'Transactions');
}

function onOpen() {
  let ui = SpreadsheetApp.getUi();
  // Or DocumentApp or FormApp.
  ui.createMenu('Custom Menu')
      .addItem('Create sheets', 'createSheets')
      .addToUi();
}

function fillColumn(row, col, mask, sheetName) {

  let col_data = col - colShift;

// clear dataVal and Value
  let sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
  let colMax = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(dataSheet).getLastColumn();
  sh.getRange(row, col + 1, 1, colMax-col_data).clearDataValidations().clearContent();
 
// find date
  let sd = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(dataSheet);
  let arrData = sd.getDataRange().getValues();
  let arrData_2 = [];
  
  let iMax = arrData.length - 1;
  for(let i=1; i<=iMax; i++) {
    if(JSON.stringify(arrData[i].slice(0, col_data)) == mask) {
      arrData_2.push(arrData[i].slice(0, col_data+1));
    }
  }
  arrData_2 = arrData_2.map(item => item.pop())
  let uniqArrDate_2 = arrData_2.filter(uniqValues); 

// add dataVal
  col++;
  sh.getRange(row, col).setDataValidation(SpreadsheetApp.newDataValidation()
  .setAllowInvalid(false)
  .requireValueInList(uniqArrDate_2, true)
  .build());
}

function uniqValues(item, index, arr) {
  return arr.indexOf(item) === index;
}

Дополнительную информацию вы можете получить из этого видео:

Читать дальше >>

Парсинг (Скрапинг) с помощью Google Apps Script

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

Имя домена берётся со страницы Google Spread Sheets. Туда же выгружаются результаты поиска.

В качестве управляющего скрипта используется функция scraper(), которая содержит два цикла. В первом цикле (for-цикл) идёт обращение к веб-страницам сайта, и их сохранение в переменной html. Во втором цикле (while-цикл) идёт последовательная обработка этой переменной с помощью трёх вспомогательных функций:

  • Функция getBlock находит часть html-кода (блок кода) внутри тега (обычно по уникальному значению атрибутов этого тега), и возвращает этот блок в виде строкового значения (без самого тега!);
  • Функция deleteBlock наоборот, удаляет найденный фрагмент html-кода внутри блока и также возвращает оставшуюся часть этого блока в виде строкового значения.
  • В отличии от первых двух функций, функция getOpenTag не удаляет найденный тег, а возвращает его в виде строкового знечения. Правде, не весь тег, а только первую (открывающую часть) этого тега.

function scraper() {
  
  const ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
  const urlDomain = ss.getRange(1, 1).getValue();
  
  let url = urlDomain;
  let count = 0;
  for (let page = 1; page < 5; page++) {
    url = urlDomain + page + '/';
    if (page == 1) url = urlDomain;
    let response = UrlFetchApp.fetch(url);
    ss.getRange(2, 1).setValue(response.getResponseCode());
    let html = response.getContentText();
    let p = 0;
    while (true) {
    
      let out = getBlock(html, 'div', html.indexOf('class="JobSearchCard-primary"', p));
      let block = out[0];
      p = out[1] + 1;
      if (p == 0) break;
      
      let title1 = getBlock(block, 'div', 0)[0];
      let title = getBlock(title1, 'a', 0)[0];
      
      let link = getOpenTag(title1, 'a', 0);
      link = getAttrName(link, 'href', 0)
      let formula = '=HYPERLINK("https://www.freelancer.com' +link + '", "' + title + '")';
      ss.getRange(3 + 3 * count, 2).setValue(formula);
      
      let price = getBlock(block, 'div', block.indexOf('class="JobSearchCard-primary-price'))[0];
      if (price.includes('span')) price =  deleteBlock(price, 'span', price.indexOf('span'));
      ss.getRange(3 + 3 * count + 1, 2).setValue(price).setHorizontalAlignment('right');
      
      let description = getBlock(block, 'p', block.indexOf('class="JobSearchCard-primary-description"'))[0];
      ss.getRange(3 + 3 * count, 1, 3).mergeVertically().setValue(description)
          .setBorder(true, true, true, true, null, null, '#000000', SpreadsheetApp.BorderStyle.SOLID)
          .setVerticalAlignment('middle')
          .setWrapStrategy(SpreadsheetApp.WrapStrategy.WRAP);
  
      ss.getRange(3 + 3 * count, 2, 3).setBorder(true, true, true, true, null, null, '#000000', SpreadsheetApp.BorderStyle.SOLID);
      
      let cat = getBlock(block, 'div', block.indexOf('class="JobSearchCard-primary-tags"'))[0];
      cat = cat.split('</a>').map(item => item.split('>')[1]);
      cat.pop();
      cat = cat.join(', ');
      ss.getRange(3 + 3 * count + 2, 2).setValue(cat);
    
      count++;
    
    }; 
  };
}

function getAttrName(html, attr, i) {
  let idxStart = html.indexOf(attr +'=' , i);
  if (idxStart == -1) return "Can't to find attr " + attr + ' !';
  idxStart = html.indexOf('"' , idxStart) + 1;
  let idxEnd = html.indexOf('"' , idxStart);
  return html.slice(idxStart,idxEnd).trim();
}

function getOpenTag(html, tag, idxStart) {
  let openTag = '<' + tag;
  let lenOpenTag = openTag.length;
  // where we are?
  if (html.slice(idxStart, idxStart + lenOpenTag) != openTag) {
    idxStart = html.lastIndexOf(openTag, idxStart);
    if (idxStart == -1) return "Can't to find openTag " + openTag + ' !';
  };
  // begin loop after openTag
  let idxEnd = html.indexOf('>', idxStart) + 1;
  if (idxStart == -1) return "Can't to find closing bracket '>' for openTag!";
  return html.slice(idxStart,idxEnd).trim();
}

function deleteBlock(html, tag, idxStart) { // delete opening & closing tag and info between them
  let openTag = '<' + tag;
  let lenOpenTag = openTag.length;
  let closeTag = '</' + tag + '>';
  let lenCloseTag = closeTag.length;
  let countCloseTags = 0;
  let iMax = html.length;
  let idxEnd = 0;
  // where we are?
  if (html.slice(idxStart, idxStart + lenOpenTag) != openTag) {
    idxStart = html.lastIndexOf(openTag, idxStart);
    if (idxStart == -1) return ["Can't to find openTag " + openTag + ' !', -1];
  };
  // begin loop after openTag
  let i = html.indexOf('>') + 1;
  
  while (i <= iMax) {
    i++;
    if (i === iMax) {
      return ['Could not find closing tag for ' + tag, -1];
    };
    let carrentValue = html[i];
    if (html[i] === '<'){
      let closingTag = html.slice(i, i + lenCloseTag);
      let openingTag = html.slice(i, i + lenOpenTag);
      if (html.slice(i, i + lenCloseTag) === closeTag) {
        if (countCloseTags === 0) {
          idxEnd = i + lenCloseTag;
          break;
        } else {
          countCloseTags -= 1;
        };
      } else if (html.slice(i, i + lenOpenTag) === openTag) {
        countCloseTags += 1;
      };
    };
  };
  return (html.slice(0, idxStart) + html.slice(idxEnd, iMax)).trim();
}

function getBlock(html, tag, idxStart) {  // <tag .... > Block </tag>
  let openTag = '<' + tag;
  let lenOpenTag = openTag.length;
  let closeTag = '</' + tag + '>';
  let lenCloseTag = closeTag.length;
  let countCloseTags = 0;
  let iMax = html.length;
  let idxEnd = 0;
  // where we are?
  if (html.slice(idxStart, idxStart + lenOpenTag) != openTag) {
    idxStart = html.lastIndexOf(openTag, idxStart);
    if (idxStart == -1) return ["Can't to find openTag " + openTag + ' !', -1];
  };
  // change start - will start after openTag!
  idxStart = html.indexOf('>', idxStart) + 1;
  let i = idxStart;
  
  while (i <= iMax) {
    i++;
    if (i === iMax) {
      return ['Could not find closing tag for ' + tag, -1];
    };
    let carrentValue = html[i];
    if (html[i] === '<'){
      let closingTag = html.slice(i, i + lenCloseTag);
      let openingTag = html.slice(i, i + lenOpenTag);
      if (html.slice(i, i + lenCloseTag) === closeTag) {
        if (countCloseTags === 0) {
          idxEnd = i - 1;
          break;
        } else {
          countCloseTags -= 1;
        };
      } else if (html.slice(i, i + lenOpenTag) === openTag) {
        countCloseTags += 1;
      };
    };
  };
  return [html.slice(idxStart,idxEnd + 1).trim(), idxEnd];
}

Более продробную информацию вы сможете найти в этом видео:

Читать дальше >>

Массивы Google Apps Script и JavaScript. Методы поиска

В примерах, приведённых ниже, подробно рассматриваются JavaScript и Apps Script методы поиска элементов массивов : find(), findIndex(), indexOf(), lastIndexOf(), includes(), every() и some().

Методы findIndex(), indexOf() и lastIndexOf() позволяют найти индекс массива, удовлетворяющий заданному условию, а метод find() позволяет найти его значение.

Метод includes() удобен для использования в операторе if , поскольку возвращает true, если элемент найден, и false, если нет.

Метод every() проверяет соответствие ВСЕХ элементов массива указанному в callback функции условию, а метод some() проверяет, удовлетворяет ли этому условию ХОТЯ БЫ ОДИН элемент массива.

const arr = [1, 2, 3, 4, 5, 3];
  
// find() returns the VALUE of the FIRST item satisfies condition of the callback function
// OR return <underfined>

  let valueGteater3 = arr.find(item => item > 3);
  console.log(valueGteater3); // 4
  
  let valueGteater5 = arr.find(item => item > 5);
  console.log(valueGteater5); // undefined

// findIndex() returns the INDEX of the FIRST item satisfies condition of the callback function
// OR return <-1>

  let indexItemGteater3 = arr.findIndex(item => item > 3);
  console.log(valueGteater3); // 3
  
  let indexItemGteater5 = arr.findIndex(item => item > 5);
  console.log(valueGteater5); // -1
  
// variant for not first item  
  
  let notFirstItem = arr.find((item, index) => {
    if (item > 3 && index > 3) return true;
  });
  console.log(notFirstItem); // 5
  
// indexOf() returns the INDEX of the FIRST item is equal the specified value
// OR return <-1>

  let valueEqual3 = arr.indexOf(3);
  console.log(valueEqual3); // 2
  
  let valueEqual3Next = arr.indexOf(3, 3);
  console.log(valueEqual3Next); // 5
 
  let valueEqual1 = arr.indexOf(1, 3);
  console.log(valueEqual1); // -1
  
// lastIndexOf() returns the INDEX of the LAST item is equal the specified value
// OR return <-1>

  let valueEqual3Last = arr.lastIndexOf(3);
  console.log(valueEqual3Last); // 5
  
  let valueEqual3NextLast = arr.lastIndexOf(3, 4);
  console.log(valueEqual3NextLast); // 2
 
  let valueEqual5Last = arr.lastIndexOf(5, 3);
  console.log(valueEqual5Last); // -1
  
// const arr = [1, 2, 3, 4, 5, 3];
// includes() returns TRUE is element is icluded in the array, and FALSE is not

  let isIncludes3 = arr.includes(3);
  console.log(isIncludes3); // true
  
  let isIncludes6 = arr.includes(6);
  console.log(isIncludes6); // false
  
// every() returns TRUE if ALL elements satisfy the condition, and FALSE is not

  let allPositive = arr.every(item => item > 0);
  console.log(allPositive); // true
  
  let moreThen1 = arr.every(item => item > 1);
  console.log(moreThen1); // false

// some() returns TRUE if AT LEAST ONE element satisfy the condition, and FALSE is not

  let moreThen4 = arr.some(item => item > 4);
  console.log(moreThen4); // true
  
  let moreThen5 = arr.some(item => item > 5);
  console.log(moreThen5); // false

Более продробную информацию вы сможете найти в этом видео:

Читать дальше >>

Массивы Google Apps Script и JavaScript. Метод .reduce()

Краткое описание метода

Метод .reduce() как и большинство других методов, использующих callback функцию, представляет собой цикл for, внутри которого callback функция последовательно обрабатывает все элементы массива. Однако, следует добавить, что что это единственный метод, который используе ещё один параметр - accumulator (аккумулятор), блягодаря чему метод .reduce() часто используется для вычислений суммы массива.

Метод .reduce() может передавать в callback функцию 4 параметра:

  1. текщее значение суммы элемента массива accumulator
  2. текщее значение элемента массива item
  3. необязательный параметр index - индекс текущего элемента
  4. необязательный и редко используемый параметр array

Этот метод может быть использован и для более сложных вычислений. Например, ниже приводится скрипт, который создаёт массив индексов элементов массива arr, представлящих собой нечётные числа.

const arr = [1, 2, 3, 4, 5];
  
// sum with for loop
  let sumArr = 0;
  for (let i = 0; i < arr.length; i++) {
    sumArr += arr[i];
  };
  console.log(sumArr);
  
// sum with forEach method
  let sumArr_1 = 0;
  arr.forEach(item => sumArr_1 += item);
  console.log(sumArr_1);
  
// sum with reduce method
  let sumArr_2 = arr.reduce((acc, item) => acc + item, 0);
  console.log(sumArr_2);
  
// with initial value
  sumArr_2 = arr.reduce((acc, item, index, array) => {
    console.log(`acc = ${acc}, item = ${item}, index = ${index}`);
    return acc + item;
  }, 0);
  console.log(sumArr_2);
  
// without initial value
  sumArr_2 = arr.reduce((acc, item, index, array) => {
    console.log(`acc = ${acc}, item = ${item}, index = ${index}`);
    return acc + item;
  });
  console.log(sumArr_2);
  
// find indexes items that are odd numbers  
  let newArr = arr.reduce((acc, item, index) => {
    if (item % 2 !== 0) acc.push(index);
    return acc;
  }, []);
  console.log(newArr); // [ 0, 2, 4 ]
  
 // reduceRight
  sumArr_2 = arr.reduceRight((acc, item, index, array) => {
    console.log(`acc = ${acc}, item = ${item}, index = ${index}`);
    return acc + item;
  });
  console.log(sumArr_2);

Дополнительную информацию вы можете найти в этом видео:

Читать дальше >>

Массивы Google Apps Script и JavaScript. Метод .forEach()

Краткое описание метода

Метод .forEach() , по сути, представляет собой цикл for, внутри которого callback функция последовательно обрабатывает все элементы массива.

Метод .forEach() может передавать в callback функцию три параметра:

  1. текщее значение элемента массива item
  2. необязательный параметр index - индекс текущего элемента
  3. необязательный и редко используемый параметр array

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

В частности, в последнем примере с помощью методов .forEach() и .includes() создаётся новый массив result, который содержит в себе общие элементы массивов arr1 и arr2, то есть является результатом пересечения этих массивов.

const arr = [1, 2, 3, 4, 5];
  
  for (let i = 0; i < arr.length; i++) {
    console.log(arr[i]);
  };

// variant #1
  arr.forEach(function(item) {
    console.log(item);
  });

// variant #2
  arr.forEach(item => console.log(item));
  
  console.log('------------------------------------');
  
// callback function parameters
  arr.forEach((item, idx, array) => console.log(item, idx, array));

  console.log('------------------------------------');

// get intersection of arrays
  let arr1 = [1, 3, 5];
  let arr2 = [1, 2, 3, 4, 5];
  let result = [];

  let res = arr1.forEach(item => {
    if (arr2.includes(item)) return result.push(item);
  });

  console.log(result); // [ 1, 3, 5 ]
  console.log(res); // undefined

Дополнительную информацию вы можете найти в этом видео:

Читать дальше >>

Массивы Google Apps Script и JavaScript. Метод .filter()

Краткое описание метода

Метод .filter() последовательно сравнивает все элементы массива с условием фильтра, указанным в callback функции (функции обратного вызова). То есть метод, по сути, представляет собой цикл for, внутри которого callback функция последовательно обрабатывает все элементы массива.

Метод .filter() может передавать в callback функцию три параметра:

  1. текщее значение элемента массива item
  2. необязательный параметр index - индекс текущего элемента
  3. необязательный и редко используемый параметр array

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

Все элементы, для которых callback функция вернула знечение true, собираются в отдельный массив, который может быть сохранён в новом массива (в примерах ниже это массив result2).

В трёх вариантов примера, представленных ниже (examples #1, #2, #3) рассмотрено одно и то же условие фильтра - отобрать все элементы массива со значением > 3.

В последнем примере (наиболее оптимальным с точки зрения краткости кода) логическое выражение, вместо оператора if , поставлено непосредственно в return . Поскольку, как известно, логическое выражение может принимать только 2 значения: true или false.

const arr = [1, 2, 9, 4, 1, 6, 5];
    
    let result = arr.filter((item, idx, arr) => console.log(item, idx, arr));
//  [20-06-15 14:53:21:082 EEST] 1 0 [ 1, 2, 9, 4, 1, 6, 5 ]
//  [20-06-15 14:53:21:084 EEST] 2 1 [ 1, 2, 9, 4, 1, 6, 5 ]
//  [20-06-15 14:53:21:085 EEST] 9 2 [ 1, 2, 9, 4, 1, 6, 5 ]
//  [20-06-15 14:53:21:086 EEST] 4 3 [ 1, 2, 9, 4, 1, 6, 5 ]
//  [20-06-15 14:53:21:087 EEST] 1 4 [ 1, 2, 9, 4, 1, 6, 5 ]
//  [20-06-15 14:53:21:089 EEST] 6 5 [ 1, 2, 9, 4, 1, 6, 5 ]
//  [20-06-15 14:53:21:090 EEST] 5 6 [ 1, 2, 9, 4, 1, 6, 5 ]
    
    console.log(result);
//  [20-06-15 14:53:21:091 EEST] []  

// variant #1
  let result2 = arr.filter(item => {
      if (item > 3) {
        return true;
      } else {
        return false;
      };
    });
    
// variant #2
    result2 = arr.filter(item => {
      return item > 3;
    });


// variant #3
    result2 = arr.filter(item => item > 3);
    
    console.log(result2); // [ 9, 4, 6, 5 ]
    
    var sf = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
    let data = sf.getDataRange().getValues().slice(1);
    
    console.log(data);
//  [ [ '', false, 'apples', 5 ],
//    [ '', true, 'carrots', 12 ],
//    [ '', false, 'grapes', 4 ],
//    [ '', false, 'plums', 3 ],
//    [ '', true, 'strawberry', 9 ],
//    [ '', false, 'perches', 4 ],
//    [ '', false, 'bananas', 1 ] ]   

    let newData = data.filter(item => item[1]);

    console.log(newData); 
// [ [ '', true, 'carrots', 12 ], 
//   [ '', true, 'strawberry', 9 ] ]

И ещё одни полезный скрипт, который не вошёл в это видео - скрипт, который фильтрует ТОЛЬКО уникальные значения.

Идея очень проста: для каждого элемента массива находится его индекс и сравнивается с текущим индексом значения этого элемента в массиве. Если элемент не встретился ранее, то значения индексов будут равны, и функция uniqValue вернёт true .

В первом примере, для лучшего понимания алгоритма, функция uniqValue написана отдельно. Во втором - непосредственно "встроена" в метод .filter

let arrayNotUniq = [1, 5, 9, 5];

//variant #1

function uniqValues(item, index, arr) {
  return arr.indexOf(item) === index;
}

let arrayUniq = arrayNotUniq.filter(uniqValues)                        // [1, 5, 9]



//variant #2

  let arrayUniq = arrayNotUniq.filter((item, index, arr) => {
    return arr.indexOf(item) === index;
  });                                                                                                     // [1, 5, 9]

Дополнительную информацию вы можете найти в этом видео:

Читать дальше >>

Массивы Google Apps Script и JavaScript. Метод .map()

Краткое описание метода

Метод .map() последовательно перебирает все элементы массива. То есть, по сути, представляет из себя цикл for, где переменная цикла изменяется от индекса первого элемента до последнего.

Этот метод может передавать в callback функцию 3 элемента:

  1. текщее значение элемента массива currentValue (в примере #2 переменная x)
  2. необязательный параметр index - индекс текущего элемента (в примере #2 переменная y)
  3. необязательный и редко используемый параметр array (в примере #2 переменная z)

В примере #1 приводится умножение кажного элемента массива на 2. То есть callback функция каждый раз возвращает элемент массива, умноженный на 2.

В примере #2 проводится исследования параметров метода. Поэтому callback функция ничего не возращет, а только выводит на печать передаваемые её параметры. В каждом цикле это: значение элемента массива, его индекс и сам массив.

var arr = [1, 2, 3];

  // example #1
  var arr2 = arr.map(x => x * 2); // [ 2, 4, 6 ]

  // example #2
  var arr2 = arr.map((x, y, z)  => {
    console.log(x + "|" + y + "|" + z);
  }); 
 
//  1|0|1,2,3
//  2|1|1,2,3
//  3|2|1,2,3

Рефакторинг кода с помощью метода .map()

Скрипты, представленные ниже, обрабатывают один и тот же массив двумя способами:

1.) с помощью цикла for

var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var arrM = ss.getRange(1, 1, 10).getValues(); // [[10. Will Smith: $35 million], [9. Paul Rudd: $41 million], [8. Chris
  var arrW = ss.getRange(14, 1, 10).getValues(); // [[10. Ellen Pompeo: $22 million], [9. Charlize Theron: $23 million], [8. 
  
  var arr = arrM.concat(arrW);
  
  for (var i = 0; i < arr.length; i++) {
    arr[i][0] = arr[i][0].replace(":", "").replace("$", "").replace(" (tie)", "(tie)").replace(" Jr", "Jr"); // 
    arr[i] = arr[i][0].split(' '); // [10., Will, Smith, 35, million]
    arr[i][1] = arr[i][1] + " " + arr[i][2]; // [10., Will Smith, Smith, 35, million]
    arr[i].splice(2, 1); // [10., Will Smith, 35, million]
    if (i < 10) {
      arr[i][3] = 'man';
    } else {
      arr[i][3] = 'woman';
    };
  };
  
  arr.sort(function(a, b) {
    return b[2] - a[2];  
  });

  for (var i = 0; i < arr.length; i++) {
    arr[i][0] = i + 1;
    Logger.log(arr[i]);
  };
  
  ss.getRange(2, 6, arr.length, arr[0].length).setValues(arr);

2.) и с помощью метода .map():

var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var arrM = ss.getRange(1, 1, 10).getValues(); // [[10. Will Smith: $35 million], [9. Paul Rudd: $41 million], [8. Chris
  var arrW = ss.getRange(14, 1, 10).getValues(); // [[10. Ellen Pompeo: $22 million], [9. Charlize Theron: $23 million], [8. 
  
  var arr = arrM.concat(arrW);
  
  var arr2 = arr.map(x => x[0].replace(":", "").replace("$", "")
                              .replace(" (tie)", "(tie)").replace(" Jr", "Jr")
                              .split(' '));  // [ [ '10.', 'Will', 'Smith', '35', 'million' ],
  
  arr = arr2.map(x => [x[2], x[1] + ' ' + x[2], x[3], x[4]]);
  arr = arr.map((x, i) => {
    (i < 10) ? x[3] = 'man' : x[3] = 'woman' ;
    return x;
  });   //  [ [ 'Smith', 'Will Smith', '35', 'man' ],
  
  arr.sort((a, b) => a[0].localeCompare(b[0])); // [ [ '1.', 'Dwayne Johnson', '89.4', 'man' ],
  
  arr = arr.map((x, i) => {
    x[0] = i + 1;
    return x;
  });
  
 
  console.log(arr);
  ss.getRange(2, 6, arr.length, arr[0].length).setValues(arr);

Дополнительную информацию вы можете найти в этом видео:

Читать дальше >>

Массивы Google Apps Script и JavaScript. Метод сортировки массивов .sort()

Предисловие.

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

Higher-order functions (Функции более высокого порядка) - называются функции, использующие в качестве аргументов и/или возвращаемых объектов функции первого класса. (Пример - функция .sort)

First-class functions (Функции первого класса) используются в качестве аргументов и/или возвращаемых объектов для функций более высокого порядка. (Пример - функции, являющиеся аргументами для функции .sort)

const arr = [1, 2, 3, 10, 20];
  
// ========== Higher-order functions ===============

  //arr.sort(sortFunction)
  
// ========== First-class functions ================

  // function definition 
  function twoTimes(x) {
    return x * 2;
  }; 
  
  // another function definition
  var twice;
  twice = function(x) {return x * 2};
  
  // arrow function
  twice = (x) => x * 2;
  
  // arrow function with 1 argument
  twice = x => x * 2;
  
  console.log(twice(3));
  console.log(typeof twice);
  
  // 1.) function as argument another function
  var fourTimes = (x, twice) => twice(x);
  
  console.log(fourTimes(3, twice)); // 6
  
  //  2.) return function
  var fourTimes = (x) => x * 4;
  
  var xTimes = (x, order) => {
    if (x / order == 4) {
      return fourTimes;
    } else if (x / order == 2) {
      return twice;
    };
  };
  
  var y = xTimes(12, 3); // 8

  console.log(y(2)); // =6
  console.log('y is ' + typeof y); // y is function

Примеры сортировки массивов

// numbers sorting
  
  const arr = [1, 2, 10, 20];
  
  arr.sort((a, b) => a - b); // [ 20, 10, 2, 1 ]
  arr.sort((a, b) => b - a); // [ 1, 2, 10, 20 ]
  
  // string sorting
  
  const arr1 = ['a', 'b', 'A', 'B', 'г', 'Г', 'д', 'Д']; 
  
  arr1.sort();                             // [ 'A', 'B', 'a', 'b', 'Г', 'Д', 'г', 'д' ]
  arr1.sort((a, b) => a.localeCompare(b)); // [ 'a', 'A', 'b', 'B', 'г', 'Г', 'д', 'Д' ]
  arr1.sort((a, b) => b.localeCompare(a)); // [ 'Д', 'д', 'Г', 'г', 'B', 'b', 'A', 'a' ]
  
  
// 2-dimensional array
  
    var arr_2 = [[1, 5, 6],
                 [3, 7, 9],
                 [9, 2, 1]];
                 
    arr_2.sort((a, b) => b[1] - a[1]); // [ [ 3, 7, 9 ], 
                                       //   [ 1, 5, 6 ], 
                                       //   [ 9, 2, 1 ] ]

// JSON objects

  var actors = [
    {name: 'Dwayne Johnson', income:	89.4},	
    {name: 'Chris Hemsworth', income: 76.4},
    {name: 'Robert DowneyJr.', income: 66},
    {name: 'Akshay Kumar', income: 65},
    {name: 'Jackie Chan', income: 58},
  ];
  
  actors.sort((a, b) => a.income - b.income); 
                   //[ { name: 'Jackie Chan', income: 58 },
                   //  { name: 'Akshay Kumar', income: 65 },
                   //  { name: 'Robert DowneyJr.', income: 66 },
                   //  { name: 'Chris Hemsworth', income: 76.4 },
                   //  { name: 'Dwayne Johnson', income: 89.4 } ]
                
  actors.sort((a, b) => a.name.localeCompare(b.name)); 
                  // [ { name: 'Akshay Kumar', income: 65 },
                  //   { name: 'Chris Hemsworth', income: 76.4 },
                  //   { name: 'Dwayne Johnson', income: 89.4 },
                  //   { name: 'Jackie Chan', income: 58 },
                  //   { name: 'Robert DowneyJr.', income: 66 } ]

Дополнительную информацию вы можете найти в этом видео:

Читать дальше >>

Создание массивов Apps Script. Добавление, Удаление и Изменение Элементов Массивов

Массивы в Apps Script можно создать несколькими способами:

  1. Декларировать в строке кода имя массива и приравнять его к последовательности значений, взятых в квадратные скобки;
  2. Считать данные с листа Google SpreadSheet помощью метода .getValues() ;
  3. Разделив строку с помощью метода .split([delimiter]) (обычно в качестве разделителя delimiter используется пробел;
  4. Пп 3, когда в качестве строки используется слово, а в качестве разделите - пустая строка.

function createArray() {
  
// ======== HOW TO CREATE ARRAY? ===========

  // 1.) Write
  var arr = [1, 2, 3];
  
  // 2.) Read from spreadsheet
  var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var arr1 = ss.getDataRange().getValues(); // [[1.0, 2.0, 3.0]]
  
  // 3.) Using string splitting
  var string = 'I am learning Google Spreadsheet';
  var arr2 = string.split(' '); // [I, am, learning, Google, Spreadsheet]
  
  // 4.) Using string splitting
  var arr3 = 'word'.split(''); // [w, o, r, d]
  
}

Добавлять элементы в массив можно разными способами:

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

  2. Если максимальное значение индекса будет превышено более, чем на единицу, то всем элементы между старым и новым максимальными индексами автоматически присваивается значение null.

  3. Добавление элементов в конец массива с помощью метода .push(value) ;
  4. Добавление элементов в начало массива с помощью метода .unshift(value) ;
  5. Удаление элемента с максимальны индексом .pop() ;

  6. Значение удаляемого элемнта можно при этом присвоить новой переменной.

  7. Удаление элемента с нулевым индексом .shift() ;
  8. Адресное изменение элементов массива с помощью метода .splice(index, hawManyDelete, whatInsert) ;

  9. где:
    • index - номер индекса, с которого будет производиться вставка / удаление индексов;
    • hawManyDelete - число элементов, которые необходимо удалить. Если hawManyDelete = 0, удаления не будет;
    • whatInsert - значене элемента (элементов), который (которые) будет вставлены, начиная в индекса номер index .
  10. Метод изменения порядка индексов в массиве с прямого на обратный и наоборот.reverse() ;
  11. Метод сортировки элементов массива .sort() . Без параметра в виде функции используется крайне редко из-за своей ограниченности: сортирует только строки в юникоде по возрастанию.

function addDeleteItems() {

// ================ ADD ITEMS =======================

  // 1.) [index]
  var arr4 = [1, 2, 3];
  arr4[3] = 4; // [1.0, 2.0, 3.0, 4.0]
  arr4[5] = 6; // [1.0, 2.0, 3.0, 4.0, null, 6.0]
  arr4[-1] = 0; // [1.0, 2.0, 3.0, 4.0, null, 6.0]
  
  // 2.) .push(value)
  arr4.push(7); //  [1.0, 2.0, 3.0, 4.0, null, 6.0, 7.0]
  
  // 3.) .unshift(value)
  arr4.unshift(0) // [0.0, 1.0, 2.0, 3.0, 4.0, null, 6.0, 7.0]
  
// ================= DEL ITEMS ======================

  // 4.) .pop()
  var x = arr4.pop(); // [0.0, 1.0, 2.0, 3.0, 4.0, null, 6.0]
  
  // 5.) .shift()
  var x = arr4.shift(); // [1.0, 2.0, 3.0, 4.0, null, 6.0]
  
// ===== ADD (INSERT), DELETE, CAHGE =================

  // 6.) .splice(index, hawManyDelete, whatInsert)
  arr4.splice(4, 0, 5); // [1.0, 2.0, 3.0, 4.0, 5.0, null, 6.0]
  arr4.splice(5, 1);    // [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]

// ============ CHANGE ORDER =========================
  // 7.) .reverse()
  arr4.reverse(); // [6.0, 5.0, 4.0, 3.0, 2.0, 1.0]
  
  // 8.) .sort()
  arr4 = [6.0, 5.0, 404.0, 41.0, 4.0, 3.0, 2.0, 1.0];
  arr4.sort();
}

Больше информации и примеров вы сможете найти в этом видео:

Читать дальше >>

Список тэгов

    Apps Script      Arrays Java Script      asynchronous code      asyncio      coroutine      Django      Dropdown List      Drop Shipping      Exceptions      GitHub      Google API      Google Apps Script      Google Docs      Google Drive      Google Sheets      multiprocessing      Parsing      Python      regex      Scraping      ssh      Test Driven Development (TDD)      threading      website monitoring      zip