Generic restructure of ruby hash (with depth of 4) - ruby

Is it possible to convert this HASH into an array of arrays based solely on the position of the key (rather than it's value). ie: I know ahead of time that the first Key will always be PROD/ALPHA, and the second Key will always be a country (that I would like to be able to change in the future at will)
The idea would be to group all servers of the same type (webservers) that are also in the same environment (production) but are located in different farms (UK, USA)
While any suggestions on how to do this are welcome, I'll be happy to just know that I'm not walking into a dead-end I won't be able to solve.
Here are some visuals to aid in my explanation:
{
"PROD": {
"USA": {
"generic": [
"nginx-240"
],
"WEB": [
"nginx-210",
"nginx-241",
"nginx-211",
"nginx-209"
],
"APP": [
"tomcat-269",
"tomcat-255",
"tomcat-119",
"tomcat-124"
]
},
"UK": {
"WEB": [
"nginx-249",
"nginx-250",
"nginx-246",
"nginx-247",
"nginx-248"
],
"generic": [
"tomcat-302"
],
"APP": [
"tomcat-396",
"tomcat-156",
"tomcat-157"
]
}
},
"ALPHA": {
"USA": {
"WEB": [
"nginx-144",
"nginx-146",
"nginx-145",
"nginx-175",
"nginx-173"
],
"APP": [
"tomcat-204",
"tomcat-206"
]
}
}
}
The expectation is that data from the lowest level in the hash would be grouped together.
Again the idea is that all Production app servers (both from UK and USA) are grouped together in the following kind of pattern:
PROD_UK_APP would be represented by
["tomcat-396","tomcat-156","tomcat-157"] as these are the lowest branches of the tree PROD->UK->applicationserver
[
[
[PROD_UK_APP],[PROD_USA_APP]
],
[
[PROD_UK_WEB],[PROD_USA_WEB]
]
]
New list..
[
[
[ALPHA_USA_WEB]
],
[
[ALPHA_USA_APP],
[
[
Again the idea is to keep this generic. Is this something that is practically achievable or am I likely to require some degree of hardcoding to ensure it always works? The idea is that if tomorrow UK becomes JAPAN, it will still work in exactly the same way, comparing between the APP and WEB tier of UK, and JAPAN (separating ALPHA from PROD).
EDIT: my attempt to try and sort it:
def walk
a = []
myhash.each do |env, data|
data.each do |dc, tier|
tier.each do |x, y|
a << y
end
end
end
p a
end
[["nginx240"], ["nginx210", "nginx241", "nginx211", "nginx209"], ["tomcat269", "tomcat255", "tomcat119", "tomcat124"], ["nginx249", "nginx250", "nginx246", "nginx247", "nginx248"], ["tomcat302"], ["tomcat396", "tomcat156", "tomcat157"], ["nginx144", "nginx146", "nginx145", "nginx175", "nginx173"], ["tomcat204", "tomcat206"]]
Thanks,

I think I follow what you're looking for and you should get what you're after with:
myhash.values.each_with_object([]) do |by_country, out_arr|
by_country.values.each do |by_type|
out_arr << by_type.values
end
end
which would return:
[
[
[
"nginx-240"
],
[
"nginx-210",
"nginx-241",
"nginx-211",
"nginx-209"
],
[
"tomcat-269",
"tomcat-255",
"tomcat-119",
"tomcat-124"
]
],
[
[
"nginx-249",
"nginx-250",
"nginx-246",
"nginx-247",
"nginx-248"
],
[
"tomcat-302"
],
[
"tomcat-396",
"tomcat-156",
"tomcat-157"
]
],
[
[
"nginx-144",
"nginx-146",
"nginx-145",
"nginx-175",
"nginx-173"
],
[
"tomcat-204",
"tomcat-206"
]
]
]
Piece by piece
Take your hash, disgard the keys and just create an array of values.
iterate over the values (array of hashes by country) and initialize an array to return.
for each hash that by_country points to, again take the values, to drop into the by type(?) hashes
iterate over your by_type hashes and again take the values of each
push each return array into the array you want to return

Related

RGeo - fix self-intersections

I have a bunch of polygons that have self-intersection which causes some errors in further postprocessing them (in particular - I can't calculate intersection area of those polygons with other polygons). Here is an example of broken polygon:
{
"type": "MultiPolygon",
"coordinates": [
[
[
[
6.881057785381658,
46.82373306675715
],
[
6.857171686909481,
46.81861230543794
],
[
6.857354659059071,
46.81856788926046
],
[
6.856993473052509,
46.82693029065604
],
[
6.8612894138116785,
46.83422796373707
],
[
6.86720955648855,
46.835636765630476
],
[
6.871281147359957,
46.83078486366309
],
[
6.871573291317274,
46.8306215963777
],
[
6.877608228639841,
46.82771553607934
],
[
6.877758462659651,
46.82772313420989
],
[
6.877852632482749,
46.827735617670285
],
[
6.880928107931434,
46.82630213148064
],
[
6.8810399979122305,
46.82622029042867
],
[
6.881117606743071,
46.826115612819855
],
[
6.881057785381658,
46.82373306675715
]
]
]
]
}
This is what it looks like on the map - as you can see, there is intersection of two polygon edges. RGeo throws an error, pointing intersection coordinate (I guess): => "Geos::GEOSException: TopologyException: Input geom 0 is invalid: Self-intersection at or near point 6.8573510795579145 46.818650764080992 at 6.8573510795579145 46.818650764080992". So, I have it at least.
My question is: is there a way to fix that intersection automatically? I read, that a possible solution is to insert 2 similar points with coordinates of self-intersection. But the problem is - the polygon has a specific order, and I don't know WHERE to insert those points.
Also, maybe there are some existing tools helping fix that...
The solution I would use is postgis's ST_MakeValid option for postgres if that is an option for you you could do something along the lines of ST_AsText(ST_MakeValid(geom_column)) or if you would rather pass in the text here is an example using the bowtie example shown in prepair:
select ST_AsText(ST_MakeValid(ST_GeomFromText('POLYGON((0 0, 0 10, 10 0, 10 10, 0 0))')));
st_astext
-----------------------------------------------------------
MULTIPOLYGON(((0 0,0 10,5 5,0 0)),((5 5,10 10,10 0,5 5)))
(1 row)
If that doesn't interest you, you could export those geometries and use a tool like prepair to convert them. To sum up how this works behind the scenes, it will split these "bowties" into multiple polygons which will then be made into a multipolygon. The same type of fix will applied to multipolygons.

Ruby, Parsing a JSON response for an array of values

I'm new to ruby so please excuse any ignorance I may bear. I was wondering how to parse a JSON reponse for every value belonging to a specific key. The response is in the format,
[
{
"id": 10008,
"name": "vpop-fms-inventory-ws-client",
"msr": [
{
"key": "blocker_violations",
"val": 0,
"frmt_val": "0"
},
]
},
{
"id": 10422,
"name": "websample Maven Webapp",
"msr": [
{
"key": "blocker_violations",
"val": 0,
"frmt_val": "0"
}...
There's some other entries in the response, but for the sake of not having a huge block of code, I've shortened it.The code I've written is:
require 'uri'
require 'net/http'
require 'JSON'
url = URI({my url})
http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Get.new(url)
request["cache-control"] = 'no-cache'
request["postman-token"] = '69430784-307c-ea1f-a488-a96cdc39e504'
response = http.request(request)
parsed = response.read_body
h = JSON.parse(parsed)
num = h["msr"].find {|h1| h1['key']=='blocker_violations'}['val']
I am essentially looking for the val for each blocker violation (the json reponse contains hundreds of entries, so im expecting hundreds of blocker values). I had hoped num would contain an array of all the 'val's. If you have any insight in this, it would be of great help!
EDIT! I'm getting a console output of
scheduler caught exception:
no implicit conversion of String into Integer
C:/dashing/test_board/jobs/issue_types.rb:20:in `[]'
C:/dashing/test_board/jobs/issue_types.rb:20:in `block (2 levels) in <top (requi
red)>'
C:/dashing/test_board/jobs/issue_types.rb:20:in `select'
I suspect that might have too much to do with the question, but some help is appreciated!
You need to do 2 things. Firstly, you're being returned an array and you're only interested in a subset of the elements. This is a common pattern that is solved by a filter, or select in Ruby. Secondly, the condition by which you wish to select these elements also depends on the values of another array, which you need to filter using a different technique. You could attempt it like this:
res = [
{
"id": 10008,
"name": "vpop-fms-inventory-ws-client",
"msr": [
{
"key": "blocker_violations",
"val": 123,
"frmt_val": "0"
}
]
},
{
"id": 10008,
"name": "vpop-fms-inventory-ws-client",
"msr": [
{
"key": "safe",
"val": 0,
"frmt_val": "0"
}
]
}
]
# define a lambda function that we will use later on to filter out the blocker violations
violation = -> (h) { h[:key] == 'blocker_violations' }
# Select only those objects who contain any msr with a key of blocker_violations
violations = res.select {|h1| h1[:msr].any? &violation }
# Which msr value should we take? Here I just take the first.
values = violations.map {|v| v[:msr].first[:val] }
The problem you may have with this code is that msr is an array. So theoretically, you could end up with 2 objects in msr, one that is a blocker violation and one that is not. You have to decide how you handle that. In my example, I include it if it has a single blocker violation through the use of any?. However, you may wish to only include them if all msr objects are blocker violations. You can do this via the all? method.
The second problem you then face is, which value to return? If there are multiple blocker violations in the msr object, which value do you choose? I just took the first one - but this might not work for you.
Depending on your requirements, my example might work or you might need to adapt it.
Also, if you've never come across the lambda syntax before, you can read more about it here

Dynamic layout additions in Rebol3

I would like to dynamically add a button to the layout of a view, with the actor causing this addition belonging to a button that is already part of the layout.
I started with this:
REBOL [title: "Dynamic Button Addition"]
tilesize: 60x60
curtile: 1
stylize [
p: button [
facets: [init-size: tilesize max-size: tilesize]
actors: [
on-action: [
++ curtile
append tiles compose [ p (to-string curtile) ]
print ? tiles/options/content
v/redraw
]
]
]
]
v: [
tiles: hgroup [ p "1" ]
]
view v
...which does not appear to have the value of tiles/options/content change with each click.
I can get it to change if make these changes:
append tiledata compose [ p (to-string curtile) ]
and
tiledata: [ p "1" ]
v: [
tiles: hgroup tiledata
However, this does not cause any change on screen. If I replace the last four lines with this:
v: view [
tiles: hgroup tiledata
]
...so that v is now the view rather than the view's layout, I get this error when I click:
** Script error: v has no value
** Where: actor all foreach do-actor unless do-face if actor all foreach do-actor if do-event do-event if do-event either -apply- wake-up loop -apply- wait forever try do-events if view do either either either -apply-
** Near: actor face :data
This makes sense to me, because v is not yet done being defined, until I exit the program, IIUC.
How, then, can I make changes to v before the program ends, but after it's been passed to view?
Not very nice, but working if you replace
v/redraw
with these two lines
unview/all
view v
And there is a real dynamic example on how to update a layout that has already be viewed
I will simplify it
stylize [
tbox: hpanel [
actors: [
on-make: [
append face/options [
content: [
]
]
do-actor/style face 'on-make none 'hpanel
]
]
]
]
view/across [
button "button 1"
on-action [
append-content test compose [
button ( join "button " 2 + length? test/gob)
]
]
test: tbox
]

Evaluating code blocks in Rebol3

I'm trying to improve the Sliding Tile Puzzle example by making the starting positions random.
There's a better way to do this--"It is considered bad practice to convert values to strings and join them together to pass to do for evaluation."--but the approach I took was to try to generate Rebol3 source, and then evaluate it. I have it generating correctly, I think:
random/seed now
arr: random collect [ repeat tilenum 9 [ keep tilenum ] ]
hgroup-data: copy {}
repeat pos 9 [
curtile: (pick arr pos)
append hgroup-data either curtile = 9
[ reduce "x: box tilesize gameback " ]
[ rejoin [ { p "} curtile {" } ] ]
if all [(pos // 3) = 0 pos != 9] [ append hgroup-data " return^/" ]
]
print hgroup-data
...outputs something like:
p "4" x: box tilesize gameback p "5" return
p "3" p "7" p "1" return
p "2" p "8" p "6"
...which if I then copy and paste into this part, works correctly:
view/options [
hgroup [
PASTE-HERE
]
] [bg-color: gameback]
However, if I try to do it dynamically:
view/options [
hgroup [
hgroup-data
]
] [bg-color: gameback]
...(also print hgroup-data, do hgroup-data, and load hgroup-data), I get this error:
** GUI ERROR: Cannot parse the GUI dialect at: hgroup-data
...(or at: print hgroup-data, etc., depending on which variation I tried.)
If I try load [ hgroup-data ] I get:
** Script error: extend-face does not allow none! for its face argument
** Where: either if forever -apply- apply init-layout make-layout actor all foreach do-actor unless -apply- apply all build-face -apply- apply init-layout make-layout actor all foreach do-actor if build-face -apply- apply init-layout make-layout actor all foreach do-actor unless make-face -apply- apply case view do either either either -apply-
** Near: either all [
word? act: dial/1
block? body: get dial...
However, if I use the syntax hgroup do [ hgroup-data ], the program runs, but there are no buttons: it appears to be somehow over-evaluated, so that the return values of the functions p and box and so on are put straight into the hgroup as code.
Surely I'm missing an easy syntax error here. What is it?
First, I would say it's better to construct a block directly, instead of constructing a string and converting it to a block. But if you really want to do that, this should do the trick:
view/options compose/only [
hgroup (load hgroup-data)
] [bg-color: gameback]

How to store a collection of floats in postgres?

I'm building a script that saves tweets to a postgres database using ruby/pg/ActiveRecord/TweetStream (gem).
This script works fine..
...
TweetStream::Client.new.track(SEARCH_TERMS) do |t|
puts "#{t.text}"
attributes = {
tweetid: t[:id],
text: t.text,
in_reply_to_user_id: t.in_reply_to_user_id,
in_reply_to_status_id: t.in_reply_to_status_id,
received_at: t.created_at,
user_statuses_count: t.user.statuses_count,
user_followers_count: t.user.followers_count,
user_profile_image_url: t.user.profile_image_url,
user_screen_name: t.user.screen_name,
user_timezone: t.user.time_zone,
user_location: t.user.location,
user_lang: t.lang,
user_id_str: t.user.id,
user_name: t.user.name,
user_url: t.user.url,
user_created_at: t.user.created_at,
user_geo_enabled: t.user.geo_enabled,
}
if StoreTweet.create(attributes)
puts "saved"
else
puts "error"
end
end
until I add
user_geo_enabled: t.user.geo_enabled,
coordinates: t.coordinates.coordinates,}
I also tried
t.coordinates
t.coordiantes[:coordinates]
t[:coordinates] #this allows me to save but is always blank if when geo_enabled is 'true'
Twitter dev center (https://dev.twitter.com/docs/platform-objects/tweets) tells me that 'coordinates' is a collection of floats like:
"coordinates":
{
"coordinates":
[
-75.14310264,
40.05701649
],
"type":"Point"
}
for the moment i use a 'text' field. Which type should i give to the field in order to store both values together?

Resources