Nested Hashes and Arrays

This question is really more of a ruby question than a SketchUp API question but here goes anyways:

I’ve created a hash of arrays:

windows = {"window1"=> [84, 80], "window2"=> [84, 120], "window3"=>[48,80], etc...}

The first value in the nested array is the location of the window along the length of the wall from the origin (left side). The second value is the window header height (or vertical position on the wall).

Using some form of the select method and given a location of “84” I first want to pull out a new hash of the two windows where their location is equal to this value. I know how to do this for a simple hash but when I nest things up like this I’m a little uncertain of how the syntax should be written.

Then I would like to sort this new hash (into an array) by the second value (window height).

This line of code appears to work for selecting the new hash:

loc_hash = windows.select {|k,v| v[0] == 84}

Perhaps this was easier than I originally thought. This line of code appears to sort the sub hash into a nested array:

loc_array = loc_hash.sort_by { |k,v| v[1] }

Now I just need to figure out how to determine what the index of say window2 is within this multi dimensional array, I probably need to flatten it out or something along those lines.

This line seems to work for grabbing the index of the window in question:

win_vert_pos = loc_array.index { |x| x[0] == ‘window2’}

Hi Nathan.
not that I am an expert in this area, I just like to say. I like the fact you share your attempts and solutions after posting the problem. It helps us novices get a insight as well. I’m sure the gurus will set it straight if there is a better way
Philip

1 Like

And the fully implemented solution is:

 # Stacked Window Check
			
loc_hash = @Windowhash_stack.select {|k,v| v[0] == win_loc}
if loc_hash.length > 1
   loc_array = loc_hash.sort_by {|k,v| v[1]}
   win_vert_pos = loc_array.index {|x| x[0] == @Windowgroupname}
   texty = texty - 8.0 * win_vert_pos
end

There isn’t much point in posting a question if you don’t follow up with the answer. :slight_smile:

This small piece of logic then allows me to properly place the window callouts when windows are stacked within a wall panel. Previously they were simply landing on top of each other in a big jumbled mess.

Maybe off topic but there is a general rule that hashes and arrays very often can be replaced by custom objects, for a clearer more descriptive code.

3 Likes

I would have likely swapped the class types. Ie, an array of “windows” (being some hash-like class.)
Ex:

windows = [
  {"name"=>"Bathroom","type"=>"3020SL","pos"=>84,"head"=>80},
  {"name"=>"Bedroom1","type"=>"3030SH","pos"=>84,"head"=>180},
  # ... etc...
]

Then …

# Sort the whole windows array ...
windows.sort_by! {|w| w["pos"] }

# Select the 84 stack and sort it by head height ...
wind84 = windows.find_all {|w| w["pos"] == 84 }.sort_by! {|w| w["head"] }

# Iterate with index and create labels ...
wind84.each_with_index do |w,i|
  create_3d_text_label( w["type"], x - 8.0 * i, w["pos"] )
end

If you prefer dot notation (w.pos) over subscript notations (w["pos"]) …
then you can use OpenStructs instead of Hashes.

2 Likes

I’d suggest using classes to store your data. I also tend to use hashes and arrays now and then when I code in ruby because, well you know, it is quick to write. But in larger projects it gets confusing and difficult to maintain with hashes without a hard given interface. When passing hashes through your code a change here will break there.

Also, a book which I would recommend to continue improving your ruby developments: https://www.amazon.de/Design-Patterns-Ruby-Addison-Wesley-Professional/dp/0321490452

4 Likes

This is why I post questions like this. Thank-you for the code examples and ruby/API education. Hopefully this thread is helpful to other developers in choosing the right system for organizing and processing their data.

1 Like

6:23 An example of creating a quick and dirty window class …

module Medeek

  class Window
    attr_accessor :name, :type, :pos, :height
    def initialize(name,type,pos,height)
      @name = name
      @type = type
      @pos = pos
      @height = height
    end
  end

end # module Medeek

Your done (6:26).

Use …

windows << Window.new("Bedroom","3030SH",84,180)
# repeat as desired
3 Likes

If you want go quick and dirty you can even use a Struct.

Wow :open_mouth: . That’s a quite nasty limitation. I haven’t used them myself (what I can remember) as I design my software around classes containing all the methods relevant for them, but I’ve heard them being reccomended as “light” classes, e.g. at an early stage before you add own methods, or when refactoring Array and Hash based code. Good to know :+1:.

But

module SW
  module StructTest
    Window = Struct.new(:name, :x, :y)
    windows = []
    windows << Window.new("first", 10,10)
    puts windows[0]
  end
end

gives you this result:

#<struct SW::StructTest::Window name=“first”, x=10, y=10>