Could someone explain vertices.flatten! in SketchUp Ruby to me? - ruby

Below is a piece of code (credit to Rafael Rivera) which plots points at the vertices of a model in SketchUp.
def pointplot
model = Sketchup.active_model
entities = model.active_entities
selection = model.selection
edges = selection.grep(Sketchup::Edge)
if edges.empty?
msg = 'Select one or more edges before using this tool.'
UI.messagebox(msg)
return
end
vertices = []
edges.each { |edge| vertices << edge.vertices }
vertices.flatten!
vertices.uniq!
vertices.each { |vertex| entities.add_cpoint vertex.position }
end
def check_line
sel = Sketchup.active_model.selection
ok = sel.find { |e| e.typename == "Edge" }
ok ? MF_ENABLED : MF_GRAYED
end
UI.add_context_menu_handler do |menu|
menu.add_separator
item = menu.add_item("Point Plot") { pointplot }
menu.set_validation_proc(item) {check_line}
end
Could someone please explain to me this line of code, what it actually does and why it's necessary for the code to work.
vertices.flatten!
I am aware what ".flatten!" does under normal circumstances. I understand this example perfectly from the rubyapi.org
a = [ 0, [ 1, [2, 3], 4 ], 5 ]
a.flatten!(1) # => [0, 1, [2, 3], 4, 5]
a = [ 0, [ 1, [2, 3], 4 ], 5 ]
a.flatten!(2) # => [0, 1, 2, 3, 4, 5]
a = [ 0, [ 1, [2, 3], 4 ], 5 ]
a.flatten!(3) # => [0, 1, 2, 3, 4, 5]
[0, 1, 2].flatten!(1) # => nil
But in the world of SketchUp, what does ".flatten!" actually do?
I 'put' the vertices array to my console and I see this as output.
#<Sketchup::Vertex:0x00000180a0788440>
#<Sketchup::Vertex:0x00000180a0788418>
#<Sketchup::Vertex:0x00000180a07883c8>
#<Sketchup::Vertex:0x00000180a07883a0>
#<Sketchup::Vertex:0x00000180a0788440>
#<Sketchup::Vertex:0x00000180a0788418>
#<Sketchup::Vertex:0x00000180a07883c8>
So what am I 'flattening' exactly?
Thanks!

It does exactly the same as the behavior you already observed with flatten with the only difference that it changes the object on which it is called instead of returning a changed object.
Let's have a look at these three lines:
vertices = []
edges.each { |edge| vertices << edge.vertices }
vertices.flatten!
First, there is an empty array created. Then by iterating over all edges the edges' vertices (which are very likely are stored in an array) are added to the array. That means after this line you have a nested array of vertices that looks like this (pseudo-code):
[[vertice_1, vertice_2], [vertice_3, vertice_4], [vertice_1, vertice_4]]
vertices.flatten! will then flatten the vertices to:
[vertice_1, vertice_2, vertice_3, vertice_4, vertice_1, vertice_4]

Related

Recursion involving an Array for Wonky Coins

Here's the prompt I've been given:
Catsylvanian money is a strange thing: they have a coin for every
denomination (including zero!). A wonky change machine in
Catsylvania takes any coin of value N and returns 3 new coins,
valued at N/2, N/3 and N/4 (rounding down).
Write a method wonky_coins(n) that returns the number of coins you
are left with if you take all non-zero coins and keep feeding them
back into the machine until you are left with only zero-value coins.
Difficulty: 3/5
describe "#wonky_coins" do
it "handles a simple case" do
wonky_coins(1).should == 3
end
it "handles a larger case" do
wonky_coins(5).should == 11
# 11
# => [2, 1, 1]
# => [[1, 0, 0], [0, 0, 0], [0, 0, 0]]
# => [[[0, 0, 0], 0, 0], [0, 0, 0], [0, 0, 0]]
end
it "handles being given the zero coin" do
wonky_coins(0).should == 1
end
end
Maybe it's because of the tests given that involve arrays, but I couldn't get my mind off of them! So my solution so far is as follows:
def wonky_coins(n)
arr = []
arr << n/2 << n/3 << n/4
#base case?
if arr.all?{|coin| coin == 0}
return arr.flatten.length
else
arr.map{|x| wonky_coins(x)}
end
end
p wonky_coins(5)
Except I get [[3,3,3],3,3] as an output if I map it. It's not actually recurring, but even before that, it's giving a strange output that I can't for the life of me understand why the output is this way!
I know it's because I'm using the map method, is it because I'm mutating it while iterating it through wonky_coins again that I'm getting this strange output I can't explain?
I've since looked at the solution and realized that arrays made it needlessly complicated, but I'm still wondering what's going on here??
Here's what Seeing is Believing shows as the code runs:
def wonky_coins(n)
arr = [] # => [], [], [], [], [], [], []
arr << n/2 << n/3 << n/4 # => [2, 1, 1], [1, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]
#base case?
if arr.all?{|coin| coin == 0} # => false, false, true, true, true, true, true
return arr.flatten.length # => 3, 3, 3, 3, 3
else
arr.map{|x| wonky_coins(x)} # => [3, 3, 3], [[3, 3, 3], 3, 3]
end
end
p wonky_coins(5) # => [[3, 3, 3], 3, 3]
# >> [[3, 3, 3], 3, 3]
Seeing is Believing is a great tool and can help dig out weirdness in code.

why Hash value gets affected in Ruby

mainhash = { 'A' => [ 0,1,2,3,4 ] , 'B' => [ 0 ,1,2 ,3 ] }
ahash = mainhash['A']
indval = ahash.shift
ahash become as follows
[1, 2, 3, 4]
and mainhash become as follows
{"A"=>[1, 2, 3, 4], "B"=>[0, 1, 2, 3]}
I am manipulating ahash variable by shifting some values from ahash, When I do this operation it affects the mainhash value. Why it is happening?
Am I missing any conceptual understanding?
It's because ahash and mainhash both have references to the same Array instance. If you modify this through ahash, referenced object is being modified, so no wonder it changes also in mainhash.
To operate on copy (shallow copy, to be precise) of the object instead of the same object, you should use dup method:
ahash = mainhash['A'].dup
Look Array#shift
Removes the first element of self and returns it (shifting all other elements down by one). Returns nil if the array is empty.
mainhash = { 'A' => [ 0,1,2,3,4 ] , 'B' => [ 0 ,1,2 ,3 ] }
ahash = mainhash['A']
p ahash.object_id # => 8577888
p mainhash['A'].object_id # => 8577888
p indval = ahash.shift # => 0
As above seen, ahash and mainhash['A'] refer to the same Array object [ 0,1,2,3,4], thus changing ahash#shift causes 0 to be removed from ahash which also causes 0 to be removed from mainhash['A'].
Said that your Hash becomes as below :
mainhash
# => {"A"=>[1, 2, 3, 4], "B"=>[0, 1, 2, 3]}
All operations are legitimate and happened as documented to the link,I have given above.
How can I avoid affecting the mainhash
As #Marek Lipka said :
you should use dup method: ahash = mainhash['A'].dup.
mainhash = { 'A' => [ 0,1,2,3,4 ] , 'B' => [ 0 ,1,2 ,3 ] }
ahash = mainhash['A'].dup
ahash.object_id # => 8577516
mainhash['A'].object_id # => 8577600
indval = ahash.shift # => 0
ahash # => [1, 2, 3, 4]
mainhash['A'] # => [0, 1, 2, 3, 4]
Why it is happening ?.
arr = [1, 2, 3]
x = arr
arr.shift
p arr
p x
--output:--
[2, 3]
[2, 3]
arr and x both refer to the same array. Assignment ('=') does not create a copy.
Now look at this code:
arr = [1, 2, 3]
x = arr.dup
arr.shift
p arr
p x
--output:--
[2, 3]
[1, 2, 3]
And by the way, the name 'ahash' is a terrible name for an array.

Find and replace in a Ruby multi dimensional array

Is there an elegant way to find and replace any integers superior to 3 (as example) in a multidimensional array? The array may have the dimension 1, 2, 3, or more. Just an example of a such array:
[ [ [ 3, 3, 5 ],
[ 4, 3, 3 ] ],
[ [ 3, 2, 3 ],
[ 0, 3, 8 ] ] ]
I would like to do so without flatten the array.
Following sepp2k idea, here is a possible implementation:
class Object
def deep_map(&block)
if self.respond_to? :each
result = []
self.each do |e|
result << e.deep_map(&block)
end
return result
else
return block.call(self)
end
end
end
Then apply deep_map as you wish on the array:
> [[[3, 3, 5], [4, 3, 3]], [[3, 2, 3], [0, 3, 8]]].deep_map { |e| e > 3 ? 0 : e }
=> [[[3, 3, 0], [0, 3, 3]], [[3, 2, 3], [0, 3, 0]]]
Or, more briefly:
class Object
def deep_map(&block)
respond_to?(:map) ? map{|e| e.deep_map(&block)} : block.call(self)
end
end
Or, polymorphically:
class Object
def deep_map(&block); block.call(self) end
end
class Array
def deep_map(&block); map{|e| e.deep_map(&block)} end
end
You can write a deep_map method, which calls map on the array and then for each element test whether it's a sub-array. If it is, call deep_map recursively with the sub-array, otherwise yield the element.
You can then use that deep_map method to transform the inner elements of your multi-dimensional array without affecting its structure.
So, if I've done this right, f(x) will traverse a multidimensional Array until it finds one containing something that isn't an Array or subclass of Array, at which point it will yield the innermost Array to the block and replace it with the block's return value.
def f x, &block
x.map do |a|
if a.first.class.ancestors.include? Array
f a, &block
else
yield a
end
end
end
p(f [ [ [ 3, 3, 5 ],
[ 4, 3, 3 ] ],
[ [ 3, 2, 3 ],
[ 0, 3, 8 ] ] ] do |e|
e.reject { |x| x > 3 }
end
)

How to create a 2D array of objects in Ruby?

I am creating a map for my roguelike game and already I stumbled on a problem. I want to create a two dimensional array of objects. In my previous C++ game I did this:
class tile; //found in another file.
tile theMap[MAP_WIDTH][MAP_HEIGHT];
I can't figure out how I should do this with Ruby.
theMap = Array.new(MAP_HEIGHT) { Array.new(MAP_WIDTH) { Tile.new } }
Use arrays of arrays.
board = [
[ 1, 2, 3 ],
[ 4, 5, 6 ]
]
x = Array.new(3){|i| Array.new(3){|j| i+j}}
Also look into the Matrix class:
require 'matrix'
Matrix.build(3,3){|i, j| i+j}
2D arrays are no sweat
array = [[1,2],[3,4],[5,6]]
=> [[1, 2], [3, 4], [5, 6]]
array[0][0]
=> 1
array.flatten
=> [1, 2, 3, 4, 5, 6]
array.transpose
=> [[1, 3, 5], [2, 4, 6]]
For loading 2D arrays try something like:
rows, cols = 2,3
mat = Array.new(rows) { Array.new(cols) }
# Let's define some class
class Foo
# constructor
def initialize(smthng)
#print_me = smthng
end
def print
puts #print_me
end
# Now let's create 2×2 array with Foo objects
the_map = [
[Foo.new("Dark"), Foo.new("side")],
[Foo.new("of the"), Foo.new("spoon")] ]
# Now to call one of the object's methods just do something like
the_map[0][0].print # will print "Dark"
the_map[1][1].print # will print "spoon"

Subtracting one Array from another in Ruby

I've got two arrays of Tasks - created and assigned.
I want to remove all assigned tasks from the array of created tasks.
Here's my working, but messy, code:
#assigned_tasks = #user.assigned_tasks
#created_tasks = #user.created_tasks
#Do not show created tasks assigned to self
#created_not_doing_tasks = Array.new
#created_tasks.each do |task|
unless #assigned_tasks.include?(task)
#created_not_doing_tasks << task
end
end
I'm sure there's a better way. What is it?
Thanks :-)
You can subtract arrays in Ruby:
[1,2,3,4,5] - [1,3,4] #=> [2,5]
ary - other_ary → new_ary Array Difference
Returns a new array that is a copy of the original array, removing any
items that also appear in other_ary. The order is preserved from the
original array.
It compares elements using their hash and eql? methods for efficiency.
[ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ]
If you need
set-like behavior, see the library class Set.
See the Array documentation.
The above solution
a - b
deletes all instances of elements in array b from array a.
[ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ]
In some cases, you want the result to be [1, 2, 3, 3, 5]. That is, you don't want to delete all duplicates, but only the elements individually.
You could achieve this by
class Array
def delete_elements_in(ary)
ary.each do |x|
if index = index(x)
delete_at(index)
end
end
end
end
test
irb(main):198:0> a = [ 1, 1, 2, 2, 3, 3, 4, 5 ]
=> [1, 1, 2, 2, 3, 3, 4, 5]
irb(main):199:0> b = [ 1, 2, 4 ]
=> [1, 2, 4]
irb(main):200:0> a.delete_elements_in(b)
=> [1, 2, 4]
irb(main):201:0> a
=> [1, 2, 3, 3, 5]
The code works even when the two arrays are not sorted. In the example, the arrays are sorted, but this is not required.

Resources