Ruby中使用键和值数组进行简单的哈希合并(附带Perl示例)

7
在Perl中,要根据键和值的数组执行哈希更新,可以像这样做:

$hash{$_} = shift @values for @keys;

@hash{'key1','key2','key3'} = ('val1','val2','val3');

在Ruby中,我可以以更复杂的方式做类似的事情:
hash.merge!(Hash[ *[['key1','key2','key3'],['val1','val2','val3']].transpose ])

好的,但我怀疑这种过程的有效性。

现在我想在一行中执行更复杂的任务。

Perl示例:

(@hash{'key1','key2','key3'}, $key4) = &some_function();

我不确定是否有一种简单的Ruby方法可以实现这样的事情。 有什么提示吗?

对于Perl编程不熟练的人来说,@hash {'key1','key2','key3'} =('a','b','c') 是一个哈希切片,相当于以下代码:

$hash{'key1'} = 'a';
$hash{'key2'} = 'b';
$hash{'key3'} = 'c';

问题在于hash{'key1','key2','key3'}是错误的语法。因此你不能这样做。 - fl00r
那个最后的 Perl 行是什么?调用一个函数并用函数的前三个返回值更新哈希表,同时将本地变量设置为第四个(及更多?)值。 - Phrogz
1
@Phrogz:那是一个哈希切片,参见这里的第二个代码块。 - mu is too short
1
@fl00r,@hash{'key1','key2','key3'} = (1, 2, 3); 在 Perl 中是完全正确的。 - geronime
最后一行是Perl而不是Ruby。我认为&some_function返回值数组,例如:[1,2,3,4]。前三个保存到哈希表中,看起来像这样:@hash = {'key1' => 1,'key2' => 2,'key3 => 3}。第四个值存储在$key4中。如果&some_function返回了多于4个的值,则$key4将存储数组= [4,5,...]。(抱歉混淆了Ruby和Perl语法 :))。 - Lukas Stejskal
我在谈论Ruby语法 :) - fl00r
3个回答

4
在Ruby 1.9中,Hash.[]可以将一个由两个值的数组组成的数组作为自己的参数(除了旧方法使用平铺的键/值参数列表)。因此,实现起来相对简单:
mash.merge!( Hash[ keys.zip(values) ] )

我不了解Perl语言,因此我不确定你最终的“更复杂的任务”是什么。你能用文字或样例输入输出来解释一下你想要实现什么吗?

编辑:根据@fl00r答案中的讨论,你可以这样做:

def f(n)
  # return n arguments
  (1..n).to_a
end

h = {}
keys = [:a,:b,:c]
*vals, last = f(4)
h.merge!( Hash[ keys.zip(vals) ] )
p vals, last, h
#=> [1, 2, 3]
#=> 4
#=> {:a=>1, :b=>2, :c=>3}

代码*a, b = some_array将会把最后一个元素赋值给b,并创建一个由其他值组成的数组a。这种语法需要Ruby 1.9及以上版本支持。如果需要兼容1.8版本,可以使用以下代码:

vals = f(4)
last = vals.pop
h.merge!( Hash[ *keys.zip(vals).flatten ] )

我将尝试解释第二个Perl示例: 我期望&some_function()返回一个由四个值组成的数组。 复杂的Perl赋值示例将把返回数组的第一个值分配到hashkey1值的位置,将返回数组的第二个值分配到hashkey2值的位置,...最后将返回数组的第四个值分配到key4变量中。 - geronime
1
这里真正的问题是,Perl会将多个哈希键解释为独立的“lvalue”目标,而Ruby则会将它们集体视为数组。在很大程度上,与Ruby相比,Perl具有更强大但微妙的表达“lvalues”的方式。 - tadman
感谢澄清。在Ruby中,没有一个简单的一行代码可以像Perl代码那样完成这个任务。最接近的是@fl00r发布的内容。在11年的Ruby编程经验中,我不认为我曾经需要从方法中提取返回值的子集并更新哈希表,同时保留其他返回值。我很感兴趣,你有使用场景吗?这个功能有用(或必要)吗? - Phrogz
@geronime 看看我的更新;虽然不是一行代码,但这是实现你目标的一种方式。 - Phrogz
@Phrogz:我用Perl编程已经有几年了,现在已经转到Ruby快一年了。我经常使用哈希切片赋值。这种复杂的赋值操作的一个例子可能是这样的(虚拟的混合Ruby/Perl代码):@counters{'done','failed'}, queue_length = @redis.exec,其中@redis.exec返回排队命令@redis.mget 'done', 'failed'@redis.llen 'queue'的值,在一个原子事务中完成。在Ruby中,我必须将计数器存储在数组中,并稍后映射到哈希表中。 - geronime

3
您可以重新定义[]=来支持这一点:
class Hash
  def []=(*args)
    *keys, vals = args # if this doesn't work in your version of ruby, use "keys, vals = args[0...-1], args.last"
    merge! Hash[keys.zip(vals.respond_to?(:each) ? vals : [vals])]
  end
end

现在使用
myhash[:key1, :key2, :key3] = :val1, :val2, :val3
# or
myhash[:key1, :key2, :key3] = some_method_returning_three_values
# or even
*myhash[:key1, :key2, :key3], local_var = some_method_returning_four_values

或者,如果 OP 想要将其中一个返回值捕获为本地变量,则应编写 *h[:a, :b, :c], d = many_values。然而,像那样猴子补丁整个 Hash 是完全不负责任的,很可能会破坏其他人的代码。这应该作为一个模块来完成,用于扩展任何要完成此操作的 Hash 实例。 - Phrogz
@Phrogz:那就是了。但我需要重新定义Hash类的[]=方法,这也不符合我的喜好。 - geronime
@jtbandes 现在我有些别扭了,但是:h[[1,2]] = [3,4] - Phrogz
@Phrogz:好的,我猜在不检查传递的参数数量的情况下保留原始功能是不可能的。这也许实际上是个好主意。我只希望有一种方法可以创建一个自定义运算符,它能够以与“[]=”相同的方式工作。 - jtbandes
好的,这可能是最好的答案。我可以重新定义Hash类的[]=方法,并进行参数计数检查,就像@jtbandes所提到的那样。由于可能没有其他足够简单的解决方案,我宁愿坚持使用两行解决方案将函数输出存储在数组中,然后将其合并到哈希中。 - geronime
显示剩余4条评论

1

你可以做到这个

def some_method
  # some code that return this:
  [{:key1 => 1, :key2 => 2, :key3 => 3}, 145]
end

hash, key = some_method
puts hash
#=> {:key1 => 1, :key2 => 2, :key3 => 3}
puts key
#=> 145

更新

Ruby中可以使用“并行赋值”,但是你不能像在Perl中那样使用哈希(hash{:a, :b, :c))。但你可以尝试这个:

hash[:key1], hash[:key2], hash[:key3], key4 = some_method

some_method 返回一个包含4个元素的数组。


那不是一个解决方案。如果我想在右侧使用某些第三方方法的结果怎么办?写一些包装器吗?可能不是最好的选择。 - geronime
@geronime,请查看我的更新。右侧的方法应该返回一个与左侧相同数量的项目数组。 - fl00r
@floor:没错,这样做是可行的,但如果我想通过一个包含超过三个元素的数组传递要更新的键,那么它就无法使用了。例如在Perl中:@keys = ('key1','key2',... lots more); @hash{@keys} = &some_function() - geronime
如果您想将额外的返回值捕获为一个数组,请将 key4 更改为 *key4。否则,其他值将被忽略,并且 key4 将始终是第四个返回值(或更准确地说,是从该方法返回的单个数组中的第四个元素)。 - Phrogz
@Phrogz:我知道,Perl的行为类似(所有冗余值都将被忽略 - 要将它们存储在数组中,我只需要将$key4标量更改为@key4数组)。 - geronime

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接