Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <h2>Place the milestones one by one</h2> <p><strong>EDIT</strong> See new implementation below (with timings).</p> <p>The key idea is the following: </p> <ol> <li>Build a list of milestones one by one, starting with one milestone at <code>0</code> and a milestone at <code>max(distances)</code>. Lets call them <em>endpoints</em>.</li> <li>The largest distance that's not accounted for has to be from one of the endpoints, which leaves at most two positions for the corresponding milestone.</li> </ol> <p>The following Python program simply checks if the milestone can be placed from the left endpoint, and if not, tries to place the milestone from the right endpoint (always using the largest distances that's not accounted for by the already placed milestones). This has to be done with back-tracking, as placements may turn out wrong later.</p> <p>Note that there is another (mirrored) solution that is not output. (I don't think there can be more than 2 solutions (symmetric), but I haven't proven it.)</p> <p>I consider the position of the milestones as the <code>solution</code> and use a helper function <code>steps</code> for the output desired by the OP.</p> <pre class="lang-python prettyprint-override"><code>from collections import Counter def milestones_from_dists(dists, milestones=None): if not dists: # all dist are acounted for: we have a solution! return milestones if milestones is None: milestones = [0] max_dist = max(dists) solution_from_left = try_milestone(dists, milestones, min(milestones) + max_dist) if solution_from_left is not None: return solution_from_left return try_milestone(dists, milestones, max(milestones) - max_dist) def try_milestone(dists, milestones, new_milestone): unused_dists = Counter(dists) for milestone in milestones: dist = abs(milestone - new_milestone) if unused_dists[dist]: unused_dists[dist] -= 1 if unused_dists[dist] == 0: del unused_dists[dist] else: return None # no solution return milestones_from_dists(unused_dists, milestones + [new_milestone]) def steps(milestones): milestones = sorted(milestones) return [milestones[i] - milestones[i - 1] for i in range(1, len(milestones))] </code></pre> <p>Example usage:</p> <pre class="lang-python prettyprint-override"><code>&gt;&gt;&gt; print(steps(milestones_from_dists([7, 10, 5, 2, 8, 3]))) [3, 5, 2] &gt;&gt;&gt; import random &gt;&gt;&gt; milestones = random.sample(range(1000), 100) &gt;&gt;&gt; dists = [abs(x - y) for x in milestones for y in milestones if x &lt; y] &gt;&gt;&gt; solution = sorted(milestones_from_dists(dists)) &gt;&gt;&gt; solution == sorted(milestones) True &gt;&gt;&gt; print(solution) [0, 10, 16, 23, 33, 63, 72, 89, 97, 108, 131, 146, 152, 153, 156, 159, 171, 188, 210, 211, 212, 215, 219, 234, 248, 249, 273, 320, 325, 329, 339, 357, 363, 387, 394, 396, 402, 408, 412, 418, 426, 463, 469, 472, 473, 485, 506, 515, 517, 533, 536, 549, 586, 613, 614, 615, 622, 625, 630, 634, 640, 649, 651, 653, 671, 674, 697, 698, 711, 715, 720, 730, 731, 733, 747, 758, 770, 772, 773, 776, 777, 778, 783, 784, 789, 809, 828, 832, 833, 855, 861, 873, 891, 894, 918, 952, 953, 968, 977, 979] &gt;&gt;&gt; print(steps(solution)) [10, 6, 7, 10, 30, 9, 17, 8, 11, 23, 15, 6, 1, 3, 3, 12, 17, 22, 1, 1, 3, 4, 15, 14, 1, 24, 47, 5, 4, 10, 18, 6, 24, 7, 2, 6, 6, 4, 6, 8, 37, 6, 3, 1, 12, 21, 9, 2, 16, 3, 13, 37, 27, 1, 1, 7, 3, 5, 4, 6, 9, 2, 2, 18, 3, 23, 1, 13, 4, 5, 10, 1, 2, 14, 11, 12, 2, 1, 3, 1, 1, 5, 1, 5, 20, 19, 4, 1, 22, 6, 12, 18, 3, 24, 34, 1, 15, 9, 2] </code></pre> <p><strong>New implementation incorporationg suggestions from the comments</strong></p> <pre class="lang-python prettyprint-override"><code>from collections import Counter def milestones_from_dists(dists): dists = Counter(dists) right_end = max(dists) milestones = [0, right_end] del dists[right_end] sorted_dists = sorted(dists) add_milestones_from_dists(dists, milestones, sorted_dists, right_end) return milestones </code></pre> <p>def add_milestone</p> <pre><code>s_from_dists(dists, milestones, sorted_dists, right_end): if not dists: return True # success! # find max dist that's not fully used yet deleted_dists = [] while not dists[sorted_dists[-1]]: deleted_dists.append(sorted_dists[-1]) del sorted_dists[-1] max_dist = sorted_dists[-1] # for both possible positions, check if this fits the already placed milestones for new_milestone in [max_dist, right_end - max_dist]: used_dists = Counter() # for backing up for milestone in milestones: dist = abs(milestone - new_milestone) if dists[dist]: # this distance is still available dists[dist] -= 1 if dists[dist] == 0: del dists[dist] used_dists[dist] += 1 else: # no solution dists.update(used_dists) # back up sorted_dists.extend(reversed(deleted_dists)) break else: # unbroken milestones.append(new_milestone) success = add_milestones_from_dists(dists, milestones, sorted_dists, right_end) if success: return True dists.update(used_dists) # back up sorted_dists.extend(reversed(deleted_dists)) del milestones[-1] return False def steps(milestones): milestones = sorted(milestones) return [milestones[i] - milestones[i - 1] for i in range(1, len(milestones))] </code></pre> <p>Timings for random milestones in the range from 0 to 100000:</p> <ul> <li><p>n = 10: 0.00s</p></li> <li><p>n = 100: 0.05s</p></li> <li><p>n = 1000: 3.20s</p></li> <li><p>n = 10000: still takes too long.</p></li> </ul>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload