Как оптимизировать нерекурсивный алгоритм сглаживания - PullRequest
0 голосов
/ 30 мая 2018

Я работаю в среде скриптов Google Apps, чтобы создать коннектор сообщества для Google Data Studio.Идея этого состоит в том, чтобы вызвать внешний API и проанализировать данные таким образом, чтобы Data Studio могла правильно читать диаграмму или таблицу из.

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

{
  campaigns: [{
    name_campaign: Example_Name,
    ads: [
      {cost: 15.98}
    ],
    budget: 237.59
  },
  {
    name_campaign: Another_Name,
    ads: [
      {cost: 98.25},
      {cost: 13.03},
      {cost: 23.44}
    ],
    budget: 15.50
  },
  ...
}

на то, что ожидает Data Studio, что является плоским набором данных ...

[
  [237.59, Example_Name, 15.98],
  [15.50, Another_Name, 98.25],
  [15.50, Another_Name, 13.03],
  [15.50, Another_Name, 23.44]
]

Хитрость в том, что, поскольку вызов API создается динамически, невозможно узнать, сколько будет полей или сколько будет различных объектов.Возможно, что есть только один объект с двумя полями или что-то похожее на описанное выше.К счастью, он был ограничен только родителями и детьми, а не братьями и сестрами.

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

function stackIt(data) {
  var totalData = [];

  //create stack with highest object
  var stack = [{
    v: data,
    parent_fields: []
  }];

  var stackLen = stack.length;
  var pushing = 0, totalFields = 0;
  var data_fields, array_field, cl, v, current_fields, temp, arr_len, row, parentFields;

  while (stackLen > 0) {
    //store current node 
    cl = stack.pop();
    if (cl === undefined)
      break;

    v = cl.v;
    parentFields = cl.parent_fields.slice(0);

    //fill new array with all parents
    data_fields = parentFields.slice(0);
    array_field = null;
    current_fields = [];

    //Does another object exist?
    //Keep track of all current fields on the chance that no objects exists below
    for (var field in v) {
      //Hold the current field
      temp = v[field];
      current_fields.push(temp);

      //found an object. So we know we need to move deeper with the current object
      if (typeof(temp) === 'object' && temp != null && array_field == null)
        array_field = field;
      //store current parent fields
      if (typeof(temp) !== "object")
        data_fields.push(temp)
    }

    //Push new node to stack to delve deeper
    //each one with with parent data points to tack on later for deepest level objects
    if (array_field != null) {
      for (var i = 0, arr_len = v[array_field].length; i < arr_len; i++) {
        //Skip broken fields
        if ('errors' in v[array_field][i])
          continue;

        stack.push({
          v: v[array_field][i],
          parent_fields: data_fields
        });
      }      
    }
    //No object exists below
    else {
      row = [];
      //re set data fields if no object was found
      //data_fields is changed in above function on the chance that there is an object below
      data_fields = parentFields.slice(0);

      //get total number of fields that should exist
      //this is to prevent pushing rows with only a subset of fields
      //only do once at deepest object to get total number of fields expected
      if (pushing == 0) {
        pushing = 1;
        totalFields = data_fields.length + current_fields.length;
      }

      //push parents fields (ex. camp.cost)
      row = data_fields.splice(0);

      //push all current fields held (ex. keyword.cost)
      row = row.concat(current_fields);
      //comes out to camp.cost, keyword.cost

      //Push all rows at lowest level; exit if done.
      if (row.length != totalFields) {
        console.log("End Stack"); 
        return totalData;
      }
      else
        totalData.push(row);
    }
  }

  console.log("End Stack with empty stack"); 
  return totalData;
}

Я пытался использовать библиотеку с открытым исходным кодом cFlatten, но это делало уплощение намного медленнее, и большинство инструментов янайденные требуют только 1-2 объекта глубины и требуют специального форматирования для них.Раньше алгоритм был рекурсивным, но, поскольку объем возвращаемых данных настолько велик, он был намного медленнее.Будем весьма благодарны за любые идеи о том, как сделать функцию более быстрой / более эффективной.

...