Каков наилучший способ удалить значение из массива в Perl?

69

В массиве много данных, и мне нужно удалить два элемента.

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

my @array = (1,2,3,4,5,5,6,5,4,9);
my $element_omitted = 5;
@array = grep { $_ != $element_omitted } @array;
  • 3
    Это удаляет три элемента.
  • 0
    необходимо убрать весь список папок в форме не-файловых элементов, и «array = grep {-f $ _} array» работает для меня как шарм :)
Теги:
arrays

12 ответов

79
Лучший ответ

Используйте сращивание, если вы уже знаете индекс элемента, который хотите удалить.

Grep работает, если вы ищете.

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

Если это имеет смысл в вашем контексте, вы можете захотеть использовать "магическое значение" для удаленных записей, а не удалять их, чтобы сохранить при перемещении данных - например, установить удаленные элементы в undef. Естественно, у этого есть свои проблемы (если вам нужно знать количество "живых" элементов, вам нужно отслеживать их отдельно и т.д.), Но может оказаться проблемой в зависимости от вашего приложения.

Изменить Фактически теперь, когда я беру второй взгляд - не используйте выше код grep. Было бы более удобно найти индекс элемента, который вы хотите удалить, а затем использовать сплайсинг для его удаления (код, на который вы накопили все несоответствующие результаты.)

my $index = 0;
$index++ until $arr[$index] eq 'foo';
splice(@arr, $index, 1);

Это удалит первое вхождение. Удаление всех вхождений очень похоже, за исключением того, что вы хотите получить все индексы за один проход:

my @del_indexes = grep { $arr[$_] eq 'foo' } 0..$#arr;

Остальное остается в качестве упражнения для читателя - помните, что массив изменяется по мере его сращивания!

Edit2 Джон Сиракуза правильно указал, что у меня была ошибка в моем примере.. исправлено, извините.

  • 13
    если строка не найдена, цикл застрянет, как и мой $ index = 0; мой $ count = scalar @arr; $ index ++ до $ arr [$ index] eq 'foo' или $ index == $ count; сплайс (@arr, $ index, 1);
  • 1
    или my ($index) = grep { $arr[$_] eq 'foo' } 0..$#arr; if (defined $index) {splice(@arr, $index, 1); } - для первого матча
12

splice удалит элемент массива по индексу. Используйте grep, как в вашем примере, для поиска и удаления.

  • 0
    Спасибо, Спулсон. У меня нет индексов, которые я должен удалить, поэтому мне пришлось прибегнуть к grep.
7

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

В вашем примере ключ будет числом, а значением будет количество элементов этого числа.

4

если вы измените

my @del_indexes = grep { $arr[$_] eq 'foo' } 0..$#arr;

to

my @del_indexes = reverse(grep { $arr[$_] eq 'foo' } 0..$#arr);

Это позволяет избежать проблемы перенумерации массива, сначала удалив элементы из задней части массива. Вставка сплайсинга() в цикле foreach очищает @arr. Относительно простой и читаемый...

foreach $item (@del_indexes) {
   splice (@arr,$item,1);
}
2

Я думаю, что ваше решение является самым простым и удобным для обслуживания.

Остальная часть сообщения документирует трудность перевода тестов на элементы в смещения splice. Таким образом, это дает более полный ответ.

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

sub array_remove ( \@& ) { 
    my ( $arr_ref, $test_block ) = @_;
    my $sp_start  = 0;
    my $sp_len    = 0;
    for ( my $inx = 0; $inx <= $#$arr_ref; $inx++ ) {
        local $_ = $arr_ref->[$inx];
        next unless $test_block->( $_ );
        if ( $sp_len > 0 && $inx > $sp_start + $sp_len ) {
            splice( @$arr_ref, $sp_start, $sp_len );
            $inx    = $inx - $sp_len;
            $sp_len = 0;
        }
        $sp_start = $inx if ++$sp_len == 1;
    }
    splice( @$arr_ref, $sp_start, $sp_len ) if $sp_len > 0;
    return;
}
  • 2
    Простой «grep» будет намного проще для понимания и более эффективным, чем это.
  • 5
    Кто-то удалил мой комментарий, чтобы вы явно не читали текст.
1

Лучшее, что я нашел, это комбинация "undef" и "grep":

foreach $index ( @list_of_indexes_to_be_skiped ) {
      undef($array[$index]);
}
@array = grep { defined($_) } @array;

Это трюк! Federico

  • 0
    undef установить значение элемента на ноль. Общее количество элементов (размер) остается прежним.
  • 1
    @BoontaweeHome, grep в конце удаляет их.
1

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


perl -le '@ar=(1 .. 20);@x=(8,10,3,17);$x=join("|",@x);@ar=grep{!/^(?:$x)$/o} @ar;print "@ar"'
1

Я использую:

delete $array[$index];

Perldoc удалить.

  • 9
    Удалить значение в массиве, скорее всего, не рекомендуется (см. документацию)
  • 3
    это просто удаляет значение, хранящееся в этом индексе массива. по крайней мере, в моей версии Perl, (5.14)
Показать ещё 1 комментарий
1

Удалить все вхождения "something" if array.

Основываясь на ответе SquareCog:

my @arr = ('1','2','3','4','3','2', '3','4','3');
my @dix = grep { $arr[$_] eq '4' } 0..$#arr;
my $o = 0;
for (@dix) {
    splice(@arr, $_-$o, 1);
    $o++;
}
print join("\n", @arr);

Каждый раз, когда мы удаляем индекс из @arr, следующий правильный индекс для удаления будет $_-current_loop_step.

0

Вы можете использовать разбиение массива вместо сплайсинга. Grep, чтобы вернуть индексы, которые вы хотите сохранить, и использовать нарезку:

my @arr = ...;
my @indicesToKeep = grep { $arr[$_] ne 'foo' } 0..$#arr;
@arr = @arr[@indiciesToKeep];
  • 0
    Мне особенно нравится логика и элегантность этого подхода.
0

Аналогичный код, который я однажды написал, чтобы удалить строки, не начинающиеся с SB.1, из массива строк

my @adoSymbols=('SB.1000','RT.10000','PC.10000');
##Remove items from an array from backward
for(my $i=$#adoSymbols;$i>=0;$i--) {  
    unless ($adoSymbols[$i] =~ m/^SB\.1/) {splice(@adoSymbols,$i,1);}
}
0

Если вам известен индекс массива, вы можете удалить() его. Разница между splice() и delete() заключается в том, что delete() не перенумерует оставшиеся элементы массива.

  • 0
    Я на самом деле имел в виду перенумерацию, что, согласно Perldoc, делает splice ().

Ещё вопросы

Сообщество Overcoder
Наверх
Меню