如何根据另一个哈希的键/值删除[子]哈希?

发布于 2024-08-27 08:42:53 字数 1397 浏览 5 评论 0原文

假设我有两个哈希值。其中一个包含一组数据,只需要保留另一个哈希中显示的内容。

例如,

my %hash1 = ( 
        test1 => { inner1 => { more => "alpha", evenmore => "beta" } },
        test2 => { inner2 => { more => "charlie", somethingelse => "delta" } },
        test3 => { inner9999 => { ohlookmore => "golf", somethingelse => "foxtrot" } }
    );

my %hash2 = (
        major=> { test2 => "inner2",
              test3 => "inner3" }  );

我想做的是,如果 hash1 中的整个子哈希不作为 hash2{major} 中的键/值存在,则删除它,最好没有模块。 “innerX”中包含的信息并不重要,它只是必须保留(除非要删除子哈希,然后它就会消失)。

在上面的示例中,执行此操作后,hash1 将如下所示:

my %hash1 = ( 
        test2 => { inner2 => { more => "charlie", somethingelse => "delta" } },
        );

它删除 hash1{test1} 和 hash1{test3},因为它们与 hash2 中的任何内容都不匹配。

这是我目前尝试过的方法,但它不起作用。这也可能不是最安全的做法,因为我在尝试删除哈希值时循环遍历哈希值。不过我正在删除每个应该没问题的吗?

这是我尝试这样做,但是 perl 抱怨:

Can't use string ("inner1") as a HASH ref while "strict refs" in use at

while(my ($test, $inner) = each %hash1)
{
    if(exists $hash2{major}{$test}{$inner})
    {
        print "$test($inner) is in exists.\n";
    }
    else
    {
        print "Looks like $test($inner) does not exist, REMOVING.\n";
       #not to sure if $inner is needed to remove the whole entry
         delete ($hash1{$test}{$inner});
    } 
}

Lets assume I have two hashes. One of them contains a set of data that only needs to keep things that show up in the other hash.

e.g.

my %hash1 = ( 
        test1 => { inner1 => { more => "alpha", evenmore => "beta" } },
        test2 => { inner2 => { more => "charlie", somethingelse => "delta" } },
        test3 => { inner9999 => { ohlookmore => "golf", somethingelse => "foxtrot" } }
    );

my %hash2 = (
        major=> { test2 => "inner2",
              test3 => "inner3" }  );

What I would like to do, is to delete the whole subhash in hash1 if it does not exist as a key/value in hash2{major}, preferably without modules. The information contained in "innerX" does not matter, it merely must be left alone (unless the subhash is to be deleted then it can go away).

In the example above after this operation is preformed hash1 would look like:

my %hash1 = ( 
        test2 => { inner2 => { more => "charlie", somethingelse => "delta" } },
        );

It deletes hash1{test1} and hash1{test3} because they don't match anything in hash2.

Here's what I've currently tried, but it doesn't work. Nor is it probably the safest thing to do since I'm looping over the hash while trying to delete from it. However I'm deleting at the each which should be okay?

This was my attempt at doing this, however perl complains about:

Can't use string ("inner1") as a HASH ref while "strict refs" in use at

while(my ($test, $inner) = each %hash1)
{
    if(exists $hash2{major}{$test}{$inner})
    {
        print "$test($inner) is in exists.\n";
    }
    else
    {
        print "Looks like $test($inner) does not exist, REMOVING.\n";
       #not to sure if $inner is needed to remove the whole entry
         delete ($hash1{$test}{$inner});
    } 
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

幻想少年梦 2024-09-03 08:42:53

你很接近。请记住,$hash2{major}{$test} 是标量,而不是哈希引用。

#! /usr/bin/perl

use strict;
use warnings;

my %hash1 = ( 
  test1 => { inner1 => { more => "alpha", evenmore => "beta" } },
  test2 => { inner2 => { more => "charlie", somethingelse => "delta" } },
  test3 => { inner9999 => { ohlookmore => "golf", somethingelse => "foxtrot" } }
);

my %hash2 = (
  major => { test2 => "inner2",
             test3 => "inner3" }
);

foreach my $k (keys %hash1) {
  my $delete = 1;
  foreach my $inner (keys %{ $hash1{$k} }) {
    $delete = 0, last if exists $hash2{major}{$k} &&
                                $hash2{major}{$k} eq $inner;
  }
  delete $hash1{$k} if $delete;
}

use Data::Dumper;
$Data::Dumper::Indent = 1;
print Dumper \%hash1;

$delete = 0, ... 开头的行有点可爱。相当于 $delete = 0; last; 在另一个条件中,但它已经嵌套了两次。不想建造一个 matryoshka 娃娃,我使用了一个 语句修饰符,但顾名思义,它修改单个语句。

这就是 Perl 的逗号运算符 发挥作用的地方:

二进制, 是逗号运算符。在标量上下文中,它评估其左侧参数,丢弃该值,然后评估其右侧参数并返回该值。这就像 C 的逗号运算符。

在本例中,左侧参数是表达式 $delete = 0,右侧参数是 last

该条件可能看起来不必要的繁琐,但

... if $hash2{major}{$k} eq $inner;

在探测 %hash2 中未提及的测试(例如 test1/inner1)时会产生未定义值警告。 使用

.. if $hash2{major}{$k} && $hash2{major}{$k} eq $inner;

如果 %hash2 中提到的测试的“内部名称”是一个错误值(例如字符串 “0”),则 会错误地删除该测试。是的,在这里使用 exists 可能会不必要地繁琐,但由于不知道你的实际哈希键,我选择了保守的路线。

输出:

$VAR1 = {
  'test2' => {
    'inner2' => {
      'somethingelse' => 'delta',
      'more' => 'charlie'
    }
  }
};

尽管您没有违反它,但请注意以下与使用 相关的警告每个

如果在迭代哈希时添加或删除哈希元素,则可能会跳过或重复条目,所以不要这样做。例外:删除 each 最近返回的项目始终是安全的,这意味着以下代码将起作用:

 while (($key, $value) = every %hash) {
      打印 $key, "\n";
      删除 $hash{$key}; # 这是安全的
    }

更新:像数组一样搜索哈希(印象深刻你的 CS 书呆子朋友说“……线性而不是对数”)是一个危险信号,上面的代码就是这样做的。一种更好的方法与 Penfold 的答案类似,

%hash1 = map +($_ => $hash1{$_}),
         grep exists $hash2{major}{$_} &&
              exists $hash1{$_}{ $hash2{major}{$_} },
         keys %hash1;

它以良好的声明式风格描述了 %hash1 所需的内容,即

  1. %hash1 的一级键> 应该在 $hash2{major} 中提及,并且
  2. $hash2{major} 中每个一级键对应的值本身应该是该键的子键%hash1

(哇,令人眼花缭乱。我们需要多个英语占位符变量!)

+($_ => $hash1{$_}) 中的一元加号消除了歧义对于糟糕的解析器来说,它知道我们希望将表达式视为“对”。有关其他信息,请参阅 关于 map 的 perlfunc 文档的末尾可能需要这样做的情况。

You were close. Remember that $hash2{major}{$test} is a scalar, not a hash reference.

#! /usr/bin/perl

use strict;
use warnings;

my %hash1 = ( 
  test1 => { inner1 => { more => "alpha", evenmore => "beta" } },
  test2 => { inner2 => { more => "charlie", somethingelse => "delta" } },
  test3 => { inner9999 => { ohlookmore => "golf", somethingelse => "foxtrot" } }
);

my %hash2 = (
  major => { test2 => "inner2",
             test3 => "inner3" }
);

foreach my $k (keys %hash1) {
  my $delete = 1;
  foreach my $inner (keys %{ $hash1{$k} }) {
    $delete = 0, last if exists $hash2{major}{$k} &&
                                $hash2{major}{$k} eq $inner;
  }
  delete $hash1{$k} if $delete;
}

use Data::Dumper;
$Data::Dumper::Indent = 1;
print Dumper \%hash1;

The line beginning with $delete = 0, ... is a bit cutesy. It's equivalent to $delete = 0; last; within another conditional, but it was already nested twice. Not wanting to build a matryoshka doll, I used a statement modifier, but as the name suggests, it modifies a single statement.

That's where Perl's comma operator comes in:

Binary , is the comma operator. In scalar context it evaluates its left argument, throws that value away, then evaluates its right argument and returns that value. This is just like C's comma operator.

In this case, the left argument is the expression $delete = 0, and the right argument is last.

The conditional might seem needlessly fussy, but

... if $hash2{major}{$k} eq $inner;

produces undefined-value warnings when probing for tests not mentioned in %hash2 (test1/inner1, for example). Using

.. if $hash2{major}{$k} && $hash2{major}{$k} eq $inner;

would incorrectly delete a test mentioned in %hash2 if its "inner name" were a false value such as the string "0". Yes, using exists here may be needlessly fussy, but not knowing your actual hash keys, I chose the conservative route.

Output:

$VAR1 = {
  'test2' => {
    'inner2' => {
      'somethingelse' => 'delta',
      'more' => 'charlie'
    }
  }
};

Although you don't violate it, be aware of the following caveat related to using each:

If you add or delete elements of a hash while you're iterating over it, you may get entries skipped or duplicated, so don't. Exception: It is always safe to delete the item most recently returned by each, which means that the following code will work:

    while (($key, $value) = each %hash) {
      print $key, "\n";
      delete $hash{$key};   # This is safe
    }

Update: Searching hashes as though they were arrays (impress your CS nerd friends by saying “… linearly rather than logarithmically”) is a red flag, and the code above does just that. A better approach, which turns out to be similar to Penfold's answer, is

%hash1 = map +($_ => $hash1{$_}),
         grep exists $hash2{major}{$_} &&
              exists $hash1{$_}{ $hash2{major}{$_} },
         keys %hash1;

In nice declarative style, it describes the desired contents of %hash1, namely

  1. first-level keys of %hash1 should be mentioned in $hash2{major}, and
  2. the value in $hash2{major} corresponding to each first-level key should itself be a subkey of that key back in %hash1

(Wow, dizzying. We need multiple placeholder variables in English!)

The unary plus in +($_ => $hash1{$_}) disambiguates for the poor parser so it knows we want the expression treated as a “pair.” See the end of the perlfunc documentation on map for other cases when this may be necessary.

逆流 2024-09-03 08:42:53

您可以将其作为单行代码来完成,因为delete() 将采用一组键。这并不像我最初想象的那么容易,但现在我已经正确地阅读了这个问题......

delete @hash1{ 
        grep(
            !(
              exists($hash2{major}->{$_}) 
                && 
              exists( $hash1{$_}->{ $hash2{major}->{$_} } )
            ),
            keys %hash1
        )
    };

You can do it as a one-liner, all because delete() will take an array of keys. It's not quite as easy as I first thought, but now I've read the problem properly...

delete @hash1{ 
        grep(
            !(
              exists($hash2{major}->{$_}) 
                && 
              exists( $hash1{$_}->{ $hash2{major}->{$_} } )
            ),
            keys %hash1
        )
    };
孤芳又自赏 2024-09-03 08:42:53
# This is the actual hash we want to iterate over.
my $keepers = $hash2{major};

%hash1 = map { $_ => $hash1{$_} }  # existing key and hash contents in %hash1
             grep { exists $keepers->{$_} and               # key there?
                    exists $hash1{$_}->{ $keepers->{$_} } } # key in hash there?
             (keys %hash1);        # All the keys we might care about

这是有效的,因为我们本质上是在三个独立的阶段中列出我们想要/不想要的东西的列表:

  1. keys 调用一步获取 hash1 中的所有键。
  2. grep 生成(作为一步)符合我们的标准的键列表。
  3. 映射(作为一个步骤)生成一组我们想要的键和值。

这样,在我们准备好之前,我们永远不会更改主哈希。如果 %hash1 包含许多键,我们将使用大量内存。如果您担心这一点,您可以这样做:

# Initialization as before ...

use File::Temp qw(tempfile);

my ($fh, $file) = tempfile();
my $keepers = $hash2{major};

print $fh "$_\n" for (keys %hash1);
close $fh;
open $fh, "<", $file or die "can't reopen tempfile $file: $!\n";
while ( defined ($_ = <$fh>) ) {
  chomp;
  delete $hash1{$_} 
    unless exists $keepers->{$_} and 
           exists $hash1{$_}->{ $keepers->{$_} }; 
}

这个之所以有效,是因为我们不是在散列上迭代,而是在其密钥的存储副本上迭代。

# This is the actual hash we want to iterate over.
my $keepers = $hash2{major};

%hash1 = map { $_ => $hash1{$_} }  # existing key and hash contents in %hash1
             grep { exists $keepers->{$_} and               # key there?
                    exists $hash1{$_}->{ $keepers->{$_} } } # key in hash there?
             (keys %hash1);        # All the keys we might care about

This works because we essentially work out the lists of things we want/don't want in three independent stages:

  1. The keys call gets all the keys that are in hash1 in one step.
  2. The grep generates (as one step) the list of keys that match our criterion.
  3. The map generates (as one step) a set of keys and values that are the ones we want.

This way we never alter the primary hash until we're ready to do so. If %hash1 contains many keys, we're going to use a lot of memory. If you're worried about that, you'd do something like this:

# Initialization as before ...

use File::Temp qw(tempfile);

my ($fh, $file) = tempfile();
my $keepers = $hash2{major};

print $fh "$_\n" for (keys %hash1);
close $fh;
open $fh, "<", $file or die "can't reopen tempfile $file: $!\n";
while ( defined ($_ = <$fh>) ) {
  chomp;
  delete $hash1{$_} 
    unless exists $keepers->{$_} and 
           exists $hash1{$_}->{ $keepers->{$_} }; 
}

This one works because we're not iterating over the hash, but over a stored copy of its keys.

不羁少年 2024-09-03 08:42:53

这就是我会这样做的方式:(第三次尝试就是魅力)

foreach ( map { [ $_ => $hash2{major}{$_} ] } keys %hash1 ) { 
    my ( $key, $value ) = @$_;
    if ( defined $value and my $new_value = $hash1{$key}{$value} ) { 
        $hash1{$key} = $new_value;
    }
    else { 
        delete $hash1{$key};
    }
}

This is the way I would do it: (Third try's the charm)

foreach ( map { [ $_ => $hash2{major}{$_} ] } keys %hash1 ) { 
    my ( $key, $value ) = @$_;
    if ( defined $value and my $new_value = $hash1{$key}{$value} ) { 
        $hash1{$key} = $new_value;
    }
    else { 
        delete $hash1{$key};
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文