Подтвердить что ты не робот

Какова альтернатива Array.prototype.filter() на месте

У меня есть массив, из которого я хотел бы удалить некоторые элементы. Я не могу использовать Array.prototype.filter(), потому что я хочу изменить массив на месте (поскольку он сохраняет выделение памяти и, что более важно для меня, делает код более простым в моем случае использования). Есть ли альтернатива альтернативе filter которую я могу использовать, возможно, аналогично тому, как Array.prototype.forEach() может использоваться как вариант на месте в Array.prototype.map()?

Изменение: Минимальный пример по запросу:

function someCallback(array) {
  // do some stuff
  array.filterInPlace(function(elem) {
    var result = /* some logic */
    return result;
  })
  // do some more stuff
}
4b9b3361

Ответ 1

Есть ли альтернатива фильтру?

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

function filterInPlace(a, condition) {
  let i = 0, j = 0;

  while (i < a.length) {
    const val = a[i];
    if (condition(val, i, a)) a[j++] = val;
    i++;
  }

  a.length = j;
  return a;
}

condition имеет ту же подпись, что и обратный вызов, передаваемый Array#filter, а именно (value, index, array). Для полной совместимости с Array#filter вы также можете принять четвертый параметр thisArg.

Использование forEach

Использование forEach имеет второстепенное преимущество в том, что он пропускает пустые слоты. Эта версия:

  • Компактные массивы с пустыми слотами
  • Реализует thisArg
  • Пропускает назначение, если мы еще не сталкивались с неисправным элементом

function filterInPlace(a, condition, thisArg) {
  let j = 0;

  a.forEach((e, i) => { 
    if (condition.call(thisArg, e, i, a)) {
      if (i!==j) a[j] = e; 
      j++;
    }
  });

  a.length = j;
  return a;
}

a = [ 1,, 3 ];
document.write('<br>[',a,']');

filterInPlace(a, x=>true);
document.write('<br>[',a,'] compaction when nothing changed');

b = [ 1,,3,,5 ];
document.write('<br>[',b,']');

filterInPlace(b, x=>x!==5);
document.write('<br>[',b,'] with 5 removed');

Ответ 2

Вы можете использовать следующее:

array.splice(0, array.length,...array.filter(/*YOUR FUNCTION HERE*/))

Объяснение:

  • Склеивание действует на месте
  • Первый аргумент означает, что мы начинаем с начала массива
  • Второй означает, что мы удалим весь массив
  • В-третьих, мы заменяем его отфильтрованной копией
  • ... является оператором распространения (только для ES6) и изменяет каждый член массива на отдельный аргумент

Ответ 3

Что вы можете использовать

  • filter возвращает массив с теми же элементами, но не обязательно все.
  • map возвращает что-то для каждого цикла, результатом является массив с той же длиной, что и исходный массив
  • forEach ничего не возвращает, но каждый элемент - это процессы, как описано выше.
  • reduce возврат, что хотите.
  • some/every возвращает логическое значение

Но ничто из выше не искажает исходный массив, о котором идет речь о длине in situ.

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

Это сохраняет индекс действительным и позволяет декремент для каждого цикла.

Пример:

var array = [0, 1, 2, 3, 4, 5],
    i = array.length;

while (i--) {
    if (array[i] % 2) {
        array.splice(i, 1);
    }
}
console.log(array);

Ответ 4

Если вы можете добавить стороннюю библиотеку, посмотрите на lodash.remove:

predicate = function(element) {
  return element == "to remove"
}
lodash.remove(array, predicate)

Ответ 5

Немного упрощенный вариант TypeScript ответа user663031:

function filter_in_place<T>(array: Array<T>, condition: (value: T) => boolean)
{
    let next_place = 0;

    for (let value of array)
    {
        if (condition(value))
            array[next_place++] = value;
    }

    array.splice(next_place);
}

Использование splice() вместо установки length приводит к ускорению в 1,2 раза для итераций 1400000 в Chrome 76.

Ответ 6

Текущий выбранный ответ работает отлично. Однако я хотел, чтобы эта функция была частью прототипа Array.

Array.prototype.filterInPlace = function(condition, thisArg) {
    let j = 0;

    this.forEach((el, index) => {
        if (condition.call(thisArg, el, index, this)) {
            if (index !== j) {
                this[j] = el;
            }
            j++;
        }
    })

    this.length = j;
    return this;
}

С этим я могу просто вызвать функцию так:

const arr = [1, 2, 3, 4];
arr.filterInPlace(x => x > 2);
// [1, 2]

Я просто храню это в файле с именем Array.js и требую его при необходимости.