Big O notation for Ruby methods? - c

How can I find the complexity of a Ruby method?
For example length? If I look at the source code, I see this:
static VALUE
rb_ary_length(VALUE ary)
{
long len = RARRAY_LEN(ary);
return LONG2NUM(len);
}
But I don't know how to read that in order to find the Big O notation.

There is no maintained list of theoretical complexities of Ruby methods. Of interest to you is minitest/benchmark, which is used like this:
require 'minitest/autorun'
require 'minitest/benchmark'
class TestFoobar < Minitest::Benchmark
def setup
#foo_arrays = ( 1 .. 10_000 ).map { |n| [ 42 ] * n }
# Because default benchmarking range is [1, 10, 100, 1_000, 10_000]
end
def bench_foo_size
assert_performance_constant 0.99 do |n| #foo_arrays[ n ].size end
end
end
If you run the above test, you can actually see that the performance of Array#size method is constant. If you change #bench_foo_size to:
def bench_foo_size
assert_performance_linear 0.99 do |n| #foo_arrays[ n ].size end
end
The test will actually fail, because Array#size is not linear, but sublinear. minitest/benchmark is flexible and you can apply it to your own code as well as to built-in assets.

It is just O(1) for the length method. The length of an array is stored in a variable and can be returned without further calculations.
How to find out? By reading the source code and analysing the implementation and algorithms.

You cannot get the relevant information from that. You have to look into the source of RARRAY_LEN and LONG2NUM.
A simple way to estimate the complexity of a method is to run benchmarks with arguments that differ along the dimension you are interested in, and plot that on a graph.

Related

Create Enumerable In Place Slice Of Array in Ruby

I'm looking to find a way to take an array in ruby, two indices in that array and return an enumerable object which will yield, in order, all the elements between and including the two indices. But for performance reasons, I want to do this subject to the following two conditions:
This slice to enum does not create a copy of the subarray I want a return an enum over. This rules out array[i..j].to_enum, for example because array[i..j] is creating a new array.
It's not necessary to loop over the entire array to create the enum.
I'm wondering if there's a way to do this using the standard library's enumerable or array functionality without having to explicitly create my own custom enumerator.
What I'm looking for is a cleaner way to create the below enumerator:
def enum_slice(array, i, j)
Enumerator.new do |y|
while i <= j
y << array[i] # this is confusing syntax for yield (see here: https://ruby-doc.org/core-2.6/Enumerator.html#method-c-new)
i += 1
end
end
end
That seems pretty reasonable, and could even be turned into an extension to Array itself:
module EnumSlice
def enum_slice(i, j)
Enumerator.new do |y|
while i <= j
y << self[i]
i += 1
end
end
end
end
Now within the Enumerator block, y represents a Proc you call when you have more data. If that block ends it's presumed you're done enumerating. There's no requirement to ever terminate, an infinite Enumerator is allowed, and in that case it's up to the caller to stop iterating.
So in other words, the y block argument can be called zero or more times, and each time it's called output is "emitted" from the enumerator. When that block exits the enumerator is considered done and is closed out, y is invalid at that point.
All y << x does is call the << method on Enumerator::Yielder, which is a bit of syntactical sugar to avoid having to do y.call(x) or y[x], both of which look kind of ugly.
Now you can add this to Array:
Array.include(EnumSlice)
Where now you can do stuff like this:
[ 1, 2, 3, 4, 5, 6 ].enum_slice(2, 4).each do |v|
p v
end
Giving you the correct output.
It's worth noting that despite having gone through all this work, this really doesn't save you any time. There's already built-in methods for this. Your enum_slice(a, i, j) method is equivalent to:
a.drop(i).take(j)
Is that close in terms of performance? A a quick benchmark can help test that theory:
require 'benchmark'
Benchmark.bm do |bm|
count = 10000
a = (0..100_000).to_a
bm.report(:enum_slice) do
count.times do
a.enum_slice(50_000, 25_000).each do
end
end
end
bm.report(:drop_take) do
count.times do
a.drop(50_000).take(25_000).each do
end
end
end
end
The results are:
user system total real
enum_slice 0.020536 0.000200 0.020736 ( 0.020751)
drop_take 7.682218 0.019815 7.702033 ( 7.720876)
So your approach is about 374x faster. Not bad!

Get hash values (from a number of hashes) in an array and sum them up [duplicate]

This question already has answers here:
Sum the value of array in hash
(8 answers)
Closed 5 years ago.
I currently have an array of hashes:
total_order = [{"Hamburger"=>5.0}, {"Hamburger"=>5.0}, {"Hamburger"=>5.0}]
I'm trying to iterate over each hash in this array and then sum all of the values. So essentially I want to return 15.0.
Here is my code at the moment. I'm not even trying to sum it yet, just return each value so that I can sum it after. When I run it I only get the array returned:
def total_order_cost
total_order.each do |item|
item.map do |k, v|
print v
end
end
end
Any idea how I go about iterating over the hashes and then sum the values within? Have checked other people's questions but don't seem to be able to make any of the solutions work in my case. Other answers I have seen do not address my question and do not include solutions such as the use of flat_map.
This is the most succinct way I came up with:
2.1.2 :013 > [{a:1},{b:2}].map(&:values).flatten.inject(:+)
=> 3
values returns just the values, not the keys, as an array and then inject sums them all together. inject is a great function, and really helped me think in terms of list comprehensions, which this kinda is.
To sum the values of a hash:
{a:1,b:2}.values.sum
#=> 3
And to do that for your array of hashes:
total_order.flat_map(&:values).sum
#=> 15.0
Note that sum is only available in Ruby 2.4.0 and later (and in Rails). In older versions you can use reduce(:+) and inject(:+) instead of sum.
Your method returns the result of total_oder.each, and Array#each just returns self, so your method simply returns total_order. This is true for all implementations of each, by the way, not just for Array; it is the standard protocol for each. each is intended to be used only for the block's side-effects.
If you want to transform an Enumerable, you need to use Enumerable#map, which transforms each element using the given block.
So, you want to get all the values from the Hashes. Look at the documentation of Hash and see if you can spot a method that could possibly be used to return the values. What about Hash#values, that sounds good, doesn't it?
total_order.map(&:values)
#=> [[5.0], [5.0], [5.0]]
Now, we want to get rid of that extra level of nesting, we could use Array#flatten for that, but it is much better to make sure that the nesting doesn't occur in the first place, so we use Enumerable#flat_map instead of Enumerable#map:
total_order.flat_map(&:values)
#=> [5.0, 5.0, 5.0]
Okay, next you want to sum those values. Again, let's just look at the documentation of Enumerable. What could a method that sums the items of an Enumerable possibly be called. How does Enumerable#sum sound?
total_order.flat_map(&:values).sum
#=> 15.0
And there you have your desired result. The moral of the story is: learn the methods in Enumerable, they are the bread and butter of Ruby programming. And, learn to navigate the documentation.
sum = 0
def total_order_cost
total_order.each do |item|
item.each do |k,v|
sum = sum + v
end
p sum
end
Your code is not too far off:
total_order = [{"Hamburger"=>5.0}, {"Hamburger"=>5.0}, {"Hamburger"=>5.0}]
def total_order_cost(order)
cost = 0
order.each do |item|
item.each do |k, v|
cost = cost + v
end
end
return cost
end
puts total_order_cost(total_order) # 15.0

Yielding a modified Ruby array to a block

I'm trying to turn 2 lines of ruby code into 1. For example:
def average(numbers)
result = numbers.compact
numbers.reduce(+) / numbers.length
end
I've been looking through array methods and can't find an appropriate one to turn this function into a one-liner. I had hoped something like this would work:
def average(numbers)
numbers.compact.<tap or another method> { |arr| arr.reduce(+) / arr.length }
end
Basically, I'm modifying the array (in the example I have to call compact to rid nil values), so I don't have access to the array variable, and I don't want an iterator, because I don't want to call reduce(+) and length on individual elements of the array.
Does anyone have an idea of methods I could look into?
I believe you mean for your method to be the following (reduce(:+), not reduce(+) and use result rather than numbers in the second line).
def average(numbers)
result = numbers.compact
result.reduce(:+) / result.length
end
average [1,2,3]
#=> 2
If you wish the average to be a float, change the second line to
result.reduce(0.0, :+) / result.length
There are various ways to combine the two lines of the method, but I don't prefer any of them to the above. Here are a few. (I don't see how Object#tap could be used here.)
numbers.compact.reduce(:+) / numbers.compact.length
(result = numbers.compact).reduce(:+) / result.compact.length
numbers.map(&:to_i).reduce(:+) / numbers.compact.length
Note that, even if numbers can be mutated, one cannot write
numbers.compact!.reduce(:+) / numbers.length
because numbers.compact! returns nil if numbers contains no nil elements.
In Ruby v2.4+ you can use Array#sum:
result.sum / result.length
You could change the way you call average
def average(numbers)
numbers.reduce(:+) / numbers.length
end
average(num_array.compact)

Each slice or keep the full array

each_slice keeps slices of length n, but in some cases I want to keep the full array, i.e. do nothing.
module MyModule
def num_slice
some_boolean_test? ? :full_array : 10 # Note : the content of some_boolean_test? in uninteresting, just assume sometimes it ca return true or false
end
end
class Foo
include MyModule
def a_method
big_array.each_slice(num_slice) do |array_slice|
# I want array_slice == big_array if num_slice returns :full_array
...
end
end
end
I could write a wrapper around Array#each_slice instead so I could define a different behaviour when the parameter is :full_array.
Could anyone help with that?
I'd first caution against significant logic differences between environments, since either one branch is less tested or you have twice the code to maintain. But assuming good reasons for the way you're doing it, here are some options:
Pass the array
Since num_slice is making a decision about the array, it seems reasonable num_slice should get access to it.
def num_slice(arr)
some_boolean_test? ? arr.size : 10
end
Environment configuration
You're using Rails, so you can set the slice size differently in production and your other environments. In production, make it 10, and in test, make it arbitrarily large; then just use the configured value. This is nice because there's no code difference.
def a_method
big_array.each_slice(Rails.application.config.slice_size) do |array_slice|
# ...
end
end
Wrap it
I wouldn't recommend this method because it causes the most significant difference between your environments, but since you asked about it, here's a way.
def a_method
magic_slice(big_array) do |array_slice|
# ...
end
end
def magic_slice(arr, &block)
if some_boolean_test?
block.call(arr)
else
arr.each_slice(10, &block)
end
end
def a_method(big_array, debug_context)
num_slice = debug_context ? big_array.length : 10
big_array.each_slice(num_slice) do |array_slice|
# array_slice will equal to big_array if debug_context == true
puts array_slice.inspect
end
end
test:
a_method([1,2,3,4,5], true)
[1, 2, 3, 4, 5]

QuickSort can't be applied to ArrayBuffer to do sorting In place in Scala

I am little confused with QuickSort in Scala. As per the specification, QuickSort can be applied to only Array, but not to ArrayBuffer. And QuickSort will do the sort in Place i.e. will change the original Array.
val intArray = Array(7,1,4,6,9) //> intArray : Array[Int] = Array(7, 1, 4, 6, 9)
Sorting.quickSort(intArray)
intArray.mkString("<"," and ",">") //> res4: String = <1 and 4 and 6 and 7 and 9>
Now I am not able to understand why the same I can't do with ArrayBuilder. Is there any reason behind this? and If I want to sort an ArrayBuilder with QuickSort Algorithm, what is the option provided by scala? Thanks for all help.
I believe the answer to your question can be found by examining the source code of Scala, to determine just what behavior you can get from the built in functions that come with ArrayBuffer : sortWith, sortBy, etc..
These functions come from defining trait "SeqLike" (you can examine the source code by browsing to that class in ScalaDoc, and clicking the link to next to source:"Seqlike" at the top of the documentation..
Anyhow most of the code related to sorting just gets pushed off this function:
def sorted[B >: A](implicit ord: Ordering[B]): Repr = {
val len = this.length
val arr = new ArraySeq[A](len)
var i = 0
for (x <- this.seq) {
arr(i) = x
i += 1
}
java.util.Arrays.sort(arr.array, ord.asInstanceOf[Ordering[Object]])
val b = newBuilder
b.sizeHint(len)
for (x <- arr) b += x
b.result()
}
Which basically makes a copy of the array and sorts it using Java.util.Arrays.Sort. So the next question becomes, "What is java doing here?" I was curious and the api described:
Implementation note: The sorting algorithm is a Dual-Pivot Quicksort by Vladimir Yaroslavskiy, Jon Bentley, and Joshua Bloch. This algorithm offers O(n log(n)) performance on many data sets that cause other quicksorts to degrade to quadratic performance, and is typically faster than traditional (one-pivot) Quicksort implementations.
So I think the basic answer is that scala relies heavily on Java's built in quicksorting, and you can make use of any Seq function (sorted, sortWith, sortBy) with better or equal performance to array quicksort functions. You may get a bit of performance hit in the "copy" phase, but classes are just pointers (it's not cloning the objects) so you're not losing much unless you have millions of items.

Resources