Каков наилучший способ удалить значение из массива в Perl? - PullRequest
76 голосов
/ 06 октября 2008

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

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

my @array = (1,2,3,4,5,5,6,5,4,9);
my $element_omitted = 5;
@array = grep { $_ != $element_omitted } @array;

Ответы [ 13 ]

83 голосов
/ 06 октября 2008

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

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 голосов
/ 06 октября 2008

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

8 голосов
/ 06 октября 2008

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

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

5 голосов
/ 07 января 2013

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

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

до

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

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

foreach $item (@del_indexes) {
   splice (@arr,$item,1);
}
3 голосов
/ 20 ноября 2017

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

my @arr = ...;
my @indicesToKeep = grep { $arr[$_] ne 'foo' } 0..$#arr;
@arr = @arr[@indiciesToKeep];
3 голосов
/ 06 октября 2008

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

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

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

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 голосов
/ 19 апреля 2016

Лучшее, что я нашел, было сочетание "undef" и "grep":

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

Это делает трюк! Federico

2 голосов
/ 11 апреля 2014

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


perl -le '@ar=(1 .. 20);@x=(8,10,3,17);$x=join("|",@x);@ar=grep{!/^(?:$x)$/o} @ar;print "@ar"'
2 голосов
/ 08 февраля 2013

Я использую:

delete $array[$index];

Perldoc удалить .

2 голосов
/ 10 августа 2012

Удалить все вхождения «что-то», если массив.

На основе ответов 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...