Making this ruby algorithm faster - ruby

Given a positive integer n I wish to find the largest integer m comprised of the digits contained in n that is less than n.
The code is to return m unless one of the following results obtain, in which case -1 it should be returned.
there is no possible variation;
if the number of digits isn't equal to the input;
if the first digit of the output == 0;
My code works, but it takes too long when "n" is a huge number! I believe it's because of the method #Permutation but I'm not sure. Can anyone shed a light on this?
Here's my code
def next_smaller (n)
new = n.to_s.split("").permutation.to_a.map { |n| n.join.to_i }
res = new.sort.reverse.select { |x| x < n }.first
res_arr = res.to_s.split("")
res.nil? || res_arr.count != n.to_s.split("").count || res_arr[0] == 0 ? -1 : res
end
Thank you

UPD: The code below works incorrectly with some input.
It is better to skip the generation of all permutations. Array#permutation can take a block of code:
def fast_next_smaller(number)
number.digits.reverse.permutation do |array|
next if array.first == 0
target_number = array.join.to_i
next if target_number == number
return target_number if target_number < number
end
-1
end
fast_next_smaller(907) #=> 790
fast_next_smaller(513) #=> 153
fast_next_smaller(153) #=> 135
fast_next_smaller(135) #=> -1
Here is the benchmark:
require 'benchmark'
n = 1000
Benchmark.bm do |x|
x.report('next_smaller') { n.times { next_smaller(rand(1_000_000..9_000_000)) } }
x.report('fast_next_smaller') { n.times { fast_next_smaller(rand(1_000_000..9_000_000)) } }
end
user system total real
next_smaller 4.433144 0.000000 4.433144 ( 4.433113)
fast_next_smaller 0.041333 0.000003 0.041336 ( 0.041313)
# With a very big number
puts Benchmark.measure { fast_next_smaller(5312495046546651005896) }
0.000000 0.000184 0.000184 ( 0.000176)

This should generally be pretty quick.
Code
def largest(n)
arr = n.to_s.chars.map(&:to_i)
nbr_chars = arr.size
case nbr_chars
when 1
-1
when 2
m = arr.reverse.join.to_i
m < 10 || m >= n ? -1 : m
else
(2..nbr_chars).each do |m|
fix_digits = arr[0,nbr_chars-m]
var_digits = arr[-m..-1]
if var_digits == var_digits.sort
return -1 if m == nbr_chars
else
a = solve_for_last_m_digits(var_digits)
if a.nil?
next if m < nbr_chars
return -1
else
x = (fix_digits + a).join.to_i
return x >= 10**(nbr_chars-1) ? x : -1
end
end
end
-1
end
end
def solve_for_last_m_digits(a)
nbr_chars = a.size
a_as_int = a.join.to_i
x = a.permutation(nbr_chars).max_by do |b|
m = b.join.to_i
m < a_as_int ? m : 0
end
x.join.to_i < a_as_int ? x : nil
end
Examples
largest 907 #=> 790
largest 531 #=> 513
largest 2638 #=> 2386
largest 78436 #=> 78364
largest 1783435893 #=> 1783435839
largest 385395038954829678 #=> 385395038954828976
largest 135 #=> -1
largest 106 #=> -1
All of the calculations were effectively instantaneous.
Explanation
See Array#permutation and Enumerable#max_by.
It's easiest to explain the algorithm with an example. Suppose the given integer were:
n = 385395038954829678
Had the last two digits been 87, rather than 78, we could simply reverse them and we'd be finished. As it is 78, however, we conclude that there is no integer less n that can be obtained by permuting the last two digits of n.
Next we consider the last three digits, 678. After examine the six permutations of these 3 digits we find that none are smaller than 678, so we conclude that there is no integer less n that can be obtained by permuting the last three digits.
Actually I don't examine the 6 permutations of the digits of 678. Rather I infer that the digits of that number cannot be permuted to produce a number smaller than 678 because they are non-decreasing (6 <= 7 <= 8). That is the purpose of the fragment
if var_digits == var_digits.sort
return -1 if m == nbr_chars
If the digits of the entire string are non-decreasing (m == nbr_chars is true), we return -1; else m is incremented by one.
We therefore move on to examining the last 4 digits of the number, 9678. As the digits comprising 9678 are not non-decreasing we know that they can be permuted to produce a number smaller than 9678 (simply swap two consecutive digits that are decreasing). After examining the 24 permutations of those four digits we find the largest number less than 9678 is 8976. Clearly, there is no permutation of digits that would produce a number less than n but larger than n with the last 4 digits replaced by 8976. The integer of interest is therefore obtained by replacing the last four digits of 385395038954829678 with 8976, which is 385395038954828976.
As soon as the last n-digits of m are not non-decreasing we know they can be rearranged to produce one more more numbers smaller than m, the largest of which will be the replacement for the last n digits of m.
The last step is to execute:
return x >= 10**(nbr_chars-1) ? x : -1
Suppose the number were 106. The largest number less than 106 that can be obtained by permuting its digits is x = 61 (061). As 61 has one or more (here one) leading zeroes, we return -1. We know there is at least one leading zéro because nbr_chars #=> 3, 10**(nbr_chars -1) #=> 100and61 < 100`.

Related

Given integers X and Y, how do you find the largest permutation of X that is less than or equal to Y?

Given two positive integers X and Y, find the largest permutation of X
that is less than or equal to Y. Return the largest permutation that is
less than or equal to Y as an integer. If there is no permutation of X
that is less than or equal to Y, return -1.
Example 1:
Input: X = 123, Y = 321
Output: 321
Example 2:
Input: X = 1733, Y = 3311
Output: 3173
Example 3:
Input: X = 999, Y = 111
Output: -1
Got this problem for an online assessment earlier yesterday, couldn't find an efficient solution for it and have been thinking about it but still can't think of the right approach. I first tried greedy, in which I would iterate Y from left to right and I create a permutation of X by appending the largest digit in X that is less than or equal to the digit in Y. But for X = 1733 and Y = 3311, my implementation would return -1 because the greedy algorithm rearranged X to 3317. So I turned to recursion, but as you'd expect this very quickly reached stack limit.
I've read this thread that seems to discuss a similar problem, but I believe the top solution fails for example 2. How do you approach this problem?
A recursive solution.
Sort the digits of X decreasingly. Then, as long as you find no solution
take in turn every digit in X that is not larger than the leading digit of Y;
if those digits are equal, recurse on X less this digit and the tail of Y;
if the digit of X is smaller (or X is empty), you are done;
if there is no such digit, you reached a dead-end.
This works because you are trying the permutations of X by decreasing value.
321 vs. 321
3 21 vs. 3 21
21 vs. 21
1 vs. 1
Done
7331 vs. 3311
3 731 vs. 3 311
3 71 vs. 3 11
1 7 vs. 1 1
Dead end
1 73 vs. 3 11
Done
999 vs. 111
Dead end
A non-recursive efficient solution, hinted by #Stef.
The permutations of X can be ordered increasingly by sorting the digits then picking every first digit and recursing on the remaining ones. This established a bijection between the permutations and the integers in [0, d!) for d digits.
For an integer m, you can retrieve the corresponding permutation using a conversion from the factorial basis (take the quotient by (d-1)! and proceed recursively with the remainder). This takes d operations, and you can compare the permutation to Y in O(d) operations.
Now just implement a dichotomic search on the d! permutations, which takes O(d.log(d!)) = O(d².log(d))) operations.
Update: the second solution only works for distinct digits otherwise the permutations do not yield increasing numbers. I hope that there is a workaround.
If X has more digits then there is no solution. If Y has more digits then a descending sort of the digits of X is the solution. Assuming X and Y have the same number of digits:
Put the digits of X in a counting hash.
For each digit of Y going in descending order (left-to-right), take the max digit of X that isn't greater than it and use that in your permutation.
If you ever place a digit lower than its counterpart in Y, place all remaining digits in descending order.
If there ever isn't a non-greater digit available then do the following: repeatedly unwind your prior move until you get to a digit where a lower digit was available. Select the max such lower digit. Then, all remaining digits can be placed in descending order from the map. If there is no such digit (where a lower digit could have been chosen) then there is no solution.
If you get through all the digits then you've produced the max solution.
This is linear in the number of digits if this is limited to base 10. If your base can vary, this is O(num_digits * base)
Here's Ruby code for this.
def get_perm(x, y)
# hist keeps a count of each of the digits of x
hist = Hash.new 0; x.digits.each { |d| hist[d] += 1 }
# output_digits is the answer we're building
output_digits = []
y_digits = y.digits
x_digits = x.digits
# If x has fewer digits then all permutations are good so pick the largest
if x.digits.length < y.digits.length
9.downto(0) do |digit|
output_digits += [digit] * hist[digit]
end
return output_digits
end
# If y has fewer digits then no permutation is good, return -1
if y.digits.length < x.digits.length
return -1
end
# parse the digits of y
(y_digits.length - 1).downto(0) do |i|
cur_y_digit = y_digits[i]
# use the current digit of y if possible
if hist[cur_y_digit] > 0
hist[cur_y_digit] -= 1
output_digits.append(cur_y_digit)
return output_digits if i == 0
# otherwise, use the largest smaller digit available if possible
else
(cur_y_digit - 1).downto(0) do |smaller_digit|
if hist[smaller_digit] > 0
# place the smaller digit, then all remaining digits in descending order
hist[smaller_digit] -= 1
output_digits.append(smaller_digit)
9.downto(0) do |digit|
output_digits += [digit] * hist[digit]
end
return output_digits
end
end
# If we make it here then no digit was available; we need to unwind moves until we
# can replace a digit of our solution with a smaller digit
smallest_digit = hist.keys.min
while i < (y.digits.length - 1) do
i += 1
cur_y_digit = y_digits[i]
cur_unwound_digit = output_digits.pop
hist[cur_unwound_digit] += 1
smallest_digit = [smallest_digit, cur_unwound_digit].min
if cur_y_digit > smallest_digit
(cur_y_digit - 1).downto(smallest_digit) do |d|
if hist[d] >= 1
output_digits.append(d)
hist[d] -= 1
9.downto(0) do |digit|
output_digits += [digit] * hist[digit]
end
return output_digits
end
end
end
end
return -1
end
end
end
Outputs for OP sample cases:
> get_perm(123, 321)
=> [3, 2, 1]
> get_perm(1733, 3311)
=> [3, 1, 7, 3]
> get_perm(999, 111)
=> -1
If Z is the answer, and the numbers have n digits, you can show that there is an index i such that Z[:i] = Y[:i], Z[i]<Y[i], and Z[i+1:] is as large as possible given digits of X \ Z[:i+1] (I use python array slice notation, and the last expression means "the set of digits of X minus those already chosen in Z up to i+1").
Given this, you can easily loop over each candidate i, and efficiently check if it's feasible to chose such i as in above. The solution is with the largest possible i.
The solution should be O(n*log(n)).
I'll leave the proof and implementation details, as I understand it's a homework :)

Extracting values without looping in Ruby

Lets say we need to extract values from range of (0..999_999_999). We need the sum of all numbers that returns true for our conditional statement. For example, the ones that have the sequence of "123" digits at the end of their numbers.
What would be the fastest way to get this sum without looping?
Edit: The conditional statement could be any, such as n.to_s.chars.map{|d| d = d.to_i}.inject(:+) % 12345 == 0 where n is the number within the range.
Edit2: Here is the code that I have trouble with:
def find_all(n, k)
arr = []
lower_limit = ("1" + "0" * (k - 1)).to_i
upper_limit = ("9" * k).to_i
while lower_limit <= upper_limit
if lower_limit.to_s.chars == lower_limit.to_s.chars.sort && lower_limit.to_s.chars.map{|v| v = v.to_i}.inject(:+) == n
arr << lower_limit
end
lower_limit += 1
end
arr.empty? ? [] : [arr.size, arr.min, arr.max]
end
where n is the sum of all digits and k is the # of digits in number.
My code should run on the server in less than 12000 ms with very huge numbers of k several times with different (n,k). Even though my code works, its algorithm is too slow, looping will not result in success.
The range is
r = 0..10**9-1
r.size
#=> 1_000_000_000 (10**9)
For any given last three digits, this range contains 10**6 numbers ending in those three digits. Suppose those last three digits were 000. Then the sum of those numbers would be the sum of the numbers in the range 0..10**6-1 multiplied by 10**3:
s = (10**3) * (0 + 10**6-1)*(10**6)/2
#=> 499999500000000
If the last three digits of each of the 10**6 numbers in this sum were 123, rather than 000, 123 would be added to each of those numbers. Therefore, the sum of the numbers ending 123 is
s + 123 * (10**6)
#=> 499999746000000

Ruby loop and code functionality

Here is the original question euler project 50
As question states "The longest sum of consecutive primes below one-thousand that adds to a prime, contains 21 terms"
So I start finding the answer after 21 terms as
next if !arr[i..i+d+c].reduce(:+).prime?
The problem is at
break if !arr[i..i+d+c].reduce(:+).prime?
I want to get out of the while loop as quickly as possible if there is no consecutive primes added to prime. It turns out there is no consecutive primes to prime in the range and counter the my logic. please enlighten me.
require 'mathn'
def consecutive_prime_sum
arr = []
Prime.each do |i|
break if i > 1000000
arr << i
end
max,sum = 0,0
d = 20
arr.each_index do |i|
c = 1
next if !arr[i..i+d+c].reduce(:+).prime?
while i+d+c <= arr.size - 1
break if !arr[i..i+d+c].reduce(:+).prime?
if c > max && arr[i..i+d+c].reduce(:+) > sum && arr[i..i+d+c].reduce(:+).prime?
max = c
sum = arr[i..i+d+c].reduce(:+)
end
c= c + 1
end
end
sum
end
p consecutive_prime_sum

Sort Integer Array Ruby

Have the function PermutationStep (num) take the num parameter being passed and return the next number greater than num using the same digits. For example: if num is 123 return 132, if it's 12453 return 12534. If a number has no greater permutations, return -1 (ie. 999)
Here's my code. I'd like to sort an array of large integers in numerical order. Using the regular sort method doesn't give the right order for some numbers. Is there a sort_by structure that I can replace 'sort' with in my code below?
def PermutationStep(num)
num = num.to_s.split('').map {|i| i.to_i}
permutations = num.permutation.to_a.sort #<= I want to sort by numerical value here
permutations.each_with_index do |n, idx|
if n == num
if n == permutations[-1]
return -1
else
return permutations[idx+1].join.to_i
end
end
end
end
For example, 11121. When I run the code it gives me 11121.I want the next highest permutation, which should be 12111.
Also, when I try { |a,b| b <=> a }, I also get errors.
You can pass a block to sort.
num.permutation.to_a.sort { |x, y| x.to_i <=> y.to_i }
This SO thread may be of some assistance: How does Array#sort work when a block is passed?
num.permutation.to_a is an array of arrays, not an array of integers, which causes the result not what you expected.
Actually you don't need to sort since you only need the minimum integer that is bigger than the input.
def PermutationStep(num)
nums = num.to_s.split('')
permutations = nums.permutation.map{|a| a.join.to_i}
permutations.keep_if{|n| n > num}.min || -1
end
puts PermutationStep(11121) # 11211
puts PermutationStep(999) # -1
Call to_i before your sort the permutations. Once that is done, sort the array an pick the first element greater than your number:
def PermutationStep(num)
numbers = num.to_s.split('')
permutations = numbers.permutation.map { |p| p.join.to_i }.sort
permutations.detect { |p| p > num } || -1
end
You don't need to consider permutations of digits to obtain the next higher number.
Consider the number 126531.
Going from right to left, we look for the first decrease in the digits. That would be 2 < 6. Clearly we cannot obtain a higher number by permuting only the digits after the 2, but we can obtain a higher number merely by swapping 2 and 6. This will not be the next higher number, however.
We therefore look for the smallest digit to the right of 2 that is greater than 2, which would be 3. Clearly, the next higher number will begin 13 and will have the remaining digits ordered smallest to largest. Therefore, the next higher number will be 131256.
You can easily see that the next higher number for 123 is 132, and for 12453 is 12534.
The proof that procedure is correct is easily established by induction, first showing that it is correct for numbers with two digits, then assuming it is correct for numbers with n>=2 digits, showing it is correct for numbers with n+1 digits.
It can be easily implemented in code:
def next_highest(n)
a = n.to_s.reverse.split('').map(&:to_i)
last = -Float::INFINITY
x,ndx = a.each_with_index.find { |d,i| res = d<last; last=d; res }
return nil unless x
swap_val = a[ndx]
swap_ndx = (0...ndx).select { |i| a[i] > swap_val }.min_by{ |i| a[i] }
a[ndx], a[swap_ndx] = a[swap_ndx], swap_val
a[0...ndx] = a[0...ndx].sort.reverse
a.join.reverse
end
next_highest(126531) #=> "131256"
next_highest(109876543210) #=> "110023456789"

Ruby Project Euler # 12 Efficiency

Working on Problem 12 of Project Euler:
The sequence of triangle numbers is generated by adding the natural numbers. So the 7th triangle number would be 1 + 2 + 3 + 4 + 5 + 6 + 7 = 28. The first ten terms would be:
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, ...
Let us list the factors of the first seven triangle numbers:
1: 1
3: 1,3
6: 1,2,3,6
10: 1,2,5,10
15: 1,3,5,15
21: 1,3,7,21
28: 1,2,4,7,14,28
We can see that 28 is the first triangle number to have over five divisors.
What is the value of the first triangle number to have over five hundred divisors?
Here's what I've got:
require 'reusable'
# The idea here is that 2^n is the smallest number with n factors,
# according to their definition, so it's a good place to start.
# It also happens to be a HUGE number, so I'm worried I'm thinking
# about this wrong. Did 4999 instead of 5000, just to make sure
# I didn't overshoot.
start = 2 * 4999
# The faster way to calculate the nth Triangle number
def nthTriangle(n)
n * (n + 1) / 2
end
def answer(num)
i = startingTriangle(num)
while true
triangle = i*(i+1)/2
puts triangle
factors = numFactors(triangle)
return "#{triangle} is triangle number #{i}, with #{factors} factors." if factors > num
i += 1
end
end
# Basic reversal of the nthTriangle thing to figure
# out which n to start with in the answer function.
def startingTriangle(n)
power = n - 2
sqrt(power * 2).to_i - 1
end
puts answer(5000)
And that required file (where I'm trying to put methods I'll reuse in a bunch of Euler problems):
def primesUpTo(n)
nums = [0, 0] + (2..n).to_a
(2..sqrt(n).to_i+1).each do |i|
if nums[i].nonzero?
(i**2..n).step(i) {|m| nums[m] = 0}
end
end
nums.find_all {|m| m.nonzero?}
end
def prime?(n)
test = primesUpTo(sqrt(n).to_i)
test.each do |i|
if n % i == 0
return false
end
end
true
end
# Just for faster, more intuitive (to me) array summing
def sum(array)
array.inject(0) {|s, n| s + n }
end
# Ditto
def product(array)
array.inject(1) {|p, n| p * n}
end
# I don't like typing the 'Math.'
def sqrt(n)
Math.sqrt(n)
end
# Returns an array of arrays of the prime factors of num
# Form [[factor1, power1],[factor2, power2]]
# Ex: primeFactors(12) == [[2,2],[3,1]]
def primeFactors(n)
array = []
# 2 3
primesUpTo((n/2).to_i+1).select{ |i| n % i == 0 }.each do |p|
pcount = 1
n = n / p
while n % p == 0
pcount += 1
n = n / p
end
array << [p, pcount]
end
array
end
# Returns the number of factors a number has
# INCLUDING both the number itself and 1
# ex: numFactors(28) = 6
def numFactors(n)
return 2 if prime?(n)
product = 1
primeFactors(n).each do |i|
product *= i[1] + 1
end
product
end
My problem is that my code is really super slow. If I start at 1 instead of my start number, it takes a minute + before it gets to like 200000 (nowhere near 2^4999). But apart from scrapping the library prime-number solution and adding all primes to an array I keep referring to -- which I feel would only make it a small amount faster -- I can't think of how to make this much faster. And it needs to be WAY faster.
Am I thinking about this all wrong? Any suggestions?
Also useful would be any suggestions for how to improve the efficiency of any of my library methods, which I'll probably be using again and again. I wanted to make them from scratch so I understood them, but I'm afraid they're very inefficient.
From your code:
The idea here is that 2^n is the smallest number with n factors
From the stated Project Euler task:
We can see that 28 is the first triangle number to have over five divisors.
I'm not sure why you think 2^n is the smallest number with n factors, but the example given in the question clearly proves your assumption wrong, as 2^5 = 32, which is greater than 28.
My solution starts the search at 1 and is reasonably efficient. I don't use primes at all.
Addendum: For the sake of completeness, the other large issue besides starting at a number far too high is searching for greater than 5000 divisors rather than greater than 500, as you noticed and pointed out in the comments.

Resources