Как я могу удалить элемент ссылочного массива?

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

@a=qw(ok now what is hi the matter);

sub zonk {
  $array=shift; # this is a reference of an array
  foreach $i (0..$#$array) { # I saw some say to avoid last element to get size
    #if (@$array[$i] =~ /hi/) { delete @$array[$i]; }
    #if ($array->[$i] =~ /hi/) { delete $array->[$i]; }
    #if ($array->[$i] =~ /hi/) { delete @$array->[$i]; }
    if ($array->[$i] =~ /hi/) { print "FOUND "; }
    print $array->[$i],"\n";
  }
  @$array = grep{$_} @$array; # removes empty elements
}
zonk(\@a);
print join(':',@a);

Если я запускаю программу выше как есть, я получаю:

ok
now
what
is
FOUND hi
the
matter
ok:now:what:is:hi:the:matter

Но если вместо этого я использую любую из закомментированных строк, я получаю:

Аргумент удаления не является элементом HASH или фрагментом в строке 10 hi.pl.

Сначала я пробовал splice, но затем индексы смещались и путали итерацию. Было бы неплохо узнать все методы, упомянутые в этом посте, однако наиболее эффективным является то, что я ищу :)

Дополнение: это прекрасно работает (я имею в виду каждую прокомментированную строку) на моем Linux-компьютере (ubuntu 9.10, perl 5.10), но вышеприведенная ошибка возникает на моем компьютере с Windows 7 при работе с perl 5.005_03. Обновление не вариант.

Спасибо


person Shawn    schedule 11.12.2010    source источник


Ответы (5)


Почему бы не использовать grep с самого начала?

@array = grep { !/hi/ } @array;
# Or, for *referenced* array
@$arrayRef = grep { !/hi/ } @$arrayRef;

Небольшой набор заметок для уточнения вопросов, возникших в комментариях:

  1. Этот метод (или любой метод, использующий grep, включая исходный код постера) увеличит использование памяти сценарием на размер нового результирующего массива.

    Например. если сценарий (без первого массива) занимает 10 МБ памяти, исходный массив занимает 15 МБ памяти, а результирующий массив занимает 14 МБ памяти, то общий объем памяти вашей программы увеличится с от 25 МБ до 39 МБ, пока работает grep.

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

  3. Однако — и это важно — даже если исходные 15 МБ данных будут удалены сборщиком мусора, Perl не вернет эти 15 МБ операционной системе — т.е. объем памяти, занимаемый сценарием, останется равным 39 МБ и не уменьшится до 24 МБ даже после сборки мусора.

  4. С другой стороны, эти высвобожденные 15 МБ будут доступны для выделения памяти в течение всего остального времени жизни вашей программы (не считая проблем с фрагментацией памяти), поэтому, если вашему скрипту потребуется выделение дополнительных 1 МБ, 5 МБ или 15 МБ памяти, его объем памяти НЕ превысит максимальную отметку в 39 МБ. И если для этого требуется 17 МБ дополнительной памяти, в результате объем памяти составит всего 41 МБ, а не 56 МБ.

  5. Если эта арифметика памяти вас не устраивает (например, если ваш исходный массив составлял 500 МБ, и вы не хотите допускать увеличения объема памяти программы до 1 ГБ), тогда Dallaylaen answer ниже представляет собой отличный алгоритм для выполнения задачи без дополнительного выделения памяти< /сильный>

person DVK    schedule 11.12.2010
comment
это похоже на ответ для меня. если нет дополнительного использования памяти... комментарий ниже говорит, что это так - person Shawn; 11.12.2010
comment
@Shawn - исправил ответ, надеюсь, исчерпывающими примечаниями о проблемах с памятью. - person DVK; 11.12.2010

Измените порядок вашего цикла, и вы можете использовать splice:

for(my $i = $#array; $i >= 0; --$i) {
    #...
}
person mu is too short    schedule 11.12.2010
comment
@Shawn - это потому, что это не итератор в смысле объекта, указывающего на связанный список. Я не думаю, что в Perl есть настоящий итератор, кроме простого создания класса LinkedList вручную. - person DVK; 11.12.2010
comment
@Shawn - в основном, массивы Perl ведут себя ОЧЕНЬ близко к связанным спискам, за исключением случайных вставок / удалений в середине списка, которые не являются O (1), как вы заметили (но сдвиг / снятие сдвига / извлечение / нажатие - O (1). )) - person DVK; 11.12.2010
comment
Тот же эффект достигается менее неловко IMO с помощью for my $i ( reverse 0 .. $#array ) { - person ishnid; 11.12.2010
comment
@ishnid: Да, reverse было бы немного стильнее. Главное - это общая техника: возвращайтесь назад при внесении изменений (удаление элементов массива, применение различий к фрагменту текста,...), чтобы не запутаться в ваших индексах. - person mu is too short; 11.12.2010

Если вы все равно делаете @$array = grep { ... } @$array, почему бы просто не придерживаться grep { $_ !~ /hi/ }?

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

my $i = @$array;
while ($i-->0) {
    splice @$array, $i, 1 if $array->[$i] =~ /hi/;
}; 

Но в худшем случае это имеет производительность n^2, так что может быть даже лучше писать на C-с-долларами, а не на реальном Perl:

my $array = [qw(ok now what is hi the matter)];
my $to = 0;
# move elements backwards
for (my $from=0; $from < @$array; $from++) {
     $array->[$from] =~ /hi/ and next;
     $array->[$to++] = $array->[$from];
};
# remove tail 
splice @$array, $to; 
print join ":", @$array;

Тем не менее, я не знаю, почему delete $array->[$i] не работает, он работает на Perl 5.10 и 5.8, которые у меня сейчас есть.

person Dallaylaen    schedule 11.12.2010
comment
Я ужасно ошибаюсь: Perl будет повторно использовать память в случае @array = grep {...} @array. Давай @DVK! - person Dallaylaen; 11.12.2010
comment
это немного сложнее, чем это, но достаточно близкое резюме. Я опубликую комментарий к своему собственному ответу о памяти в деталях. Тем не менее, ваше решение мне больше всего нравится, поскольку оно ограничено памятью - +1! - person DVK; 11.12.2010

Прокрутите каждую клавишу, поместите каждый элемент для удаления в массив, а затем используйте окончательное удаление — одним махом!

 foreach my $key(keys %$my_array) {     
    my $val= $my_array->{$key};    

    if ($val eq "BAD") {
        push (@unwanted,$key);
    }            
}
delete @{$my_array}{@unwanted};
person PodTech.io    schedule 19.05.2016

person    schedule
comment
Не работает на моей машине Windows 7 с Perl 5.005. Я добавил дополнение к своему первоначальному вопросу. - person Shawn; 11.12.2010