以条件方式构建哈希表

52

我正在使用 Ruby on Rails 3.0.10,并且我想以条件方式构建一个哈希键值对。也就是说,如果满足条件,我想添加一个键及其相关的值:

hash = {
  :key1 => value1,
  :key2 => value2, # This key2\value2 pair should be added only 'if condition' is 'true'
  :key3 => value3,
  ...
}

我该如何做到这一点,同时保持代码的可读性?我是否需要强制使用merge方法?


键和值来自哪里?不同的哈希表吗? - Ray Toal
@Ray Toal - 不是从另一个哈希表中获取。我正在从头开始构建一个哈希表。 - Backo
3
抱歉,我不理解。数据必须来自某个地方:一个字符串、另一个哈希、一个数组或其他地方。即使您是“从头构建哈希表”,也必须拥有数据。在Ruby中,没有使用哈希字面量的条件插入;您可以使用Chris Jester-Young的技术在之后删除。 - Ray Toal
所有条件值都由一个条件控制吗? - Martin DeMello
@Martin DeMello - 只有一个条件用于添加或不添加一个键值对。 - Backo
2
@backo 你的意思是整个哈希表中只有一个条件值吗?如果是的话,我会建议将其插入到末尾。 - Martin DeMello
11个回答

69

我更喜欢使用 tap,因为我认为它提供了比此处描述的方法更加干净的解决方案,无需删除元素并清晰地定义建立哈希的范围。

这也意味着你不需要声明一个不必要的本地变量,而这正是我所厌恶的。

如果你以前没有接触过,tap非常简单——它是一个在 Object 上的方法,接受一个块并始终返回调用它的对象。因此,要有条件地构建哈希,你可以这样做:

Hash.new.tap do |my_hash|
  my_hash[:x] = 1 if condition_1
  my_hash[:y] = 2 if condition_2
  ...
end

tap 有许多有趣的用途,这只是其中之一。


2
作业丢失了吗?hash = Hash.new.tap do ... - tokland
6
这要看你是否想将它分配给任何东西。对我来说,通常这已经足够让方法自己完成,所以这就是整个方法体 - 不需要将其分配给任何东西。 - Russell
在这里也可以做一个 else 条件吗? - Sankalp Singha
@SankalpSingha 是的,但不够优美:可以使用三元表达式,如 condition_1 ? my_hash[:x1] : my_hash[:x2] 或者 if condition_1 then my_hash[:x1] else my_hash[:x2] end,甚至将其拆分成多行也可以。 - Joshua Pinter

43

使用Hash.compact的函数式方法:

hash = {
  :key1 => 1,
  :key2 => (2 if condition),
  :key3 => 3,
}.compact 

这也是一个很好的答案。 - Greg Blass
事实上,如果你只是想在键不存在的情况下不包含它们(就像我的情况一样),这种方法更加简洁。 - Greg Blass
2
这种方法的缺点是,如果你需要哈希中的某些值为nil。 - Andy Baird
2
如果您使用的是Rails且未升级到Ruby 2.4+,ActiveSupport还提供了Hash#compact - Jerph
如果您正在尝试构建标签助手选项的哈希,并且“tap”方法不太灵活,则此方法非常有用。 - Alan Lattimore

14

如果你担心可读性,最好保持简单:

hash = {}
hash[:key1] = value1
hash[:key2] = value2 if condition?
hash[:key3] = value3
...

我喜欢 tap,但老实说,这可能是最佳选择,因为它没有做任何花哨或神奇的事情。这是“最低公共分母”解决方案。 - Joshua Pinter
我回到了这个问题8个月后,不知不觉中,我得出了与过去自己相同的结论。越少用魔法就越好。 - Joshua Pinter

7

保持简单:

hash = {
  key1: value1,
  key3: value3,
}

hash[:key2] = value2 if condition

这样做还可以使您的特殊情况在散列文字赋值中被忽视时更加突出。


1
这应该是被接受的答案。所有其他答案要么过于复杂,要么对眼睛造成更大的负担。 - coisnepe

4

在这种情况下,我使用merge和三目运算符。

hash = {
  :key1 => value1,
  :key3 => value3,
  ...
}.merge(condition ? {:key2 => value2} : {})

3
简单如下:
hash = {
  :key1 => value1,
  **(condition ? {key2: value2} : {})
}

希望能帮到你!

1

如果你从可枚举的数据构建哈希表,你可以使用inject方法,例如:

raw_data.inject({}){ |a,e| a[e.name] = e.value if expr; a }

1

如果您想在单个条件下添加几个键,可以使用merge

hash = {
  :key1 => value1,
  :key2 => value2,
  :key3 => value3
}

if condition
  hash.merge!(
    :key5 => value4,
    :key5 => value5,
    :key6 => value6
  )
end

hash

0

首先按照以下方式构建您的哈希表:

hash = {
  :key1 => value1,
  :key2 => condition ? value2 : :delete_me,
  :key3 => value3
}

在构建哈希表之后,执行以下操作:

hash.delete_if {|_, v| v == :delete_me}

除非您的哈希表被冻结或以其他方式不可变,否则这将有效地仅保留存在的值。


我必须从零开始构建一个哈希表。 - Backo
@Backo:没错。先构建你的哈希表(包括有值和空值),然后运行那个delete_if语句来清除空值。 - C. K. Young
正确。该条件不是在“值”本身上运行,而是在“外部”方法上运行。我更新了问题。 - Backo
@Backo:已相应更新帖子。 - C. K. Young
如果value2的合法值为:delete_me,则该方法无法正常工作。在这种情况下,它会被删除。 - Dave Morse

0

如果你需要从其他地方填充哈希表的可选属性,使用fetch会非常有用。看看这个例子:

def create_watchable_data(attrs = {})
  return WatchableData.new({
    id:             attrs.fetch(:id, '/catalog/titles/breaking_bad_2_737'),
    titles:         attrs.fetch(:titles, ['737']),
    url:            attrs.fetch(:url, 'http://www.netflix.com/shows/breaking_bad/3423432'),
    year:           attrs.fetch(:year, '1993'),
    watchable_type: attrs.fetch(:watchable_type, 'Show'),
    season_title:   attrs.fetch(:season_title, 'Season 2'),
    show_title:     attrs.fetch(:id, 'Breaking Bad')
  })
end

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