I have hash structure like ['a'=> false, 'b' => false, 'c' => false, ......].
My conditions are:
I am using multi-threading for each element and running some piece of code for making 'false' to true based on certain condition.
This loop will continue till all element's value become 'true' or certain 'timeout'.
I want a watcher which should collect those keys on each loop whose values become 'true' as i am running another code for these 'true' valued elements.
However, the code i am writing doesn't have ruby-ism. It feels like writing java code.
Please help me for the ruby-ism approach.
Here is the code to show the differences between two hashes extracted from active_support.
# from https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/hash/diff.rb
def hash_diff(h1, h2)
h1.dup.delete_if { |k, v| h2[k] == v }.merge!(h2.dup.delete_if { |k, v| h1.has_key?(k) })
end
Now you this is how you can use to detect differences between each iteration of your loop:
previous ||= my_hash
diff = hash_diff(my_hash, previous)
puts "Difference: #{diff.inspect}" unless diff.keys.empty?
previous = my_hash
Note that you may have to use mutexes for this code to avoid race conditions and other nasty errors.
Related
I want to be able to find a custom class in my set given just a string. Like so:
require 'set'
Rank = Struct.new(:name, keyword_init: true) {
def hash
name.hash
end
def eql?(other)
hash == other.hash
end
def ==(other)
hash == other.hash
end
}
one = Rank.new(name: "one")
two = Rank.new(name: "two")
set = Set[one, two]
but while one == "one" and one.eql?("one") are both true, set.include?("one") is still false. what am i missing?
thanks!
Set is built upon Hash, and Hash considers two objects the same if:
[...] their hash value is identical and the two objects are eql? to each other.
What you are missing is that eql? isn't necessarily commutative. Making Rank#eql? recognize strings doesn't change the way String#eql? works:
one.eql?('one') #=> true
'one'.eql?(one) #=> false
Therefore it depends on which object is the hash key and which is the argument to include?:
Set['one'].include?(one) #=> true
Set[one].include?('one') #=> false
In order to make two objects a and b interchangeable hash keys, 3 conditions have to be met:
a.hash == b.hash
a.eql?(b) == true
b.eql?(a) == true
But don't try to modify String#eql? – fiddling with Ruby's core classes isn't recommended and monkey-patching probably won't work anyway because Ruby usually calls the C methods directly for performance reasons.
In fact, making both hash and eql? mimic name doesn't seem like a good idea in the first place. It makes the object's identity ambiguous which can lead to very strange behavior and hard to find bugs:
h = { one => 1, 'one' => 1 }
#=> {#<struct Rank name="one">=>1, "one"=>1}
# vs
h = { 'one' => 1, one => 1 }
#=> {"one"=>1}
what am i missing?
What you are missing is that "one" isn't in your set. one is in your set, but "one" isn't.
Therefore, the answer Ruby is giving you is perfectly correct.
All that you have done with your implementation of Rank is that any two ranks with the same name are considered to be the same by a Hash, Set, or Array#uniq. But, a Rank is not the same as a String.
If you want to be able to have a set-like data structure where you can look up things by one of their attributes, you will have to write it yourself.
Something like (untested):
class RankSet < Set
def [](*args)
super(*args.map(&:name))
end
def each
return enum_for(__callee__) unless block_given?
super {|e| yield e.name }
end
end
might get you started.
Or, instead of writing your own set, you can just use the fact that any arbitrary rank with the right name can be used for lookup:
set.include?(Rank.new(name: "one"))
#=> true
# even though it is a *different* `Rank` object
I'm parsing XML files and wanting to omit duplicate values from being added to my Array. As it stands, the XML will looks like this:
<vulnerable-software-list>
<product>cpe:/a:octopus:octopus_deploy:3.0.0</product>
<product>cpe:/a:octopus:octopus_deploy:3.0.1</product>
<product>cpe:/a:octopus:octopus_deploy:3.0.2</product>
<product>cpe:/a:octopus:octopus_deploy:3.0.3</product>
<product>cpe:/a:octopus:octopus_deploy:3.0.4</product>
<product>cpe:/a:octopus:octopus_deploy:3.0.5</product>
<product>cpe:/a:octopus:octopus_deploy:3.0.6</product>
</vulnerable-software-list>
document.xpath("//entry[
number(substring(translate(last-modified-datetime,'-.T:',''), 1, 12)) > #{last_imported_at} and
cvss/base_metrics/access-vector = 'NETWORK'
]").each do |entry|
product = entry.xpath('vulnerable-software-list/product').map { |product| product.content.split(':')[-2] }
effected_versions = entry.xpath('vulnerable-software-list/product').map { |product| product.content.split(':').last }
puts product
end
However, because of the XML input, that's parsing quite a bit of duplicates, so I end up with an array like ['Redhat','Redhat','Redhat','Fedora']
I already have the effected_versions taken care of, since those values don't duplicate.
Is there a method of .map to only add unique values?
If you need to get an array of unique values, then just call uniq method to get the unique values:
product =
entry.xpath('vulnerable-software-list/product').map do |product|
product.content.split(':')[-2]
end.uniq
There are many ways to do this:
input = ['Redhat','Redhat','Redhat','Fedora']
# approach 1
# self explanatory
result = input.uniq
# approach 2
# iterate through vals, and build a hash with the vals as keys
# since hashes cannot have duplicate keys, it provides a 'unique' check
result = input.each_with_object({}) { |val, memo| memo[val] = true }.keys
# approach 3
# Similar to the previous, we iterate through vals and add them to a Set.
# Adding a duplicate value to a set has no effect, and we can convert it to array
result = input.each_with_object.(Set.new) { |val, memo| memo.add(val) }.to_a
If you're not familiar with each_with_object, it's very similar to reduce
Regarding performance, you can find some info if you search for it, for example What is the fastest way to make a uniq array?
From a quick test, I see these performing in increasing time. uniq is 5 times faster than each_with_object, which is 25% slower than the Set.new approach. Probably because sort is implemetned using C. I only tested with only an arbitrary input though, so it might not be true for all cases.
Could not find a previous post that answers my question...I'm learning how to use destructive vs. non-destructive methods in Ruby. I found an answer to the exercise I'm working on (destructively adding a number to hash values), but I want to be clear on why some earlier solutions of mine did not work. Here's the answer that works:
def modify_a_hash(the_hash, number_to_add_to_each_value)
the_hash.each { |k, v| the_hash[k] = v + number_to_add_to_each_value}
end
These two solutions come back as non-destructive (since they all use "each" I cannot figure out why. To make something destructive is it the equals sign above that does the trick?):
def modify_a_hash(the_hash, number_to_add_to_each_value)
the_hash.each_value { |v| v + number_to_add_to_each_value}
end
def modify_a_hash(the_hash, number_to_add_to_each_value)
the_hash.each { |k, v| v + number_to_add_to_each_value}
end
The terms "destructive" and "non-destructive" are a bit misleading here. Better is to use the conventional "in-place modification" vs. "returns a copy" terminology.
Generally methods that modify in-place have ! at the end of their name to serve as a warning, like gsub! for String. Some methods that pre-date this convention do not have them, like push for Array.
The = performs an assignment within the loop. Your other examples don't actually do anything useful since each returns the original object being iterated over regardless of any results produced.
If you wanted to return a copy you'd do this:
def modify_a_hash(the_hash, number_to_add)
Hash[
the_hash.collect do |k, v|
[ k, v + number_to_add ]
end
]
end
That would return a copy. The inner operation collect transforms key-value pairs into new key-value pairs with the adjustment applied. No = is required since there's no assignment.
The outer method Hash[] transforms those key-value pairs into a proper Hash object. This is then returned and is independent of the original.
Generally a non-destructive or "return a copy" method needs to create a new, independent version of the thing it's manipulating for the purpose of storing the results. This applies to String, Array, Hash, or any other class or container you might be working with.
Maybe this slightly different example will be helpful.
We have a hash:
2.0.0-p481 :014 > hash
=> {1=>"ann", 2=>"mary", 3=>"silvia"}
Then we iterate over it and change all the letters to the uppercase:
2.0.0-p481 :015 > hash.each { |key, value| value.upcase! }
=> {1=>"ANN", 2=>"MARY", 3=>"SILVIA"}
The original hash has changed because we used upcase! method.
Compare to method without ! sign, that doesn't modify hash values:
2.0.0-p481 :017 > hash.each { |key, value| value.downcase }
=> {1=>"ANN", 2=>"MARY", 3=>"SILVIA"}
taglist = [{:name=>"Daniel_Xu_Forever", :tag=>["helo", "world"]},
{:name=>"kcuf", :tag=>["hhe"]},
{:name=>"fine", :tag=>[]},
{:name=>"how hare you", :tag=>[]},
{:name=>"heki", :tag=>["1", "2", "3"]},
{:name=>"railsgirls", :tag=>[]},
{:name=>"_byoy", :tag=>[]},
{:name=>"ajha", :tag=>[]},
{:name=>"nimei", :tag=>[]}]
How to get specified name's tag from taglist
For example , I want to extract user "fine"'s tag?
Could this be achieved without do iterator?
This will return the contents of the :tag key for any users name which == 'fine'
taglist.select { |x| x[:name] == 'fine' }.map { |u| u[:tag] }
First you select out only the users you are interested with .select.
And then use .map to collect an array of only what you want.
In this case the end result will be: []
Is do really an iterator?
taglist.find{|tl| tl[:name] == 'fine'}[:tag]
Just to be silly how about:
eval taglist.to_s[/:name=>"fine", :tag=>(.*?)}/, 1]
#=> []
No, it cannot be done without a loop.
And even if you find a solution where your code avoids a loop, for sure the library function that you're calling will include a loop. Finding an element in an array requires a loop. Period.
For example, take this (contrived) example
pattern = "fine"
def pattern.===(h); self == h[:name]; end
taglist.grep(pattern)
which does not seem to use a loop, but calls grep which is implemented using a loop.
Or another, equally contrived, example
class Hash; def method_missing(sym); self[sym]; end; end
taglist.group_by(&:name)["fine"]
which again does seem to call group_by without a loop, but actually it does.
So the answer is, no.
So my first answer missed the no do rule.
Here is an answer that doesn't use a do block.
i=0
begin
if taglist[i][:name] == 'fine'
tag = taglist[i][:tag]
break
end
i+=1
end while i < taglist.length - 0
Technically I think this is still using a block. But probably satisfies the restriction.
So I am figuring out how to set up some options for a class. 'options' is a hash. I want to
1) filter out options I don't want or need
2) set some instance variables to use elsewhere
3) and set up another hash with the processed options as #current_options.
def initialize_options(options)
#whitelisted_options, #current_options = [:timestamps_offset, :destructive, :minimal_author], {}
n_options = options.select { |k,v| #whitelisted_options.include?(k) }
#current_options[:timestamps_offset] = #timestamp_offset = n_options.fetch(:timestamps_offset, 0)*(60*60*24)
#current_options[:destructive] = #destructive = n_options.fetch(:destructive, false)
#current_options[:minimal_author] = #minimal_author = n_options.fetch(:minimal_author, false)
end
I'm guessing this is a bit much, no matter what I pass in I get:
{:timestamps_offset=>0, :destructive=>false, :minimal_author=>false}
When I do this line by line from the command line, it works as I want it to but not in my class. So what is going on and how do I clean this up?
EDIT: this actually works disembodied from the class I'm using it in, but inside it doesn't so the reality is something is going on I'm not aware of right now.
attr_reader :current_options is how this is set on the class, perhaps that needs some revision.
EDIT2: line 2 of the method is supposed to select from #whitelisted_options
EDIT3: Actually turned out to be something I wasn't thinking of..."options" comes in parsed from a yaml file as strings....and I was fetching symbols, changing that around makes a difference where before the method was looking for symbols and finding none, e.g. "destructive" vs :destructive, so always defaulting to the defaults. In short, I just needed to symbolize the hash keys when options are imported.
Your #current_options is initialized as an empty hash. When you filter the options passed as params, none of the keys will be present in #current_options so n_options will end up empty.
Then when you set up #current_options in the following lines, it will always grab the default values (0, false, false), and that's why your output's always the same.
You solve this problem by conditionally initializing #current_options so that it's only set to {} once:
#current_options ||= {}
Post-OP edit:
Your issue's with options.select -- in Ruby 1.8, it doesn't return a Hash, but rather an Array. Your calls to fetch are then always failing (as symbols can't be array indexes), so always returning defaults.
Instead, try:
n_options = options.inject({}) {|h, p| h[p[0]] = p[1] if #whitelisted_options.include? p[0]; h }
where p is an array containing each key/value pair.
In Ruby 1.9.2, Hash.select behaves the way you expected it to.
Edit 2: Here's how I'd approach it:
class Foo
##whitelisted_options= {:timestamps_offset => 0, :destructive => false, :minimal_author =>false}
##whitelisted_options.keys.each do |option|
define_method(option) { return #current_options[option] rescue nil}
end
def initialize_options(options)
#current_options = {}
##whitelisted_options.each {|k, v| #current_options[k] = options[k] || v}
#current_options
end
end
In use:
f = Foo.new
f.destructive #=> nil
f.initialize_options(:minimal_author => true, :ignore => :lol)
f.destructive #=> false
f.minimal_author #=> true
f.timestamps_offset #=> 0
What is #whitelisted_options for?
What do you want to happen if :destructive is not a key in options? Do you want to have :destructive => false, or do you want #current_options to not mention :destructive at all?