Perl 해시의 키를 반복하는 가장 안전한 방법은 무엇입니까?


107

(키, 값) 쌍이있는 Perl 해시가있는 경우 모든 키를 반복하는 선호하는 방법은 무엇입니까? 나는 each어떤 식 으로든 사용 하면 의도하지 않은 부작용이 발생할 수 있다고 들었습니다 . 그렇다면 그게 사실이며 다음 두 가지 방법 중 하나가 가장 좋거나 더 나은 방법이 있습니까?

# Method 1
while (my ($key, $value) = each(%hash)) {
    # Something
}

# Method 2
foreach my $key (keys(%hash)) {
    # Something
}

답변:


199

경험상 필요에 가장 적합한 기능을 사용하는 것이 좋습니다.

키만 원하고 값을 읽지 않으 려면 keys ()를 사용하십시오.

foreach my $key (keys %hash) { ... }

값만 원하면 values ​​()를 사용하십시오.

foreach my $val (values %hash) { ... }

값 이 필요하면 each ()를 사용하세요.

keys %hash; # reset the internal iterator so a prior each() doesn't affect the loop
while(my($k, $v) = each %hash) { ... }

반복 중에 현재 키를 삭제하는 것을 제외하고 어떤 방식 으로든 해시의 키를 변경하려는 경우 each ()를 사용하지 않아야합니다. 예를 들어, 값이 두 배인 새로운 대문자 키 세트를 만드는이 코드는 keys ()를 사용하여 잘 작동합니다.

%h = (a => 1, b => 2);

foreach my $k (keys %h)
{
  $h{uc $k} = $h{$k} * 2;
}

예상되는 결과 해시 생성 :

(a => 1, A => 2, b => 2, B => 4)

그러나 each ()를 사용하여 동일한 작업을 수행합니다.

%h = (a => 1, b => 2);

keys %h;
while(my($k, $v) = each %h)
{
  $h{uc $k} = $h{$k} * 2; # BAD IDEA!
}

예측하기 어려운 방식으로 잘못된 결과를 생성합니다. 예를 들면 :

(a => 1, A => 2, b => 2, B => 8)

그러나 이것은 안전합니다.

keys %h;
while(my($k, $v) = each %h)
{
  if(...)
  {
    delete $h{$k}; # This is safe
  }
}

이 모든 것은 perl 문서에 설명되어 있습니다.

% perldoc -f keys
% perldoc -f each

6
공백 컨텍스트 키 % h를 추가하십시오. 반복기를 사용하여 안전하게 표시하려면 각 루프 앞에.
ysth dec

5
각각에 대한 또 다른주의 사항이 있습니다. 이터레이터는 컨텍스트가 아니라 해시에 바인딩됩니다. 즉, 재진입이 아닙니다. 예를 들어 해시를 반복하고 해시 펄을 인쇄하면 반복기가 내부적으로 재설정되어이 코드가 끝없이 반복됩니다. my % hash = (a => 1, b => 2, c => 3,); while (my ($ k, $ v) = 각 % hash) {print % hash; } blogs.perl.org/users/rurban/2014/04/do-not-use-each.html
Rawler 2014

28

사용할 때주의해야 할 한 가지는 each해시에 "상태"를 추가하는 부작용이 있다는 것입니다 (해시는 "다음"키가 무엇인지 기억해야합니다). 한 번에 전체 해시를 반복하는 위에 게시 된 스 니펫과 같은 코드를 사용하는 경우 일반적으로 문제가되지 않습니다. 그러나 모든 키를 처리하기 전에 루프 를 종료 하거나 each같은 명령문과 함께 사용하면 문제를 추적하기가 어렵습니다 (경험에서 말합니다.) .lastreturnwhile ... each

이 경우 해시는 이미 반환 된 키를 기억 each하고 다음 번에 사용할 때 (아마도 완전히 관련되지 않은 코드 부분에서)이 위치에서 계속됩니다.

예:

my %hash = ( foo => 1, bar => 2, baz => 3, quux => 4 );

# find key 'baz'
while ( my ($k, $v) = each %hash ) {
    print "found key $k\n";
    last if $k eq 'baz'; # found it!
}

# later ...

print "the hash contains:\n";

# iterate over all keys:
while ( my ($k, $v) = each %hash ) {
    print "$k => $v\n";
}

이것은 다음을 인쇄합니다.

found key bar
found key baz
the hash contains:
quux => 4
foo => 1

"bar"와 baz "키는 어떻게 되었습니까? 키는 여전히 존재하지만 두 번째 키 each는 첫 번째가 중단 된 지점에서 시작하고 해시의 끝에 도달하면 중지되므로 두 번째 루프에서는 볼 수 없습니다.


22

each문제를 일으킬 수있는 곳 은 범위가 지정되지 않은 진정한 반복자입니다. 예를 들어 :

while ( my ($key,$val) = each %a_hash ) {
    print "$key => $val\n";
    last if $val; #exits loop when $val is true
}

# but "each" hasn't reset!!
while ( my ($key,$val) = each %a_hash ) {
    # continues where the last loop left off
    print "$key => $val\n";
}

each모든 키와 값 을 가져 오는지 확인 해야하는 경우 keys또는 values먼저 사용하는지 확인해야합니다 (반복자를 재설정하므로). 각에 대한 설명서를 참조하십시오 .


14

각 구문을 사용하면 전체 키 집합이 한 번에 생성되지 않습니다. 이는 수백만 개의 행이있는 데이터베이스에 연결된 해시를 사용하는 경우 중요 할 수 있습니다. 한 번에 전체 키 목록을 생성하고 실제 메모리를 소모하고 싶지는 않습니다. 이 경우 각각은 반복자 역할을하는 반면 키는 실제로 루프가 시작되기 전에 전체 배열을 생성합니다.

따라서 "각각"이 실제로 사용되는 유일한 장소는 해시가 매우 큰 경우입니다 (사용 가능한 메모리에 비해). 휴대용 데이터 수집 장치 나 메모리가 작은 것을 프로그래밍하지 않는 한 해시 자체가 메모리 자체에 존재하지 않는 경우에만 발생할 수 있습니다.

메모리가 문제가되지 않는 경우 일반적으로 맵 또는 키 패러다임이 더 빠르고 읽기 쉬운 패러다임입니다.


6

이 주제에 대한 몇 가지 기타 생각 :

  1. 해시 반복자 자체에 대해 안전하지 않은 것은 없습니다. 안전하지 않은 것은 반복하는 동안 해시의 키를 수정하는 것입니다. (값을 수정하는 것은 완벽하게 안전합니다.) 제가 생각할 수있는 유일한 잠재적 부작용 values은 별칭 을 반환하는 것입니다. 즉, 별칭을 수정하면 해시의 내용이 수정됩니다. 이것은 의도적으로 설계된 것이지만 상황에 따라 원하는 것이 아닐 수도 있습니다.
  2. John의 대답 은 한 가지 예외를 제외하고는 좋습니다. 문서는 해시를 반복하는 동안 키를 추가하는 것이 안전하지 않다는 것이 분명합니다. 일부 데이터 세트에서는 작동하지만 해시 순서에 따라 다른 데이터 세트에서는 실패합니다.
  3. 이미 언급했듯이에서 반환 한 마지막 키를 삭제하는 것이 안전합니다 each. 입니다 하지 마찬가지 keyseach반복자 동안입니다 keys반환 목록을 표시합니다.

2
다시 "키에 대해서는 사실이 아닙니다.", 오히려 키에 적용 할 수 없으며 모든 삭제는 안전합니다. 사용하는 문구는 키를 사용할 때 아무것도 삭제하는 것이 안전하지 않다는 것을 의미합니다.
ysth

2
Re : "해시 반복자에 대해 안전하지 않은 것은 없습니다."다른 위험은 다른 사람들이 언급했듯이 반복기가 각 루프를 시작하기 전에 시작 부분에 있다고 가정하는 것입니다.
ysth

3

나는 항상 방법 2도 사용합니다. 각각을 사용하는 유일한 이점은 해시 항목의 값을 재 할당하는 대신 읽기만하는 경우 지속적으로 해시를 참조 해제하지 않는다는 것입니다.


3

이것에 물릴지도 모르지만 개인적 취향이라고 생각합니다. 문서에서 각 ()에 대한 참조가 keys () 또는 values ​​()와 다른 것을 찾을 수 없습니다 (명백한 "그들은 서로 다른 것을 반환합니다"답변 제외). 사실 문서는 동일한 반복자를 사용하고 모두 복사본 대신 실제 목록 값을 반환하며 호출을 사용하여 반복하는 동안 해시를 수정하는 것은 좋지 않습니다.

즉, 해시 자체를 통해 키의 값에 액세스하는 것이 일반적으로 더 자체 문서화되어 있기 때문에 거의 항상 keys ()를 사용합니다. 값이 큰 구조에 대한 참조이고 해시에 대한 키가 이미 구조에 저장되어있는 경우 값 ()을 가끔 사용합니다.이 시점에서 키가 중복되어 필요하지 않습니다. 10 년 동안 Perl 프로그래밍에서 each ()를 2 번 사용했다고 생각하는데 두 번 모두 잘못된 선택이었을 것입니다 =)


2

나는 일반적으로을 사용 keys하고 마지막으로 사용하거나 읽은 시간을 생각할 수 없습니다 each.

map루프에서 수행중인 작업에 따라를 잊지 마세요 !

map { print "$_ => $hash{$_}\n" } keys %hash;

6
반환 값을 원하지 않는 한 map을 사용하지 마십시오
ko-dos

-1

나는 woudl 말한다 :

  1. 대부분의 사람들에게 가장 읽기 / 이해하기 쉬운 것을 사용하십시오 (그래서 키, 일반적으로 나는 주장 할 것입니다)
  2. 전체 코드베이스를 통해 일관되게 결정한 것을 사용하십시오.

이는 두 가지 주요 이점을 제공합니다.

  1. "일반적인"코드를 찾기가 더 쉬워서 함수 / 방법으로 리팩토링 할 수 있습니다.
  2. 미래의 개발자가 유지하기가 더 쉽습니다.

나는 각각에 대해 키를 사용하는 것이 더 비싸지 않다고 생각하므로 코드에서 동일한 것에 대해 두 가지 다른 구조가 필요하지 않습니다.


1
keys하여 메모리 사용이 증가합니다 hash-size * avg-key-size. 키 크기는 메모리에 의해서만 제한된다는 점을 감안할 때 (그들은 "그들의"해당 값과 같은 배열 요소이기 때문에) 일부 상황에서는 메모리 사용량과 복사에 걸리는 시간 모두에서 엄청나게 더 비쌀 수 있습니다 .
Adrian Günter
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.