Access variable hash depth values with square brackets notation - ruby

Given this hash:
hash1= { node1: { node2: { node3: { node4: { node5: 1 } } } } }
We access inside nodes with square brackets like this:
hash1[:node1][:node2][:node3][:node4]
Now I have a hash that I know will always be nested as it is an XML response from a SOAP webservice, but neither the depth of the hash nor the names of the nodes stay the same. So it would be nice if I could ask the user of my application for the hash depth and store it in a variable. And then be able to do hash1[:hash_depth] and achieve the same result as above.
I have accomplished what I want by the following code:
str = 'node1,node2,node3,node4'
str_a = str.split(',')
hash_copy = hash1
str_a.each { |s| hash_copy = hash_copy.[](s.to_sym) }
hash_copy
=> {:node5=>1}
hash1[:node1][:node2][:node3][:node4]
=> {:node5=>1}
that is asking the user to enter the hash depth separated by commas, store it in a string, split it, make an array, clone the original hash, go down each level and modify the hash till I get to the desired node. Is there a way to do it with the square brackets notation and using a variable to store the depth without modifying the hash or needing to clone it?
Edit:
someone answered with the following (can't see his post anymore???)
hash_depth="[:node1][:node2][:node3][:node4]"
eval "hash1#{hash_depth}"

Although eval does everything you need, there is another approach, since you already have the working code for comma-separated list:
hash_depth="[:node1][:node2][:node3][:node4]"
csh = hash_depth.gsub(/\A\[:|\]\[:|\]\Z/, { '][:' => ',' })
#⇒ "node1,node2,node3,node4"
And now you are free to apply your existing function to csh.

If this is a webapp, I think you should prepare a list of short textareas, which starts with a single text item, and the user can keep adding a new item to the list by clicking on a button. The areas will be filled by the user, and will be sent.
Then, you will probably receive this through some serialized form. You decode this to get an array of strings:
str_a = ["node1", "node2", "node3", "node4"]
and you can reach the inner element by doing:
str_a.inject(hash1){|h, s| h[s.to_sym]} #=> {:node5 => 1}

Related

How to parse two elements from a list to make a new one

I have this input repeated in 1850 files:
[
{
"id"=>66939,
"login"=>"XXX",
"url"=>"https://website.com/XX/users/XXX"
},
...
{}
]
And I wanted to make a list in a way that by looking for the login I can retrieve the ID using a syntax like:
users_list[XXX]
This is my desired output:
{"XXX"=>"66570", "XXX"=>"66570", "XXX"=>"66570", "XXX"=>"66570", ... }
My code is:
i2 = 1
while i2 != users_list_raw.parsed.count
temp_user = users_list_raw.parsed[i2]
temp_user_login = temp_user['login']
temp_user_id = temp_user['id']
user = {
temp_user_login => temp_user_id
}
users_list << user
i2 += 1
end
My output is:
[{"XXX":66570},{"XXX":66569},{"XXX":66568},{"XXX":66567},{"XXX":66566}, ... {}]
but this is not what I want.
What's wrong with my code?
hash[key] = value to add an entry in a hash. So I guess in your case users_list[temp_user_login] = temp_user_id
But I'm unsure why you'd want to do that. I think you could look up the id of a user by having the login with a statement like:
login = XXX
user = users_list.select {|user| user["login"] == login}.first
id = user["id"]
and maybe put that in a function get_id(login) which takes the login as its parameter?
Also, you might want to look into databases if you're going to manipulate large amounts of data like this. ORMs (Object Relational Mappers) are available in Ruby such as Data Mapper and Active Record (which comes bundled with Rails), they allow you to "model" the data and create Ruby objects from data stored in a database, without writing SQL queries manually.
If your goal is to lookup users_list[XXX] then a Hash would work well. We can construct that quite simply:
users_list = users_list_raw.parsed.each.with_object({}) do |user, list|
list[user['login']] = user['id']
end
Any time you find yourself writing a while loop in Ruby, there might be a more idiomatic solution.
If you want to keep track of a mapping from keys to values, the best data structure is a hash. Be aware that assignment via the array operator will replace existing values in the hash.
login_to_id = {}
Dir.glob("*.txt") { |filename| # Use Dir.glob to find all files that you want to process
data = eval(File.read(filename)) # Your data seems to be Ruby encoded hash/arrays. Eval is unsafe, I hope you know what you are doing.
data.each { |hash|
login_to_id[hash["login"]] = hash["id"]
}
}
puts login_to_id["XXX"] # => 66939

Parse a string with multiple XML-like tags using Ruby

I have a string which looks like the following:
string = " <SET-TOPIC>INITIATE</SET-TOPIC>
<SETPROFILE>
<PROFILE-KEY>predicates_live</PROFILE-KEY>
<PROFILE-VALUE>yes</PROFILE-VALUE>
</SETPROFILE>
<think>
<set><name>first_time_initiate</name>yes</set>
</think>
<SETPROFILE>
<PROFILE-KEY>first_time_initiate</PROFILE-KEY>
<PROFILE-VALUE>YES</PROFILE-VALUE>
</SETPROFILE>"
My objective is to be able to read out each top level that is in caps with the parse. I use a case statement to evaluate what is the top level key, such as <SETPROFILE> but there can be lots of different values, and then run a method that does different things with the contnts of the tag.
What this means is I need to be able to know very easily:
top_level_keys = ['SET-TOPIC', 'SET-PROFILE', 'SET-PROFILE']
when I pass in the key know the full value
parsed[0].value = {:PROFILE-KEY => predicates_live, :PROFILE-VALUE => yes}
parsed[0].key = ['SET-TOPIC']
I currently parse the whole string as follows:
doc = Nokogiri::XML::DocumentFragment.parse(string)
parsed = doc.search('*').each_with_object({}){ |n, h|
h[n.name] = n.text
}
As a result, I only parse and know of the second tag. The values from the first tag do not show up in the parsed variable.
I have control over what the tags are, if that helps.
But I need to be able to parse and know the contents of both tag as a result of the parse because I need to apply a method for each instance of the node.
Note: the string also contains just regular text, both before, in between, and after the XML-like tags.
It depends on what you are going to achieve. The problem is that you are overriding hash keys by new values. The easiest way to collect values is to store them in array:
parsed = doc.search('*').each_with_object({}) do |n, h|
# h[n.name] = n.text :: removed because it overrides values
(h[n.name] ||= []) << n.text
end

Iterate an array of hashes

I have a hash with a key of cities and the value is an array of hashes containing location data. It looks like this:
#locations = {
"cities"=>[
{"longitude"=>-77.2497049, "latitude"=>38.6581722, "country"=>"United States", "city"=>"Woodbridge, VA"},
{"longitude"=>-122.697236, "latitude"=>58.8050174, "country"=>"Canada", "city"=>"Fort Nelson, BC"},
...
]
}
I'd like to iterate through and print all the values for the key city:
Woodbridge, VA
Fort Nelson, BC
...
I can't say why would you have that structure, anyway, in the data format you have above, you would access it like
#locations[1].each { |c| p c["city"] }
Although, this implies that you should always expect second object in the array to be the required cities array. Further you need to put in required nil check.
For your corrected data format:
#locations = { "cities"=>[
{ "longitude"=>-77.2497049,
"latitude"=>38.6581722,
"country"=>"United States",
"city"=>"Woodbridge, VA"},
{ "longitude"=>-122.697236,
"latitude"=>58.8050174,
"country"=>"Canada",
"city"=>"Fort Nelson, BC" }] }
#locations["cities"].each { |h| puts h["city"] }
Woodbridge, VA
Fort Nelson, BC
or to save in an array:
#locations["cities"].each_with_object([]) { |h,a| a << h["city"] }
#=> ["Woodbridge, VA", "Fort Nelson, BC"]
As suggested by others, you have to do the exact same thing but let me explain whats happening in there.
Your example is an array and has multiple elements which could be just string like cities or an array of hashes like you mentioned.
So in order to iterate through the hashes and get the city values printed, you first of all have to access the array that has hashes. By doing so
#locations["cities"]
=> [{"longitude"=>-77.2497049, "latitude"=>38.6581722, "country"=>"United States", "city"=>"Woodbridge, VA"}, {"longitude"=>-122.697236, "latitude"=>58.8050174, "country"=>"Canada", "city"=>"Fort Nelson, BC"}]
Now that you have go the array you required, you can just integrate through them and get the result printed like this
#locations["cities"].map{|hash| p hash['city']}
In case your getting nil errors as you have stated in comments, just see what happens when you try to access the array of hashes. if you still are experiencing issues, then you may have to provide the full input so as to understand where the problem is.

How do I extract a value from this Ruby hash?

I'm using the Foursquare API, and I want to extract the "id" value from this hash
[{"id"=>"4fe89779e4b09fd3748d3c5a", "name"=>"Hitcrowd", "contact"=>{"phone"=>"8662012805", "formattedPhone"=>"(866) 201-2805", "twitter"=>"hitcrowd"}, "location"=>{"address"=>"1275 Glenlivet Drive", "crossStreet"=>"Route 100", "lat"=>40.59089895083072, "lng"=>-75.6291255071468, "postalCode"=>"18106", "city"=>"Allentown", "state"=>"Pa", "country"=>"United States", "cc"=>"US"}, "categories"=>[{"id"=>"4bf58dd8d48988d125941735", "name"=>"Tech Startup", "pluralName"=>"Tech Startups", "shortName"=>"Tech Startup", "icon"=>"https://foursquare.com/img/categories/shops/technology.png", "parents"=>["Professional & Other Places", "Offices"], "primary"=>true}], "verified"=>true, "stats"=>{"checkinsCount"=>86, "usersCount"=>4, "tipCount"=>0}, "url"=>"http://www.hitcrowd.com", "likes"=>{"count"=>0, "groups"=>[]}, "beenHere"=>{"count"=>0}, "storeId"=>""}]
When I try to extract it by using ['id'], I get this error can't convert Symbol into Integer. How do I extract the value using ruby? Also, how do I do this for multiple hashes extracting the "id" value each time?
Please pardon my inexperience. Thanks!
It's wrapped in an array, that's what the [ and ] mean on the start and end. But it also looks like this array only one object in it, which is the hash you really want.
So assuming you want the first object in this array:
mydata[0]['id'] # or mydata.first['id'] as Factor Mystic suggests
But usually when an API returns an Array there is a reason (it might return many results instead of just one), and naively plucking the first item from it my not be what you want. So be sure you are getting the kind of data you really expect before hard coding this into your application.
For multiple hashes, if you want to do something with the id (run a procedure of some kind) then
resultsArray.each do |person|
id = person["id"] #then do something with the id
end
If you want to just get an array containing the ids then
resultsArray.map{|person| person["id"]}
# ["4fe89779e4b09fd3748d3c5a", "5df890079e4b09fd3748d3c5a"]
To just grab the one item from the array, see Alex Wayne's answer
To get an array of ids, try: resultsArray.map { |result| result["id"] }

Accessing an array of vectors trouble

My code looks like this:
a = IO.readlines("input.txt").map { |line| Vector.[](line.split) }
Now I want to access a single component of the first vector within my a array. I'm writing the following to address a vector:
puts a[0]
The behavior is pretty much expected - I receive the following:
Vector[1.2357, 2.1742, -5.4834, -2.0735]
Now let's try to address a single component this way:
puts a[0][0]
and voila, I receive a list of all the vector components,like:
1.2357
2.1742
-5.4834
-2.0735
How come? Maybe the last attempt was wrong? How do I correctly address a scalar inside a vector within an array?
Due to your code I think the array construction should be:
a = IO.readlines("input.txt").map { |line| Vector[*line.split] }

Resources