Related
How do I speed up the rank calculation of a sparse matrix in pure ruby?
I'm currently calculating the rank of a matrix (std lib) to determine the rigidity of a graph.
That means I have a sparse matrix of about 2 rows * 9 columns to about 300 rows * 300 columns.
That translates to times of several seconds to determine the rank of the matrix, which is very slow for a GUI application.
Because I use Sketchup I am bound to Ruby 2.0.0.
I'd like to avoid the hassle of setting up gcc on windows, so nmatrix is (I think) not a good option.
Edit:
Example matrix:
[[12, -21, 0, -12, 21, 0, 0, 0, 0],
[12, -7, -20, 0, 0, 0, -12, 7, 20],
[0, 0, 0, 0, 14, -20, 0, -14, 20]]
Edit2:
I am using integers instead of floats to speed it up considerably.
I have also added a fail fast mechanism earlier in the code in order to not call the slow rank function at all.
Edit3:
Part of the code
def rigid?(proto_matrix, nodes)
matrix_base = Array.new(proto_matrix.size) { |index|
# initialize the row with 0
arr = Array.new(nodes.size * 3, 0.to_int)
proto_row = proto_matrix[index]
# ids of the nodes in the graph
node_ids = proto_row.map { |hash| hash[:id] }
# set the values of both of the nodes' positions
[0, 1].each { |i|
vertex_index = vertices.find_index(node_ids[i])
# predetermined vector associated to the node
vec = proto_row[i][:vec]
arr[vertex_index * 3] = vec.x.to_int
arr[vertex_index * 3 + 1] = vec.y.to_int
arr[vertex_index * 3 + 2] = vec.z.to_int
}
arr
}
matrix = Matrix::rows(matrix_base, false)
rank = matrix.rank
# graph is rigid if the rank of the matrix is bigger or equal
# to the amount of node coordinates minus the degrees of freedom
# of the whole graph
rank >= nodes.size * 3 - 6
end
Class Image initializes with an array of 0's and 1's. I have method transform, such that
[[0,0,0],
[0,1,0],
[0,0,0]]
returns
[[0,1,0],
[1,1,1],
[0,1,0]]
I want to implement method blur(n), which iterates n times with transform, such calling blur(2) with
[[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,1,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0]]
returns
[[0,0,0,0,1,0,0,0,0],
[0,0,0,1,1,1,0,0,0],
[0,0,1,1,1,1,1,0,0],
[0,0,0,1,1,1,0,0,0],
[0,0,0,0,1,0,0,0,0]]
I'm trying to use transform iteratively to achieve this, but I'm getting undefined method 'map' for #<Context::Image:0x000000012eb020> when calling blur with an instance of Image. How can I iterate over each successive transformation, such that blur returns the latest version with the maximum n transformations?
class Image
attr_accessor :array
def initialize(array)
self.array = array
end
def output_image
self.array.each do |item|
puts item.join
end
end
def transform #changes adjacent a 1's adjacent 0's into 1
cloned = self.array.map(&:clone)
#scan original array for 1; map crosses into clone if found
self.array.each.with_index do |row, row_index|
row.each.with_index do |cell, col|
if cell == 1
cloned[row_index][col+1] = 1 unless col+1 >= row.length #copy right
cloned[row_index+1][col] = 1 unless row_index+1 >= cloned.length # copy down
cloned[row_index][col-1] = 1 unless col.zero? # copy left
cloned[row_index-1][col] = 1 unless row_index.zero? #copy up
end
end
end
cloned
end
def blur(n) #should call transform iteratively n times
blurred = Image.new(self)
n.times do
blurred = blurred.transform
end
blurred
end
end
You could use the Matrix class.
require 'matrix'
class Matrix
def el(r,c)
if r < 0 || r >= row_count || c < 0 || c >= column_count
0
else
self[r,c]
end
end
def transform
Matrix.build(row_count, column_count) { |r,c|
[el(r,c), el(r-1,c), el(r+1,c), el(r,c-1), el(r,c+1)].max }
end
end
Given a row-column pair, r, c, the helper method el returns 0 if the row or column is outside the bounds of the matrix and the value at [r,c] otherwise.
nrows = 5
ncols = 5
m = Matrix.build(nrows, ncols) { |r,c| (r==nrows/2 && c==ncols/2) ? 1 : 0 }
#=> Matrix[[0, 0, 0, 0, 0],
# [0, 0, 0, 0, 0],
# [0, 0, 1, 0, 0],
# [0, 0, 0, 0, 0],
# [0, 0, 0, 0, 0]]
m = m.transform
#=> Matrix[[0, 0, 0, 0, 0],
# [0, 0, 1, 0, 0],
# [0, 1, 1, 1, 0],
# [0, 0, 1, 0, 0],
# [0, 0, 0, 0, 0]]
m = m.transform
# Matrix[[0, 0, 1, 0, 0],
# [0, 1, 1, 1, 0],
# [1, 1, 1, 1, 1],
# [0, 1, 1, 1, 0],
# [0, 0, 1, 0, 0]]
m.to_a
#=> [[0, 0, 1, 0, 0],
# [0, 1, 1, 1, 0],
# [1, 1, 1, 1, 1],
# [0, 1, 1, 1, 0],
# [0, 0, 1, 0, 0]]
map is a method available to an Array, but not to your custom class Image.
I suggest calling map on your instance variable #array instead. Then, when your transforms are completed, create a new Image instance with that transformed array.
Below is an example of code that should work. Note that transform and blur take input arrays as parameters, so they do not rely on any instance state. Therefore, I've made them class methods instead of instance methods. This allows your users to use them without having to create an instance, if all they want to do is the array transformation. It also makes those methods easy to extract to a module in future refactorings. I've added an instance method, blurred_image, which applies the transformation to the instance and returns a new Image instance.
def self.transform(input_array) #changes adjacent a 1's adjacent 0's into 1
cloned = input_array.map(&:clone)
#scan original array for 1; map crosses into clone if found
input_array.each.with_index do |row, row_index|
row.each.with_index do |cell, col|
if cell == 1
cloned[row_index][col+1] = 1 unless col+1 >= row.length #copy right
cloned[row_index+1][col] = 1 unless row_index+1 >= cloned.length # copy down
cloned[row_index][col-1] = 1 unless col.zero? # copy left
cloned[row_index-1][col] = 1 unless row_index.zero? #copy up
end
end
end
cloned
end
def self.blur(input_array, transform_count) #should call transform iteratively n times
blurred = input_array
transform_count.times { blurred = transform(blurred) }
Image.new(blurred)
end
def blurred_image(transform_count)
self.class.new(self.class.blur(array, transform_count))
end
Given an array of ints I want to quantize each value so that the sum of quantized values is 100. Each quantized value should also be an integer. This works when the whole array is quantized, but when a subset of quantized values is added up it doesn't remain quantized with respect to the rest of the values.
For example, the values 44, 40, 7, 2, 0, 0 are quantized to 47, 43, 8, 2, 0, 0 (the sum of which is 100). If you take the last 4 quantized values the sum is 53 which is consistent with the first value (i.e. 47 + 53 = 100).
But with the values 78, 7, 7, 1, 0, 0, the sum of the last 4 quantized values (8, 8, 1, 0, 0) is 17. The first quantized value is 84 which when added to 17 does not equal 100. Clearly the reason for this is due to the rounding. Is there a way to adjust the rounding so that subsets are still consistent?
Here is the Ruby code:
class Quantize
def initialize(array)
#array = array.map { |a| a.to_i }
end
def values
#array.map { |a| quantize(a) }
end
def sub_total(i, j)
#array[i..j].map { |a| quantize(a) }.reduce(:+)
end
private
def quantize(val)
(val * 100.0 / total).round(0)
end
def total
#array.reduce(:+)
end
end
And the (failing) tests:
require 'quantize'
describe Quantize do
context 'first example' do
let(:subject) { described_class.new([44, 40, 7, 2, 0, 0]) }
context '#values' do
it 'quantizes array to add up to 100' do
expect(subject.values).to eq([47, 43, 8, 2, 0, 0])
end
end
context '#sub_total' do
it 'adds a subset of array' do
expect(subject.sub_total(1, 5)).to eq(53)
end
end
end
context 'second example' do
let(:subject) { described_class.new([78, 7, 7, 1, 0, 0]) }
context '#values' do
it 'quantizes array to add up to 100' do
expect(subject.values).to eq([84, 8, 8, 1, 0, 0])
end
end
context '#sub_total' do
it 'adds a subset of array' do
expect(subject.sub_total(1, 5)).to eq(16)
end
end
end
end
As noted in the comments on the question, the quantization routine does not perform correctly: the second example [78, 7, 7, 1, 0, 0] is quantized as [84, 8, 8, 1, 0, 0] — which adds to 101 and not to 100.
Here is an approach that will yield correct results:
def quantize(array, value)
quantized = array.map(&:to_i)
total = array.reduce(:+)
remainder = value - total
index = 0
if remainder > 0
while remainder > 0
quantized[index] += 1
remainder -= 1
index = (index + 1) % quantized.length
end
else
while remainder < 0
quantized[index] -= 1
remainder += 1
index = (index + 1) % quantized.length
end
end
quantized
end
This solves your problem, as stated in the question. The troublesome result becomes [80, 8, 8, 2, 1, 1], which adds to 100 and maintains the subset relationship that you described. The solution can, of course, be made more performant — but it has the advantage of working and being dead simple to understand.
How is n being rounded down to integers in this example so that the terminators are effective??
For example with wonky_coins(5), the next round of recursion should call for wonky_coins(2.5), wonky_coins(1.666) and wonky_coins(1.25), correct???
def wonky_coins(n)
return 1 if n == 0
return 3 if n == 1
return wonky_coins(n / 2) + wonky_coins(n / 3) + wonky_coins(n / 4)
end
wonky_coins(5) == 11
Round 1: [2, 1, 1]
Round 2: [[1, 0, 0], [0, 0, 0], [0, 0, 0]]
Round 3: [[[0, 0, 0], 0, 0], [0, 0, 0], [0, 0, 0]]
Try it with wonky_coins(5.0) !
In short, Ruby interprets / as integer division if both operands are Fixnum type.
If one or other of the operands is a Float then the result will be a Float, with the associated precision (and matching what you expected to see when you looked at the example code in the question).
This can catch you out sometimes. Often when you are generating ratios between values that might be integers, you don't want this behaviour, and may need to use defensive code like this:
ratio = x.to_f / y
which forces at least one of the operands to be Float, just in case.
Also, you will quite often see Float literals specified like this:
ONE_THIRD = 1.0 / 3
because 1 / 3 == 0.
Summary: The basic question here was, I've discovered, whether you can pass a code block to a Ruby array which will actually reduce the contents of that array down to another array, not to a single value (the way inject does). The short answer is "no".
I'm accepting the answer that says this. Thanks to Squeegy for a great looping strategy to get streaks out of an array.
The Challenge: To reduce an array's elements without looping through it explicitly.
The Input: All integers from -10 to 10 (except 0) ordered randomly.
The Desired Output: An array representing streaks of positive or negative numbers. For instance, a -3 represents three consecutive negative numbers. A 2 represents two consecutive positive numbers.
Sample script:
original_array = (-10..10).to_a.sort{rand(3)-1}
original_array.reject!{|i| i == 0} # remove zero
streaks = (-1..1).to_a # this is a placeholder.
# The streaks array will contain the output.
# Your code goes here, hopefully without looping through the array
puts "Original Array:"
puts original_array.join(",")
puts "Streaks:"
puts streaks.join(",")
puts "Streaks Sum:"
puts streaks.inject{|sum,n| sum + n}
Sample outputs:
Original Array:
3,-4,-6,1,-10,-5,7,-8,9,-3,-7,8,10,4,2,5,-2,6,-1,-9
Streaks:
1,-2,1,-2,1,-1,1,-2,5,-1,1,-2
Streaks Sum:
0
Original Array:
-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,1,2,3,4,5,6,7,8,9,10
Streaks:
-10,10
Streaks Sum:
0
Note a few things:
The streaks array has alternating positive and negative values.
The sum of the elements streaks array is always 0 (as is the sum of the original).
The sum of the absolute values of the streak array is always 20.
Hope that's clear!
Edit: I do realize that such constructs as reject! are actually looping through the array in the background. I'm not excluding looping because I'm a mean person. Just looking to learn about the language. If explicit iteration is necessary, that's fine.
Well, here's a one-line version, if that pleases you more:
streaks = original_array.inject([]) {|a,x| (a.empty? || x * a[-1] < 0 ? a << 0 : a)[-1] += x <=> 0; a}
And if even inject is too loopy for you, here's a really silly way:
streaks = eval "[#{original_array.join(",").gsub(/((\-\d+,?)+|(\d+,?)+)/) {($1[0..0] == "-" ? "-" : "") + $1.split(/,/).size.to_s + ","}}]"
But I think it's pretty clear that you're better off with something much more straightforward:
streaks = []
original_array.each do |x|
xsign = (x <=> 0)
if streaks.empty? || x * streaks[-1] < 0
streaks << xsign
else
streaks[-1] += xsign
end
end
In addition to being much easier to understand and maintain, the "loop" version runs in about two-thirds the time of the inject version, and about a sixth of the time of the eval/regexp one.
PS: Here's one more potentially interesting version:
a = [[]]
original_array.each do |x|
a << [] if x * (a[-1][-1] || 0) < 0
a[-1] << x
end
streaks = a.map {|aa| (aa.first <=> 0) * aa.size}
This uses two passes, first building an array of streak arrays, then converting the array of arrays to an array of signed sizes. In Ruby 1.8.5, this is actually slightly faster than the inject version above (though in Ruby 1.9 it's a little slower), but the boring loop is still the fastest.
new_array = original_array.dup
<Squeegy's answer, using new_array>
Ta da! No looping through the original array. Although inside dup it's a MEMCPY, which I suppose might be considered a loop at the assembler level?
http://www.ruby-doc.org/doxygen/1.8.4/array_8c-source.html
EDIT: ;)
original_array.each do |num|
if streaks.size == 0
streaks << num
else
if !((streaks[-1] > 0) ^ (num > 0))
streaks[-1] += 1
else
streaks << (num > 0 ? 1 : -1)
end
end
end
The magic here is the ^ xor operator.
true ^ false #=> true
true ^ true #=> false
false ^ false #=> false
So if the last number in the array is on the same side of zero as the number being processed, then add it to the streak, otherwise add it to the streaks array to start a new streak. Note that sine true ^ true returns false we have to negate the whole expression.
Since Ruby 1.9 there's a much simpler way to solve this problem:
original_array.chunk{|x| x <=> 0 }.map{|a,b| a * b.size }
Enumerable.chunk will group all consecutive elements of an array together by the output of a block:
>> original_array.chunk{|x| x <=> 0 }
=> [[1, [3]], [-1, [-4, -6]], [1, [1]], [-1, [-10, -5]], [1, [7]], [-1, [-8]], [1, [9]], [-1, [-3, -7]], [1, [8, 10, 4, 2, 5]], [-1, [-2]], [1, [6]], [-1, [-1, -9]]]
This is almost exactly what OP asks for, except the resulting groups need to be counted up to get the final streaks array.
More string abuse, a la Glenn McDonald, only different:
runs = original_array.map do |e|
if e < 0
'-'
else
'+'
end
end.join.scan(/-+|\++/).map do |t|
"#{t[0..0]}#{t.length}".to_i
end
p original_array
p runs
# => [2, 6, -4, 9, -8, -3, 1, 10, 5, -7, -1, 8, 7, -2, 4, 3, -5, -9, -10, -6]
# => [2, -1, 1, -2, 3, -2, 2, -1, 2, -4]