myocom has asked for the wisdom of the Perl Monks concerning the following question:

I'm sure this is straightforward enough, but for some reason I can't wrap my brain around an elegant solution. I have a hash that has keys of (100, 101, 102, 103, 104, 105, 106, 107). I'd like to remove some number of elements starting with some other arbitrary element. For example, 3 elements, starting with the 2nd (101, 102, 103). This is easy enough to compute with an array, since they're indexed on number, but I'm not sure how to do it with a hash.

The ugly way would be to create a parallel array that has the sorted keys of the hash, then foreach over a slice of that array and delete the appropriate hash elements. But surely there's a more elegant way, yes?

Replies are listed 'Best First'.
Re: Removing a certain number of hash keys
by dws (Chancellor) on Dec 12, 2000 at 02:37 UTC
    You need a hash slice.
    my %h = ( 100 => "a", 101 => "b", 102 => "c", 103 => "d", 104 => "e" );
    print join(",", keys %h), "\n";
    
    my $start = 101;
    my $stop = 103;
    delete @h{ $start .. $stop };
    print join(",", keys %h), "\n";
    
    prints
    101,102,103,104
    101,104

      That's the sort of thing I was looking for. Now to complicate matters a bit, I oversimplified my example somewhat. I actually have a hash that looks more like this:

      my %hash = ( 100 => 1, 100.1 => 1, 103 => 1, 103.1 => 1, 103.2 => 1, 1 +05 => 1 );

      More to the point, the keys will always be nice and sortable, but they won't necessarily be integers and they won't always be consecutive. Is there a way to do some sort of indexing into a hash slice?

        Not nearly as efficient as delete @hash{101..105}, but the following looks nice:

        delete @hash{ grep {/^101$/../^105$/} sort keys %hash };
        Unless you keep a sorted list of keys separately, I don't think you can do much better than that.

        Updated slightly.

                - tye (but my friends call me "Tye")
        This should still work, provided you still want to delete all in a range:
        map { delete $hash{$_} if ($_ > 101 && $_ < 103) } keys %hash;

        Alan "Hot Pastrami" Bellows
(jeffa) Re: Removing a certain number of hash keys
by jeffa (Bishop) on Dec 12, 2000 at 02:33 UTC
    How about:
    for(101..103) { my $key = sprintf("%03d", $_); # sprintf adds leading 0's if needed delete $hash{$key}; }
    But I may not really understand what you want.

    The Cookbook (5.6) has a solution that allows you to retrieve items from a hash in insertion order. It uses Tie::IxHash.

    use Tie::IxHash; tie %HASH, "Tie::IxHash"; #keys in insertion order @keys = keys %HASH;
    but I haven't personally tried this yet . . .

    Jeff

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    F--F--F--F--F--F--F--F--
    (the triplet paradiddle)
    
Re: Removing a certain number of hash keys
by eg (Friar) on Dec 12, 2000 at 02:34 UTC

    No, there isn't really any more elegant way. This is because the keys to a hash are (in theory) unordered, so saying something the "the second key" is meaningless.

    Either use a parallel array or sort the keys before use:

    delete( @hash{(sort(keys{%hash}))[2..4]} );
    

    will delete the 2nd through 4th sorted keys from the %hash.

Re: Removing a certain number of hash keys
by cwest (Friar) on Dec 12, 2000 at 02:45 UTC
    No need to put things in order... just harness the powers of .. and a literal hash slice:
    my %hash = qw( 100 g 101 j 102 d 103 h 104 t 105 a ); my $number = 3; my $start = 101; delete @hash{$start .. $start+$number-1};
    enjoy!
    --
    Casey
       I am a superhero.
    
Re: Removing a certain number of hash keys
by Hot Pastrami (Monk) on Dec 12, 2000 at 02:38 UTC
    Assuming you want to delete all keys between 101 and 103 as in your example, this would work:
    for (101..103) { delete $hash{$_}; }
    ...or, if you're feeling silly...
    map { delete $hash{$_} if ($_ > 101 && $_ < 103) } keys %hash;

    Alan "Hot Pastrami" Bellows