Message Boards Message Boards

Sort pairs that add up to a perfect square?

To be more specific, I am working on a code to help me find patterns and solve this problem: Which sets of numbers of size n (starting at one) can be divided into pairs that add up to a perfect square, such that every number has a partner, and there are no repeats? A simple known solution is 8, because the set 8 can be paired (1,8) (2,7) (3,6) and (4,5) with each pair adding to 9.

Here is my code that works:

Remove["Global`*"]
f[n_] := Permutations[Table[i, {i, 1, n}], {2}]
listfunction[n_] :=
 Module[
  {},
  newlist = {};
  duplicates = {};
  Do[
   If[
    f[n][[j, 1]] < f[n][[j, 2]],
    AppendTo[ newlist, f[n][[j]]],
    AppendTo[duplicates, f[n][[j]]]]
   , {j, 1, (n!/(n - 2)!)}]; Module[{},
   perfectsquares100 =
    Table[x^2, {x, 1, 100}];
   possibles = {};
   impossibles = {};
   Do[
    If[
     MemberQ[perfectsquares100, newlist[[k, 1]] + newlist[[k, 2]]], 
     AppendTo[possibles, newlist[[k]]],
     AppendTo[impossibles, newlist[[k]]]],
    {k, 1, Length[newlist]}]; Print[possibles]]]

Which generates the list for n of all the pairs whose sum is a perfect square.

So now, I need to whittle that list down to the numbers 1 to n, so that each digit is only represented once, eliminating extra pairs.

This is the code I have come up with to do that, that doesn't work. The following code will use the immediate output from the above code.

finallist[possibles] = Module[{},
  rejects = {};
  answer = {};
  Do[
   If[MemberQ[possibles[[h + 1 ;; Length[possibles]]], 
      possibles[[h, 1]], 2] \[And] 
     MemberQ[possibles[[h + 1 ;; Length[possibles]]], 
      possibles[[h, 2]], 2], AppendTo[rejects, possibles[[h]]], 
    AppendTo[answer, possibles[[h]]]], {h, 1, Length[possibles]}];
  Print[answer]]

I built the code working from n=8, but when I tried it on n=16, it fails to provide the correct answer.

So, any ideas on what commands might work?

POSTED BY: Ashley Louk
17 Replies
Posted 8 years ago

What would the answer for 16 be? If all the pairs have to add up to the same square, at least any odd square minus one would work, so for example the list {1, 2, ..., 24} gives you {{1, 24}, {2, 23}, ..., {12, 13}}, all adding to 25.

POSTED BY: Robert Dickau

16 is all the pairs from 8, and (16,9) (15,10) (14,11) (13,12). Yes, 24 is what I like to call a zipper set. It zips up nicely pairing 1 to n, and 2 to n-1, etc. I'm working on a theory that all odd multiples of 8 zip that way.

To add to my question, I'm thinking now that the key to eliminating the correct pairs is to find the non-duplicate numbers. Often, that is n, but not always. For 14, it's 9 and 10.

Here is some new code I wrote to find a pair with a specific element. (Note, n needs to match the last time you ran listfunction in my previous code for this to work at the moment.) randomlist={}; run[n_] := Do[ If[ MemberQ[ possibles[[h]], n, 2], Print[possibles[[h]]], AppendTo[randomlist, possibles[[h]]]], {h, 1, Length[possibles]}]

POSTED BY: Ashley Louk
Posted 8 years ago

Something I am doing wrong, since I still have numbers repeated at the beginning of each tuple, please take a look, thanks in advance.

Here is my code

mere[n_Integer] := 
 Module[{perm, squared, novo, rec}, 
  perm = Permutations[Range[n], {2}]; squared = Range[100]^2; 
  novo = Select[perm, #[[1]] < #[[2]] &];
  rec = DeleteCases[If[MemberQ[squared, Total[#]], #] & /@ novo, 
    Null]; Select[rec, Total[#] >= n &]]

See my results

errors obteined

Any help is welcoming ....

POSTED BY: Luis Ledesma

Dear Ashley,

This is a recursive function that will start with a list of all the integers up to n. This list I call 'left'. From the list 'left' it will pick the first and from the other 2...Length[left] it will pick those that sum up to be square together with the first element. For all those possibilities it will call itself, now with the first argument the good matches, and the remaining digits that are 'left'. If there is nothing left, then we have a solution. I hope you're following my logic, not that easy to explain. This has the advantage that it does not have to compute all possibilities in advance (like permutations and subsets) which takes lots (too much) memory easily. This should be relatively cheap in memory.

$HistoryLength = 1;
ClearAll[DividePerfectSquarePairs, DividePerfectSquarePairsRecurse, SquareQ]
SquareQ[n_Integer] := SquareQ[n] = IntegerQ[Sqrt[n]]
DividePerfectSquarePairsRecurse[pairs_List, left_List] := Module[{cand},
  If[Length[left] > 2,
   Do[
    cand = {left[[1]], left[[i]]};
    If[SquareQ[Total[cand]],
     DividePerfectSquarePairsRecurse[Append[pairs, cand], Delete[left, {{1}, {i}}]]]
    , {i, 2, Length[left]}
    ]
   ,
   If[SquareQ[Total[left]], Throw[Append[pairs, left]];]
   ]
  ]
DividePerfectSquarePairs[n_Integer?EvenQ] := Catch[DividePerfectSquarePairsRecurse[{}, Range[n]]; Missing[]]
DividePerfectSquarePairs[n_Integer?OddQ] := Missing[]

This will recurse through all possibilities and return the first it finds (it can easily be modified to return all possibilities if you want).

DividePerfectSquarePairs[16]
Total /@ %

{{1, 8}, {2, 7}, {3, 6}, {4, 5}, {9, 16}, {10, 15}, {11, 14}, {12, 13}}
{9, 9, 9, 9, 25, 25, 25, 25}

Is it true it can not be done for 2,4, 6, 10, 12, 20, and 22 (and all the odd n of course)?

POSTED BY: Sander Huisman

How can your method have a solution to 35? The numbers 1..35 can not be divided in two pairs; it is an odd number?

POSTED BY: Sander Huisman

Thank you so much! Your response is incredibly helpful. As to your question, it is quickly shown that 2 does not work, because 1+2 =3. Similarly with 4, not every number can be placed in a perfect square pair, like 4. 4+1 fails, 4+2 fails, and 4+3 fails, so 4 has no partner. 6 fails because 6+3 works, and 4+5, but that leaves out 1 and 2. I have noticed that most failures happen around numbers that force an even pairing. Like 10 for instance. 10+6 works. But now we would need a strictly odd pair to balance out the even pair, like 1+3. But that leaves 8 with no possible partner. That has been my struggle with coding in Mathematica. I know how my brain approaches the problem, but that's entirely different from trying to write code that will do the problem.

POSTED BY: Ashley Louk

It can be slightly improved because some of them can be easily solved:

$HistoryLength = 1;
ClearAll[DividePerfectSquarePairs, DividePerfectSquarePairsRecurse, SquareQ]
SquareQ[n_Integer] := SquareQ[n] = IntegerQ[Sqrt[n]]
DividePerfectSquarePairsRecurse[pairs_List, left_List] := Module[{cand},
  If[Length[left] > 2,
   Do[
    cand = {left[[1]], left[[i]]};
    If[SquareQ[Total[cand]],
     DividePerfectSquarePairsRecurse[Append[pairs, cand], Delete[left, {{1}, {i}}]]
     ]
    ,
    {i, 2, Length[left]}
    ]
   ,
   If[SquareQ[Total[left]],
    Throw[Append[pairs, left]];
    ]
   ]
  ]
DividePerfectSquarePairs[n_Integer?EvenQ] := Module[{},
  If[SquareQ[n + 1],
   Transpose[{Range[1, n/2], Range[n, n/2 + 1, -1]}]
   ,
   Catch[DividePerfectSquarePairsRecurse[{}, Range[n]]; Missing[]]
   ]
  ]
DividePerfectSquarePairs[n_Integer?OddQ] := Missing[]
POSTED BY: Sander Huisman

So this code accounts for what I think of as zipper sets, where n is one less than a perfect square, to the set can be paired (1,n),(1,n-1),etc.

POSTED BY: Ashley Louk

Correct!

POSTED BY: Sander Huisman

A nice way to visualize is as follows:

VisualizePairs[sol : {{_Integer, _Integer} ..}] := Module[{numbers, len, pos, angles, txts, lines, colors},
  numbers = Union @@ sol;
  colors = ColorData[109] /@ (Sqrt[Total[sol, {2}]] - 1);
  len = Length[numbers];
  pos = Reverse[CirclePoints[{0.0, 0.0}, {1.5, \[Pi]/2 + \[Pi]/len}, len]];
  angles = If[#1 < 0, -{##}, {##}] & @@@ pos;
  txts = MapThread[Text[#1, #2, {0, 0}, #3] &, {numbers, pos, angles}];
  lines = Association[Thread[numbers -> pos]];
  lines = Map[lines, sol, {2}];
  lines = 0.9 {#1, {0, 0}, #2} & @@@ lines;
  Graphics[{txts, Thick, Riffle[colors, BezierCurve /@ lines]}, ImageSize -> 300, PlotRange -> 1.6]
  ]

We can now generate some test:

Partition[
  VisualizePairs[DividePerfectSquarePairs[#]] & /@ 
   DeleteCases[Range[8, 46, 2], 10 | 12 | 20 | 22], UpTo[2]] // Grid

giving:

enter image description here

POSTED BY: Sander Huisman

I further updated the code to be more smart for large n:

$HistoryLength = 1;
ClearAll[DividePerfectSquarePairs, DividePerfectSquarePairsRecurse, SquareQ, PureQ, VisualizePairs]
SquareQ[n_Integer] := SquareQ[n] = IntegerQ[Sqrt[n]]
PureQ[l_List] := (l === Range[1, Length[l]])
DividePerfectSquarePairsRecurse[pairs_List, left_List] := Module[{cand, n, t, newpairs},
  If[PureQ[left] \[And] Length[left] >= 62,
   n = Length[left];
   t = SelectFirst[Range[n], SquareQ[# + n] \[And] OddQ[#] \[And] # >= 25 &];
   newpairs = {Range[t, t + ((n - t) - 1)/2], Reverse@Range[t + ((n - t) - 1)/2 + 1, n]};
   DividePerfectSquarePairsRecurse[Join[Transpose[newpairs], pairs], Range[t - 1]]
   ,
   If[Length[left] > 2,
    Do[cand = {left[[1]], left[[i]]};
     If[SquareQ[Total[cand]],
      DividePerfectSquarePairsRecurse[Append[pairs, cand], 
       Delete[left, {{1}, {i}}]]
      ]
     ,
     {i, 2, Length[left]}
     ]
    ,
    If[SquareQ[Total[left]],
     Throw[Append[pairs, left]];
     ]
    ]
   ]
  ]
DividePerfectSquarePairs[n_Integer?EvenQ] := Module[{},
  If[SquareQ[n + 1],
   Transpose[{Range[1, n/2], Range[n, n/2 + 1, -1]}]
   ,
   Catch[DividePerfectSquarePairsRecurse[{}, Range[n]]; Missing[]]
   ]
  ]
DividePerfectSquarePairs[n_Integer?OddQ] := Missing[]
VisualizePairs[sol : {{_Integer, _Integer} ..}] := 
 Module[{numbers, len, pos, angles, txts, lines, colors},
  numbers = Union @@ sol;
  colors = ColorData[109] /@ (Sqrt[Total[sol, {2}]] - 1);
  len = Length[numbers];
  pos = Reverse[CirclePoints[{0.0, 0.0}, {1.5, \[Pi]/2 + \[Pi]/len}, len]];
  angles = If[#1 < 0, -{##}, {##}] & @@@ pos;
  txts = MapThread[Text[#1, #2, {0, 0}, #3] &, {numbers, pos, angles}];
  lines = Association[Thread[numbers -> pos]];
  lines = Map[lines, sol, {2}];
  lines = 0.9 {#1, {0, 0}, #2} & @@@ lines;
  Graphics[{txts, Thick, Riffle[colors, BezierCurve /@ lines]}, ImageSize -> 300, PlotRange -> 1.6]
 ]

It can now easily handle n=1000 or even much much bigger (for fun I tried 10^6 and it found the solution in ~19 seconds). It does so by finding a low m (but not too low) such that m....a and a+1 ... n can form pairs that form squares. This reduces then the problem from finding a solution for n, to finding a solution for m-1 (because m...n are 'eliminated').

So now one can type in:

VisualizePairs[DividePerfectSquarePairs[250]]

and quickly get the visual:

enter image description here

Notice that the problem is quickly reduced to a problem for n = 38. For which we use the old method. One could hard-code the solution for n=2...60 and then this function will be nearly instantaneous for all n (except those that can't be split up).

POSTED BY: Sander Huisman

Wow. That's impressive. Thanks for all your help.

POSTED BY: Ashley Louk

I figured it out! SelectFirst is not a function in my version of Mathematica. But by changing the code to First[Select[... it works as expected. I stared at that for hours before it finally dawned on me that SelectFirst should not be blue after I evaluate the cell, unless it's not been given and assignment.

POSTED BY: Ashley Louk

SelectFirst

Was introduced in version 10. So that explains it. You can replace it indeed by the code you mentioned or define a SelectFirst yourself... note that the built-in is a bit more efficient; it will stop 'selecting' after finding the first one, where as yours will 'select' all of them first...

Good luck

POSTED BY: Sander Huisman

Any suggestions on what I can replace CirclePoints and Association with? They are also mathematica 10 commands. I guess I really do need to hassle the IT department for an upgrade.

POSTED BY: Ashley Louk

CirclePoints can be changed to circlepoints:

circlepoints[{x_, y_}, {r_, \[Theta]_}, n_Integer] := 
 Table[{x + r Cos[t + \[Theta]], y + r Sin[t + \[Theta]]}, {t, 
   Most@Range[0, 2 Pi, 2 Pi/n]}]
POSTED BY: Sander Huisman

And Association can be avoided by replacing the two consecutive lines as follows:

lines = Thread[numbers -> pos];
lines = Map[# /. lines &, sol, {2}];
POSTED BY: Sander Huisman
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract