Удалить дубликаты для большого набора данных, как истинные дубликаты (вся строка повторяется), так и дубликат на основе одного столбца - PullRequest
0 голосов
/ 09 февраля 2020

У меня довольно большой набор данных. Минимальные строки находятся в диапазоне 8K. Мне нужно удалить дубликаты на двух условиях. Первым будет то, что я называю «Истинный дубликат». По определению это означает, что вся строка является дубликатом. Вот сценарий, который у меня есть, который работает для этого сценария.

function removeDuplicates(sheet) {
  var data = sheet.getDataRange().getValues();
  var newData = [];
  var trueDuplicateCount = 0;

  for (var i in data) {
    var row = data[i];
    var duplicate = false;
    for (var j in newData) {
      if (row.join() == newData[j].join()) {//Look for duplicates across all rows. True Duplicate
        duplicate = true;
        trueDuplicateCount = trueDuplicateCount + 1;
      }
    }
    if (!duplicate) {
      newData.push(row);
    }
  }
  sheet.clearContents();
  sheet.getRange(1, 1, newData.length, newData[0].length).setValues(newData);
  return trueDuplicateCount;
}

Другое условие будет дубликатом, основанным на информации одного столбца. После удаления «True Duplicates» мне нужно удалить дубликаты на основе столбца. Я хотел бы сохранить строку с самой ранней датой в другом столбце.

Вот то, что я пытался, но не работает для этого сценария.

function removeDuplicates(sheet) {
  var data = sheet.getDataRange().getValues();
  var newData = [];
  var trueDuplicateCount = 0;
  var diffDateDuplicateCount = 0;

  for (var i in data) {
    var row = data[i];
    var duplicate = false;
    for (var j in newData) {
      if (row.join() == newData[j].join()) {//Look for duplicates across all rows. True Duplicate
        duplicate = true;
        trueDuplicateCount = trueDuplicateCount + 1;
      }
      if(row[1] == newData[j][1] && row[0] > newData[j][0]){
        duplicate = true;
        diffDateDuplicateCount = diffDateDuplicateCount + 1
      }
    }
    if (!duplicate) {
      newData.push(row);
    }
  }
  sheet.clearContents();
  sheet.getRange(1, 1, newData.length, newData[0].length).setValues(newData);
  return [trueDuplicateCount, diffDateDuplicateCount];
}

Вот пример набора данных

enter image description here

после удаления «истинных дубликатов»

enter image description here

После удаления дубликата с более поздней датой

enter image description here

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

Текущий сценарий работает для части True Duplicates, но я меня беспокоит скорость и, возможно, время ожидания для большого набора данных. С 8K рядами я уже вижу почти 3 минуты бега. С учетом сказанного, вот мои вопросы.

Условия

  • Скорость, скорость, скорость. Есть ли более эффективный способ справиться с этим? Это мое самое большое беспокойство.
  • Необходимо удалить дубликаты с более поздней датой и сохранить один с самой ранней датой.
  • Необходимо вернуть счет для каждого типа удаленных дубликатов .

Надеясь, это прояснит ситуацию. Я показал, что я хочу сделать с каждым шагом. (Номер Акта составлен)

enter image description here

Комментарий о выбранном решении

Я выбрал решение, которое выполнен быстрее всех. В то время как Танайке и Мастер работали, я пошел с Мастером, потому что я ожидаю много строк в будущем. Каждая миллисекунда считается.

Я просто хочу поблагодарить тех, кто ответил, особенно Танаике, которые проделали большую работу. Надеюсь, этот вопрос станет святым Граалем для удаления дубликатов, потому что ваше решение не v8 по-прежнему отлично подходит для тех, кто не v8.

Ответы [ 3 ]

2 голосов
/ 09 февраля 2020

Вы можете использовать встроенный метод removeDuplicates, который удалит дубликаты на месте. Используйте объект ha sh, чтобы впоследствии удалить дубликаты даты:

Пример сценария:

function remDups(sheet) {
  let sh = sheet || SpreadsheetApp.getActive().getSheetByName('Sheet1');
  let rg = sh.getRange(2, 1, sh.getLastRow() - 1, 2);
  let initDataSz = rg.getNumRows();
  let newRg = rg.removeDuplicates();
  let newDataSz = newRg.getNumRows();
  //console.info({ initDataSz, newDataSz });
  let trueDups = initDataSz - newDataSz;
  let values = newRg.getValues();
  //newRg.copyTo(sh.getRange('C1'));
  newRg.clearContent();

  let out = Object.entries(
    values.reduce((obj, [date, color]) => {
      let oldDate = (obj[color] = obj[color] || Infinity);
      if (oldDate - date > 0) {
        obj[color] = date;
      }
      return obj;
    }, {})
  ).map(e => e.reverse());
  let falseDups = newDataSz - out.length;
  sh.getRange(2, 1, out.length, out[0].length).setValues(out);
  return [`${trueDups}`, `${falseDups}`];
}

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

  • ~ 2,6 секунды для 15000 строк на движке V8

Ссылки:

1 голос
/ 09 февраля 2020
  • Вы хотите удалить дублирующиеся значения даты и цвета.
  • Вы хотите добиться результата, который отображается в виде изображений в вашем вопросе.
  • Вы хотите чтобы снизить стоимость обработки вашего скрипта Google Apps.

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

Поток:

  1. Извлечение значений из листа.
  2. Создание объекта для извлечения trueDuplicateCount.
  3. Создать объект для извлечения diffDateDuplicateCount.
  4. Создать массив для размещения в электронной таблице.
  5. Поместить значения в электронную таблицу.
  6. Рассчитать trueDuplicateCount и diffDateDuplicateCount.

Пример сценария:

function removeDuplicates(sheet) {
  // var sheet = SpreadsheetApp.getActiveSheet();

  // Retrieve values from the sheet.
  var data = sheet.getDataRange().getValues();
  var header = data.shift(); // Remove the header row.

  // Create an object for retrieving trueDuplicateCount.
  var object1 = data.reduce(function(o, [a, b], i) {
    var key = b + "_" + a.getTime();
    o[key] = key in o ? o[key] + 1 : 1;
    return o;
  }, {});

  // Create an object for retrieving diffDateDuplicateCount.
  var object2 = Object.keys(object1).reduce(function(o, e) {
    var [c, d] = e.split("_");
    d = Number(d);
    o[c] = c in o ? (o[c] > d ? d : o[c]) : d;
    return o
  }, {});

  // Create an array for putting to Spreadsheet.
  var ar = Object.keys(object2).map(function(e) {return [new Date(object2[e]), e]});
  ar.unshift(header);

  // Put the values to the Spreadsheet.
  sheet.clearContents();
  sheet.getRange(1, 1, ar.length, ar[0].length).setValues(ar);

  // Calculate trueDuplicateCount and diffDateDuplicateCount.
  var trueDuplicateCount = data.length - Object.keys(object1).length;
  var diffDateDuplicateCount = Object.keys(object1).length - Object.keys(object2).length;
  return [trueDuplicateCount, diffDateDuplicateCount];
}
  • В приведенном выше сценарии предполагается, что строка заголовка существует в 1-й строке. Если вы не используете строку заголовка, удалите data.shift().
  • . В этом случае значения результата выводятся из 1-й строки, как ваш скрипт. Так что в этом случае строка заголовка очищается. Пожалуйста, будьте осторожны.

Примечание:

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

Добавлено:

Когда можно использовать среду выполнения v8, приведенный выше сценарий можно записать следующим образом.

function removeDuplicates_v8(sheet) {
  // var sheet = SpreadsheetApp.getActiveSheet();

  // Retrieve values from the sheet.
  const data = sheet.getDataRange().getValues();
  const header = data.shift(); // Remove the header row.

  // Create an object for retrieving trueDuplicateCount.
  const object1 = data.reduce((o, [a, b], i) => ({...o, [(b + "_" + a.getTime())]: true}), {});

  // Create an object for retrieving diffDateDuplicateCount and an array for putting to Spreadsheet.
  const ar = Object.entries(Object.keys(object1).reduce((o, e) => {
    let [c, d] = e.split("_");
    d = Number(d);
    return {...o, [c]: new Date(c in o ? (o[c] > d ? d : o[c]) : d)};
  }, {})).map(([a, b]) => [b, a]);

  // Calculate trueDuplicateCount and diffDateDuplicateCount.
  const trueDuplicateCount = data.length - Object.keys(object1).length;
  const diffDateDuplicateCount = Object.keys(object1).length - ar.length;

  // Put the values to the Spreadsheet.
  sheet.clearContents();
  ar.unshift(header);
  sheet.getRange(1, 1, ar.length, ar[0].length).setValues(ar);
  return [trueDuplicateCount, diffDateDuplicateCount];
}
1 голос
/ 09 февраля 2020

Попробуйте:

function removeDuplicates(sh) {
  var v=sh.getDataRange().getValues();
  var u=[];
  var u0=[];
  var t=0;
  var t0=0;
  //var d=0;
  v.forEach(function(r,i){
    var found=false;
    //whole row match
    if(u.indexOf(r.join())==-1) {
      u.push(r.join());
    }else{
      sh.deleteRow(i+1-d++);
      t++;
      found=true;
    }
    if(!found) {
      //one column match setup for date
      var dts=Utilities.formatDate(new Date(r[0]),Session.getScriptTimeZone(), "yyyy/MM/dd");
      if(u0.indexOf(dts)==-1) {
        u0.push(dts);
      }else{
        sh.deleteRow(i+1-d++)
        t0++;
      } 
    }
  });
  return [t,t0];
}

Как вы сказали, это может быть быстрее. Так что попробуйте.

function removeDuplicates(sh) {
  var v=sh.getDataRange().getValues();
  var u=[];
  var u0=[];
  var oA=[]
  var t=0;
  var t0=0;
  var d=0;
  v.forEach(function(r,i){
    var found=false;
    //whole row match
    if(u.indexOf(r.join())==-1) {
      u.push(r.join());
      oA.push(r);
    }else{
      //sh.deleteRow(i+1-d++);
      t++;
      found=true;
    }
    if(!found) {
      //one column match
      var dts=Utilities.formatDate(new Date(r[0]),Session.getScriptTimeZone(), "yyyy/MM/dd");
      if(u0.indexOf(dts)==-1) {
        u0.push(dts);
        oA.push(r);
      }else{
        //sh.deleteRow(i+1-d++)
        t0++;
      } 
    }
  });
  sh.clearContents();
  sh.getRange(1,1,oA.length,oA[0].length).setValues(oA);
  return [t,t0];
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...