# Mix and Match (#186)

**URL:** <https://www.ruby-forum.com/t/mix-and-match-186/155513>\
**Category:** Ruby\
**Created:** [December 12, 2008, 7:44pm UTC](https://www.ruby-forum.com/t/mix-and-match-186/155513 "2008-12-12T19:44:21Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![mansfiem](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/fbc32d/32.png) [@mansfiem](https://www.ruby-forum.com/u/mansfiem)\
**Post date:** [December 12, 2008, 7:44pm UTC](https://www.ruby-forum.com/t/mix-and-match-186/155513/1 "2008-12-12T19:44:21Z")

</div>

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

The three rules of Ruby Q. 2:

1. Please do not post any solutions or spoiler discussion for this  
quiz until 48 hours have passed from the time on this message.

2. Support Ruby Q. 2 by submitting ideas as often as you can!  
Visit [http://splatbang.com/rubyquiz/](http://splatbang.com/rubyquiz/).

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem  
helps everyone on Ruby T. follow the discussion. Please reply to  
the original quiz message, if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

## Mix and Match (#186)

I purchased a number of scented candles recently for sending out to  
friends and family. While I could be accused of being lazy by getting  
candles for several people, I’d like to mix up the candles a bit so  
that each recipient gets a different combination of scents.

Please help me out! Your task is to write a method that randomizes and  
mixes up the individual candles into groups, one per recipient, in  
order to minimize group duplication. So, for example:

```
 candles = { :orange => 3,
             :vanilla => 2,
             :lavender => 2,
             :garden => 4 }

 recipients = %w(janet nancy susan)

 candles_per_recipient = 3

 > mix_and_match(candles, recipients, candles_per_recipient)

 => { "janet" => [:garden, :lavender, :orange],
      "nancy" => [:garden, :orange, :vanilla],
      "susan" => [:garden, :lavender, :vanilla],
      :extra => { :orange => 1,
                   :vanilla => 0,
                   :lavender => 0,
                   :garden => 1
                 }
     }

```

If it is impossible to have a unique combination for every recipient,  
you should still generate some set of combinations, minimizing  
repetition of combinations.

If the number of recipients times the number of candles per recipient  
is more than the supply, generate an error.

---

<div class="post-metadata">

**Author:** ![mansfiem](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/fbc32d/32.png) [@mansfiem](https://www.ruby-forum.com/u/mansfiem)\
**Post date:** [December 16, 2008, 7:19pm UTC](https://www.ruby-forum.com/t/mix-and-match-186/155513/2 "2008-12-16T19:19:30Z")

</div>

Wow…it’s been too long since I’ve posted to this list.

Anyway, we can treat each type of candle as a digit of a number in an  
arbitrarily-large base. We can thus interpret the total number of  
candles as a single number, and each possible combination of different  
types of candles as other numbers whose only digits are 0 and 1 in that  
base. Under some permutation of the digits, we are trying to add the  
integers representing these combinations in a way that gets us the  
closest to the integer representing the total number of candles.

Could use a little more rigor, but I think the above makes it pretty  
clear: this is essentially the Knapsack Problem. Good luck improving  
significantly on brute-force!

* * *

From: Matthew M. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)  
To: ruby-talk ML [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)  
Sent: Friday, December 12, 2008 12:36:55 PM  
Subject: [QUIZ] Mix and Match (#186)

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

The three rules of Ruby Q. 2:

1. Please do not post any solutions or spoiler discussion for this  
quiz until 48 hours have passed from the time on this message.

2. Support Ruby Q. 2 by submitting ideas as often as you can!  
Visit [http://splatbang.com/rubyquiz/](http://splatbang.com/rubyquiz/).

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem  
helps everyone on Ruby T. follow the discussion. Please reply to  
the original quiz message, if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

## Mix and Match (#186)

I purchased a number of scented candles recently for sending out to  
friends and family. While I could be accused of being lazy by getting  
candles for several people, I’d like to mix up the candles a bit so that  
each recipient gets a different combination of scents.

Please help me out! Your task is to write a method that randomizes and  
mixes up the individual candles into groups, one per recipient, in order  
to minimize group duplication. So, for example:

```
candles = { :orange => 3,
            :vanilla => 2,
            :lavender => 2,
            :garden => 4 }

recipients = %w(janet nancy susan)

candles_per_recipient = 3

> mix_and_match(candles, recipients, candles_per_recipient)

=> { "janet" => [:garden, :lavender, :orange],
     "nancy" => [:garden, :orange, :vanilla],
     "susan" => [:garden, :lavender, :vanilla],
     :extra => { :orange => 1,
                  :vanilla => 0,
                  :lavender => 0,
                  :garden => 1
                }
    }

```

If it is impossible to have a unique combination for every recipient,  
you should still generate some set of combinations, minimizing  
repetition of combinations.

If the number of recipients times the number of candles per recipient is  
more than the supply, generate an error.

---

<div class="post-metadata">

**Author:** ![mansfiem](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/fbc32d/32.png) [@mansfiem](https://www.ruby-forum.com/u/mansfiem)\
**Post date:** [December 17, 2008, 2:04am UTC](https://www.ruby-forum.com/t/mix-and-match-186/155513/3 "2008-12-17T02:04:28Z")

</div>

Wow, it’s been a long time since I’ve posted to this list.

Anyway, we can treat the candles of each type as a digit in an integer  
in an arbitrarily-high base. Applying that transformation, the problem  
becomes “Given several numbers, find a way to add them up so that each  
digit is as close as possible to a given number.” Not sure how to  
rigorously prove it, but that’s highly suggesting that this quiz is  
reducible to the Knapsack Problem.

* * *

From: Matthew M. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)  
To: ruby-talk ML [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)  
Sent: Friday, December 12, 2008 12:36:55 PM  
Subject: [QUIZ] Mix and Match (#186)

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

The three rules of Ruby Q. 2:

1. Please do not post any solutions or spoiler discussion for this  
quiz until 48 hours have passed from the time on this message.

2. Support Ruby Q. 2 by submitting ideas as often as you can!  
Visit [http://splatbang.com/rubyquiz/](http://splatbang.com/rubyquiz/).

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem  
helps everyone on Ruby T. follow the discussion. Please reply to  
the original quiz message, if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

## Mix and Match (#186)

I purchased a number of scented candles recently for sending out to  
friends and family. While I could be accused of being lazy by getting  
candles for several people, I’d like to mix up the candles a bit so that  
each recipient gets a different combination of scents.

Please help me out! Your task is to write a method that randomizes and  
mixes up the individual candles into groups, one per recipient, in order  
to minimize group duplication. So, for example:

```
candles = { :orange => 3,
            :vanilla => 2,
            :lavender => 2,
            :garden => 4 }

recipients = %w(janet nancy susan)

candles_per_recipient = 3

> mix_and_match(candles, recipients, candles_per_recipient)

=> { "janet" => [:garden, :lavender, :orange],
     "nancy" => [:garden, :orange, :vanilla],
     "susan" => [:garden, :lavender, :vanilla],
     :extra => { :orange => 1,
                  :vanilla => 0,
                  :lavender => 0,
                  :garden => 1
                }
    }

```

If it is impossible to have a unique combination for every recipient,  
you should still generate some set of combinations, minimizing  
repetition of combinations.

If the number of recipients times the number of candles per recipient is  
more than the supply, generate an error.

---

<div class="post-metadata">

**Author:** ![mansfiem](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/fbc32d/32.png) [@mansfiem](https://www.ruby-forum.com/u/mansfiem)\
**Post date:** [December 19, 2008, 12:18am UTC](https://www.ruby-forum.com/t/mix-and-match-186/155513/4 "2008-12-19T00:18:04Z")

</div>

_This week’s summary is provided by Peter, originally [posted to his  
blog](http://www.rubyrailways.com/ruby-quiz-mix-and-match/). I am providing it here with permission._

In my interpretation, this is a simple combinatorial problem: say the  
number of recipients is `r` and candles per recipient is `c`, then you  
are looking for a (preferably non-repeating) random selection of `r`  
elements of `c`-combinations of the original set of candles. (In fact  
it’s a bit more complicated than that: the `c`-combinations have to be  
recalculated from the remaining candles each time you give away a  
group of candles, so we’ll get to that). Sounds confusing? Don’t  
worry, after the implementation everything will be clear!

So first, define a k-combination for a histogram (a Hash like candles  
above, where keys are elements and values are cardinalities):

```
 class Hash
   def comb(group_size)
     result = []
     inner_comb = lambda do |head,tail|
       tail[0..-(group_size-head.size)].each do |e|
         if (head.size >= group_size-1)
           tail.each {|t| result << head + [t]}
         else
           inner_comb[head + [e], tail[tail.index(e)+1..-1]]
         end
       end
     end
     inner_comb[[],self.inject([]) {|a,v| v[1].times{a << v[0]}; a}]
     result.uniq
   end

```

For example:

```
 candles = { :orange => 2,
             :vanilla => 1,
             :lavender => 1,
             :garden => 1 }

 pp candles.comb(3)

 => [[:lavender, :garden, :orange],
     [:lavender, :garden, :vanilla],
     [:lavender, :orange, :orange],
     [:lavender, :orange, :vanilla],
     [:garden, :orange, :orange],
     [:garden, :orange, :vanilla],
     [:orange, :orange, :vanilla]]

```

So, for a set of candles, this method generates all possible 3-  
combinations of the candles. We can then pick one and assign it to one  
of the recipients. Then recalculate the above from the remaining  
candles, give it to the next recipient - and so on and so forth.  
That’s the basic idea, but we also need to ensure the candle  
combinations are as non-repeating as possible. So let’s define some  
further utility methods:

```
 class Hash
   def remove_set(set)
     set.each {|e| self[e] -= 1}
   end
 end

```

The above code adjusts the number of candles in the original hash once  
we give away some of them. So for example:

```
 candles = { :orange => 2,
             :vanilla => 1,
             :lavender => 1,
             :garden => 1 }

 candles.remove_set([:orange,:orange,:lavender])
 p candles
 => {:lavender=>0, :garden=>1, :orange=>0, :vanilla=>1}

```

And some Array extensions:

```
 class Array
   def rand
     uniqs = self.select{|e| e.uniq.size == e.size}
   uniqs.empty? ? self[Kernel.rand(length)] :

```

uniqs[Kernel.rand(uniqs.length)]  
end

```
   def unordered_include?(other)
     self.map{|e| e.map{|s| s.to_s}.sort}.include? other.map{|s|

```

s.to\_s}.sort  
end  
end

`Array#rand` is trying to pick a random non-repeating combination if  
there is one (e.g. `[:orange, :lavender, :garden]`) or, if there is no  
such combination, then just a random one (e.g.  
`[:orange, :orange, :garden]` - orange is repeating, but we have no  
other choice).

`Array#unordered_include?` is like normal `Array#include?`, but  
disregards the ordering of the elements. So for example:

```
 [[:lavender, :garden, :orange]].include?

```

[:lavender, :orange, :garden] =\> false  
[[:lavender, :garden, :orange]].unordered\_include?  
[:lavender, :orange, :garden] =\> true

Hmm… it would have been much more effective to use a set here rather  
than the above CPU-sucker, but now I am lazy to change it. 😉

OK, so finally for the solution:

```
 ERROR_STRING = "The number of recipients times the number of

```

candles per recipient is more than the supply!"

```
 def mix_and_match(candles, recipients, candles_per_recipient)
   return ERROR_STRING if ((candles.values.inject{|a,v| a+v}) <

```

(recipients.size \* candles\_per\_recipient))  
candle\_set = recipients.inject({}) do |a,v|  
tried = []  
tries = 0  
loop do  
random\_pick = candles.comb(candles\_per\_recipient).rand  
tried \<\< random\_pick unless tried.unordered\_include?  
random\_pick  
break unless a.values.unordered\_include? random\_pick  
break if (tries+=1) \> candles.values.size \* 2  
end  
candles.remove\_set(tried.last)  
a[v] = tried.last  
a  
end  
candle\_set.merge({:extra =\> candles})  
end

So, in the inner loop we randomly pick a candles-per-recipient-  
combination of all the possible combinations; If no one has that combo  
yet, we assign it to the next recipient. If someone has it already, we  
try to find an unique combination (loop on), unless it is impossible.  
In this case we simply start giving out any combinations. Once we give  
away a set of candles, we remove them from the original set. Easy-peasy.

You can check out the source code here.

This was a great quiz, too bad that not many people took a stab at it  
(so far 1 except me ;-)). The hardest part for me was the  
implementation of the k-combination (and the result looks awful to me

- I didn’t check any algorithm/pseudocode/other solution though, I  
wanted to roll my own) - after that the problem was pretty simple.
