2017-11-14 28 views
7

Tôi muốn so sánh giá trị băm nếu cặp khóa-giá trị giống nhau trong băm thứ hai. Tôi không muốn sử dụng smartmatch vì nó đưa ra cảnh báo.So sánh giá trị của hai băm với các loại hỗn hợp

Cách tốt nhất để so sánh hai băm với số nguyên, chuỗi và cũng có thể là mảng trong đó?

use warnings; 
use diagnostics; 

my $hash1={"key_str"=>"a string", "key_int"=>4}; 
my $hash2={"key_str"=>"b string", "key_int"=>2}; 

foreach my $key (keys(%$hash1)) { 
    if ($hash1->{$key} != $hash2->{$key}) { 
     print($key); 
    } 
} 

đầu ra như mong đợi là:

Argument "b string" isn't numeric in numeric ne (!=) at hash_compare.pl line 8 (#1) 
    (W numeric) The indicated string was fed as an argument to an operator 
    that expected a numeric value instead. If you're fortunate the message 
    will identify which operator was so unfortunate. 

Argument "a string" isn't numeric in numeric ne (!=) at hash_compare.pl line 8 (#1) 

Trả lời

8

Thứ nhất, Perl không có các loại. Nó không phân biệt giữa các chuỗi và số (ở bên ngoài).

Hơn nữa, nó không tạo sự khác biệt giữa các số và chuỗi ở cấp này. ngữ cảnh sốngữ cảnh chuỗi là vấn đề nếu bạn kiểm tra xem có gì lớn hơn hoặc nhỏ hơn. Xem xét việc này:

my $foo = 200; 
my $bar = 99; 
print $foo > $bar ? $foo : $bar; 

Rõ ràng nó sẽ in 200, vì 200 là số lượng lớn hơn 99.

my $foo = 200; 
my $bar = 99; 
print $foo gt $bar ? $foo : $bar; 

Nhưng điều này sẽ in 99, vì 9 là alphanumerically (như trong chuỗi) lớn hơn 2. Nó so sánh số lượng các điểm mã cho các ký tự.

Nhưng nếu tất cả những gì bạn muốn làm là kiểm tra sự bất bình đẳng, toán tử ne là phạt. Ngay cả khi bạn không chắc liệu có những thứ nào ngoài số trong đầu vào của bạn hay không.

foreach my $key (keys(%$hash1)) { 
    if ($hash1->{$key} ne $hash2->{$key}) { 
     print($key); 
    } 
} 

eq (và ne) là đủ thông minh để xem nếu một số bước đầu đã được một chuỗi hoặc một số mà không có dấu ngoặc kép, bởi vì các đại diện nội bộ của những người khác.

Cảnh báo, chi tiết kỹ thuật phía trước.

Giá trị vô hướng được lưu trong _SV_s. Những điều khoản này có thể chứa những thứ khác nhau. Có một loại nội bộ đặc biệt cho các số nguyên đơn giản được gọi là IV và cũng được gọi là PV cho chuỗi. Perl chuyển đổi nội bộ giữa hai thứ đó khi cần thiết khi bạn sử dụng các số bên trong các chuỗi hoặc ngược lại.

Bạn có thể nhận được một số thông tin gỡ lỗi về biểu diễn dữ liệu nội bộ với Dump từ Devel::Peek.

use Devel::Peek; 

Dump("01"); 
Dump(01); 

Sản lượng này sẽ:

SV = PV(0x19560d0) at 0x19327d0 
    REFCNT = 1 
    FLAGS = (POK,READONLY,IsCOW,pPOK) 
    PV = 0x1c94fd0 "01"\0 
    CUR = 2 
    LEN = 10 
    COW_REFCNT = 0 
SV = IV(0x19739b0) at 0x19739c0 
    REFCNT = 1 
    FLAGS = (IOK,READONLY,pIOK) 
    IV = 1 

Như bạn có thể thấy, một trong những đầu tiên là một chuỗi, và điều thứ hai là một số. Nhưng nếu chúng ta làm điều này

print "01" eq 01; 

không có đầu ra, vì 01 là một số nguyên và sẽ được chuyển đổi sang "1" để so sánh.Vì số 0 của "01" không bằng 1, không có gì được in.


Nếu giá trị của cấu trúc dữ liệu phức tạp hơn, bạn cần phải đi bộ cấu trúc. Mỗi loại phần tử cần phải có xử lý riêng. Có thể có tham chiếu mảng, tham chiếu băm, tham chiếu vô hướng, vô hướng, tham chiếu glob, dualvars và cứ tiếp tục như vậy. Có thể có đối tượng mà bạn muốn đối xử đặc biệt.

Tôi khuyên bạn nên xem cách Test::Deep triển khai điều này. Nếu bạn quyết định sử dụng nó trong mã sản xuất (và không phải là một thử nghiệm đơn vị), bạn có thể sử dụng Test::Deep::NoTest.

+0

Phần lớn phụ thuộc vào việc bạn muốn '" 1 "' và '" 01 "' vv để so sánh bằng hoặc không bằng nhau. – Borodin

+0

@ Borodin đó là sự thật. Nhưng 'eq' quan tâm đến điều đó, bởi vì biểu diễn bên trong. Tôi nghĩ rằng nó biết rằng '" 1 "' là một SV nội bộ, và '1' là một IV. Xem ví dụ này: 'nói 01 eq 1; nói 01 eq "1"; nói "01" eq "1"; "01" eq 1; ' – simbabque

+0

Có, nhưng quan điểm của tôi là OP có thể (hoặc có thể không) muốn' "1" 'thành * khớp *' "01" 'và, thực sự' "1000E-3" '. Có hai hành vi, và trong trường hợp của một * dualvar * kết quả có thể khác nhau. – Borodin

1

Bạn có thể use Scalar::Util qw(looks_like_number); để xác định xem giá trị có phải là số hay chuỗi không. Scalar::Util là một mô-đun chuẩn được bao gồm trong Perl. Để biết danh sách các mô-đun chuẩn, hãy xem perldoc perlmodlib.

#!/usr/bin/env perl 

# always use these two 
use strict; 
use warnings; 

# handle errors in open and close 
use autodie; # See http://perldoc.perl.org/autodie.html 

use Scalar::Util qw(looks_like_number); 

my $hash1={"key_str"=>"a string", "key_int"=>4}; 
my $hash2={"key_str"=>"b string", "key_int"=>2}; 

foreach my $key (keys(%$hash1)) { 
    if(looks_like_number($hash1->{$key}) && looks_like_number($hash2->{$key})){ 
     if ($hash1->{$key} != $hash2->{$key}) { 
      print "number value of $key is different\n"; 
     } 
    }else{ 
     if ($hash1->{$key} ne $hash2->{$key}) { 
      print "string value of $key is different\n"; 
     } 
    } 
} 
1

Tôi đã viết chương trình không sử dụng bất kỳ mô-đun nào. Tôi đã thử nghiệm chương trình dưới đây cho nhiều trường hợp, làm việc tốt, nhưng nếu bạn tìm thấy bất kỳ trường hợp mà nó không thành công thì xin vui lòng cho tôi biết.

Luôn sử dụng ne để so sánh nếu bạn không chắc chắn về kiểu dữ liệu được so sánh. != chỉ hoạt động với số nguyên, ne cho số nguyên và chuỗi cả hai.

use strict; 
use warnings; 

use feature 'say'; 

my $hash1 = { 
    'key1' => 'value1', 
    'key2' => [1, 2, 2], 
    'key3' => {1=>1, 2=> [5, 7]}, 
}; 

my $hash2 = { 
    'key1' => 'value1', 
    'key2' => [1, 2, 2], 
    'key3' => {1=>1, 2=> [5, 7]}, 
}; 

my $is_same = 0; 
$is_same = compare($hash1, $hash2); 

if ($is_same) { 
    say "Same"; 
} else { 
    say "Not same"; 
} 

sub compare { 
    my ($value1, $value2) = @_; 
    my $is_same = 1; 

    if (ref($value1) eq "ARRAY") { 
     if (is_same_sized_array($value1, $value2)) { 
      foreach (my $i = 0; $i < @$value1; $i++) { 
       if (ref $value1->[$i] eq ref $value2->[$i]) { 
        $is_same = compare($value1->[$i], $value2->[$i]); 
        return 0 unless $is_same; 
       } else { 
        return 0; 
       } 
      } 
     } else { 
      return 0; 
     } 
    } elsif (ref($value1) eq "HASH") { 
     if (is_same_sized_array([keys %$value1], [keys %$value2])) { 
      foreach my $key (sort keys %$value1) { 
       if (exists $value2->{$key} && ref $value1->{$key} eq ref $value2->{$key}) { 
        $is_same = compare($value1->{$key}, $value2->{$key}); 
        return 0 unless $is_same; 
       } else { 
        return 0; 
       } 
      } 
     } else { 
      return 0; 
     } 
    } else { 
     if ($value1 ne $value2) { 
      return 0; 
     } 
    } 
    return $is_same; 
} 

sub is_same_sized_array { 
    my ($arr1, $arr2) = @_; 
    return (@$arr1 == @$arr2) || 0; 
} 
Các vấn đề liên quan