【问题标题】:Combine hashes with arrays for some keys将哈希与某些键的数组结合起来
【发布时间】:2016-09-24 05:09:04
【问题描述】:

我对 perl 比较陌生,而且我已经坚持了好几天了。希望你能帮助我。

我使用两个文件,因为我必须事先处理它们,所以我将对其进行简化:

file_onenames (name_1, name_2, name_3...) 和 numbers (number_1, number_2, number_3...) 的列表分别关联

file_two 以及numbers(number_2 和 number_6)和items(与 number_2 相关联的 item_a、item_b 和与 number_6 相关联的 item_b、item_c)的列表

我的想法是对两个文件进行哈希处理并将它们组合起来。我卡住的地方是当我需要将项目列表加入散列(数组的散列)然后使用它时。所以第一个哈希工作正常,但第二个有问题。

我尝试使用push (@{ $hash2{$numbers} }, $items),但由于我使用的参考,我不知道如何将它与另一个结合起来。

最后的任务是比较两个名称,以获取它们共享的项目。如果可以只使用 perl 并尽可能不使用任何模块,那就太好了。

非常感谢

【问题讨论】:

  • 粘贴两个文件的 sn-p 对理解问题非常有用。无需花哨的格式,只需突出显示它并点击{} 获取代码示例。
  • 一个例子总比一个描述好,只要你展示一整套案例。 “file_one 与名称列表 (name_1, name_2, name_3...) 和数字 (number_1, number_2, number_3...) 分别关联” Schwern,下面,似乎对他对你的话的解释相当有信心,但我有疑问
  • markdown中没有办法建表。最好的选择是将几行数据文件复制并粘贴到您的问题中。如果您将所有这些行缩进四个空格(您可以通过选择所有行并按 Ctrl-K 来完成),那么它们将显示为预先格式化。
  • 为什么没有模块?

标签: arrays perl hash


【解决方案1】:

如果我对你的理解正确,你有这个:

foo => 1
bar => 2
baz => 3

那么你有:

2 => a, b
3 => b, c

并且您想知道barbaz 共享哪些项目(例如)。

一种选择是将它们放入SQLite 数据库中的两个表中并使用SQL。这可能是处理此类关系数据的最简单、最灵活和最高效的方式。特别是如果它有很多,特别是如果你想对它进行很多不同的搜索。这避免了编写一堆自定义代码和可能越来越复杂的数据结构。


在 Perl 中进行,这是一个草图。

首先,将包含叶子(项目不指向其他任何内容)的第二个文件读入数组散列。您最终会得到如下结构:

$nums2items{2} = [qw(a, b)];

然后将第一个文件读入哈希,但不是将数字存储为值,而是存储 %nums2items 引用的内容。

$names2items{foo} = $nums2items{1};

现在如果您想检查barbaz 是否共享任何内容,您可以获取数组并找到它们与Array::Utils 的交集。

use Array::Utils qw(intersect);

print join ", ", intersect( @{$names2items{bar}}, @{$names2items{baz}});

如果您要经常这样做,并且项目的顺序并不重要,那么将项目存储为哈希会更有效。这避免了必须对两个列表进行排序和比较。这就是intersect 所做的事情,将一个列表转换为哈希(或集合)并将其与另一个列表进行比较。

use strict;
use warnings;
use v5.10;

my %nums2items = (
    2   => { a => 1, b => 1,         d => 1 },
    3   => {         b => 1, c => 1, d => 1, e => 1 },
);
my %names2nums = (
    bar => $nums2items{2},
    baz => $nums2items{3}
);

# Take the intersection in O(n) time.
say join ", ", grep { $names2nums{bar}{$_} } keys %{$names2nums{baz}};

使用这样的散列,其中键是事物,值是 1,是表示集合的一种非常常见且有效的方式。

或者您可以使用Set::Tiny 模块。这是非常直接的。如果您想学习如何在 Perl 中使用集合,我强烈建议您阅读它的源代码。

【讨论】:

  • 是的,基本上你的解释是正确的,谢谢你的回答。但我只需要为此使用 perl,也不需要模块,因为我想学习如何做到这一点......我的意思是当然如果可能的话
  • @Gutnu 当然这是可能的。编写 Array::Utils 的人做到了。阅读源代码以了解它是如何完成的。
  • @Gutinu “我不想/不能使用模块”为 Perl 程序员提出了一个危险信号。它们非常重要,许多地方都误导了“无 CPAN 模块”政策。为了学习我建议的方法是长手,然后用模块做,然后用 SQLite 做。这包括“我想尝试自己编写它”以及“这就是你如何使用大量数据进行生产”。尝试自己写intersect,然后peek inside Array::Utils 看看他们是如何做到的。
  • @Gutnu 我强烈推荐你read the source of Set::Tiny 看看如何高效地使用 Perl 中的集合。这非常简单。
【解决方案2】:

从您对 Schwern 的评论看来,您的文件看起来像这样:

foo, 1
bar, 2
biz, 3
bas, 4

1, jacks blue horse
2, the green horse
3, jacks
4, bing

并且您成功地将它们读入两个散列,其中逗号之前的值作为键,逗号之后的值作为值。现在你要明智地使用单词并打印出它们共同的单词。您不想使用任何模块,而是在原始 Perl 中使用。

首先,如果是数字键,为什么第二个不是数组数组而不是哈希?

其次,你为什么要合并它们?为什么不使用嵌套循环:

my @key_list = keys %hash_1;
while ( @key_list )
   {
   my $curr_key = shift @key_list;
   for my $next_key ( @key_list )
      {
      my @curr_list = @{$hash_2{$hash_1{$curr_key}}};
      my @next_list = @{$hash_2{$hash_1{$next_key}}};
      while ( @curr_list )
         {
         my $curr_word = shift @curr_list;
         for my $next_word ( @next_list )
            {
            print "$curr_key and $next_key share $curr_word\n"
               if $curr_word eq $next_word;
            }
         }
      }
   }

这有点蛮力,但它可以完成工作。相反,您可以使用出色的 Set:: 模块。了解和使用 Perl 或 C++ 等现代语言的一部分是了解标准和通用库并使用它们。

【讨论】:

  • "为什么不使用嵌套循环?" 答案是因为它非常低效并且很难理解 四个 嵌套循环。您的循环至少是 O(n!) 并且可能更糟,这意味着随着键列表翻倍,您的运行时间将翻两番。最后,取消引用所有列表会使每个列表的副本增加内存使用量。或者您非常聪明并说明了为什么应该使用模块而不是自己编写模块。 :)
  • 我很清楚效率低下,但 OP 表示他希望避免使用除了蛮力之外几乎没有其他方法的模块。我更喜欢你的 Arrays::Utils 方法。我倾向于使用 Set::Scalar 的交集。这就是为什么最后我指出了解和使用可用模块的重要性。
  • 我在我的答案中添加了一个使用哈希作为集合的 O(n) 解决方案。看看吧。
猜你喜欢
  • 1970-01-01
  • 2012-11-19
  • 1970-01-01
  • 2021-05-14
  • 1970-01-01
  • 2014-03-19
  • 2020-07-09
  • 2019-05-03
  • 1970-01-01
相关资源
最近更新 更多