Perl의 배열에서 값을 삭제하는 가장 좋은 방법은 무엇입니까?


81

배열에는 많은 데이터가 있으며 두 개의 요소를 삭제해야합니다.

아래는 내가 사용중인 코드 스 니펫입니다.

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

3
이렇게하면 세 가지 요소가 삭제됩니다.
Medlock Perlman

필요 상단 모든 비 파일 항목 양식 디렉토리 목록을 제거하고 "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 John Siracusa는 내 예제에 버그가 있다고 정확하게 지적했습니다. 수정되었습니다. 죄송합니다.


13
문자열을 찾을 수 없으면 루프가 멈출 것이므로 내 $ index = 0; 내 $ count = 스칼라 @arr; $ arr [$ index] eq 'foo'또는 $ index == $ count까지 $ index ++; splice (@arr, $ index, 1);
Amir.F 2013 년

1
또는 my ($index) = grep { $arr[$_] eq 'foo' } 0..$#arr; if (defined $index) {splice(@arr, $index, 1); }-첫 경기
Reflective

13

스플 라이스 는 인덱스로 배열 요소를 제거합니다. 예에서와 같이 grep을 사용하여 검색하고 제거하십시오.


고마워요 spoulson. 삭제해야하는 인덱스가 없어서 grep에 의존해야했습니다.
user21246

8

이것이 당신이 많이 할 일입니까? 그렇다면 다른 데이터 구조를 고려할 수 있습니다. Grep은 매번 전체 어레이를 검색 할 것이며 대형 어레이의 경우 비용이 많이들 수 있습니다. 속도가 문제라면 대신 해시를 사용하는 것이 좋습니다.

귀하의 예에서 키는 숫자이고 값은 해당 숫자의 요소 수입니다.


5

변경하면

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

스 플라이 싱 대신 어레이 슬라이싱을 사용할 수 있습니다. 유지하려는 인덱스를 반환하고 슬라이싱을 사용하려면 Grep :

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

저는이 접근 방식의 논리와 우아함이 특히 마음에 듭니다.
Keve

예, 실제로 @arr = @arr[grep ...]내가 특히 좋아하는 것과 같은 한 줄로 작성할 수도 있습니다 . 얼마나 효율적인지 잘 모르겠지만 다른 솔루션보다 나쁠 수 없기 때문에 사용을 시작하겠습니다.
soger

3

귀하의 솔루션이 가장 간단하고 유지 관리가 용이하다고 생각합니다.

나머지 포스트에서는 요소에 대한 테스트를 splice오프셋 으로 전환하는 것이 얼마나 어려운지 설명 합니다. 따라서 더 완전한 대답으로 만듭니다.

목록 항목에 대한 테스트를 색인으로 전환하는 효율적인 (예 : 1 회 통과) 알고리즘을 갖기 위해 거쳐야 하는 회전 을 살펴보십시오 . 그리고 그것은 전혀 직관적이지 않습니다.

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"은 그보다 훨씬 이해하기 쉽고 더 효율적입니다.
Randal Schwartz

5
누군가 당신이 텍스트를 읽지 않았다는 내 댓글을 삭제했습니다.
Axeman

2

나는 사용한다:

delete $array[$index];

Perldoc 삭제 .


9
배열 값에 대한 삭제 가 더 이상 사용되지 않을 가능성이 있습니다 (문서 참조)
e2-e4 2013

3
이것은 단지 그 배열 인덱스에 저장된 값을 삭제합니다. 적어도 내 버전의 perl, (5.14)
Rooster

이것은 당신이 생각하는 것을 실제로 삭제하지 않습니다. 값만 삭제하여 undef. 게다가 ringø에 의해 링크 된 문서에서 : "경고 : 배열 값에 대한 삭제 호출은 강력히 권장하지 않습니다. Perl 배열 요소의 존재를 삭제하거나 확인하는 개념은 개념적으로 일관성이 없으며 놀라운 동작을 초래할 수 있습니다." (문서의 이전 단락에는 모든 세부 사항이 있습니다).
mivk

2

배열 인 경우 'something'항목을 모두 삭제합니다.

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는 요소 값을 null로 설정합니다. 총 요소 (크기)는 여전히 동일합니다.
Boontawee Home 2016-06-14

1
@BoontaweeHome, grep끝에는 제거됩니다.
Deanna

1

내가 grep 및 map 솔루션을 벤치마킹했는지 확인하기 위해 먼저 일치하는 요소 (제거 할 요소)의 색인을 검색 한 다음 색인을 검색하지 않고 grep으로 요소를 직접 제거합니다. 나는 Sam이 그의 질문을 할 때 제안한 첫 번째 해결책이 이미 가장 빠른 것 같습니다.

    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

배열 인덱스를 알고 있으면 삭제할 수 있습니다 () . splice ()와 delete ()의 차이점은 delete ()가 배열의 나머지 요소의 번호를 다시 매기 지 않는다는 것입니다.


나는 실제로 Renumber를 의미했는데 Perldoc에 따르면 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";
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.