Passing a splat parameter from one method to another method - ruby

I would like to create methods called 'add' and 'subtract' using splat parameter as below:
def add(*numbers)
numbers.inject(0) { |sum, n| sum + n }
end
def subtract(*numbers)
numbers[0] - add(numbers[1..-1])
end
But it didn't work.
What should I do to pass the splat parameter from one to another? (Especially for some specific range...)
Thank you!

When you call add function, add * before the array object to unpack array as multiple arguments.
def add(*numbers)
numbers.inject(0) { |sum, n| sum + n }
end
def subtract(*numbers)
numbers[0] - add(*numbers[1..-1])
# ^
end
subtract(9, 0, 1, 2) # => 6
subtract(9, 1) # => 8

Alternatively, you can accept numbers into the add function as an array.
def add(numbers)
numbers.inject(0) { |sum, n| sum + n }
end
def subtract(*numbers)
numbers[0] - add(numbers[1..-1])
end
A more compact way of doing this (by the way) would be
def subtract(*numbers)
numbers.shift - numbers.inject(:+)
end

def add *numbers
[*numbers].flatten.inject 0, :+
end
def subtract *numbers
numbers.shift - add(numbers)
end
subtract(9, 0, 1, 2) # ⇒ 6
subtract(9, 1) # ⇒ 8

Related

How to implement Java's Comparable module in Ruby

I'm currently going over Robert Sedgewick's Algorithms book. In the book for the implementation of a Priority Queue there is the use of the Comparable module. While going over the top k frequent elements leetcode problem I noticed that there would be an error in my Ruby implementation.
def top_k_frequent(nums, k)
ans = []
h = Hash.new(0)
nums.each do |num|
h[num] += 1
end
heap = Heap.new
h.each do |k,v|
heap.insert({k => v})
end
k.times do
a = heap.del_max
ans.push(a.keys[0])
end
ans
end
class Heap
def initialize
#n = 0
#pq = []
end
def insert(v)
#pq[#n += 1] = v
swim(#n)
end
def swim(k)
while k > 1 && less((k / 2).floor, k)
swap((k / 2).floor, k)
k = k/2
end
end
def swap(i, j)
temp = #pq[i]
#pq[i] = #pq[j]
#pq[j] = temp
end
def less(i, j)
#pq[i].values[0] < #pq[j].values[0]
end
def del_max
max = #pq[1]
swap(1, #n)
#n -= 1
#pq[#n + 1] = nil
sink(1)
max
end
def sink(k)
while 2 * k <= #n
j = 2 * k
if !#pq[j + 1].nil?
j += 1 if j > 1 && #pq[j].values[0] < #pq[j + 1].values[0]
end
break if !less(k, j)
swap(k, j)
k = j
end
end
end
Above is the Java Priority Queue implementation.
Ruby's comparable operator is <=> which will return one of -1, 0, 1 and nil (nil mean could not compare).
In order to compare two objects , both need to implement a method def <=>(other). This is not on Object, so is not available on any objects that don't implement it or extend from a class that does implement it. Numbers and Strings, for example, do have an implementation. Hashes do not.
I think in your case, the issue is slightly different.
When you call queue.insert(my_hash) what you're expecting is for the algorithm to break up my_hash and build from that. Instead, the algorithm takes the hash as a single, atomic object and inserts that.
If you add something like:
class Tuple
attr_accessor :key, :value
def initialize(key, value)
#key = key
#value = value
end
def <=>(other)
return nil unless other.is_a?(Tuple)
value <=> other.value
end
end
then this will allow you to do something like:
hsh = { 1 => 3, 2 => 2, 3 => 1}
tuples = hsh.map { |k, v| Tuple.new(k, v) }
tuples.each { |tuple| my_heap.insert(tuple) }
you will have all of your data in the heap.
When you retrieve an item, it will be a tuple, so you can just call item.key and item.value to access the data.

How do I fix a problem to call a function in Ruby?

I'm trying to use some ruby code that I've found in Github. I've downloaded the code and did the necessary imports the "requires" and tried to run it as it is described in the readme file on github repository. The code is the following:
In the file pcset_test.rb the code is the following:
require './pcset.rb'
require 'test/unit'
#
# When possible, test cases are adapted from
# Introduction to Post-Tonal Theory by Joseph N. Straus,
# unless obvious or otherwise noted.
#
class PCSetTest < Test::Unit::TestCase
def test_init
#assert_raise(ArgumentError) {PCSet.new []}
assert_raise(ArgumentError) {PCSet.new [1, 2, 3, 'string']}
assert_raise(ArgumentError) {PCSet.new "string"}
assert_raise(ArgumentError) {PCSet.new [1, 2, 3.6, 4]}
assert_equal([0, 1, 2, 9], PCSet.new([0, 1, 2, 33, 13]).pitches)
assert_equal([3, 2, 1, 11, 10, 0], PCSet.new_from_string('321bac').pitches)
assert_equal([0,2,4,5,7,11,9], PCSet.new([12,2,4,5,7,11,9]).pitches)
assert_nothing_raised() {PCSet.new []}
end
def test_inversion
end
def test_transposition
end
def test_multiplication
end
#
# set normal prime forte #
# 0,2,4,7,8,11 7,8,11,0,2,4 0,1,4,5,7,9 6-31
# 0,1,2,4,5,7,11 11,0,1,2,4,5,7 0,1,2,3,5,6,8 7-Z36
# 0,1,3,5,6,7,9,10,11 5,6,7,9,10,11,0,1,3 0,1,2,3,4,6,7,8,10 9-8
#
def test_normal_form
testPC = PCSet.new [0,4,8,9,11]
assert_kind_of(PCSet, testPC.normal_form)
assert_equal([8,9,11,0,4], testPC.normal_form.pitches)
assert_equal([10,1,4,6], PCSet.new([1,6,4,10]).normal_form.pitches)
assert_equal([2,4,8,10], PCSet.new([10,8,4,2]).normal_form.pitches)
assert_equal([7,8,11,0,2,4], PCSet.new([0,2,4,7,8,11]).normal_form.pitches)
assert_equal([11,0,1,2,4,5,7], PCSet.new([0,1,2,4,5,7,11]).normal_form.pitches)
assert_equal([5,6,7,9,10,11,0,1,3], PCSet.new([0,1,3,5,6,7,9,10,11]).normal_form.pitches)
end
def test_prime_form
assert_equal([0,1,2,6], PCSet.new([5,6,1,7]).prime.pitches)
assert_equal([0,1,4], PCSet.new([2,5,6]).prime.pitches)
assert_equal([0,1,4,5,7,9], PCSet.new([0,2,4,7,8,11]).prime.pitches)
assert_equal([0,1,2,3,5,6,8], PCSet.new([0,1,2,4,5,7,11]).prime.pitches)
assert_equal([0,1,2,3,4,6,7,8,10], PCSet.new([0,1,3,5,6,7,9,10,11]).prime.pitches)
end
def test_set_class
testPcs = PCSet.new([2,5,6])
testPrime = testPcs.prime
assert_equal([
[2,5,6], [3,6,7], [4,7,8], [5,8,9], [6,9,10], [7,10,11],
[8,11,0],[9,0,1], [10,1,2],[11,2,3],[0,3,4], [1,4,5],
[6,7,10],[7,8,11],[8,9,0], [9,10,1],[10,11,2],[11,0,3],
[0,1,4], [1,2,5], [2,3,6], [3,4,7], [4,5,8], [5,6,9]
].sort, PCSet.new([2,5,6]).set_class.map{|x| x.pitches})
assert_equal(testPcs.set_class.map{|x| x.pitches}, testPrime.set_class.map{|x| x.pitches})
end
def test_interval_vector
assert_equal([2,1,2,1,0,0], PCSet.new([0,1,3,4]).interval_vector)
assert_equal([2,5,4,3,6,1], PCSet.new([0,1,3,5,6,8,10]).interval_vector)
assert_equal([0,6,0,6,0,3], PCSet.new([0,2,4,6,8,10]).interval_vector)
end
def test_complement
assert_equal([6,7,8,9,10,11], PCSet.new([0,1,2,3,4,5]).complement.pitches)
assert_equal([3,4,5], PCSet.new([0,1,2], 6).complement.pitches)
end
#
# Test values from (Morris 1991), pages 105-111
# Citation:
# Morris. Class Notes for Atonal Music Theory
# Lebanon, NH. Frog Peak Music, 1991.
#
def test_invariance_vector
assert_equal([1,0,0,0,5,6,5,5],PCSet.new([0,2,5]).invariance_vector)
assert_equal([2,2,2,2,6,6,6,6],PCSet.new([0,1,6,7]).invariance_vector)
assert_equal([6,6,6,6,6,6,6,6],PCSet.new([0,2,4,6,8,10]).invariance_vector)
assert_equal([1,0,0,0,0,0,0,0],PCSet.new([0,1,2,3,4,5,8]).invariance_vector)
assert_equal([1,0,0,1,0,0,0,0],PCSet.new([0,1,2,3,5,6,8]).invariance_vector)
assert_equal([12,12,12,12,0,0,0,0],PCSet.new([0,1,2,3,4,5,6,7,8,9,10,11]).invariance_vector)
end
#
# Test values from (Huron 1994). Huron rounds, thus the 0.01 margin of error.
# Citation:
# Huron. Interval-Class Content in Equally Tempered Pitch-Class Sets:
# Common Scales Exhibit Optimum Tonal Consonance.
# Music Perception (1994) vol. 11 (3) pp. 289-305
#
def test_huron
h1 = PCSet.new([0,1,2,3,4,5,6,7,8,9,10,11]).huron
assert_in_delta(-0.2, h1[0], 0.01)
assert_in_delta(0.21, h1[1], 0.01)
h2 = PCSet.new([0,2,4,5,7,9,11]).huron
assert_in_delta(4.76, h2[0], 0.01)
assert_in_delta(0.62, h2[1], 0.01)
end
def test_coherence
end
end
And in the file pcset.rb the folloing code:
#
# => PCSet Class for Ruby
# => Beau Sievers
# => Hanover, Fall 2008.
#
#
# TODO: Make this a module to avoid namespace collisions.
# Lilypond and MusicXML output
#
include Math
def choose(n, k)
return [[]] if n.nil? || n.empty? && k == 0
return [] if n.nil? || n.empty? && k > 0
return [[]] if n.size > 0 && k == 0
c2 = n.clone
c2.pop
new_element = n.clone.pop
choose(c2, k) + append_all(choose(c2, k-1), new_element)
end
def append_all(lists, element)
lists.map { |l| l << element }
end
def array_to_binary(array)
array.inject(0) {|sum, n| sum + 2**n}
end
# the following method is horrifically inelegant
# but avoids monkey-patching.
# TODO: do this right, incl. error checking
def pearsons(x, y)
if !x.is_a?(Array) || !y.is_a?(Array) then raise StandardError, "x and y must be arrays", caller end
if x.size != y.size then raise StandardError, "x and y must be same size", caller end
sum_x = x.inject(0) {|sum, n| sum + n}
sum_y = y.inject(0) {|sum, n| sum + n}
sum_square_x = x.inject(0) {|sum, n| sum + n * n}
sum_square_y = y.inject(0) {|sum, n| sum + n * n}
xy = []
x.zip(y) {|a, b| xy.push(a * b)}
sum_xy = xy.inject(0) {|sum, n| sum + n}
num = sum_xy - ((sum_x * sum_y)/x.size)
den = Math.sqrt((sum_square_x - ((sum_x*sum_x)/x.size)) * (sum_square_y - ((sum_y*sum_y)/x.size)))
(num/den)
end
class PCSet
include Comparable
attr_reader :pitches, :base, :input
def initialize(pcarray, base = 12)
if pcarray.instance_of?(Array) && pcarray.all?{|pc| pc.instance_of?(Fixnum)}
#base, #input = base, pcarray
#pitches = pcarray.map{ |x| x % #base }.uniq
else
raise ArgumentError, "Improperly formatted PC array", caller
end
end
def PCSet.new_from_string(pcstring, base = 12)
if base > 36 then raise StandardError, "Use PCSet.new to create pcsets with a base larger than 36", caller end
pcarray = []
pcstring.downcase.split(//).each do |c|
if c <= 'z' and c >= '0' then pcarray.push(c.to_i(36)) end
end
PCSet.new pcarray, base
end
def <=>(pcs)
#pitches <=> pcs.pitches
end
def [](index)
#pitches[index]
end
# Intersection
def &(other)
PCSet.new #pitches & other.pitches
end
# Union
def |(other)
PCSet.new #pitches | other.pitches
end
def inspect
#pitches.inspect
end
def length
#pitches.length
end
def invert(axis = 0)
PCSet.new #pitches.map {|x| (axis-x) % #base}
end
def invert!(axis = 0)
#pitches.map! {|x| (axis-x) % #base}
end
def transpose(interval)
PCSet.new #pitches.map {|x| (x + interval) % #base}
end
def transpose!(interval)
#pitches.map! {|x| (x + interval) % #base}
end
def multiply(m = 5)
PCSet.new #pitches.map {|x| (x * m) % #base}
end
def multiply!(m = 5)
#pitches.map! {|x| (x * m) % #base}
end
def zero
transpose(-1 * #pitches[0])
end
def zero!
transpose!(-1 * #pitches[0])
end
def transpositions
(0..(#base-1)).to_a.map{|x| #pitches.map {|y| (y + x) % #base}}.sort.map {|x| PCSet.new x}
end
def transpositions_and_inversions(axis = 0)
transpositions + invert(axis).transpositions
end
#
# Normal form after Straus. Morris and AthenaCL do this differently.
#
def normal_form
tempar = #pitches.sort
arar = [] # [[1,4,7,8,10],[4,7,8,10,1], etc.] get each cyclic variation
tempar.each {arar.push PCSet.new(tempar.unshift(tempar.pop))}
most_left_compact(arar)
end
def normal_form!
#pitches = normal_form.pitches
end
def is_normal_form?
self.pitches == self.normal_form.pitches
end
def set_class
transpositions_and_inversions.map{|pcs| pcs.normal_form}.sort
end
def prime
most_left_compact([normal_form.zero, invert.normal_form.zero])
end
def prime!
self.pitches = self.prime.pitches
end
def is_prime?
self.pitches == self.prime.pitches
end
def complement
new_pitches = []
#base.times do |p|
if !#pitches.include? p then
new_pitches.push p
end
end
PCSet.new new_pitches
end
def full_interval_vector
pairs = choose(#pitches, 2) # choose every pc pair
intervals = pairs.map {|x| (x[1] - x[0]) % #base} # calculate every interval
i_vector = Array.new(#base-1).fill(0)
intervals.each {|x| i_vector[x-1] += 1} # count the intervals
i_vector
end
def interval_vector
i_vector = full_interval_vector
(0..((#base-1)/2)-1).each {|x| i_vector[x] += i_vector.pop}
i_vector
end
#
# Morris's invariance vector
#
def invariance_vector(m = 5)
t = transpositions.map!{|pcs| self & pcs}
ti = invert.transpositions.map!{|pcs| self & pcs}
tm = multiply(m).transpositions.map!{|pcs| self & pcs}
tmi = invert.multiply(m).transpositions.map!{|pcs| self & pcs}
tc = complement.transpositions.map!{|pcs| self & pcs}
tic = complement.invert.transpositions.map!{|pcs| self & pcs}
tmc = complement.multiply(m).transpositions.map!{|pcs| self & pcs}
tmic = complement.invert.multiply(m).transpositions.map!{|pcs| self & pcs}
[t, ti, tm, tmi, tc, tic, tmc, tmic].map{|x| x.reject{|pcs| pcs.pitches != #pitches}.length}
end
# Huron's aggregate dyadic consonance measure.
# Huron. Interval-Class Content in Equally Tempered Pitch-Class Sets:
# Common Scales Exhibit Optimum Tonal Consonance.
# Music Perception (1994) vol. 11 (3) pp. 289-305
def huron
if #base != 12 then raise StandardError, "PCSet.huron only makes sense for mod 12 pcsets", caller end
# m2/M7 M2/m7 m3/M6 M3/m6 P4/P5 A4/d5
huron_table = [-1.428, -0.582, 0.594, 0.386, 1.240, -0.453]
interval_consonance = []
interval_vector.zip(huron_table) {|x, y| interval_consonance.push(x * y) }
aggregate_dyadic_consonance = interval_consonance.inject {|sum, n| sum + n}
[aggregate_dyadic_consonance, pearsons(interval_vector, huron_table)]
end
#
# Balzano's vector of relations. Citation for all Balzano methods:
#
# Balzano. "The Pitch Set as a Level of Description for Studying Musical
# Pitch Perception" in Music, Mind, and Brain ed. Clynes. Plenum Press. 1982.
#
def vector_of_relations
(0..length-1).to_a.map do |i|
(0..length-1).to_a.map do |j|
(#pitches[(i + j) % length] - #pitches[i]) % #base
end
end
end
#
# Checks if the set satisfies Balzano's uniqueness.
#
def is_unique?
vector_of_relations.uniq.size == vector_of_relations.size
end
#
# Checks if the set satisfies Balzano's scalestep-semitone coherence.
# For all s[i] and s[i1]:
# j < k => v[i][j] < v[i1][k]
# Where j and k are scalestep-counting indices.
# And unless v[i][j] == 6 (a tritone), in which case the strict inequality is relaxed.
#
def is_coherent?
v = vector_of_relations
truth_array = []
all_pair_indices = choose((0..length-1).to_a, 2)
all_pair_indices.each do |i, i1|
all_pair_indices.each do |j, k|
if v[i][j] == 6
truth_array.push(v[i][j] <= v[i1][k])
else
truth_array.push(v[i][j] < v[i1][k])
end
if v[i1][j] == 6
truth_array.push(v[i1][j] <= v[i][k])
else
truth_array.push(v[i1][j] < v[i][k])
end
end
end
!truth_array.include?(false)
end
#
# Strict Balzano coherence, no inequality relaxation for tritones.
#
def is_strictly_coherent?
v = vector_of_relations
truth_array = []
all_pair_indices = choose((0..length-1).to_a, 2)
all_pair_indices.each do |i, i1|
all_pair_indices.each do |j, k|
truth_array.push(v[i][j] < v[i1][k])
truth_array.push(v[i1][j] < v[i][k])
end
end
!truth_array.include?(false)
end
def notes(middle_c = 0)
noteArray = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B']
if #base != 12 then raise StandardError, "PCSet.notes only makes sense for mod 12 pcsets", caller end
out_string = String.new
transpose(-middle_c).pitches.each do |p|
out_string += noteArray[p] + ", "
end
out_string.chop.chop
end
def info
print "modulo: #{#base}\n"
print "raw input: #{#input.inspect}\n"
print "pitch set: #{#pitches.inspect}\n"
print "notes: #{notes}\n"
print "normal: #{normal_form.inspect}\n"
print "prime: #{prime.inspect}\n"
print "interval vector: #{interval_vector.inspect}\n"
print "invariance vector: #{invariance_vector.inspect}\n"
print "huron ADC: #{huron[0]} pearsons: #{huron[1]}\n"
print "balzano coherence: "
if is_strictly_coherent?
print "strictly coherent\n"
elsif is_coherent?
print "coherent\n"
else
print "false\n"
end
end
# def lilypond
#
# end
#
# def musicXML
#
# end
###############################################################################
private
#
# Convert every pitch array to a binary representation, e.g.:
# [0,2,4,8,10] -> 010100010101
# 2^n: BA9876543210
# The smallest binary number is the most left-compact.
#
def most_left_compact(pcset_array)
if !pcset_array.all? {|pcs| pcs.length == pcset_array[0].length}
raise ArgumentError, "PCSet.most_left_compact: All PCSets must be of same cardinality", caller
end
zeroed_pitch_arrays = pcset_array.map {|pcs| pcs.zero.pitches}
binaries = zeroed_pitch_arrays.map {|array| array_to_binary(array)}
winners = []
binaries.each_with_index do |num, i|
if num == binaries.min then winners.push(pcset_array[i]) end
end
winners.sort[0]
end
end
I'm calling them as follows:
> my_pcset = PCSet.new([0,2,4,6,8,10])
> my_pcset2 = PCSet.new([1,5,9])
It shoud return:
> my_pcset = PCSet.new([0,2,4,6,8,10])
=> [0, 2, 4, 6, 8, 10]
> my_pcset2 = PCSet.new([1,5,9])
=> [1, 5, 9]
But is returning nothing.
The code is available on github
Thanks
Try this in terminal: irb -r ./path_to_directory/pcset.rb and then initialize the objects.
I think the documentation for the repo is bad as it does not explain how you should be running this.
The result of
my_pcset = PCSet.new([0,2,4,6,8,10])
should set my_pcset to an instance of a PCSet not an array, so these lines from the README file are confusing at best.
3. How to use it
Make new PCSets:
my_pcset = PCSet.new([0,2,4,6,8,10])
=> [0, 2, 4, 6, 8, 10]
my_pcset2 = PCSet.new([1,5,9])
=> [1, 5, 9]
Looking at the code, I see inspect has been delegated to #pitches
def inspect
#pitches.inspect
end
I think if you inspect my_pcset you will get the expected result.
my_pcset = PCSet.new([0,2,4,6,8,10])
p my_pcset # will print [0, 2, 4, 6, 8, 10]
or `my_pcset.inspect` will return what you are expecting.

Turning a method into an enumerable method

I rewrote the map method:
def my_map(input, &block)
mod_input = []
x = -1
while x < input.length - 1
x = x + 1
if block == nil
return input
break
end
mod_input.push(block.call(input[x]))
end
return mod_input
end
I need to call this code as I would call map or reverse. Does anyone know the syntax for that?
Are you asking how you put a method into a module? That's trivial:
module Enumerable
def my_map(&block)
mod_input = []
x = -1
while x < length - 1
x = x + 1
if block == nil
return self
break
end
mod_input.push(block.call(self[x]))
end
return mod_input
end
end
[1, 2, 3, 4, 5].my_map(&2.method(:*))
# => [2, 4, 6, 8, 10]
Or are you asking how to make your method an Enumerable method? That's more involved: your method currently uses many methods that are not part of the Enumerable API. So, even if you make it a member of the Enumerable module, it won't be an Enumerable method. Enumerable methods can only use each or other Enumerable methods. You use length and [] both of which are not part of the Enumerable interface, for example, Set doesn't respond to [].
This would be a possible implementation, using the Enumerable#inject method:
module Enumerable
def my_map
return enum_for(__method__) unless block_given?
inject([]) {|res, el| res << yield(el) }
end
end
[1, 2, 3, 4, 5].my_map(&2.method(:*))
# => [2, 4, 6, 8, 10]
A less elegant implementation using each
module Enumerable
def my_map
return enum_for(__method__) unless block_given?
[].tap {|res| each {|el| res << yield(el) }}
end
end
[1, 2, 3, 4, 5].my_map(&2.method(:*))
# => [2, 4, 6, 8, 10]
Note that apart from being simply wrong, your code is very un-idiomatic. There is also dead code in there.
the break is dead code: the method returns in the line just before it, therefore the break will never be executed. You can just get rid of it.
def my_map(&block)
mod_input = []
x = -1
while x < length - 1
x = x + 1
if block == nil
return self
end
mod_input.push(block.call(self[x]))
end
return mod_input
end
Now that we have gotten rid of the break, we can convert the conditional into a guard-style statement modifier conditional.
def my_map(&block)
mod_input = []
x = -1
while x < length - 1
x = x + 1
return self if block == nil
mod_input.push(block.call(self[x]))
end
return mod_input
end
It also doesn't make sense that it is in the middle of the loop. It should be at the beginning of the method.
def my_map(&block)
return self if block == nil
mod_input = []
x = -1
while x < length - 1
x = x + 1
mod_input.push(block.call(self[x]))
end
return mod_input
end
Instead of comparing an object against nil, you should just ask it whether it is nil?: block.nil?
def my_map(&block)
return self if block.nil?
mod_input = []
x = -1
while x < length - 1
x = x + 1
mod_input.push(block.call(self[x]))
end
return mod_input
end
Ruby is an expression-oriented language, the value of the last expression that is evaluated in a method body is the return value of that method body, there is no need for an explicit return.
def my_map(&block)
return self if block.nil?
mod_input = []
x = -1
while x < length - 1
x = x + 1
mod_input.push(block.call(self[x]))
end
mod_input
end
x = x + 1 is more idiomatically written x += 1.
def my_map(&block)
return self if block.nil?
mod_input = []
x = -1
while x < length - 1
x += 1
mod_input.push(block.call(self[x]))
end
mod_input
end
Instead of Array#push with a single argument it is more idiomatic to use Array#<<.
def my_map(&block)
return self if block.nil?
mod_input = []
x = -1
while x < length - 1
x += 1
mod_input << block.call(self[x])
end
mod_input
end
Instead of Proc#call, you can use the .() syntactic sugar.
def my_map(&block)
return self if block.nil?
mod_input = []
x = -1
while x < length - 1
x += 1
mod_input << block.(self[x])
end
mod_input
end
If you don't want to store, pass on or otherwise manipulate the block as an object, there is no need to capture it as a Proc. Just use block_given? and yield instead.
def my_map
return self unless block_given?
mod_input = []
x = -1
while x < length - 1
x += 1
mod_input << yield(self[x])
end
mod_input
end
This one is opinionated. You could move incrementing the counter into the condition.
def my_map
return self unless block_given?
mod_input = []
x = -1
while (x += 1) < length
mod_input << yield(self[x])
end
mod_input
end
And then use the statement modifier form.
def my_map
return self unless block_given?
mod_input = []
x = -1
mod_input << yield(self[x]) while (x += 1) < length
mod_input
end
Also, your variable names could use some improvements. For example, what does mod_input even mean? All I can see is that it is what you output, so why does it even have "input" in its name? And what is x?
def my_map
return self unless block_given?
result = []
index = -1
result << yield(self[index]) while (index += 1) < length
result
end
This whole sequence of initializing a variable, then mutating the object assigned to that variable and lastly returning the object can be simplified by using the K Combinator, which is available in Ruby as Object#tap.
def my_map
return self unless block_given?
[].tap {|result|
index = -1
result << yield(self[index]) while (index += 1) < length
}
end
The entire while loop is useless. It's just re-implementing Array#each, which is a) unnecessary because Array#each already exists, and b) means that your my_map method will only work with Arrays but not other Enumerables (for example Set or Enumerator). So, let's just use each instead.
def my_map
return self unless block_given?
[].tap {|result|
each {|element|
result << yield(element)
}
}
end
Now it starts to look like Ruby code! What you had before was more like BASIC written in Ruby syntax.
This pattern of first creating a result object, then modifying that result object based on each element of a collection and in the end returning the result is very common, and it even has a fancy mathematical name: Catamorphism, although in the programming world, we usually call it fold or reduce. In Ruby, it is called Enumerable#inject.
def my_map
return self unless block_given?
inject([]) {|result, element|
result << yield(element)
}
end
That return self is strange. map is supposed to return a new object! You don't return a new object, you return the same object. Let's fix that.
def my_map
return dup unless block_given?
inject([]) {|result, element|
result << yield(element)
}
end
And actually, map is also supposed to return an Array, but you return whatever it is that you called map on.
def my_map
return to_a unless block_given?
inject([]) {|result, element|
result << yield(element)
}
end
But really, if you look at the documentation of Enumerable#map, you will find that it returns an Enumerator and not an Array when called without a block.
def my_map
return enum_for(:my_map) unless block_given?
inject([]) {|result, element|
result << yield(element)
}
end
And lastly, we can get rid of the hardcoded method name using the Kernel#__method__ method.
def my_map
return enum_for(__method__) unless block_given?
inject([]) {|result, element|
result << yield(element)
}
end
Now, that looks a lot better!
class Array
def my_map(&block)
# your code, replacing `input` with `self`
end
end
The code itself is not really idiomatic Ruby - while is very rarely used for iteration over collections, and if you don't need to pass a block somewhere else, it is generally cleaner to use block_given? instead of block.nil? (let alone block == nil), and yield input[x] instead of block.call(input[x]).

Rewriting Ruby Inject

Here is a brain bender.
I am trying to rewrite the Ruby Inject method. I have got as far as below.
class Array
def injector(input = nil)
if input == nil
num = self.first
else
num = input
end
self[0..-1].each do |x|
num = yield(num, x)
end
return num
end
end
It is passing some tests, but it is not fully accurate, for example;
[1,2,3,4,5].injector(0) {|x,y| x + y} #=> 14
As opposed to the expected output 15, is it a rounding error? I cannot seem to figure this one out
Additional example (above updated [0..-1]):
[9,8,7,6,5].injector {|x,y| x * y} #=> 136080
Ruby .inject outputs 15120
The starting index is important as it depends on your input.
class Array
def injector(input = nil)
if input.nil?
start = 1
num = self.first
else
start = 0
num = input
end
self[start..-1].each do |x|
num = yield(num, x)
end
return num
end
end
Using nil as the default is probably wrong, I should be able to pass nil in as the default memo.
class Array
def injector(memo = (i=1; first))
(i||0).upto(length-1) { |i| memo = yield memo, self[i] }
memo
end
end
[1,2,3,4,5].injector(1) { |sum, n| sum + n }
[1,2,3,4,5].injector(0) { |sum, n| sum + n }
[1,2,3,4,5].injector { |sum, n| sum + n }
[1,2,3].injector(2) { |product, n| product * n }
[1,2,3].injector(1) { |product, n| product * n }
[1,2,3].injector { |product, n| product * n }
['b', 'c', 'd'].injector('a') { |str, char| str + char } # => "abcd"
['b', 'c', 'd'].injector { |str, char| str + char } # => "bcd"
seen = []
[1].injector(nil) { |prev, crnt| seen << prev << crnt }
seen # => [nil, 1]

Calculating Median in Ruby

How do I calculate the median of an array of numbers using Ruby?
I am a beginner and am struggling with handling the cases of the array being of odd and even length.
Here is a solution that works on both even and odd length array and won't alter the array:
def median(array)
return nil if array.empty?
sorted = array.sort
len = sorted.length
(sorted[(len - 1) / 2] + sorted[len / 2]) / 2.0
end
Similar to nbarraille's, but I find it a bit easier to keep track of why this one works:
class Array
def median
sorted = self.sort
half_len = (sorted.length / 2.0).ceil
(sorted[half_len-1] + sorted[-half_len]) / 2.0
end
end
half_len = number of elements up to and including (for array with odd number of items) middle of array.
Even simpler:
class Array
def median
sorted = self.sort
mid = (sorted.length - 1) / 2.0
(sorted[mid.floor] + sorted[mid.ceil]) / 2.0
end
end
If by calculating Median you mean this
Then
a = [12,3,4,5,123,4,5,6,66]
a.sort!
elements = a.count
center = elements/2
elements.even? ? (a[center] + a[center+1])/2 : a[center]
def median(array) #Define your method accepting an array as an argument.
array = array.sort #sort the array from least to greatest
if array.length.odd? #is the length of the array odd?
array[(array.length - 1) / 2] #find value at this index
else array.length.even? #is the length of the array even?
(array[array.length/2] + array[array.length/2 - 1])/2.to_f
#average the values found at these two indexes and convert to float
end
end
More correct solution with handling edge cases:
class Array
def median
sorted = self.sort
size = sorted.size
center = size / 2
if size == 0
nil
elsif size.even?
(sorted[center - 1] + sorted[center]) / 2.0
else
sorted[center]
end
end
end
There is a specs to prove:
describe Array do
describe '#median' do
subject { arr.median }
context 'on empty array' do
let(:arr) { [] }
it { is_expected.to eq nil }
end
context 'on 1-element array' do
let(:arr) { [5] }
it { is_expected.to eq 5 }
end
context 'on 2-elements array' do
let(:arr) { [1, 2] }
it { is_expected.to eq 1.5 }
end
context 'on odd-size array' do
let(:arr) { [100, 5, 2, 12, 1] }
it { is_expected.to eq 5 }
end
context 'on even-size array' do
let(:arr) { [7, 100, 5, 2, 12, 1] }
it { is_expected.to eq 6 }
end
end
end
I like to use Refinements, which is a safe way to Monkey Patch the ruby classes without collateral effects over the system.
The usage become much more cleaner than a new method.
With the Refinements you can monkey patch the Array class, implement the Array#median and this method will only be available inside the scope of the class that is using the refinement! :)
Refinements
module ArrayRefinements
refine Array do
def median
return nil if empty?
sorted = sort
(sorted[(length - 1) / 2] + sorted[length / 2]) / 2.0
end
end
end
class MyClass
using ArrayRefinements
# You can use the Array#median as you wish here
def test(array)
array.median
end
end
MyClass.new.test([1, 2, 2, 2, 3])
=> 2.0
def median(array)
half = array.sort!.length / 2
array.length.odd? ? array[half] : (array[half] + array[half - 1]) / 2
end
*If the length is even, you must add the middle point plus the middle point - 1 to account for the index starting at 0
def median(arr)
sorted = arr.sort
if sorted == []
return nil
end
if sorted.length % 2 != 0
result = sorted.length / 2 # 7/2 = 3.5 (rounded to 3)
return sorted[result] # 6
end
if sorted.length % 2 == 0
result = (sorted.length / 2) - 1
return (sorted[result] + sorted[result+1]) / 2.0 # (4 + 5) / 2
end
end
p median([5, 0, 2, 6, 11, 10, 9])
Here's a solution:
app_arry = [2, 3, 4, 2, 5, 6, 16].sort
# check array isn't empty
if app_arry.empty? || app_arry == ""
puts "Sorry, This will not work."
return nil
end
length = app_arry.length
puts "Array length = #{length}"
puts "Array = #{app_arry}"
if length % 2 == 0
# even number of elements
puts "median is #{(app_arry[length/2].to_f + app_arry[(length-1)/2].to_f)/2}"
else
# odd number of elements
puts "median is #{app_arry[(length-1)/2]}"
end
OUTPUT
Array length = 7
Array = [2, 3, 4, 2, 5, 6, 16]
median is 2
def median(array, already_sorted=false)
return nil if array.empty?
array = array.sort unless already_sorted
m_pos = array.size / 2
return array.size % 2 == 1 ? array[m_pos] : mean(array[m_pos-1..m_pos])
end
There are many ways to do this, but for both performance and reliability, I suggest using the enumerable-statistics library created by Ruby committer mrkn.
https://github.com/mrkn/enumerable-statistics
require 'enumerable/statistics'
ary = [1,2,3,3,4]
ary.mean # => 2.6
ary.median # => 3
I think it's good:
#!/usr/bin/env ruby
#in-the-middle value when odd or
#first of second half when even.
def median(ary)
middle = ary.size/2
sorted = ary.sort_by{ |a| a }
sorted[middle]
end
or
#in-the-middle value when odd or
#average of 2 middle when even.
def median(ary)
middle = ary.size/2
sorted = ary.sort_by{ |a| a }
ary.size.odd? ? sorted[middle] : (sorted[middle]+sorted[middle-1])/2.0
end
I used sort_by rather than sort because it's faster: Sorting an array in descending order in Ruby.

Resources