Home > Software engineering >  Ruby multiple enumerables on one arg?
Ruby multiple enumerables on one arg?

Time:01-24

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]
  •  Tags:  
  • Related