I was not aware you could do this in Ruby, can someone please give me an explanation on how this works, and is there a name for this? Like is he using multiple enums. on just nums? Need a decent explanation, as I'm still a newbie obviously.
This method is using splat, so you can give any amount of arguments example:
mutual_factors(16,8,24) #=> [1, 2, 4, 8]
and it will give the common factors that work with all the arguments.
def mutual_factors(*nums)
nums
.map { |n| factors(n) }
.inject(:&)
end
def factors(number)
(1..number).select {|num| number % num == 0}
end
CodePudding user response:
factors is easy to understand. Given a number, just computes the list of all common factors. Better algorithm do exist for this.
mutual_factors creates a common factor for every element that is provided and takes intersection among all the arrays. [https://docs.ruby-lang.org/en/3.0/Array.html#method-i-26) is used for this. You can understand in this way.
(ins)irb(main):039:0> factors(10)
=> [1, 2, 5, 10]
(ins)irb(main):040:0> factors(20)
=> [1, 2, 4, 5, 10, 20]
(ins)irb(main):041:0> factors(10) & factors(20)
=> [1, 2, 5, 10]
A explicit loop version which is more beginner friendly
def mutual_factors_explicit_looping(*nums)
common_factor = nil
nums.each do |num|
common_factor = if common_factor.nil?
factors(num)
else
common_factor & factors(num)
end
end
common_factor
end
CodePudding user response:
#inject takes an Enumerable object and "injects" an operation between the elements.
Consider a simple example:
irb(main):001:0> a = [1, 2, 3, 4]
=> [1, 2, 3, 4]
irb(main):002:0> a.inject { |a, b| a b }
=> 10
irb(main):003:0> a.inject { |a, b| a * b }
=> 24
irb(main):004:0> a.inject(: )
=> 10
irb(main):005:0> a.inject(:*)
=> 24
We know factors(8), factors(6), and factors(16) give us [1, 2, 4, 8], [1, 2, 3, 6], and [1, 2, 4, 8, 16] respectively.
So [8, 6, 16].map { |x| factors x } gives us [[1, 2, 4, 8], [1, 2, 3, 6], [1, 2, 4, 8, 16]]. If we inject :& into that, we have the equivalent of:
([1, 2, 4, 8] & [1, 2, 3, 6]) & [1, 2, 4, 8, 16]
Which reduces to:
[1, 2] & [1, 2, 4, 8, 16]
And then to:
[1, 2]
CodePudding user response:
I believe your question has been answered so I would like to suggest an alternative way to compute the common factors that is generally more efficient than simply looping through values.
This will make use of the method Prime#prime_division.
Suppose we wish to get the factors of 360. We find that
require 'prime'
arr = Prime.prime_division(720)
#=> [[2, 4], [3, 2], [5, 1]]
This means that
720 = (2**4) * (3**2) * (5**1)
where 2, 3, and 5 are the prime divisors of 360.
For convenience I will write
h = Prime.prime_division(720).to_h
#=> {2=>4, 3=>2, 5=>1}
Similarly,
Prime.prime_division(2100).to_h
#=> {2=>2, 3=>1, 5=>2, 7=>1}
meaning that
2100 = (2**2) * (3**1) * (5**2) * (7**1)
We know that every factor of both 720 and 2100 will equal
(2**w) * (3**x) * (5**y) * (7**z)
where
0 <= w <= [4, 2].min = 2
0 <= x <= [2, 1].min = 1
0 <= y <= [1, 2].min = 1
0 <= z <= [0, 1].min = 0
In other words, 2 may have a power of 0, 1 or 2, 3 may have powers of 0 or 1, 5 may have a power of 0 or 1 and 7 must have a power of 0, generating 12 common factors (3*2*2*1). The common factors are therefore the following. (I will disregard powers of 7 as zero is the only one.)
(2**0) * (3**0) * (5**0) #= 1
(2**0) * (3**1) * (5**0) #= 3
(2**0) * (3**0) * (5**1) #= 5
(2**0) * (3**1) * (5**1) #= 15
(2**1) * (3**0) * (5**0) #= 2
(2**1) * (3**1) * (5**0) #= 6
(2**1) * (3**0) * (5**1) #= 10
(2**1) * (3**1) * (5**1) #= 30
(2**2) * (3**0) * (5**0) #= 4
(2**2) * (3**1) * (5**0) #= 12
(2**2) * (3**0) * (5**1) #= 20
(2**2) * (3**1) * (5**1) #= 60
We can generate those factors as follows. First, define a helper method.
def factor(primes, powers)
primes.zip(powers).reduce(1) { |tot,(pr,pow)| tot * (pr**pow) }
end
factor([2,3,5], [4,2,1])
#=> 720
Next, for a given hash whose keys are primes and whose values are the maximum power, we can generate all factors of a number as follows.
def factors(h)
primes = h.keys
first, *rest = h.values.map { |v| (0..v).to_a }
first.product(*rest).map { |powers| factor(primes, powers) }
end
factors({2=>2, 3=>1, 5=>1})
#=> [1, 5, 3, 15, 2, 10, 6, 30, 4, 20, 12, 60]
It remains to put this all together.
def common_factors(first, *rest)
factors(rest.each_with_object(Prime.prime_division(first).to_h) do |n,h|
g = Prime.prime_division(n).to_h
g.keys.each { |k| h[k] = (h.key?(k) ? [h[k], g[k]].min : 0) }
end)
end
common_factors(720, 2100)
#=> [1, 5, 3, 15, 2, 10, 6, 30, 4, 20, 12, 60]
common_factors(720, 2100, 90)
#=> [1, 5, 3, 15, 2, 10, 6, 30]
