Phil Harvey’s Puzzle
November 15, 2011
According to the binomial theorem, there are 12870 ways to choose 8 items from a set of 16:
(define (choose n k)
(if (zero? k) 1
(* n (/ k) (choose (- n 1) (- k 1)))))
> (choose 16 8)
12870
A recursive algorithm enumerates the k-subsets of n items. Consider the first element of the list. There are two kinds of subsets: those that include the first element, and those that don’t. Those that do can be enumerated by combining the first element with all (k−1)-subsets of the rest of the elements. Those that don’t can be enumerated by taking all the k-subsets of the rest of the elements. The recursion stops when k is zero or the list is empty. Here is our version of the algorithm:
(define (combs n xs)
(define (comb x xs)
(if (null? xs) xs
(if (pair? (car xs))
(cons (append (list x) (car xs)) (comb x (cdr xs)))
(cons (list x (car xs)) (comb x (cdr xs))))))
(if (or (zero? n) (null? xs)) (list)
(if (= n 1) xs
(append (comb (car xs) (combs (- n 1) (cdr xs)))
(combs n (cdr xs))))))
Now it’s easy. The sum of the numbers from 1 to 16 is 136, the sum of their squares is 1496, and the sum of their cubes is 18496, so we enumerate all 12870 subsets and check for half of each of the three sums:
> (let loop ((xss (combs 8 (range 1 17))))
(let* ((xs (car xss))
(s1 (apply + xs))
(s2 (apply + (map square xs)))
(s3 (apply + (map cube xs))))
(if (and (= s1 68) (= s2 748) (= s3 9248)) xs
(loop (cdr xss)))))
(1 4 6 7 10 11 13 16)
The other partition is thus (2 3 5 8 9 12 14 15)
.
We used range
from the Standard Prelude. The definitions of square
and cube
are obvious, and are given at http://programmingpraxis.codepad.org/90qQobLz, where you can run the program.
Here’s my Python interactive session, with one (typo, error) removed.
If you run my version of the program, you’ll see that there are two unique solutions, only one of which divides { 1..16 } into subsets of equal size. Both solutions have the same sums though. Interesting.
Ah. That teaches me to code before breakfast. I have a small error in the program above: the line which reads val = i should be “val = i + 1 ;”.
When that change is made, it finds the single, unique solution to the problem. My hint that something was wrong was that the sum of the numbers was 60. The sum of the numbers from 1 to 16 should be 136, and since they are divided into two partitions, they should each sum to 68.
Since Michi and kernelbob already posted some Python, here’s a Common Lisp solution:
and here’s a Haskell one:
In both, I calculated the sums of the integers, squares of integers, and cubes of integers in {1, …, 16} via formulae for the first three types of figurate numbers.
Here’s a ruby version …
This problem can be formulated as a (binary) integer programming problem.
For example, using GMPL
I just found the “snoob” function over here. http://www.steike.com/code/useless/evil-c/ That led to this somewhat golfed C solution.
Basically, snoob(n) returns the next integer after n with the same number of bits set. If we use a bit mask to denote the members of the first partition, we can use snoob() to iterate through the partitions directly.
for (i = 0xFF; i <= 0xFF00; i = snoob(i)) { …}
// 0xFF is the first partition, {1, 2, 3, 4, 5, 6, 7, 8}.
// 0xFF00 is the last partition, {9, 10, 11, 12, 13, 14, 15, 16}.
snoob() should be blazingly fast since it's about 7 arithmetic operations and no memory references.
#include <stdio.h>
#include <string.h>
unsigned snoob(unsigned x) {
unsigned smallest = x & -x;
unsigned ripple = x + smallest;
unsigned ones = x ^ ripple;
ones = (ones >> 2) / smallest;
return ripple | ones;
}
int main()
{
unsigned i, j, k, l;
for (i = 0xFF; i <= 0xFF00; i = snoob(i)) {
unsigned sums[2][3] = { { 0, 0, 0 }, { 0, 0, 0 } };
for (j = 0; j < 16; j++)
for (k = 0, l = 1; k < 3; k++)
sums[!(i & 1 << j)][k] += l *= j + 1;
if (!memcmp(sums[0], sums[1], sizeof sums[0])) {
for (j = 0; j < 16; j++)
if (i & 1 << j)
printf("%d ", j + 1);
printf("\n");
}
}
return 0;
}
Oops. Formatted.
A haskell one-liner in ghci:
And a little sorter solution with list comprehension:
@neez Nice Haskell solution. You can also do it without precalculating the constants.
@Jan Van lent
Wow, beautiful soution, especially using the i=0 case to partition into equal sized sets!
@neez Well, it is only a small change from your solution. I used the same trick in the GMPL solution. Note that the constraint that the sets be of equal size is actually redundant. You can try with [1..3], [2..3] and even just 3 and they all give the same solution.
Create 2 (random) partitions, and swap between them, making sure the difference is less. Keep doing this until we get a difference of 0.
package com.algorithm;
import java.util.Arrays;
public class SameSumSquareCube {
private static int[] set1;
private static int[] set2;
public static void main(String[] args) {
set1 = new int[] {1, 2, 3, 4, 5, 6, 7, 8};
set2 = new int[] {9, 10, 11, 12, 13, 14, 15, 16};
recursive(7);
System.out.println(“completed”);
}
private static void recursive(int count) {
for (int i=count; i>=0; i–) {
for (int j=7; j>=0; j–) {
int temp = set1[i];
set1[i] = set2[j];
set2[j] = temp;
if (isCubeSumEqual(set1, set2) && isSquareSumEqual(set1, set2) && isSumEqual(set1, set2)) {
System.out.println(Arrays.toString(set1));
System.out.println(Arrays.toString(set2));
}
}
recursive(–count);
}
}
private static boolean isCubeSumEqual(int[] set1, int[] set2) {
int set1CubeSum = 0;
for (int number : set1) {
set1CubeSum += number * number * number;
}
int set2CubeSum = 0;
for (int number : set2) {
set2CubeSum += number * number * number;
}
return set1CubeSum == set2CubeSum;
}
private static boolean isSquareSumEqual(int[] set1, int[] set2) {
int set1SquareSum = 0;
for (int number : set1) {
set1SquareSum += number * number;
}
int set2SquareSum = 0;
for (int number : set2) {
set2SquareSum += number * number;
}
return set1SquareSum == set2SquareSum;
}
private static boolean isSumEqual(int[] set1, int[] set2) {
int set1Sum = 0;
for (int number : set1) {
set1Sum += number;
}
int set2Sum = 0;
for (int number : set2) {
set2Sum += number;
}
return set1Sum == set2Sum;
}
}
[…] the weekend, I was amusing myself with this problem from the always entertaining Programming Praxis. The problem is to partition the set {1, 2, […]