Тщательно удаляя N элементов из «круглого» вектора (или, возможно, просто NSMutableArray) - PullRequest
3 голосов
/ 13 января 2011

Представьте себе вектор std: скажем, со 100 вещами на нем (от 0 до 99) в настоящее время. Вы рассматриваете это как петлю. Таким образом, 105-й пункт - это индекс 4; форвард 7 из индекса 98 равен 5.

Вы хотите удалить N элементов после позиции индекса P.

Итак, удалите 5 пунктов после индекса 50; легко.

Или 5 элементов после индекса 99: при удалении 0 пять раз или с 4 по 0, отмечая, что позиция на 99 будет стерта из существования.

Хуже всего, 5 пунктов после индекса 97 - вам приходится иметь дело с обоими способами удаления.

Какой элегантный и солидный подход?

Вот скучная рутина, которую я написал

-(void)knotRemovalHelper:(NSMutableArray*)original
         after:(NSInteger)nn howManyToDelete:(NSInteger)desired
    {

#define ORCO ((NSInteger)[original count])

    static NSInteger kount, howManyUntilLoop, howManyExtraAferLoop;

    if ( ... our array is NOT a loop ... )
            // trivial, if messy...
        {
        for ( kount = 1; kount<=desired; ++kount  )
            {
            if ( (nn+1) >= ORCO )
                return;
            [original removeObjectAtIndex:( nn+1 )];
            }

        return;
        }
    else    // our array is a loop
            // messy, confusing and inelegant. how to improve?
            // here we go...
        {
        howManyUntilLoop = (ORCO-1) - nn;

        if ( howManyUntilLoop > desired )
            {
            for ( kount = 1; kount<=desired; ++kount  )
                [original removeObjectAtIndex:( nn+1 )];
            return;
            }

        howManyExtraAferLoop = desired - howManyUntilLoop;

        for ( kount = 1; kount<=howManyUntilLoop; ++kount  )
            [original removeObjectAtIndex:( nn+1 )];

        for ( kount = 1; kount<=howManyExtraAferLoop; ++kount )
            [original removeObjectAtIndex:0];

        return;
        }

#undef ORCO
    }

Обновление!

Второй ответ InVariant приводит к следующему отличному решению. «начинать с» гораздо лучше, чем «начинать после». Таким образом, процедура теперь использует «начать с». Второй ответ Инварианта приводит к этому очень простому решению ...

N раз, если P <текущий размер удалить P еще удалить 0 </strong>

-(void)removeLoopilyFrom:(NSMutableArray*)ra
    startingWithThisOne:(NSInteger)removeThisOneFirst
    howManyToDelete:(NSInteger)countToDelete
{
// exception if removeThisOneFirst > ra highestIndex
// exception if countToDelete is > ra size

// so easy thanks to Invariant:

for ( do this countToDelete times )
    {
    if ( removeThisOneFirst < [ra count] )
          [ra removeObjectAtIndex:removeThisOneFirst];
    else
          [ra removeObjectAtIndex:0];
    }
}

Обновление!

Toolbox указал на отличную идею работы над новым массивом - супер KISS.

Ответы [ 5 ]

5 голосов
/ 13 января 2011

Вот идея с макушки головы.

Сначала создайте массив целых чисел, представляющих индексы для удаления.Таким образом, «удалить 5 из индекса 97» будет генерировать [97,98,99,0,1].Это можно сделать с применением простого оператора модуля.

Затем отсортируйте этот массив по убыванию, давая [99,98,97,1,0], а затем удалите записи в этом порядке.

Должно работать во всех случаях.

2 голосов
/ 13 января 2011

Нет ничего плохого в двухконтурных решениях, если они читаемы и не делают ничего лишнего. Я не знаю синтаксиса Objective-C, но вот подход с псевдокодом:

endIdx = after + howManyToDelete
if (Len <= after + howManyToDelete) //will have a second loop
    firstloop = Len - after; //handle end in the first loop, beginning in second
else
    firstpass = howManyToDelete; //the first loop will get them all

for (kount = 0; kount < firstpass; kount++)
    remove after+1
for ( ; kount < howManyToDelete; kount++) //if firstpass < howManyToDelete, clean up leftovers
    remove 0

Это решение не использует mod, выполняет расчет пределов за пределами цикла и касается соответствующих выборок по одному разу. Второй цикл for не будет выполнен, если все выборки были обработаны в первом цикле.

Обычный способ сделать это в DSP - использовать кольцевой буфер. Это просто буфер фиксированной длины с двумя связанными счетчиками:

//make sure BUFSIZE is a power of 2 for quick mod trick
#define BUFSIZE 1024
int CircBuf[BUFSIZE];
int InCtr, OutCtr;

void PutData(int *Buf, int count) {
    int srcCtr;
    int destCtr = InCtr & (BUFSIZE - 1); // if BUFSIZE is a power of 2, equivalent to and faster than destCtr = InCtr % BUFSIZE

    for (srcCtr = 0; (srcCtr < count) && (destCtr < BUFSIZE); srcCtr++, destCtr++)
        CircBuf[destCtr] = Buf[srcCtr];
    for (destCtr = 0; srcCtr < count; srcCtr++, destCtr++)
        CircBuf[destCtr] = Buf[srcCtr];
    InCtr += count;
}

void GetData(int *Buf, int count) {
    int srcCtr = OutCtr & (BUFSIZE - 1);
    int destCtr = 0;

    for (destCtr = 0; (srcCtr < BUFSIZE) && (destCtr < count); srcCtr++, destCtr++)
        Buf[destCtr] = CircBuf[srcCtr];
    for (srcCtr = 0; srcCtr < count; srcCtr++, destCtr++)
        Buf[destCtr] = CircBuf[srcCtr];
    OutCtr += count;
}

int BufferOverflow() {
    return ((InCtr - OutCtr) > BUFSIZE);
}

Это довольно легкий, но эффективный. И кроме материала ctr = BigCtr & (SIZE-1), я бы сказал, что он очень удобочитаемый. Единственная причина уловки & в старых средах DSP, mod была дорогой операцией, поэтому для чего-то, что выполнялось часто, например, каждый раз, когда буфер был готов к обработке, вы могли найти способы удалить подобные вещи. И если вы делали БПФ, ваши буферы были, вероятно, степенью 2 в любом случае.

В наши дни, конечно, у вас есть процессоры с тактовой частотой 1 ГГц и массивы с магическим изменением размера. Вы, дети, сходите с моей лужайки.

2 голосов
/ 13 января 2011

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

Предположим, kNumElements, kStartIndex и kNumToRemove определены как const size_t значения.

vector<int> my_vec(kNumElements);
for (size_t i = 0; i < my_vec.size(); ++i) {
    my_vec[i] = i;
}

for (size_t i = 0, cur = 0; i < my_vec.size(); ++i) {
    // What is the "distance" from the current index to the start, taking
    // into account the wrapping behavior?
    size_t distance = (i + kNumElements - kStartIndex) % kNumElements;

    // If it's not one of the ones to remove, then we keep it by copying it
    // into its proper place.
    if (distance >= kNumToRemove) {
        my_vec[cur++] = my_vec[i];
    }
}

my_vec.resize(kNumElements - kNumToRemove);
1 голос
/ 13 января 2011

Я не программирую iphone для информации, поэтому я представляю себе std :: vector, это довольно просто, просто и достаточно элегантно:

#include <iostream>
using std::cout;
#include <vector>
using std::vector;
#include <cassert> //no need for using, assert is macro

template<typename T>
void eraseCircularVector(vector<T> & vec, size_t position, size_t count)
{
    assert(count <= vec.size());
    if (count > 0)
    {
        position %= vec.size(); //normalize position
        size_t positionEnd = (position + count) % vec.size(); 
        if (positionEnd < position)
        {
            vec.erase(vec.begin() + position, vec.end());
            vec.erase(vec.begin(), vec.begin() + positionEnd);
        }
        else
            vec.erase(vec.begin() + position, vec.begin() + positionEnd);
    }
}

int main()
{
    vector<int> values;
    for (int i = 0; i < 10; ++i) 
        values.push_back(i);

    cout << "Values: ";
    for (vector<int>::const_iterator cit = values.begin(); cit != values.end(); cit++)
        cout << *cit << ' ';
    cout << '\n';

    eraseCircularVector(values, 5, 1); //remains 9: 0,1,2,3,4,6,7,8,9
    eraseCircularVector(values, 16, 5); //remains 4: 3,4,6,7

    cout << "Values: ";
    for (vector<int>::const_iterator cit = values.begin(); cit != values.end(); cit++)
        cout << *cit << ' ';
    cout << '\n';

    return 0;
}

Тем не менее, вы можете рассмотреть:

  • создание нового класса loop_vector, если вы используете этот вид функциональности достаточно
  • используя список, если вы выполняете много удалений (или несколько удалений (не с конца, это просто pop_back), но большой массив)

Если ваш контейнер (NSMutableArray или любой другой) не является списком, а вектором (то есть массивом с изменяемым размером), вам определенно не нужно удалять элементы один за другим, а весь диапазон (например, std :: vector erase (begin, конец)!

Редактировать : реагировать на комментарии, чтобы полностью понять, что должно быть сделано вектором, если вы удалите элемент, отличный от последнего: он должен скопировать все значения после этого элемента (например, 1000 элементов в массиве, сначала вы стираете 999x копирование (перемещение) предмета, что очень дорого). Пример:

#include <iostream>
#include <vector>
#include <ctime>
using namespace std;

int main()
{
    clock_t start, end;

    vector<int> vec;
    const int items = 64 * 1024;
    cout << "using " << items << " items in vector\n";

    for (size_t i = 0; i < items; ++i) vec.push_back(i);    

    start = clock();
    while (!vec.empty()) vec.erase(vec.begin());
    end = clock();

    cout << "Inefficient method took: " 
        << (end - start) * 1.0 / CLOCKS_PER_SEC << " ms\n";

    for (size_t i = 0; i < items; ++i) vec.push_back(i);    

    start = clock();
    vec.erase(vec.begin(), vec.end());
    end = clock();

    cout << "Efficient method took: " 
        << (end - start) * 1.0 / CLOCKS_PER_SEC << " ms\n";

    return 0;
}

Производит продукцию:

using 65536 items in vector
Inefficient method took: 1.705 ms
Efficient method took: 0 ms

Обратите внимание, что очень легко стать неэффективным, посмотрите, например, иметь на http://www.cplusplus.com/reference/stl/vector/erase/

1 голос
/ 13 января 2011

Другой метод:

N times do {remove entry at index P mod max(ArraySize, P)}

Пример:

N=5, P=97, ArraySize=100

1: max(100, 97)=100 so remove at 97%100 = 97
2: max(99, 97)=99 so remove at 97%99 = 97  // array size is now 99
3: max(98, 97)=98 so remove at 97%98 = 97
4: max(97, 97)=97 so remove at 97%97 = 0
5: max(96, 97)=97 so remove at 97%97 = 0
...