Kyra Westwood
Kyra Westwood

Reputation: 147

How to return true if only one specific key in a hash has a true value (all the other values are false)

For instance:

options = { fight: true,
 use_item: false,
 run_away: false,
 save_game: false }

I want a boolean expression that evaluates to true iff only :fight is true, and the rest are false (as illustrated above).

I can hack this together, but I'm trying to train myself to write more elegant ruby. Thanks!

EDIT: The hack being:

(options[:fight] == true && options.delete(:fight).values.all {|x| !x})

Upvotes: 5

Views: 1106

Answers (8)

randym
randym

Reputation: 2460

If you control the content of the hash, and it is relatively small, I would use something like this in a private method as a generic solution.

def exclusively_true?(hash, key)
  return false unless hash.delete(key) == true
  !hash.has_value? true
end

require 'test/unit'
class TestExclusive < Test::Unit::TestCase
  def setup
    @test_hash = {foo: true, bar: false, hoge: false}
  end
  def test_exclusive
    assert_equal(true, exclusively_true?(@test_hash, :foo))
  end
  def test_inexclusive
    @test_hash[:bar] = true
    assert_equal(false, exclusively_true?(@test_hash, :foo))
  end
end

require 'benchmark'

h = {foo: true}
999.times {|i| h["a#{i}"] = false}
Benchmark.bmbm(30) do |x|
  x.report('exclusively_true') do
    1000.times do
      exclusively_true?(h, :foo)
    end
  end
end

Contrived benchmarks: (OS X 10.8.3 / 3 GHz / 8 GB)

ruby -v: ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-darwin12.3.0]
Rehearsal ------------------------------------------------------------------
exclusively_true                 0.000000   0.000000   0.000000 (  0.000412)
--------------------------------------------------------- total: 0.000000sec

                                     user     system      total        real
exclusively_true                 0.000000   0.000000   0.000000 (  0.000331)

Upvotes: 0

Victor Moroz
Victor Moroz

Reputation: 9225

options.select{ |k, v| v } == [[:fight, true]]

Upvotes: 0

Arup Rakshit
Arup Rakshit

Reputation: 118271

options.find_all{|k,v| v } == [[:fight, true]]

or

options.values.count(true) == 1 && options[:fight]

Upvotes: 1

Shawn Balestracci
Shawn Balestracci

Reputation: 7530

Inspired by Vitaliy's answer:

options[:flight] && options.values.one?

Upvotes: 8

tessi
tessi

Reputation: 13574

How about:

options.all? {|k,v| k == :fight ? v : !v}

For a more general approach:

def is_action?(options, action)
  options.all? {|k,v| k == action ? v : !v}
end

is_action? options, :fight
# => true

Upvotes: 1

Nerve
Nerve

Reputation: 6871

This one is independent of number of key/elems in the hash.

options[:fight] && options.find_all{|arr| !arr[1]}.size == options.size-1

Also Just a tip, in ruby, you never need to write something like:

options[:fight] == true

Upvotes: 1

Vitaliy Yanchuk
Vitaliy Yanchuk

Reputation: 1491

I think your hack is not bad. It can be simplified a little bit though:

options.delete(:flight) && options.values.none?

Upvotes: 2

DanSingerman
DanSingerman

Reputation: 36502

Assuming all values are strictly boolean, it's as simple as:

options == {fight: true, use_item: false, run_away: false, save_game: false}

See documentation for the == method

Upvotes: 9

Related Questions