Perl 배열에 특정 값이 포함되어 있는지 어떻게 확인할 수 있습니까?


239

배열을 반복하지 않고 배열에 값이 있는지 확인하는 방법을 찾으려고합니다.

매개 변수에 대한 파일을 읽고 있습니다. 처리하고 싶지 않은 매개 변수의 긴 목록이 있습니다. 이러한 원치 않는 매개 변수를 배열에 배치했습니다 @badparams.

새 매개 변수를 읽고 싶습니다.에없는 경우 @badparams처리하십시오. 에이 (가) 있으면 @badparams다음 읽기로 이동하십시오.


3
기록에 대한 답변은 상황에 따라 다릅니다. 반복적 인 조회를 원하는 것처럼 들리므로 jkramer가 제안한대로 해시를 사용하는 것이 좋습니다. 하나의 조회 만 만들려면 반복 할 수도 있습니다. (그리고 어떤 경우에는 해시 대신 이진 검색을 원할 수도 있습니다!)
Cascabel


6
기록을 위해 (그리고 이것은 당신의 상황에 완전히 적용되지 않을 수도 있습니다) 일반적으로 알려진 '나쁜 가치'를 걸러 내기보다는 '좋은 가치'를 식별하고 나머지를 무시하는 것이 더 좋습니다. 당신이 물어봐야 할 질문은 당신이 아직 모르는 나쁜 가치가있을 수 있는지의 여부입니다.
그랜트 맥린

답변:


187

간단히 배열을 해시로 바꾸십시오.

my %params = map { $_ => 1 } @badparams;

if(exists($params{$someparam})) { ... }

목록에 더 많은 (고유 한) 매개 변수를 추가 할 수도 있습니다.

$params{$newparam} = 1;

나중에 (고유 한) 매개 변수 목록을 다시 가져옵니다.

@badparams = keys %params;

38
레코드의 경우이 코드는 여전히 배열을 반복합니다. map {} 호출은 단순히 반복을 타이핑하기가 매우 쉽습니다.
Kenny Wyland

3
@badparams의 값이 의사 정적이고 맵에 대해 많은 검사를 수행하려는 경우에만이 작업을 수행합니다. 단일 검사에는 권장하지 않습니다.
Aaron T Harris

동일한 값을 가진 여러 항목이있는 배열에 적합하지 않습니까?
Rob Wells

3
@RobWells 아니오, 잘 작동합니다. 다음에 동일한 값을 볼 때 해시의 항목을 덮어 쓰 므로이 경우 1다시 설정 합니다.
andrewrjones

222

가장 일반적인 용도-특히 짧은 배열 (1000 개 이하) 및 요구에 가장 적합한 최적화가 무엇인지 잘 모르는 코더.

# $value can be any regex. be safe
if ( grep( /^$value$/, @array ) ) {
  print "found it";
}

grep은 배열의 첫 번째 값이 일치하더라도 모든 값을 통과한다고 언급했습니다. 이것은 사실이지만 grep은 여전히 ​​대부분의 경우 매우 빠릅니다 . 짧은 배열 (1000 개 미만의 항목)에 대해 이야기하는 경우 대부분의 알고리즘은 어쨌든 매우 빠릅니다. 매우 긴 배열 (1,000,000 품목)에 대해 이야기하고 있다면 grep은 항목이 배열의 첫 번째인지 중간인지 마지막인지에 관계없이 매우 빠릅니다.

더 긴 어레이를위한 최적화 사례 :

배열이 정렬 된 경우 "이진 검색"을 사용하십시오.

은 IF 와 동일한 배열을 반복적으로 검색하여 여러 번, 제 해시에 복사 한 다음 해시를 확인한다. 메모리가 중요한 경우 각 항목을 배열에서 해시로 이동하십시오. 더 메모리 효율적이지만 원래 배열을 파괴합니다.

경우 같은 값이 반복적으로 검색되는 배열 내에서 유유히 캐시를 구축 할 수 있습니다. (각 항목을 검색 할 때 먼저 검색 결과가 지속 된 해시에 저장되어 있는지 확인하십시오. 검색 결과가 해시에서 발견되지 않으면 배열을 검색하고 결과를 지속 된 해시에 넣어 다음에 우리가 해시에서 찾아서 검색을 건너 뜁니다.

참고 : 이러한 최적화는 긴 배열을 처리 할 때만 더 빠릅니다. 과도하게 최적화하지 마십시오.


12
이중 물결표가 Perl 5.10에 도입되었습니다
추후 공지가있을 때까지 일시 중지되었습니다.

15
@DennisWilliamson ... 그리고 5.18에서는 experimantal로 간주됩니다 .
Xaerxess

5
생산 코드에서 스마트 매치를 피하십시오. 추가 통지가있을 때 불안정하거나 실험적입니다.
벡터 Gorgoth

1
나는 또한 더 읽기 쉽다 는 것을 알지만 사용하지 않으면 효율적이지 않으며 모든 요소가 첫 번째 요소 인 경우에도 검사합니다.
giordano

7
if ( "value"~~ @array)를 사용하지 마십시오. ~~는 Smartmatch라는 실험 기능입니다. 실험은 실패한 것으로 보이며 이후 버전의 Perl에서 제거되거나 수정 될 것입니다.
yahermann

120

다음과 같이 Perl 5.10 에서 스마트 매치 기능을 사용할 수 있습니다 .

리터럴 값 조회의 경우 아래 작업을 수행하면 트릭이 수행됩니다.

if ( "value" ~~ @array ) 

스칼라 조회의 경우 아래 작업을 수행하면 위와 같이 작동합니다.

if ($val ~~ @array)

아래 작업을 수행하는 인라인 배열의 경우 위와 같이 작동합니다.

if ( $var ~~ ['bar', 'value', 'foo'] ) 

에서 펄 5.18 그러므로 당신이 설정하여 경고를 해제해야 실험적으로 플래그가 smartmatch 실험 스크립트 / 모듈에 아래 추가하여 프라그 :

use experimental 'smartmatch';

또는 smartmatch의 사용을 피하려면 Aaron이 말한 것처럼 :

if ( grep( /^$value$/, @array ) ) {
  #TODO:
}

4
이것은 좋지만 Perl 5.10에 새로운 것 같습니다. 구문 오류가 발생하는 이유를 알아 내기 전에 시간이 걸렸습니다.
Igor Skochinsky

17
경고 : 운영자는 버전마다 동작이 다르며 그 동안 실험 으로 표시 되었으므로이 방법을 피할 수 있습니다 . 따라서 펄 버전을 완벽하게 제어 할 수 없다면 (그리고 누가 가지고 있는지) 피해야합니다.
Maze

1
설정 이 권장되는 이유에 대한 이 설명이 마음 에 듭니다 use experimental 'smartmatch'. 내 펄 버전 (내부 시스템)을 제어 할 때이 no warnings 'experimental::smartmatch';진술을 사용합니다 .
lepe

43

이 블로그 게시물 에서는이 질문에 대한 최상의 답변에 대해 설명합니다.

간단한 요약으로 CPAN 모듈을 설치할 수있는 경우 가장 읽기 쉬운 솔루션은 다음과 같습니다.

any(@ingredients) eq 'flour';

또는

@ingredients->contains('flour');

그러나 더 일반적인 관용구는 다음과 같습니다.

any { $_ eq 'flour' } @ingredients

그러나 first()기능을 사용하지 마십시오 ! 코드의 의도를 전혀 나타내지 않습니다. ~~"스마트 일치"연산자를 사용하지 마십시오. 작동하지 않습니다 . 그리고 grep()해시와 함께 솔루션을 사용 하거나 사용하지 마십시오 . 전체 목록을 반복합니다.

any() 당신의 가치를 발견하자마자 멈출 것입니다.

자세한 내용은 블로그 게시물을 확인하십시오.


8
어떤 요구use List::Util qw(any);. List::Util 핵심 모듈 .
Onlyjob

13

방법 1 : grep (값이 정규식 일 것으로 예상되는 동안주의 할 수 있음)

grep리소스 를 살펴보면를 사용하지 마십시오 .

if ( grep( /^$value$/, @badparams ) ) {
  print "found";
}

방법 2 : 선형 검색

for (@badparams) {
    if ($_ eq $value) {
       print "found";
       last;
    }
}

방법 3 : 해시 사용

my %hash = map {$_ => 1} @badparams;
print "found" if (exists $hash{$value});

방법 4 : 스마트 매치

(Perl 5.10에 추가 된 Mark는 Perl 5.18에서 실험적 임).

use experimental 'smartmatch';  # for perl 5.18
print "found" if ($value ~~ @badparams);

방법 5 : 모듈 사용 List::MoreUtils

use List::MoreUtils qw(any);
@badparams = (1,2,3);
$value = 1;
print "found" if any {$_ == $value} @badparams;

12

@eakssjo의 벤치 마크 가 깨졌습니다-루프에서 해시 생성과 루프에서 정규 표현식 생성 측정. 고정 버전 (및 추가 한 List::Util::firstList::MoreUtils::any) :

use List::Util qw(first);
use List::MoreUtils qw(any);
use Benchmark;

my @list = ( 1..10_000 );
my $hit = 5_000;
my $hit_regex = qr/^$hit$/; # precompute regex
my %params;
$params{$_} = 1 for @list;  # precompute hash
timethese(
    100_000, {
        'any' => sub {
            die unless ( any { $hit_regex } @list );
        },
        'first' => sub {
            die unless ( first { $hit_regex } @list );
        },
        'grep' => sub {
            die unless ( grep { $hit_regex } @list );
        },
        'hash' => sub {
            die unless ( $params{$hit} );
        },
    });

그리고 결과 (@eakssjo의 답변보다 10 배 더 많은 100_000 회 반복)입니다.

Benchmark: timing 100000 iterations of any, first, grep, hash...
       any:  0 wallclock secs ( 0.67 usr +  0.00 sys =  0.67 CPU) @ 149253.73/s (n=100000)
     first:  1 wallclock secs ( 0.63 usr +  0.01 sys =  0.64 CPU) @ 156250.00/s (n=100000)
      grep: 42 wallclock secs (41.95 usr +  0.08 sys = 42.03 CPU) @ 2379.25/s (n=100000)
      hash:  0 wallclock secs ( 0.01 usr +  0.00 sys =  0.01 CPU) @ 10000000.00/s (n=100000)
            (warning: too few iterations for a reliable count)

6
여러 요소를 테스트하려는 경우 해시를 미리 작성하면 시간이 절약됩니다. 그러나 단일 요소가 포함되어 있는지 여부를 알고 싶다면 이미 해시가 없습니다. 따라서 해시 작성은 컴퓨팅 시간의 일부 여야합니다. 정규 표현식의 경우 더욱 그렇습니다. 찾는 각 요소에 대해 새로운 정규 표현식이 필요합니다.
fishinear 2012 년

1
@fishinear True이지만 여러 검사가 아닌 한 검사에만 관심이 있다면 마이크로 초가 중요하지 않기 때문에 어떤 방법이 더 빠른지 궁금해하는 것은 마이크로 최적화입니다. 경우 당신이이 검사를 다시 실행 싶어, 해시 갈 방법은, 한 번 해시를 만드는 원인이 비용은 무시 될 정도로 작은입니다. 위의 벤치 마크는 설정을 포함하지 않고 다른 테스트 방법 측정 합니다 . 예, 유스 케이스에서는 유효하지 않을 수 있지만 다시 한 번 확인하십시오. 단일 검사 만 수행하는 경우 자신과 동료에게 가장 읽기 쉬운 것을 사용해야합니다.
Xaerxess

10

사용하기 편리하지만 해시 변환 솔루션은 상당히 많은 비용이 들기 때문에 문제가되었습니다.

#!/usr/bin/perl
use Benchmark;
my @list;
for (1..10_000) {
    push @list, $_;
}

timethese(10000, {
  'grep'    => sub {
            if ( grep(/^5000$/o, @list) ) {
                # code
            }
        },
  'hash'    => sub {
            my %params = map { $_ => 1 } @list;
            if ( exists($params{5000}) ) {
                # code
            }
        },
});

벤치 마크 테스트 결과 :

Benchmark: timing 10000 iterations of grep, hash...
          grep:  8 wallclock secs ( 7.95 usr +  0.00 sys =  7.95 CPU) @ 1257.86/s (n=10000)
          hash: 50 wallclock secs (49.68 usr +  0.01 sys = 49.69 CPU) @ 201.25/s (n=10000)

5
List::Util::first일치하는 항목을 찾으면 반복이 중지되므로 사용 속도가 더 빠릅니다.
RobEarl

1
-1 벤치 마크는 결함이 grep있다 크게 전자가 O (N)와 후자 O (1)이기 때문에, 느린 해시를 생성하고 검색하는 것보다. 루프 외부에서 해시 생성을 한 번만 수행하고 정규식을 미리 계산하여 메소드 만 측정 하십시오 (내 답변 참조 ).
Xaerxess

4
@ Xaerxess : 필자의 경우 하나의 조회를 원했기 때문에 해시 / 정규식 작성과 조회 / grep을 모두 계산하는 것이 공정하다고 생각합니다. 그것은 많은 조회를 수행하는 것이 었습니다. 솔루션이 더 낫습니다.
aksel

3
한 번의 반복 만 수행하려는 경우 선택한 방법간에 차이를 구분할 수 없으므로이 경우 사소한 미세 최적화이므로 벤치 마크가 잘못됩니다.
Xaerxess

2
정규 표현식은 플래그 'o'를 갖기 때문에 한 번만 컴파일됩니다.
Apoc

3

@files는 기존 배열입니다

my @new_values =  grep(/^2[\d].[\d][A-za-z]?/,@files);

print join("\n", @new_values);

print "\n";

/^2[\d].[\d][A-za-z]?/ = 2에서 시작하는 값은 정규식을 넣을 수 있습니다


2

당신은 확실히 해시를 원합니다. 잘못된 매개 변수를 해시에 키로 배치 한 다음 특정 매개 변수가 해시에 존재하는지 판별하십시오.

our %bad_params = map { $_ => 1 } qw(badparam1 badparam2 badparam3)

if ($bad_params{$new_param}) {
  print "That is a bad parameter\n";
}

배열을 사용하여 실제로 관심이 있다면 List::Util또는List::MoreUtils


0

이를 수행 할 수있는 두 가지 방법이 있습니다. 다른 게시물에서 제안한대로 조회 값을 조회 테이블의 해시에 던져 넣을 수 있습니다. (또 다른 관용구를 추가하겠습니다.)

my %bad_param_lookup;
@bad_param_lookup{ @bad_params } = ( 1 ) x @bad_params;

그러나 주로 단어 문자이며 메타가 너무 많지 않은 경우 정규식 대체로 덤프 할 수 있습니다.

use English qw<$LIST_SEPARATOR>;

my $regex_str = do { 
    local $LIST_SEPARATOR = '|';
    "(?:@bad_params)";
 };

 # $front_delim and $back_delim being any characters that come before and after. 
 my $regex = qr/$front_delim$regex_str$back_delim/;

이 솔루션은 원하는 "잘못된 값"유형에 맞게 조정해야합니다. 다시 말하지만, 특정 유형의 문자열에는 전혀 부적절 할 수 있으므로 주의하십시오 .


1
을 쓸 수도 @bad_param_lookup{@bad_params} = ()있지만 exists멤버십을 테스트하는 데 사용해야 합니다.
Greg Bacon

-1
my @badparams = (1,2,5,7,'a','zzz');

my $badparams = join('|',@badparams);   # '|' or any other character not present in params

foreach my $par (4,5,6,7,'a','z','zzz')
{
    if ($badparams =~ /\b$par\b/)
    {
        print "$par is present\n";
    }
    else
    {
        print "$par is not present\n";
    }
}

숫자 선행 공백이 일치하는지 확인할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.