【问题标题】:Perl Hash : get keys if the value is in-between a rangePerl Hash:如果值在范围之间,则获取键
【发布时间】:2021-12-07 05:43:15
【问题描述】:

我有一个像这样的带有 100K 键(主键)的二维散列,我需要获取主键 - 仅在满足特定条件时才获取水果的名称;

like - 如果价格在 35 到 55 之间;期望的输出是Orange and Grape

并且有一个独特价格范围的列表(数以百计),我需要每个范围内的水果列表。

为每个价格范围一次又一次地遍历哈希需要大量时间。 有没有一种方法可以快速完成,而不是遍历每个价格范围的整个哈希?

哈希格式:

$fruits{"Mango"}{color}=Yellow

$fruits{"Mango"}{price}=80

$fruits{"Orange"}{color}=Orange

$fruits{"Orange"}{price}=40

$fruits{"Grape"}{color}=Green

$fruits{"Grape"}{price}=50

【问题讨论】:

    标签: loops perl hash literate-programming


    【解决方案1】:

    这是一个示例,说明如何通过按数字顺序对价格进行排序来对水果进行单次扫描。这应该比为每个价格范围扫描一次整个哈希更快:

    package Main;
    use v5.20.0;
    use feature qw(say);
    use strict;
    use warnings;
    use experimental qw(signatures);
    {
        my %fruits;
        $fruits{Mango}{color}  = "Yellow";
        $fruits{Mango}{price}  = 80;
        $fruits{Orange}{color} = "Orange";
        $fruits{Orange}{price} = 40;
        $fruits{Grape}{color}  = "Green";
        $fruits{Grape}{price}  = 50;
        my @ranges = ( [35, 55], [45, 55], [2, 85] );
        my $self = Main->new(
            fruits => \%fruits,
            ranges => \@ranges
        );
        $self->init_mapping_arrays();
        my $names = $self->get_fruit_names_for_all_ranges();
    }
    
    
    sub init_mapping_arrays( $self ) {
        my @prices;
        my @names;
        for my $fruit (keys %{ $self->{fruits} }) {
            push @names, $fruit;
            push @prices, $self->{fruits}{$fruit}{price};
        }
        my @idx = map { $_->[0] }
          sort { $a->[1] <=> $b->[1] } map { [$_, $prices[$_]] } 0..$#prices;
        $self->{prices} = [@prices[@idx]];
        $self->{names} = [@names[@idx]];
    }
    
    sub get_fruit_names_for_all_ranges ($self) {
        my @names;
        my $prices = $self->{prices};
        my $ranges = $self->{ranges};
        for my $i (0..$#$prices) {
            for my $range (0..$#$ranges) {
                if (   ($ranges->[$range][0] <= $prices->[$i])
                    && ($ranges->[$range][1] >= $prices->[$i]))
                {
                    push @{$names[$range]}, $self->{names}[$i];
                }
            }
        }
        return \@names;
    }
    
    sub new( $class, %args ) { bless \%args, $class }
    

    如果这还不够快,还可以通过对范围进行排序来进一步优化 get_fruit_names_for_all_ranges() 子。

    【讨论】:

      【解决方案2】:

      如果对水果进行排序,两次二分搜索会很快找到水果。

      sub search_cmp 
      
      my @fruits = (
         { name => "Orange", price => 40, ... },
         ...
      );
      
      my @ranges = (
         [ 35, 55 ],
         ...   
      );
      
      my @sorted_fruits = sort { $a->{price} <=> $b->{price} } @fruits;
      
      for my $range (@ranges) {
         my $i = binsearch { $a <=> $b->{price} } $range[0], @sorted_fruits, 0;
         $i = ~$i if $i < 0;
      
         my $j = binsearch { $a <=> $b->{price} } $range[1], @sorted_fruits, $i;
         $j = ~$j - 1 if $j < 0;
      
         say "[$range->{min}, $range->{max}]: @fruits[$i..$j]";
      }
      
      sub _unsigned_to_signed { unpack('j', pack('J', $_[0])) }
      
      sub binsearch(&$\@;$$) {
         my  $compare = $_[0];
         #my $value   = $_[1];
         my  $array   = $_[2];
         my  $min     = $_[3] // 0;
         my  $max     = $_[4] // $#$array;
      
         return -1 if $max == -1;
      
         my $ap = do { no strict 'refs'; \*{caller().'::a'} };  local *$ap;
         my $bp = do { no strict 'refs'; \*{caller().'::b'} };  local *$bp;
      
         *$ap = \($_[1]);
         while ($min <= $max) {
            my $mid = int(($min+$max)/2);
            *$bp = \($array->[$mid]);
      
            my $cmp = $compare->()
               or return $mid;
      
            if ($cmp < 0) {
               $max = $mid - 1;
            } else {
               $min = $mid + 1;
            }
         }
      
         return _unsigned_to_signed(~$min);
      }
      

      性能分析

      最好的最坏情况是 O(R * F),因为每个范围都可以匹配每个水果。

      要求替换的 OP 描述的幼稚方法是 O(R * F)。那么它是否尽可能快?不,因为天真的方法总是采用更糟糕的情况。

      在实践中,如果我们可以假设每个范围只匹配几个水果,我们可以从上面得到更好的平均结果:O( ( F + R ) log F )

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-12-09
        • 2016-02-25
        • 1970-01-01
        • 1970-01-01
        • 2018-10-16
        • 2016-02-04
        • 1970-01-01
        相关资源
        最近更新 更多