0

I have a random number and I would like to divide it into several parts (addends) with conditions that a part can not be more than 20 and the parts must be as close to each other as possible.

For example, if my random number is 41, addends should be 14, 14, 13. If random number is 60 addends should be 20, 20, 20. If random number is 21 addends should be 11 and 10 and so on.

My code is in Ruby (Rails) so I would most appreciate an answer which gives an efficient implementation of this in Ruby, though pseudo-code or other programming languages are welcome as well.

This is what I found for arrays but I really need to do this thing with numbers: "Splitting an array into equal parts in ruby"

Community
  • 1
  • 1
mzrnsh
  • 2,260
  • 2
  • 20
  • 25
  • 1
    Welcome to SO. Please read "[ask]" including the links in that page. You're asking us to write code for you? That's not what Stack Overflow is for. Instead, you do the research, you try, then, when you run into a problem, you show us what you tried and we help you fix it. http://meta.stackoverflow.com/q/261592/128421 is useful to read. – the Tin Man Apr 05 '16 at 00:14
  • 1
    How many addends do you expect? – Aetherus Apr 05 '16 at 00:15
  • @Aetherus It does not matter, I do have a maximum limit on my random number so it will not get too wild. – mzrnsh Apr 05 '16 at 00:18
  • @theTinMan I don't need you to write my code of course, what I need is to see if there is something similar for integers as for arrays in the question I linked – mzrnsh Apr 05 '16 at 00:18
  • @mizurnix If the number of addends does not matter, why not just split the number into `1`'s? – Aetherus Apr 05 '16 at 00:20
  • 1
    Then that would be off-topic: "Questions asking us to recommend or find a book, tool, software library, tutorial or other off-site resource are off-topic for Stack Overflow as they tend to attract opinionated answers and spam. Instead, [describe the problem](http://meta.stackoverflow.com/questions/254393) and what has been done so far to solve it." Currently it looks like you looked at a single page. Where else? – the Tin Man Apr 05 '16 at 00:20
  • @Aetherus Good catch. I guess I should have given one more condition - addends should also be as big as possible. So for example 40 should be split into 20 and 20, not 10, 10, 10, and 10 – mzrnsh Apr 05 '16 at 00:23
  • 2
    @theTinMan You know I see your comments before you edit them, right? There is something seriously wrong with your attitude my friend and I hope you will get better someday. In the meantime, please see this question, it has similar problems you mentioned and I think this is your chance to make the world a better place: http://stackoverflow.com/questions/13585591/how-to-divide-a-number-into-multiple-parts-so-that-the-resulting-sum-is-equal-to – mzrnsh Apr 05 '16 at 00:34
  • 2
    Your last comment is highly inappropriate. @theTinMan has, for many years, made tremendous contributions to the Ruby community on SO, having, as I recall, the third-highest rep. He has merely informed you of SO policies and your response is to tell him he has a bad attitude and how he could spend his time more productively. As you you spend more time on SO you will see that tTM leaves many similar comments in an effort to improve the quality of questions posted to SO. There's no reason to be rude just because it's not something you would do. – Cary Swoveland Apr 06 '16 at 20:35
  • Only because someone has a big contribution into something I can not pretend they act cool when they don't. In one of tTM's comment before edition he sounded pretty angry with the fact that I argued Rails was not a Ruby based framework. Now, where did I say it was a JS one? That's right - I did not. And tTM should be a smart person of course, so he realized how wrong he was after reading the question again, so he edited the comment. But the fact that he made such comment without actually reading the question speaks for his attitude, which is wrong and that's what I said. That is not being rude – mzrnsh Apr 06 '16 at 22:10
  • And yes, my previous comment had 0 characters left and that was take 1, no word changed during typing. And that means programming god is on my side, topic closed – mzrnsh Apr 06 '16 at 22:12

3 Answers3

19

You can use this cool one-liner. It should work dividing by any amount of pieces you want:

def split_into n, p
    [n/p + 1] * (n%p) + [n/p] * (p - n%p)
end

print (split_into 32, 3) # => [11, 11, 10]

It basically divides the number into equal parts and splits the remainder by the first ones (1 for each). That and the fact that you can multiply and add arrays together, like so:

[1] * 3     # => [1, 1, 1]
[1] + [2]   # => [1, 2]

EDIT: Considering the rule that each piece should be smaller than 20, you can calculate the ideal amount of pieces as suggested by @Cary Swoveland:

p = (n / 20.0).ceil
SlySherZ
  • 1,631
  • 1
  • 16
  • 25
  • @CarySwoveland You got the question right. Though I believe this answer is quite close. It only needed a small addition, namely move 'p' inside method and set it to 1. Then put it into a loop and `p+=1` until `n/p < 21` – mzrnsh Apr 05 '16 at 02:46
  • @CarySwoveland This is the answer we are talking about right? With my loop `split_into 1_000_000, 1) #=> [1000000]` is not true. Since 1000000/1 > 20, it will then try 1000000/2, then 1000000/3 and so on, until it reaches the value which returns a number which is either 20 or less. After this, Sly's code works like a charm – mzrnsh Apr 05 '16 at 02:57
  • 2
    Then that needs to be done. There's no need to iterate: `p = (n/20.0).ceil`. – Cary Swoveland Apr 05 '16 at 03:01
4

Code

Let n be the given number that is to be divided and m the maximum value of each component.

def doit(n,m)
  nbr_components = (n/(m.to_f)).ceil
  smaller, nbr_larger = n.divmod(nbr_components)
  { smaller=>nbr_components-nbr_larger, smaller+1=>nbr_larger }.
    delete_if { |_,v| v.zero? }
end

Fixnum#divmod is an oft-overlooked method with many uses.

Examples

(1..300).to_a.sample(15).sort.each { |n| puts "doit(#{n},20) = #{doit(n,20)}" }
doit(2,20)   = {2=>1}
doit(7,20)   = {7=>1}
doit(13,20)  = {13=>1}
doit(19,20)  = {19=>1}
doit(32,20)  = {16=>2}
doit(36,20)  = {18=>2}
doit(47,20)  = {15=>1, 16=>2}
doit(61,20)  = {15=>3, 16=>1}
doit(77,20)  = {19=>3, 20=>1}
doit(146,20) = {18=>6, 19=>2}
doit(149,20) = {18=>3, 19=>5}
doit(160,20) = {20=>8}
doit(199,20) = {19=>1, 20=>9}
doit(253,20) = {19=>7, 20=>6}
doit(275,20) = {19=>5, 20=>9}

doit(11_347_155, 20)
  #=> {19=>5, 20=>567353}
5*19 + 567353*20
  #=> 11347155

Explanation

Suppose:

n = 77
m = 20

The steps are as follows:

nbr_components = (n/(m.to_f)).ceil
  #=> (77/20.0).ceil
  #=> 3.85.ceil
  #=> 4 
smaller, nbr_larger = n.divmod(nbr_components)
  #=> 77.divmod(4) 
  #=> [19, 1] 
smaller
  #=> 19
nbr_larger
  #=> 1
h = { smaller=>nbr_components-nbr_larger, smaller+1=>nbr_larger }
  #=> {19=>3, 20=>1}
h.delete_if { |_,v| v.zero? }
  #=> {19=>3, 20=>1}
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
3

There isn't a built in function to do this.

I can write out the solution for you, but in general you should include your own attempt in StackOverflow questions. This type of problem seems like practice to learn the fundamentals of the ruby language. I recommend going through a Ruby textbook if you're not sure where to start.

def split_number_into_three(number) # i.e. number=32
  base = Array.new(3, (number / 3)) # i.e. base=[10,10,10]
  remainder = number % 3            # i.e. remainer=2
  return base if remainder.zero?
  while remainder > 0
    base.each_with_index do |num, idx|
      if remainder > 0
        base[idx] += 1
        remainder -= 1
      end
    end
  end
  base
end

Running this code:

irb(main):035:0> split_number_into_three(32)
=> [11, 11, 10]
max pleaner
  • 26,189
  • 9
  • 66
  • 118
  • great, awesome Max – Shiva Apr 05 '16 at 00:55
  • 1
    Thank you. I did implement this method and it works great. And of course it's very easy to modify it in a way to call it split_number_into_n to gain control over number of parts I want my number to be split into. Still SlySherZ's answer seems to be closer to what I asked so I am upvoting this and accepting that – mzrnsh Apr 05 '16 at 01:54
  • mizurnix, that obviously cannot be true because this method does not take account of the the largest permissible value (i.e, 20). Note: `split_into 1_000_000, 1) #=> [1000000]`. – Cary Swoveland Apr 05 '16 at 02:51
  • @mizurnix yes I understand why'd you'd pick the "cool one liner". They can't all be winners! – max pleaner Apr 05 '16 at 06:34