How to get all the divisible pairs in a range faster? - algorithm

I have a function to compute all the divisible pairs in a given range [start, end] and I would like to know if there's a way to improve the performance of it.
Here is my current implementation in Kotlin:
fun getDivisiblePairsInRange(
start: Int,
end: Int,
ignoreSelfDivision: Boolean = false,
): List<IntArray>
{
if (start == 0) throw IllegalArgumentException("start parameter can't be 0")
val oneOrZero = if (ignoreSelfDivision) 1.0 else 0.0
val divisiblePairs = mutableListOf<IntArray>()
for (numerator in start..end)
{
val optimizedEnd = ceil(sqrt(numerator - oneOrZero)).toInt()
var divisorFromPreviousIteration = -1
for (denominator in start..optimizedEnd)
{
if (numerator % denominator != 0 || denominator == divisorFromPreviousIteration) continue
divisiblePairs += intArrayOf(numerator, denominator)
val secondDivisor = numerator / denominator
if (!(ignoreSelfDivision && numerator == secondDivisor) && denominator != secondDivisor) divisiblePairs += intArrayOf(numerator, secondDivisor)
divisorFromPreviousIteration = secondDivisor
}
}
return divisiblePairs
}
As you can see the time complexity is O(n^1.5) and there's an ignoreSelfDivision to avoid getting pairs like (a, a), but it's not relevant.
These are some outputs from concrete examples to better show how the function behaves:
(1, 3) -> [[1, 1], [2, 1], [2, 2], [3, 1], [3, 3]]
(1, 5) -> [[1, 1], [2, 1], [2, 2], [3, 1], [3, 3], [4, 1], [4, 4], [4, 2], [5, 1], [5, 5]]
(1, 8) -> [[1, 1], [2, 1], [2, 2], [3, 1], [3, 3], [4, 1], [4, 4], [4, 2], [5, 1], [5, 5], [6, 1], [6, 6], [6, 2], [6, 3], [7, 1], [7, 7], [8, 1], [8, 8], [8, 2], [8, 4]]
(1, 10) -> [[1, 1], [2, 1], [2, 2], [3, 1], [3, 3], [4, 1], [4, 4], [4, 2], [5, 1], [5, 5], [6, 1], [6, 6], [6, 2], [6, 3], [7, 1], [7, 7], [8, 1], [8, 8], [8, 2], [8, 4], [9, 1], [9, 9], [9, 3], [10, 1], [10, 10], [10, 2], [10, 5]]
---------- UPDATE ----------
This is a faster version from #Dave adapted to Kotlin:
fun getDivisiblePairsInRangeFromDave(
start: Int,
end: Int,
ignoreSelfDivision: Boolean = false,
): List<IntArray>
{
var ratio = end / start
var currentDenominator = start
val divisiblePairs = mutableListOf<IntArray>()
while (ratio >= 1)
{
for (multiple in 1..ratio)
{
val numerator = currentDenominator * multiple
if (ignoreSelfDivision && currentDenominator == numerator) continue
divisiblePairs += intArrayOf(numerator, currentDenominator)
}
ratio = end / ++currentDenominator
}
return divisiblePairs
}
Another way of improving the performance a little bit is define a function to only get the count of pairs (it would be similar but without the mutable list of pairs) which is faster to calculate and then use an array instead of a mutable list, the improvement can be around 30%.

This is linear in the output.
Let's say end/start = r (integer division).
If r == 0 there are no solutions.
Otherwise, 1start, 2start, 3start, ..., rstart are all in your range.
ditto for start+i until at some point r*(start+i) is out of range at which point you can decrement r.
Repeat until r gets decremented to 1.
Ruby code:
def get_pairs(first_int, last_int)
r = last_int / first_int
pairs = []
cur_int = first_int
while r >= 1
1.upto(r) do |mult|
pairs.append([cur_int * mult, cur_int])
end
cur_int += 1
r = last_int / cur_int
end
return pairs.to_s
end
> get_pairs(1,3)
=> [[1, 1], [2, 1], [3, 1], [2, 2], [3, 3]]
get_pairs(1,5)
=> [[1, 1], [2, 1], [3, 1], [4, 1], [5, 1], [2, 2], [4, 2], [3, 3], [4, 4], [5, 5]]
> get_pairs(1,8)
=> [[1, 1], [2, 1], [3, 1], [4, 1], [5, 1], [6, 1], [7, 1], [8, 1], [2, 2], [4, 2], [6, 2], [8, 2], [3, 3], [6, 3], [4, 4], [8, 4], [5, 5], [6, 6], [7, 7], [8, 8]]
get_pairs(1,10)
=> [[1, 1], [2, 1], [3, 1], [4, 1], [5, 1], [6, 1], [7, 1], [8, 1], [9, 1], [10, 1], [2, 2], [4, 2], [6, 2], [8, 2], [10, 2], [3, 3], [6, 3], [9, 3], [4, 4], [8, 4], [5, 5], [10, 5], [6, 6], [7, 7], [8, 8], [9, 9], [10, 10]]
--- update: an example of a succinct approach ---
So this is succinct except that I'm expressing the output as long strings for clarity. For actual use, you could return pairs representing the multiple and the max integer in the range that can be multiplied by that mult and still be in range.
This is O(last_int / first_int), or linear in the number of different multiples. It could be further improved by treating 1 as a special case since everything can always be multiplied by 1 and still be in range, but I don't do that here.
def get_pairs_succinct(first_int, last_int)
mult = 1
summary = ""
while last_int / mult >= first_int
summary += "max int we can multiply by #{mult} and still be in-range: #{last_int / mult}\n"
mult += 1
end
puts summary
end
get_pairs_succinct(1,3)
max int we can multiply by 1 and still be in-range: 3
max int we can multiply by 2 and still be in-range: 1
max int we can multiply by 3 and still be in-range: 1
size of output = 3 vs 5 non-succinct
> get_pairs_succinct(1,5)
max int we can multiply by 1 and still be in-range: 5
max int we can multiply by 2 and still be in-range: 2
max int we can multiply by 3 and still be in-range: 1
max int we can multiply by 4 and still be in-range: 1
max int we can multiply by 5 and still be in-range: 1
size of output = 5 vs 10 non-succinct
> get_pairs_succinct(1,8)
max int we can multiply by 1 and still be in-range: 8
max int we can multiply by 2 and still be in-range: 4
max int we can multiply by 3 and still be in-range: 2
max int we can multiply by 4 and still be in-range: 2
max int we can multiply by 5 and still be in-range: 1
max int we can multiply by 6 and still be in-range: 1
max int we can multiply by 7 and still be in-range: 1
max int we can multiply by 8 and still be in-range: 1
size of output = 8 vs 20 non-succinct
> get_pairs_succinct(1,10)
max int we can multiply by 1 and still be in-range: 10
max int we can multiply by 2 and still be in-range: 5
max int we can multiply by 3 and still be in-range: 3
max int we can multiply by 4 and still be in-range: 2
max int we can multiply by 5 and still be in-range: 2
max int we can multiply by 6 and still be in-range: 1
max int we can multiply by 7 and still be in-range: 1
max int we can multiply by 8 and still be in-range: 1
max int we can multiply by 9 and still be in-range: 1
max int we can multiply by 10 and still be in-range: 1
size of output = 10 vs 27 non-succinct
Lastly, if we try this for a larger range or a range farther from one, the differences become more dramatic. E.g., for the range (10, 100):
non-succinct: output is 201 pairs, vs succinct: same information in 10 rows and proportional computation.
> get_pairs_succinct(10, 100)
max int we can multiply by 1 and still be in-range: 100
max int we can multiply by 2 and still be in-range: 50
max int we can multiply by 3 and still be in-range: 33
max int we can multiply by 4 and still be in-range: 25
max int we can multiply by 5 and still be in-range: 20
max int we can multiply by 6 and still be in-range: 16
max int we can multiply by 7 and still be in-range: 14
max int we can multiply by 8 and still be in-range: 12
max int we can multiply by 9 and still be in-range: 11
max int we can multiply by 10 and still be in-range: 10

Related

Maximizing a boolean function of AND and XOR on paritions of set

Given a set of distinct positive integers S we need to partition a set in such a way that the following function is maximize. Let S1, S2, ... Sn be the parition. n can be atleast 2.
F(S1, S2, ..., Sn) = AND(XOR(S1), XOR(S2), ..., XOR(Sn)))
where, AND is bitwise AND operation and XOR is bitwise XOR operation
We need to print the all such partitions possible.
I have tried the exponential approach as shown below.
I am looking for a solution with lesser complexity.
This problem is part of a homework, so only provide the hint.
from functools import reduce
from collections import defaultdict
def partition(collection):
if len(collection) == 1:
yield [ collection ]
return
first = collection[0]
for smaller in partition(collection[1:]):
# insert `first` in each of the subpartition's subsets
for n, subset in enumerate(smaller):
yield smaller[:n] + [[ first ] + subset] + smaller[n+1:]
# put `first` in its own subset
yield [ [ first ] ] + smaller
# print("END OF THE LOOP")
x = 4
initialAssum = [2, 4, 8, 16]
something = initialAssum
def andxor(xs):
def xorlist(xs):
return reduce(lambda i, j: i ^ j, xs)
tmp = [xorlist(x) for x in xs]
return reduce(lambda i, j: i & j, tmp)
ans = defaultdict(list)
for n, p in enumerate(partition(something), 1):
r = andxor(p)
if len(p) > 1:
ans[r].append(p)
m = max(ans.keys())
for a in ans[m]:
print(a)
#print(a, len(a))
print(f'{m}')
INPUT: the set itself
OUTPUT: max value possible followed by the all the partitions producing it
INPUT:
[2, 3, 4]
OUTPUT:
2
[[4, 2], [3]]
[[2], [4, 3]]
INPUT:
[2, 4, 6, 8]
OUTPUT:
0
[[2], [4, 8, 16]]
[[2, 4], [8, 16]]
[[4], [2, 8, 16]]
[[2], [4], [8, 16]]
[[2, 4, 8], [16]]
[[4, 8], [2, 16]]
[[2], [4, 8], [16]]
[[2, 8], [4, 16]]
[[8], [2, 4, 16]]
[[2], [8], [4, 16]]
[[2, 4], [8], [16]]
[[4], [2, 8], [16]]
[[4], [8], [2, 16]]
[[2], [4], [8], [16]]
This problem is part of a homework, so only provide the hint.
Hint: if we consider each bit separately from the highest to lowest, we can consider bit k as being set in the result if and only if that bit can appear an odd number of times in each part of the partition.

Algorithm: Factor Combinations

I'm working on the following algorithm from Leetcode:
Numbers can be regarded as product of its factors. For example,
8 = 2 x 2 x 2;
= 2 x 4.
Write a function that takes an integer n and return all possible combinations of its factors.
Note:
You may assume that n is always positive.
Factors should be greater than 1 and less than n.
Examples:
input: 1
output:
[]
input: 37
output:
[]
input: 12
output:
[
[2, 6],
[2, 2, 3],
[3, 4]
]
input: 32
output:
[
[2, 16],
[2, 2, 8],
[2, 2, 2, 4],
[2, 2, 2, 2, 2],
[2, 4, 4],
[4, 8]
]
Here's the code that I have thus far:
def get_factors(n)
factors = []
(2...n).each do |candidate|
if n % candidate == 0
factors << [candidate, (n/candidate)]
get_factors(n/candidate).each do |factor_set|
factors << [candidate] + factor_set
end
end
end
factors
end
This code works really well, but doesn't handle duplicates (e.g [3, 2, 2] will be inserted along with [2, 2, 3]). I tried using a Set with the following code,
def get_factors(n)
seen = Set.new
factors = []
(2...n).each do |candidate|
if n % candidate == 0 && !seen.include?(candidate)
factors << [candidate, (n/candidate)]
get_factors(n/candidate).each do |factor_set|
factors << [candidate] + factor_set
end
end
seen << (n/candidate)
end
factors
end
but that only works to solve some test cases and not others. I'm not sure how to go about ensuring no duplicates in an efficient way? The really inefficient way is to generate some sort of hash value for each array depending on it's elements (and not dependent on order), and while this would work, there definitely should be a better way. Any ideas?
I think always going forward is a good policy (i.e when checking, say, with 5, do not check with 2, 3, 4 etc). That way, searching for duplicates can be eliminated.
Since the algorithm already uses a lot of time, I don't see a problem sorting each answer and removing duplicates. This requires no proof to ensure it works, which the answer provided by mac does.
Code
require 'prime'
def get_factors(n)
primes, nbr = Prime.prime_division(n).transpose
powers = nbr.map { |m| (0..m).to_a }
powers.shift.
product(*powers).
map { |pows| primes.zip(pows).reduce(1) { |t,(pr,po)| t * (pr**po) } }.
sort
end
The array returned includes 1 and n (which are factors of n). If those values should be excluded, replace .sort with .sort - [1, n].
Examples
get_factors(24)
#=> [1, 2, 3, 4, 6, 8, 12, 24]
get_factors(64)
#=> [1, 2, 4, 8, 16, 32, 64]
get_factors(90)
#=> [1, 2, 3, 5, 6, 9, 10, 15, 18, 30, 45, 90]
Explanation
Consider
n = 60
The steps are as follows.
a = Prime.prime_division(30)
#=> [[2, 2], [3, 1], [5, 1]]
Ergo, the primes of 30 are 2, 3 and 5, and
60 = 2**2 * 3**1 * 5**1
See Prime::prime_division. Continuing,
primes, nbr = a.transpose
#=> [[2, 3, 5], [2, 1, 1]]
primes
#=> [2, 3, 5]
nbr
#=> [2, 1, 1]
powers = nbr.map { |m| (0..m).to_a }
#=> [[0, 1, 2], [0, 1], [0, 1]]
This means that each factor will be the product of 0, 1 or 2 2's, 0 or 1 3's and 0 or 1 5's.
b = powers.shift
#=> [0, 1, 2]
powers
#=> [[0, 1], [0, 1]]
c = b.product(*powers)
#=> [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1],
# [1, 1, 0], [1, 1, 1], [2, 0, 0], [2, 0, 1], [2, 1, 0], [2, 1, 1]]
d = c.map { |pows| primes.zip(pows).reduce(1) { |t,(pr,po)| t * (pr**po) } }
#=> [1, 5, 3, 15, 2, 10, 6, 30, 4, 20, 12, 60]
d.sort
#=> [1, 5, 3, 15, 2, 10, 6, 30, 4, 20, 12, 60]
Consider now the calculation of d. The 10th element of c that is passed to the block is [2, 0, 1]. The block calculation for that element is as follows.
pows = [2, 0, 1]
e = primes.zip(pows)
#=> [[2, 2], [3, 0], [5, 1]]
e.reduce(1) { |t,(pr,po)| t * (pr**po) }
#=> 20
The reduce calculation is equivalent to
2**2 * 3**0 * 5**1
#=> 4 * 1 * 5 => 20
The calculations for the other values of c passed to the block are similar.
A simple way is to replace the last line of your method with
factors.map(&:sort).uniq
which sorts all the subarrays and then eliminates duplicates.

Algorithmic Optimization [duplicate]

The question I'm working on is:
Find which sum of squared factors are a perfect square given a specific range.
So if the range was (1..10) you would get each number's factors (all factors for 1, all factors for 2, all factors for 3 ect..) Square those factors, then add them together. Finally check if that sum is a perfect square.
I am stuck on refactoring/optimization because my solution is too slow.
Here is what I came up with:
def list_squared(m, n)
ans = []
range = (m..n)
range.each do |i|
factors = (1..i).select { |j| i % j == 0 }
squares = factors.map { |k| k ** 2 }
sum = squares.inject { |sum,x| sum + x }
if sum == Math.sqrt(sum).floor ** 2
all = []
all += [i, sum]
ans << all
end
end
ans
end
This is an example of what I would put in the method:
list_squared(1, 250)
And then the desired output would be an array of arrays with each array containing the number whose sum of squared factors was a perfect square and the sum of those squared factors:
[[1, 1], [42, 2500], [246, 84100]]
I would start by introducing some helper methods (factors and square?) to make your code more readable.
Furthermore, I would reduce the number of ranges and arrays to improve memory usage.
require 'prime'
def factors(number)
[1].tap do |factors|
primes = number.prime_division.flat_map { |p, e| Array.new(e, p) }
(1..primes.size).each do |i|
primes.combination(i).each do |combination|
factor = combination.inject(:*)
factors << factor unless factors.include?(factor)
end
end
end
end
def square?(number)
square = Math.sqrt(number)
square == square.floor
end
def list_squared(m, n)
(m..n).map do |number|
sum = factors(number).inject { |sum, x| sum + x ** 2 }
[number, sum] if square?(sum)
end.compact
end
list_squared(1, 250)
A benchmark with a narrow range (up to 250) shows only a minor improvement:
require 'benchmark'
n = 1_000
Benchmark.bmbm(15) do |x|
x.report("original_list_squared :") { n.times do; original_list_squared(1, 250); end }
x.report("improved_list_squared :") { n.times do; improved_list_squared(1, 250); end }
end
# Rehearsal -----------------------------------------------------------
# original_list_squared : 2.720000 0.010000 2.730000 ( 2.741434)
# improved_list_squared : 2.590000 0.000000 2.590000 ( 2.604415)
# -------------------------------------------------- total: 5.320000sec
# user system total real
# original_list_squared : 2.710000 0.000000 2.710000 ( 2.721530)
# improved_list_squared : 2.620000 0.010000 2.630000 ( 2.638833)
But a benchmark with a wider range (up to 10000) shows a much better performance than the original implementation:
require 'benchmark'
n = 10
Benchmark.bmbm(15) do |x|
x.report("original_list_squared :") { n.times do; original_list_squared(1, 10000); end }
x.report("improved_list_squared :") { n.times do; improved_list_squared(1, 10000); end }
end
# Rehearsal -----------------------------------------------------------
# original_list_squared : 36.400000 0.160000 36.560000 ( 36.860889)
# improved_list_squared : 2.530000 0.000000 2.530000 ( 2.540743)
# ------------------------------------------------- total: 39.090000sec
# user system total real
# original_list_squared : 36.370000 0.120000 36.490000 ( 36.594130)
# improved_list_squared : 2.560000 0.010000 2.570000 ( 2.581622)
tl;dr: The bigger the N the better my code performs compared to the original implementation...
One way to make it more efficient is to use Ruby's built-in method Prime::prime_division.
For any number n, if prime_division returns an array containing a single element, that element will be [n,1] and n will have been shown to be prime. That prime number has factors n and 1, so must be treated differently than numbers that are not prime.
require 'prime'
def list_squared(range)
range.each_with_object({}) do |i,h|
facs = Prime.prime_division(i)
ssq =
case facs.size
when 1 then facs.first.first**2 + 1
else facs.inject(0) { |tot,(a,b)| tot + b*(a**2) }
end
h[i] = facs if (Math.sqrt(ssq).to_i)**2 == ssq
end
end
list_squared(1..10_000)
#=> { 1=>[], 48=>[[2, 4], [3, 1]], 320=>[[2, 6], [5, 1]], 351=>[[3, 3], [13, 1]],
# 486=>[[2, 1], [3, 5]], 1080=>[[2, 3], [3, 3], [5, 1]],
# 1260=>[[2, 2], [3, 2], [5, 1], [7, 1]], 1350=>[[2, 1], [3, 3], [5, 2]],
# 1375=>[[5, 3], [11, 1]], 1792=>[[2, 8], [7, 1]], 1836=>[[2, 2], [3, 3], [17, 1]],
# 2070=>[[2, 1], [3, 2], [5, 1], [23, 1]], 2145=>[[3, 1], [5, 1], [11, 1], [13, 1]],
# 2175=>[[3, 1], [5, 2], [29, 1]], 2730=>[[2, 1], [3, 1], [5, 1], [7, 1], [13, 1]],
# 2772=>[[2, 2], [3, 2], [7, 1], [11, 1]], 3072=>[[2, 10], [3, 1]],
# 3150=>[[2, 1], [3, 2], [5, 2], [7, 1]], 3510=>[[2, 1], [3, 3], [5, 1], [13, 1]],
# 4104=>[[2, 3], [3, 3], [19, 1]], 4305=>[[3, 1], [5, 1], [7, 1], [41, 1]],
# 4625=>[[5, 3], [37, 1]], 4650=>[[2, 1], [3, 1], [5, 2], [31, 1]],
# 4655=>[[5, 1], [7, 2], [19, 1]], 4998=>[[2, 1], [3, 1], [7, 2], [17, 1]],
# 5880=>[[2, 3], [3, 1], [5, 1], [7, 2]], 6000=>[[2, 4], [3, 1], [5, 3]],
# 6174=>[[2, 1], [3, 2], [7, 3]], 6545=>[[5, 1], [7, 1], [11, 1], [17, 1]],
# 7098=>[[2, 1], [3, 1], [7, 1], [13, 2]], 7128=>[[2, 3], [3, 4], [11, 1]],
# 7182=>[[2, 1], [3, 3], [7, 1], [19, 1]], 7650=>[[2, 1], [3, 2], [5, 2], [17, 1]],
# 7791=>[[3, 1], [7, 2], [53, 1]], 7889=>[[7, 3], [23, 1]],
# 7956=>[[2, 2], [3, 2], [13, 1], [17, 1]],
# 9030=>[[2, 1], [3, 1], [5, 1], [7, 1], [43, 1]],
# 9108=>[[2, 2], [3, 2], [11, 1], [23, 1]], 9295=>[[5, 1], [11, 1], [13, 2]],
# 9324=>[[2, 2], [3, 2], [7, 1], [37, 1]]}
This calculation took approximately 0.15 seconds.
For i = 6174
(2**1) * (3**2) * (7**3) #=> 6174
and
1*(2**2) + 2*(3**2) + 3*(7**2) #=> 169 == 13*13
The trick that frequently solves questions like this is to switch from trial division to a sieve. In Python (sorry):
def list_squared(m, n):
factor_squared_sum = {i: 0 for i in range(m, n + 1)}
for factor in range(1, n + 1):
i = n - n % factor # greatest multiple of factor less than or equal to n
while i >= m:
factor_squared_sum[i] += factor ** 2
i -= factor
return {i for (i, fss) in factor_squared_sum.items() if isqrt(fss) ** 2 == fss}
def isqrt(n):
# from http://stackoverflow.com/a/15391420
x = n
y = (x + 1) // 2
while y < x:
x = y
y = (x + n // x) // 2
return x
The next optimization is to step factor only to isqrt(n), adding the factor squares in pairs (e.g., 2 and i // 2).

Optimization for finding perfect-square algorithm

The question I'm working on is:
Find which sum of squared factors are a perfect square given a specific range.
So if the range was (1..10) you would get each number's factors (all factors for 1, all factors for 2, all factors for 3 ect..) Square those factors, then add them together. Finally check if that sum is a perfect square.
I am stuck on refactoring/optimization because my solution is too slow.
Here is what I came up with:
def list_squared(m, n)
ans = []
range = (m..n)
range.each do |i|
factors = (1..i).select { |j| i % j == 0 }
squares = factors.map { |k| k ** 2 }
sum = squares.inject { |sum,x| sum + x }
if sum == Math.sqrt(sum).floor ** 2
all = []
all += [i, sum]
ans << all
end
end
ans
end
This is an example of what I would put in the method:
list_squared(1, 250)
And then the desired output would be an array of arrays with each array containing the number whose sum of squared factors was a perfect square and the sum of those squared factors:
[[1, 1], [42, 2500], [246, 84100]]
I would start by introducing some helper methods (factors and square?) to make your code more readable.
Furthermore, I would reduce the number of ranges and arrays to improve memory usage.
require 'prime'
def factors(number)
[1].tap do |factors|
primes = number.prime_division.flat_map { |p, e| Array.new(e, p) }
(1..primes.size).each do |i|
primes.combination(i).each do |combination|
factor = combination.inject(:*)
factors << factor unless factors.include?(factor)
end
end
end
end
def square?(number)
square = Math.sqrt(number)
square == square.floor
end
def list_squared(m, n)
(m..n).map do |number|
sum = factors(number).inject { |sum, x| sum + x ** 2 }
[number, sum] if square?(sum)
end.compact
end
list_squared(1, 250)
A benchmark with a narrow range (up to 250) shows only a minor improvement:
require 'benchmark'
n = 1_000
Benchmark.bmbm(15) do |x|
x.report("original_list_squared :") { n.times do; original_list_squared(1, 250); end }
x.report("improved_list_squared :") { n.times do; improved_list_squared(1, 250); end }
end
# Rehearsal -----------------------------------------------------------
# original_list_squared : 2.720000 0.010000 2.730000 ( 2.741434)
# improved_list_squared : 2.590000 0.000000 2.590000 ( 2.604415)
# -------------------------------------------------- total: 5.320000sec
# user system total real
# original_list_squared : 2.710000 0.000000 2.710000 ( 2.721530)
# improved_list_squared : 2.620000 0.010000 2.630000 ( 2.638833)
But a benchmark with a wider range (up to 10000) shows a much better performance than the original implementation:
require 'benchmark'
n = 10
Benchmark.bmbm(15) do |x|
x.report("original_list_squared :") { n.times do; original_list_squared(1, 10000); end }
x.report("improved_list_squared :") { n.times do; improved_list_squared(1, 10000); end }
end
# Rehearsal -----------------------------------------------------------
# original_list_squared : 36.400000 0.160000 36.560000 ( 36.860889)
# improved_list_squared : 2.530000 0.000000 2.530000 ( 2.540743)
# ------------------------------------------------- total: 39.090000sec
# user system total real
# original_list_squared : 36.370000 0.120000 36.490000 ( 36.594130)
# improved_list_squared : 2.560000 0.010000 2.570000 ( 2.581622)
tl;dr: The bigger the N the better my code performs compared to the original implementation...
One way to make it more efficient is to use Ruby's built-in method Prime::prime_division.
For any number n, if prime_division returns an array containing a single element, that element will be [n,1] and n will have been shown to be prime. That prime number has factors n and 1, so must be treated differently than numbers that are not prime.
require 'prime'
def list_squared(range)
range.each_with_object({}) do |i,h|
facs = Prime.prime_division(i)
ssq =
case facs.size
when 1 then facs.first.first**2 + 1
else facs.inject(0) { |tot,(a,b)| tot + b*(a**2) }
end
h[i] = facs if (Math.sqrt(ssq).to_i)**2 == ssq
end
end
list_squared(1..10_000)
#=> { 1=>[], 48=>[[2, 4], [3, 1]], 320=>[[2, 6], [5, 1]], 351=>[[3, 3], [13, 1]],
# 486=>[[2, 1], [3, 5]], 1080=>[[2, 3], [3, 3], [5, 1]],
# 1260=>[[2, 2], [3, 2], [5, 1], [7, 1]], 1350=>[[2, 1], [3, 3], [5, 2]],
# 1375=>[[5, 3], [11, 1]], 1792=>[[2, 8], [7, 1]], 1836=>[[2, 2], [3, 3], [17, 1]],
# 2070=>[[2, 1], [3, 2], [5, 1], [23, 1]], 2145=>[[3, 1], [5, 1], [11, 1], [13, 1]],
# 2175=>[[3, 1], [5, 2], [29, 1]], 2730=>[[2, 1], [3, 1], [5, 1], [7, 1], [13, 1]],
# 2772=>[[2, 2], [3, 2], [7, 1], [11, 1]], 3072=>[[2, 10], [3, 1]],
# 3150=>[[2, 1], [3, 2], [5, 2], [7, 1]], 3510=>[[2, 1], [3, 3], [5, 1], [13, 1]],
# 4104=>[[2, 3], [3, 3], [19, 1]], 4305=>[[3, 1], [5, 1], [7, 1], [41, 1]],
# 4625=>[[5, 3], [37, 1]], 4650=>[[2, 1], [3, 1], [5, 2], [31, 1]],
# 4655=>[[5, 1], [7, 2], [19, 1]], 4998=>[[2, 1], [3, 1], [7, 2], [17, 1]],
# 5880=>[[2, 3], [3, 1], [5, 1], [7, 2]], 6000=>[[2, 4], [3, 1], [5, 3]],
# 6174=>[[2, 1], [3, 2], [7, 3]], 6545=>[[5, 1], [7, 1], [11, 1], [17, 1]],
# 7098=>[[2, 1], [3, 1], [7, 1], [13, 2]], 7128=>[[2, 3], [3, 4], [11, 1]],
# 7182=>[[2, 1], [3, 3], [7, 1], [19, 1]], 7650=>[[2, 1], [3, 2], [5, 2], [17, 1]],
# 7791=>[[3, 1], [7, 2], [53, 1]], 7889=>[[7, 3], [23, 1]],
# 7956=>[[2, 2], [3, 2], [13, 1], [17, 1]],
# 9030=>[[2, 1], [3, 1], [5, 1], [7, 1], [43, 1]],
# 9108=>[[2, 2], [3, 2], [11, 1], [23, 1]], 9295=>[[5, 1], [11, 1], [13, 2]],
# 9324=>[[2, 2], [3, 2], [7, 1], [37, 1]]}
This calculation took approximately 0.15 seconds.
For i = 6174
(2**1) * (3**2) * (7**3) #=> 6174
and
1*(2**2) + 2*(3**2) + 3*(7**2) #=> 169 == 13*13
The trick that frequently solves questions like this is to switch from trial division to a sieve. In Python (sorry):
def list_squared(m, n):
factor_squared_sum = {i: 0 for i in range(m, n + 1)}
for factor in range(1, n + 1):
i = n - n % factor # greatest multiple of factor less than or equal to n
while i >= m:
factor_squared_sum[i] += factor ** 2
i -= factor
return {i for (i, fss) in factor_squared_sum.items() if isqrt(fss) ** 2 == fss}
def isqrt(n):
# from http://stackoverflow.com/a/15391420
x = n
y = (x + 1) // 2
while y < x:
x = y
y = (x + n // x) // 2
return x
The next optimization is to step factor only to isqrt(n), adding the factor squares in pairs (e.g., 2 and i // 2).

How to generate partially repeated permutations in ruby?

I have a range of numbers R = (1..n). I also have another character 'a'. I want to generate strings of length L (L > n + 2) that have all the numbers in the same order, but go through every repeated permutation of 'a' to fill the length L. For example, if n = 3, and L = 7, then some valid strings would be :
"123aaaa",
"1a23aaa",
"1aa2a3a",
"aaaa123"
while the following strings would be invalid:
"213aaaa", # invalid, because 1,2,3 are not in order
"123a", #invalid, because length < L
"1123aaa", # invalid because a number is repeated
I am currently doing this, which is way too inefficient:
n = 3
L = 7
all_terms = (1..n).to_a + Array.new(L - n, 'a')
all_terms.permutation.each do |permut|
if(valid_permut? permut) # checks if numbers are in their natural order
puts permut.join
end
end
How do I directly generate valid strings more efficiently?
The problem is equivalent to: select n elements from index 0 to L - 1, fill these with 1 to n accordingly, and fill the rest with some constant character.
In your example, it's taking 3 elements from 0..6:
(0..6).to_a.combination(3).to_a
=> [[0, 1, 2], [0, 1, 3], [0, 1, 4], [0, 1, 5], [0, 1, 6], [0, 2, 3], [0, 2, 4],
[0, 2, 5], [0, 2, 6], [0, 3, 4], [0, 3, 5], [0, 3, 6], [0, 4, 5], [0, 4, 6], [0, 5, 6],
[1, 2, 3], [1, 2, 4], [1, 2, 5], [1, 2, 6], [1, 3, 4], [1, 3, 5], [1, 3, 6], [1, 4, 5],
[1, 4, 6], [1, 5, 6], [2, 3, 4], [2, 3, 5], [2, 3, 6], [2, 4, 5], [2, 4, 6], [2, 5, 6],
[3, 4, 5], [3, 4, 6], [3, 5, 6], [4, 5, 6]]
Every subarray here represents a possible result. For example, [0, 2, 3] corresponds to '0a12aaa', [3, 5, 6] corresponds to 'aaa0a12', etc. The code for this conversion is straight-forward.
You can model this as all possible interleavings of two strings, where relative order of the input elements is preserved. Here's a recursive solution. It works by choosing an element from one list, and prepending it to all possible subproblems, then doing it again where an element is chosen from the second list instead, and combining the two solution sets at the end.
# Returns an array of all possible interleaving of two strings
# Maintains relative order of each character of the input strings
def interleave_strings_all(a1, a2)
# Handle base case where at least one input string is empty
return [a1 + a2] if a1.empty? || a2.empty?
# Place element of first string, and prepend to all subproblems
set1 = interleave_strings_all(a1[1..-1], a2).map{|x| a1[0] + x}
# Place element of second string and prepend to all subproblems
set2 = interleave_strings_all(a1, a2[1..-1]).map{|x| a2[0] + x}
# Combine solutions of subproblems into overall problem
return set1.concat(set2)
end
if __FILE__ == $0 then
l = 5
n = 3
a1 = (1..n).to_a.map{|x| x.to_s}.join()
a2 = 'a' * (l - n)
puts interleave_strings_all(a1, a2)
end
The output is:
123aa
12a3a
12aa3
1a23a
1a2a3
1aa23
a123a
a12a3
a1a23
aa123

Resources