Який найкращий спосіб видалити значення з масиву в Perl?


81

У масиві багато даних, і мені потрібно видалити два елементи.

Нижче наведено фрагмент коду, який я використовую,

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

3
Це видаляє три елементи.
Медлок Перлман,

потрібно зверху видалити всі нефайлові переліки каталогів елементів списку, і "array = grep {-f $ _} array" спрацював для мене як шарм :)
taiko,

Відповіді:


87

Використовуйте сплайсинг, якщо ви вже знаєте індекс елемента, який потрібно видалити.

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 = скаляр @arr; $ index ++ до $ arr [$ index] eq 'foo' або $ index == $ count; зрощення (@arr, $ index, 1);
Amir.F

1
або my ($index) = grep { $arr[$_] eq 'foo' } 0..$#arr; if (defined $index) {splice(@arr, $index, 1); }- для першого матчу
Відображувальний

13

сплайсинг видалить елементи масиву за індексом. Використовуйте grep, як у вашому прикладі, для пошуку та видалення.


Спасибі Spoulson. Я не знаю індексів, які мені доводиться видаляти, і тому мені довелося вдатися до grep.
user21246

8

Це те, чим ти збираєшся робити багато? Якщо так, ви можете розглянути іншу структуру даних. Grep збирається шукати весь масив кожного разу, і для великого масиву це може бути досить дорогим. Якщо швидкість є проблемою, то, можливо, ви захочете використовувати Hash.

У вашому прикладі ключем буде число, а значенням буде кількість елементів цього числа.


5

якщо ти переодягнешся

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

до

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

Це дозволяє уникнути проблеми перенумерації масиву, попередньо видаливши елементи із зворотного боку масиву. Поміщення сплайсингу () у цикл foreach очищає @arr. Порівняно простий і читабельний ...

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

5

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

my @arr = ...;
my @indicesToKeep = grep { $arr[$_] ne 'foo' } 0..$#arr;
@arr = @arr[@indiciesToKeep];

Мені особливо подобається логіка та елегантність цього підходу.
Кейв

Так, ви навіть можете написати це як однокласний лайнер типу: @arr = @arr[grep ...]що мені особливо подобається. Я не впевнений, наскільки це ефективно, але я почну його використовувати, оскільки він не може бути гіршим за інші рішення.
согер

3

Я думаю, що ваше рішення є найпростішим та найбільш ремонтопридатним.

Решта поштових документів ускладнює перетворення тестів на елементи на 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
Простий "grep" стане набагато легшим для розуміння та ефективнішим за це.
Рендал Шварц

5
Хтось видалив мій коментар про те, що ви явно не читали текст.
Аксеман

2

Я використовую:

delete $array[$index];

Perldoc видалити .


9
видалити значення масиву, ймовірно, буде застарілим (див. ваш документ)
e2-e4

3
це просто видаляє значення, що зберігається в цьому індексі масиву. принаймні в моїй версії perl, (5.14)
Півень

Це насправді НЕ видаляє те, що ви думаєте. Він лише видаляє значення, роблячи його undef. Крім того, з документа, зв’язаного ringø: "ПОПЕРЕДЖЕННЯ: Виклик видалення значень масиву настійно не рекомендується. Поняття видалення або перевірки існування елементів масиву Perl не є концептуально цілісним і може призвести до дивовижної поведінки." (попередній абзац у документі містить усі криваві деталі).
mivk

2

Видалити всі випадки "щось", якщо масив.

На основі відповідей 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.


2

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


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

2

Найкраще, що я знайшов, - це поєднання "undef" та "grep":

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

У цьому трюк! Федеріко


undef встановити значення елемента як нуль. Загальна кількість елементів (розмір) незмінна.
Boontawee Home

1
@BoontaweeHome, grepв кінці потім видаляє їх.
Deanna

1

Щоб бути впевненим, що я здійснив тестування grep та map рішень, спочатку шукаючи індекси відповідних елементів (ті, що видаляються), а потім безпосередньо видаляючи елементи grep без пошуку індексів. Мені здається, що перше рішення, яке запропонував Сем, ставлячи своє запитання, було вже найшвидшим.

    use Benchmark;
    my @A=qw(A B C A D E A F G H A I J K L A M N);
    my @M1; my @G; my @M2;
    my @Ashrunk;
    timethese( 1000000, {
      'map1' => sub {
          my $i=0;
          @M1 = map { $i++; $_ eq 'A' ? $i-1 : ();} @A;
      },
      'map2' => sub {
          my $i=0;
          @M2 = map { $A[$_] eq 'A' ? $_ : () ;} 0..$#A;
      },
      'grep' => sub {
          @G = grep { $A[$_] eq 'A' } 0..$#A;
      },
      'grem' => sub {
          @Ashrunk = grep { $_ ne 'A' } @A;
      },
    });

Результат:

Benchmark: timing 1000000 iterations of grem, grep, map1, map2...
  grem:  4 wallclock secs ( 3.37 usr +  0.00 sys =  3.37 CPU) @ 296823.98/s (n=1000000)
  grep:  3 wallclock secs ( 2.95 usr +  0.00 sys =  2.95 CPU) @ 339213.03/s (n=1000000)
  map1:  4 wallclock secs ( 4.01 usr +  0.00 sys =  4.01 CPU) @ 249438.76/s (n=1000000)
  map2:  2 wallclock secs ( 3.67 usr +  0.00 sys =  3.67 CPU) @ 272702.48/s (n=1000000)
M1 = 0 3 6 10 15
M2 = 0 3 6 10 15
G = 0 3 6 10 15
Ashrunk = B C D E F G H I J K L M N

Як показують минулі часи, марно намагатися реалізувати функцію видалення за допомогою grep або індексів, визначених картою. Просто grep-remove безпосередньо.

До тестування я думав, що "map1" буде найефективнішим ... Мені слід частіше покладатися на Benchmark. ;-)


0

Якщо вам відомий індекс масиву, його можна видалити () . Різниця між сплайсингом () та delete () полягає в тому, що delete () не перенумерує інші елементи масиву.


Я насправді мав на увазі перенумерацію, що, на думку Перлдока, splice () робить.
Powerlord

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

Ви можете просто зробити це:

my $input_Color = 'Green';
my @array = qw(Red Blue Green Yellow Black);
@array = grep {!/$input_Color/} @array;
print "@array";
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.