it-swarm.com.ru

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

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

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

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

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

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 Джон Сиракуза правильно указал, что у меня была ошибка в моем примере ... исправлена, извините за это.

84
SquareCog

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

13
spoulson

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

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

8
tvanfosson

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

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);
}
5
dean

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

Остальная часть публикации описывает сложность превращения тестов для элементов в смещения 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;
}
3
Axeman

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

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

Я использую:

delete $array[$index];

Perldoc удалить .

2
Ariel Monaco

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

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

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

2
Federico

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

на основе ответов 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
Tom Lime

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


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

Просто чтобы убедиться, что я тестировал решения 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-удалить напрямую.

До тестирования я думал, что "map1" будет наиболее эффективным ... Я должен чаще полагаться на Benchmark, я думаю. ;-)

1
Gilles Maisonneuve

Аналогичный код, который я однажды написал, чтобы удалить строки, не начинающиеся с 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
BBT

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

0
Powerlord