Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <body>
- <pre><code>
- 1 2 2 1
- 1 3 3 1
- 1 3 3 2
- 2 2 2 2
- </code></pre>
- <pre><code>3 3 4 5
- 5 4 2 3
- 4 2 1 1
- 5 5 2 4
- </code></pre>
- <pre><code>2 5 3 1
- 4 3 1 3
- 4 5 1 3
- 4 1 5 4
- </code></pre>
- <pre><code>5 1 4 2
- 1 5 1 3
- 2 4 3 5
- 1 2 3 2
- </code></pre>
- <pre><code>3 3 2 5
- 3 2 5 3
- 2 4 1 5
- 4 2 3 1
- </code></pre>
- <pre><code>4 4 1 4
- 5 1 5 1
- 3 5 1 3
- 1 4 3 4
- </code></pre>
- <pre><code>1 7 2 7 6
- 5 5 5 5 3
- 6 5 6 6 2
- 1 4 1 2 7
- 4 6 1 4 1
- </code></pre>
- <pre><code>
- 1 2 5 5 3 4
- 1 3 5 1 7 2
- 5 5 5 1 2 2
- 5 1 7 1 3 7
- 6 3 4 1 4 9
- 3 6 3 4 4 4
- </code></pre>
- </body>
- <script type='application/javascript'>
- /*12/23/16 [Hard] puzzle for /r/dailyprogrammer */
- /*
- Input is a grid of numbers,
- official base spec is a 4x4 grid of numbers 1-5
- Any adjacent cells (sharing a wall) that have the same value form a group.
- Groups can be incremented or decremented by 1 as a 'move',
- If their new value equals that of an adjacent group, the two groups merge.
- The challenge is to come up with the minumum moves to 'flatten' a grid into one group
- I felt like this problem could be solved fairly difinitively, rather than relying on
- a constrained brute-force approach.
- */
- (()=>{
- let start = new Date();
- let blocks = document.getElementsByTagName('code');
- for(let b in blocks){
- b = blocks[b];
- let input = b.innerHTML;
- if(!input) { continue; }
- let str = input;
- str = str.replace(/\n/g,'');
- str = str.replace(/\s/g,'');
- log('entibo.fr/lvlr/#'+str);// The problem as a game
- let puzzle = new flood_fill(input);
- puzzle.solve();
- }
- let end = new Date();
- log('elapsed: ' + (end-start) +'ms')
- })()
- function log(msg){ console.log(msg+'\n'); }
- function flood_fill(input){
- this.initial_input = input;
- this.input = [], // initial input stored as two-dimensional array for coordinate parsing.
- this.cells = {},
- this.groups = {},
- input = input.split('\n');// parse initial input into two-dimensional array
- for(let i in input){ if(input[i].length>0){ this.input.push(input[i].split(' ')); } }
- // First pass: traverse input array
- // Define 'cell' objects for each coordinate
- // Create new groups for them, or merge them with appropriate existing groups
- // Create interlocking references with the cells and the groups
- for(let x in this.input){
- let col = this.input[x];
- let abc = 'abcdefghijklmnopqrstuvwxyz';// Used in group-naming
- for(let y in col){
- let new_cell = {
- coords: [x,y],
- name : x+','+y,
- value : col[y],
- group : null
- }
- let first_neighbor = this.cells[x+','+(y-1)];
- let second_neighbor = this.cells[(x-1)+','+y];
- if(first_neighbor && first_neighbor.value === new_cell.value){
- first_neighbor.group.cells.push(new_cell);
- new_cell.group = first_neighbor.group;
- }
- else if (second_neighbor && second_neighbor.value === new_cell.value){
- second_neighbor.group.cells.push(new_cell);
- new_cell.group = second_neighbor.group;
- }
- else {
- for(let l in abc){
- let letter = abc[l];
- if(this.groups[new_cell.value+letter]){ continue; }
- let new_group = {
- name : new_cell.value+letter,
- value : new_cell.value,
- cells : [new_cell],
- neighbors : []
- }
- this.groups[new_group.name] = new_group;
- new_cell.group = new_group;
- break;
- }
- }
- this.cells[new_cell.name] = new_cell;
- }
- }
- // Second pass: loop over our defined cells
- // If two adjacent groups share the same value, they get merged.
- // Each group records their neighbors.
- for(let c in this.cells){
- let cells = this.cells
- let cell = cells[c];
- let coords = [+cell.coords[0],+cell.coords[1]];
- let neighbors = [
- cells[(coords[0]-1)+','+coords[1]],
- cells[coords[0]+','+(coords[1]-1)],
- cells[(coords[0]+1)+','+coords[1]],
- cells[coords[0]+','+(coords[1]+1)]
- ];
- for(let n in neighbors){
- let neighbor = neighbors[n];
- if(!neighbor){ continue; }
- // If our neighbor shares our value but not our group, we merge the groups
- if(cell.value === neighbor.value && cell.group !== neighbor.group){
- let ng = neighbor.group;
- for(let nc in ng.cells){ // for each of the neighbor's members
- nc = ng.cells[nc];
- cell.group.cells.push(nc);// merge that group's cells into ours
- nc.group = cell.group;// and set their group to ours
- }
- delete this.groups[ng.name]; // then kill off the old reference
- }// Else, we can add a reference to the neighboring group if we don't have one
- else if (neighbor.value !== cell.value && !cell.group.neighbors.includes(neighbor.group)){
- cell.group.neighbors.push(neighbor.group);
- }
- }
- }
- /*** ending initialization ***/
- /*** beginning solution helper functions ***/
- let puzzle = this;
- /* Identifies, sorts, and returns, all peaks and troughs.
- Peaks and troughs are groups where all adjacent groups have a lower/higher value. */
- let get_peaks_and_troughs = function (groups){
- let return_value = { peaks:[],troughs:[] }
- groups.forEach((g)=>{
- let is_peak = true, is_trough = true;
- g.neighbors.forEach((n)=>{
- if(groups.includes(n)){
- if(n.value > g.value) { is_peak = false; }
- if(n.value < g.value) { is_trough = false; }
- }
- });
- if(is_peak) { return_value.peaks.push(g); }
- if(is_trough) { return_value.troughs.push(g); }
- });
- return_value.peaks.sort((x,y)=>{ return x.value < y.value; });
- return_value.troughs.sort((x,y)=>{ return x.value>y.value; });
- return return_value;
- }
- /* Part of our path finding algorithm. Takes an existing path or
- seeds a new one. Then attempts to fork that path into the next
- 1-4 adjacent possibilities. */
- let get_paths = function(working_group, needed_groups, current_path){
- let return_paths = [];
- // We start from our last added group or, if none, from one of our needed groups
- // and add all neighbors we don't already have that are within our permissible set.
- let current_group = current_path[0] || needed_groups[0];
- current_group.neighbors.forEach((n)=>{
- let current = current_path.slice();
- if(!current.includes(n) && working_group.includes(n)){
- current.unshift(n);
- return_paths.push(current);
- }
- });
- return return_paths;
- }
- /* Finds the cheapest path (group of groups) taht connects all needed_groups.
- cheapness is the cost to raise or lower all members of the group to the set_to value
- working_group: The group of cells we are working with, either the whole board or a subset.
- needed_groups: The groups we need to connect to one another
- score_func: either raise_troughs() or lower_peaks depending on our needs during the call.
- set_to: when evaluating the score of a path, it is the number of steps to have all members
- of the path be set to this value. */
- get_best_path = function(working_group, needed_groups, score_func, set_to){
- let best_path;
- let paths = get_paths(working_group,needed_groups,[]);
- // Path finding: early-exit permutations of all groups that connect a set of groups
- for(let _ in puzzle.cells){
- if(paths.length ===0){ break; }
- let copy = paths.slice();
- paths = [];
- copy.forEach((p)=>{ // for each existing path, get all next-step branches
- paths = paths.concat(get_paths(working_group,needed_groups,p));
- });
- let complete_paths = [];
- for(let p in paths){
- p = paths[p];
- // score each path in relation to the best we have,
- // if it is more expensive, drop it.
- let path_score = score_func(p, set_to);
- if(best_path && path_score >= best_path.score){
- paths.splice(paths.indexOf(p));
- continue;
- }
- // then, check if it is a completed path
- let needed = needed_groups.slice();
- p.forEach((g)=>{
- g.neighbors.forEach((n)=>{
- let ndx = needed.indexOf(n);
- if(ndx > -1) { needed.splice(ndx,1); }
- });
- });
- if(needed.length === 0) { // If it is complete, compare it to our best
- if(!best_path || path_score.cost < best_path.cost){
- best_path = path_score;
- best_path.path = p;
- }
- // and remove any completed paths.
- paths.splice(paths.indexOf(p));
- }
- }
- }
- // fixes edge-cases where needed_group[0], which is used to seed the paths, is in the middle of a linear path.
- if(!best_path){ needed_groups.push(needed_groups.shift());
- best_path = get_best_path(working_group, needed_groups, score_func, set_to); }
- return best_path;
- }
- /* Get all the peaks in a set of groups, and calculate the cost to lower them down to the lowest value */
- let lower_peaks = (groups, lower_to)=>{
- let pk = get_peaks_and_troughs(groups);
- let peaks = pk.peaks;
- let cost_to_flatten = 0;
- if(peaks.length > 1){ cost_to_flatten += peaks[0].value - peaks[1].value; }
- let start_value = peaks.length>1?peaks[1].value:peaks[0].value;
- let message = 'Lowering high group: ';
- peaks.forEach((p)=>{ message += p.name + ', '; });
- if(cost_to_flatten > 0){ message += '\n ' +cost_to_flatten + ' points spent to equalize top 2+ members'; }
- let path;
- if(peaks.length > 1){
- path = get_best_path(groups, peaks, raise_troughs, start_value);
- message += '\n Bridging peaks with a common path. ';
- message += '\n Bridge consists of members: ';
- path.path.forEach((p)=>{ message+=p.name+', '; });
- message += '\n Spending ' + path.cost + ' points to raise bridge to ' + start_value;
- cost_to_flatten += path.cost;
- }
- let low_value = 0; // Lowest value is the lowest value in groups that isn't a part of our path.
- pk.troughs.forEach((t)=>{
- if(path && path.path.includes(t)){ }
- else if(low_value === 0 || t.value < low_value){ low_value = t.value; }
- });
- if(lower_to){ low_value = lower_to; }
- let last_cost = start_value - low_value;
- message += '\n Spending ' + last_cost + ' points to lower peak/s to ' + low_value;
- return { message:message, cost: cost_to_flatten+last_cost, value: low_value };
- }
- /* Get all the troughs in a set of groups, and calculate the cost to raise them to the highest value. */
- let raise_troughs = (groups, raise_to)=>{
- let pk = get_peaks_and_troughs(groups);
- let troughs = pk.troughs;
- let cost_to_raise = 0;
- if(troughs.length > 1) { cost_to_raise += troughs[1].value - troughs[0].value; }
- let start_value = troughs.length > 1 ? troughs[1].value : troughs[0].value;
- let message = 'Raising low group: ';
- troughs.forEach((p)=>{ message += p.name + ', '; });
- if(cost_to_raise > 0) { message += '\n ' +cost_to_raise+ ' points spent to equalize bottom 2 members'; }
- let path;
- if(troughs.length > 1){
- path = get_best_path(groups,troughs,lower_peaks,start_value);
- message += '\n Bridging troughs with a common path. ';
- message += '\n Bridge consists of members: ';
- path.path.forEach((p)=>{ message+=p.name+', '; });
- message += '\n Spending ' + path.cost + ' points to lower bridge to ' + start_value;
- cost_to_raise += path.cost;
- }
- let high_value = 0;
- pk.peaks.forEach((p)=>{
- if(path && path.path.includes(p)){}
- else if(high_value === 0 || p.value > high_value) { high_value = p.value; }
- });
- if(raise_to){ high_value = raise_to; }
- let last_cost = high_value - start_value;
- message += '\n Sending ' + last_cost + ' points to raise trough/s to ' + high_value;
- return { message:message, cost: cost_to_raise+last_cost, value : high_value };
- }
- // Calculates the 'score' of a group of groups,
- // or how many moves it will take to raise or lower all members
- // to a uniform goal number
- this.solve = function(){
- let groups = [];
- log('Solving for input: ');
- log(puzzle.initial_input);
- for(let g in this.groups){ groups.push(this.groups[g]); }
- let lower = lower_peaks(groups);
- let raise = raise_troughs(groups);
- let best = (lower.cost <= raise.cost) ? lower : raise;
- log('Solved with cost of ' + best.cost + ' with following approach');
- log(best.message)
- }
- return this;
- }
- </script>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement