Even distribution of numbers in array - algorithm

My problem is that I have a given array of n numbers between 1 and 100. The goal is to pick 5 numbers which result in a minimum total distance. The total distance is calculated by summing up the distance of each number in the initial array to the closest of the 5 picked numbers.
What I (sort of) tried and thought about:
Taking the average number of the array and dividing it by 5 to get something useful?
Dividing the array length by 5, that numbers x and then the first number is array[x] the second one is array[x*2] and so on
Example
Input [5, 10, 15, 20, ..., 85, 90, 95, 100]
Output [10, 30, 50, 70, 90]
(There might be a better output but I hope this makes the goal clear)
As you can see I'm pretty lost and just can't come up with a solution. There probably is a super easy solution to this that I just don't get.
I am just looking for a hint not a solution, I wan't to figure that out myself.

Here is an algorithm that works in polynomial time.
First, sort your array of n things. Next, calculate a 2-dim array which for every 0 <= i <= j < n contains the index of the optimal element to fill the range from the ith element to the jth element. Fill out a similar array of the total distance for each interval from that optimal array.
As an example with the above sample output, the first 2-dim array could look like:
optimal_index = [
[ 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9],
[ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10],
[ 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10],
[ 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11],
[ 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11],
[ 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12],
[ 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12],
[ 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13],
[ 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13],
[ 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14],
[10, 10, 11, 11, 12, 12, 13, 13, 14, 14],
[11, 11, 12, 12, 13, 13, 14, 14, 15],
[12, 12, 13, 13, 14, 14, 15, 15],
[13, 13, 14, 14, 15, 15, 16],
[14, 14, 15, 15, 16, 16],
[15, 15, 16, 16, 17],
[16, 16, 17, 17],
[17, 17, 18],
[18, 18],
[19],
]
where the index of the optimal element for the range from i to j is at optimal_index[i][j-i]. With the same indexing scheme, the cost matrix would be:
optimal_cost = [
[ 0, 5, 10, 20, 30, 45, 60, 80, 100, 125, 150, 180, 210, 245, 280, 320, 360, 405, 450, 500],
[ 0, 5, 10, 20, 30, 45, 60, 80, 100, 125, 150, 180, 210, 245, 280, 320, 360, 405, 450],
[ 0, 5, 10, 20, 30, 45, 60, 80, 100, 125, 150, 180, 210, 245, 280, 320, 360, 405],
[ 0, 5, 10, 20, 30, 45, 60, 80, 100, 125, 150, 180, 210, 245, 280, 320, 360],
[ 0, 5, 10, 20, 30, 45, 60, 80, 100, 125, 150, 180, 210, 245, 280, 320],
[ 0, 5, 10, 20, 30, 45, 60, 80, 100, 125, 150, 180, 210, 245, 280],
[ 0, 5, 10, 20, 30, 45, 60, 80, 100, 125, 150, 180, 210, 245],
[ 0, 5, 10, 20, 30, 45, 60, 80, 100, 125, 150, 180, 210],
[ 0, 5, 10, 20, 30, 45, 60, 80, 100, 125, 150, 180],
[ 0, 5, 10, 20, 30, 45, 60, 80, 100, 125, 150],
[ 0, 5, 10, 20, 30, 45, 60, 80, 100, 125],
[ 0, 5, 10, 20, 30, 45, 60, 80, 100],
[ 0, 5, 10, 20, 30, 45, 60, 80],
[ 0, 5, 10, 20, 30, 45, 60],
[ 0, 5, 10, 20, 30, 45],
[ 0, 5, 10, 20, 30],
[ 0, 5, 10, 20],
[ 0, 5, 10],
[ 0, 5],
[ 0],
]
Now what about if we fill ranges with 2 elements? This is a question of taking each range, and looking the costs at each point we could divide it. That new data structure just needs to contain the places to separate between "closest to first element" and "closest to second". From this division we can take any range and quickly divide it into the optimal 2, then tell you what the two selected elements are, and the total cost. This can be filled in with a similar matrix. Note that the previous optimal_cost matrix will make these calculations very straightforward.
Next, what about ranges with 4 elements? This is exactly the same as ranges of 2 elements, except that we are now dividing between the first pair and the second pair. But the logic is the same.
And finally, what about our problem with 5 elements? That's just a question of calculating the optimal division between closest to the first 4 elements and closest to the last one. So just try all of the possibilities.
The natural generalization of this to filling k things in an array of size n is O(n^3 log(k)).

Related

split an array which comtains partially relatively order into two sorted array in O(n) time

Assume I have two arrays, both of them are sorted, for example:
A: [1, 4, 5, 8, 10, 24]
B: [3, 6, 9, 29, 50, 65]
And then I merge these two array into one array and keep original relative order of both two array
C: [1, 4, 3, 5, 6, 9, 8, 29, 10, 24, 50, 65]
Is there any way to split C into two sorted array in O(n) time?
note: not necessarily into the original A and B
Greedily assign your integers to list 1 if they can go there. If they can't, assign them to list 2.
Here's some Ruby code to play around with this idea. It randomly splits the integers from 0 to n-1 into two sorted lists, then randomly merges them, then applies the greedy approach.
def f(n)
split1 = []
split2 = []
0.upto(n-1) do |i|
if rand < 0.5
split1.append(i)
else
split2.append(i)
end
end
puts "input 1: #{split1.to_s}"
puts "input 2: #{split2.to_s}"
merged = []
split1.reverse!
split2.reverse!
while split1.length > 0 && split2.length > 0
if rand < 0.5
merged.append(split1.pop)
else
merged.append(split2.pop)
end
end
merged += split1.reverse
merged += split2.reverse
puts "merged: #{merged.to_s}"
merged.reverse!
greedy1 = [merged.pop]
greedy2 = []
while merged.length > 0
if merged[-1] >= greedy1[-1]
greedy1.append(merged.pop)
else
greedy2.append(merged.pop)
end
end
puts "greedy1: #{greedy1.to_s}"
puts "greedy2: #{greedy2.to_s}"
end
Here's sample output:
> f(20)
input 1: [2, 3, 4, 5, 8, 9, 10, 18, 19]
input 2: [0, 1, 6, 7, 11, 12, 13, 14, 15, 16, 17]
merged: [2, 0, 1, 6, 3, 4, 5, 8, 9, 7, 10, 11, 18, 12, 13, 19, 14, 15, 16, 17]
greedy1: [2, 6, 8, 9, 10, 11, 18, 19]
greedy2: [0, 1, 3, 4, 5, 7, 12, 13, 14, 15, 16, 17]
> f(20)
input 1: [1, 3, 5, 6, 8, 9, 10, 11, 13, 15]
input 2: [0, 2, 4, 7, 12, 14, 16, 17, 18, 19]
merged: [0, 2, 4, 7, 12, 14, 16, 1, 3, 5, 6, 8, 17, 9, 18, 10, 19, 11, 13, 15]
greedy1: [0, 2, 4, 7, 12, 14, 16, 17, 18, 19]
greedy2: [1, 3, 5, 6, 8, 9, 10, 11, 13, 15]
> f(20)
input 1: [0, 1, 2, 6, 7, 9, 11, 14, 15, 18]
input 2: [3, 4, 5, 8, 10, 12, 13, 16, 17, 19]
merged: [3, 4, 5, 8, 10, 12, 0, 13, 16, 17, 1, 19, 2, 6, 7, 9, 11, 14, 15, 18]
greedy1: [3, 4, 5, 8, 10, 12, 13, 16, 17, 19]
greedy2: [0, 1, 2, 6, 7, 9, 11, 14, 15, 18]
Let's take your example.
[1, 4, 3, 5, 6, 9, 8, 29, 10, 24, 50, 65]
In time O(n) you can work out the minimum of the tail.
[1, 3, 3, 5, 6, 8, 8, 10, 10, 24, 50, 65]
And now the one stream is all cases where it is the minimum, and the other is the cases where it isn't.
[1, 3, 5, 6, 8, 10, 24, 50, 65]
[ 4, 9, 29, ]
This is all doable in time O(n).
We can go further and now split into 3 streams based on which values in the first stream could have gone in the last without changing it being increasing.
[ 3, 5, 6, 8, 10, 24, ]
[1, 5, 6, 8, 50, 65]
[ 4, 9, 29, ]
And now we can start enumerating the 2^6 = 64 different ways of splitting the original stream back into 2 increasing streams.

Make a square multiplication table in Ruby

I got this question in an interview and got almost all the way to the answer but got stuck on the last part. If I want to get the multiplication table for 5, for instance, I want to get the output to be formatted like so:
1, 2, 3, 4, 5
2, 4, 6, 8, 10
3, 6, 9, 12, 15
4, 8, 12, 16, 20
5, 10, 15, 20, 25
My answer to this is:
def make_table(n)
s = ""
1.upto(n).each do |i|
1.upto(n).each do |j|
s += (i*j).to_s
end
s += "\n"
end
p s
end
But the output for make_table(5) is:
"12345\n246810\n3691215\n48121620\n510152025\n"
I've tried variations with array but I'm getting similar output.
What am I missing or how should I think about the last part of the problem?
You can use map and join to get a String in one line :
n = 5
puts (1..n).map { |x| (1..n).map { |y| x * y }.join(', ') }.join("\n")
It iterates over rows (x=1, x=2, ...). For each row, it iterates over cells (y=1, y=2, ...) and calculates x*y. It joins every cells in a row with ,, and joins every rows in the table with a newline :
1, 2, 3, 4, 5
2, 4, 6, 8, 10
3, 6, 9, 12, 15
4, 8, 12, 16, 20
5, 10, 15, 20, 25
If you want to keep the commas aligned, you can use rjust :
puts (1..n).map { |x| (1..n).map { |y| (x * y).to_s.rjust(3) }.join(',') }.join("\n")
It outputs :
1, 2, 3, 4, 5
2, 4, 6, 8, 10
3, 6, 9, 12, 15
4, 8, 12, 16, 20
5, 10, 15, 20, 25
You could even go fancy and calculate the width of n**2 before aligning commas :
n = 11
width = Math.log10(n**2).ceil + 1
puts (1..n).map { |x| (1..n).map { |y| (x * y).to_s.rjust(width) }.join(',') }.join("\n")
It outputs :
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22
3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33
4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44
5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55
6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66
7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77
8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88
9, 18, 27, 36, 45, 54, 63, 72, 81, 90, 99
10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110
11, 22, 33, 44, 55, 66, 77, 88, 99, 110, 121
Without spaces between the figures, the result is indeed unreadable. Have a look at the % operator, which formats strings and numbers. Instead of
s += (i*j).to_s
you could write
s += '%3d' % (i*j)
If you really want to get the output formatted in the way you explained in your posting (which I don't find that much readable), you could do a
s += "#{i*j}, "
This leaves you with two extra characters at the end of the line, which you have to remove. An alternative would be to use an array. Instead of the inner loop, you would have then something like
s += 1.upto(n).to_a.map {|j| i*j}.join(', ') + "\n"
You don't need to construct a string if you're only interested in printing the table and not returning the table(as a string).
(1..n).each do |a|
(1..n-1).each { |b| print "#{a * b}, " }
puts a * n
end
This is how I'd do it.
require 'matrix'
n = 5
puts Matrix.build(n) { |i,j| (i+1)*(j+1) }.to_a.map { |row| row.join(', ') }
1, 2, 3, 4, 5
2, 4, 6, 8, 10
3, 6, 9, 12, 15
4, 8, 12, 16, 20
5, 10, 15, 20, 25
See Matrix::build.
You can make it much shorter but here's my version.
range = Array(1..12)
range.each do |element|
range.map { |item| print "#{element * item} " } && puts
end

How to split a range into N parts

I am wondering how to split a range into N parts in ruby, While adding them to a hash with a zero based value for each range generated.
For example:
range = 1..60
p split(range, 4)
#=> {1..15 => 0, 16..30 => 1, 31..45 => 2, 46..60 => 3}
I've read How to return a part of an array in Ruby? for how to slice a range into an array, and a few others on how to convert the slices back into ranges, but I can't quite seem to piece all the pieces together to create the method I want.
Thanks for the help
range = 1..60
range.each_slice(range.last/4).with_index.with_object({}) { |(a,i),h|
h[a.first..a.last]=i }
#=> {1..15=>0, 16..30=>1, 31..45=>2, 46..60=>3}
The steps are as follows:
enum0 = range.each_slice(range.last/4)
#=> range.each_slice(60/4)
# #<Enumerator: 1..60:each_slice(15)>
You can convert this enumerator to an array to see the (4) elements it will generate and pass to each_with_index:
enum0.to_a
#=> [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
# [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30],
# [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45],
# [46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60]]
enum1 = enum0.with_index
#=> #<Enumerator: #<Enumerator: 1..60:each_slice(15)>:with_index>
enum1.to_a
#=> [[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0],
# [[16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30], 1],
# [[31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45], 2],
# [[46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60], 3]]
enum2 = enum1.with_object({})
#=> #<Enumerator: #<Enumerator: #<Enumerator: 1..60:each_slice(15)>
# :with_index>:with_object({})>
enum2.to_a
#=> [[[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0], {}],
# [[[16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30], 1], {}],
# [[[31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45], 2], {}],
# [[[46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60], 3], {}]]
Carefully examine the return values for the calculations of enum1 and enum2. You might want to think of them as "compound" enumerators. The second and last element of each of enum2's four arrays is the empty hash that is represented by the block variable h. That hash will be constructed in subsequent calculations.
enum2.each { |(a,i),h| h[a.first..a.last]=i }
#=> {1..15=>0, 16..30=>1, 31..45=>2, 46..60=>3}
The first element of enum2 that is passed by each to the block (before enum.each... is executed) is
arr = enum2.next
#=>[[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0], {}]
The block variables are assigned to the elements of arr using parallel assignment (sometimes called multiple assignment)
(a,i),h = arr
#=> [[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0], {}]
a #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
i #=> 0
h #=> {}
The block calculation is therefore
h[a.first..a.last]=i
#=> h[1..15] = 0
Now
h #=> {1..15=>0}
The calculations are similar for each of the other 3 elements generated by enum2.
The expression
enum2.each { |(a,i),h| h[(a.first..a.last)]=i }
could alternatively be written
enum2.each { |((f,*_,l),i),h| h[(f..l)]=i }

What's happening with FromDigits?

I thought I knew how FromDigits works, but it's doing something crazy now.
n[[990;;]]
FromDigits[n[[990;;]]]
outputs:
{9, 50, 0, 50, 1, 50, 2, 50, 3, 50, 4, 50, 5, 50, 6, 50, 7, 50, 8, 50, 9}
1405060708091011121309
instead of, you know, 950050150...
what's going on?
Documentation says that
FromDigits : constructs an integer from the list of its decimal digits.
So each number in the array must be less that 10 (decimal digits) for a simple concatenation.
Digits larger than the base are "carried": For example
FromDigits[{7, 11, 0, 0, 0, 122}] will give 810122
For more information go to http://reference.wolfram.com/language/ref/FromDigits.html
I think "string hacking" might be what you are asking for. This
myn = {9, 50, 0, 50, 1, 50, 2, 50, 3, 50, 4, 50, 5, 50, 6, 50, 7, 50, 8, 50, 9};
ToExpression[StringReplace[ToString[myn], ", " -> ""]][[1]]
gives you this integer
9500501502503504505506507508509
That turns your list into a string, replaces each comma space separator with nothing, turns that resulting string back into an integer and discards the now unneeded curly brackets.
A couple other ways..
FromDigits#Flatten#IntegerDigits#
{9, 50, 0, 50, 1, 50, 2, 50, 3, 50, 4, 50, 5, 50, 6, 50, 7, 50, 8, 50, 9}
9500501502503504505506507508509
(ToString /# # // StringJoin // ToExpression) &#
{9, 50, 0, 50, 1, 50, 2, 50, 3, 50, 4, 50, 5, 50, 6, 50, 7, 50, 8, 50, 9}
9500501502503504505506507508509

How do I generate an array of pairwise distances in Ruby?

Say I have an array that represents a set of points:
x = [2, 5, 8, 33, 58]
How do I generate an array of all the pairwise distances?
x = [2, 5, 8, 33, 58]
print x.collect {|n| x.collect {|i| (n-i).abs}}.flatten
I think that would do it.
x.map{|i| x.map{|j| (i-j).abs } }
gives
[[0, 3, 6, 31, 56],
[3, 0, 3, 28, 53],
[6, 3, 0, 25, 50],
[31, 28, 25, 0, 25],
[56, 53, 50, 25, 0]]
(format it like this by printing it with 'pp' instead of puts)
and
x.map{|i| x.map{|j| (i-j).abs } }.flatten
gives
[0, 3, 6, 31, 56, 3, 0, 3, 28, 53, 6, 3, 0, 25, 50, 31, 28, 25, 0, 25, 56, 53, 50, 25, 0]
if you really want an array
If you really do want an array instead of a matrix, this is O(n^2/2) instead of O(n^2).
result=[]
x.each_index{|i| (i+1).upto(x.size-1){|j| result<<(x[i]-x[j]).abs}}

Resources