How to order the graph in obtained from a shelf - algorithm

Let me describe the picture and then ask the question. So below is the shelf having three rows. Each of the numbers represents a box. The black dot represents the centroid of the box and can be represented in the $(x,y)$ coordinate. This can be seen as input. So input is the list of $(x,y)$ tuples. In this case, there are a list of 29 tuples. I wanted a programme to output the tuples in the order that is
1<< 2 << 3 <<4 ......
My initial idea is to use lexicographic ordering which is defined as follows
(a,b) << (c,d) if and only if a< c or (a= c, b < d)
Its total ordering like that is given any two points we can tell if one is less than the other.
Now here the problem arises, so putting lexicographic ordering I can it can identify
1<< 2 but then as box 3 have a coordinate y less than 2 the order becomes 1<< 3 <<2 and so on.
I put a short code for lexicographic ordering
Data = [(1,1) , (2,1) ,( 1,3), (2,) ]
for i in range(len(data)):
for j in range(i, len(data) - 1):
if data[i][0] > data[j][0]:
temp = data[j]
data[j] = data[i]
data[i] = temp
else:
if data[i][1] > data[j][1]:
temp = data[j]
data[j] = data[i]
data[i] = temp
Now one possible way to get around the problem is to make the boxes stacked on the shelf in horizontal alignment and then use lexicographic ordering. But the point is given input as a list of tuples coordinates it doesn't understand what is adjacent to it. So I am thinking there should be some explanation with graphs. So let's make a graph in which each node is a box and we connect two nodes if the box in the same shelf and adjacent to each other. Now if the node has a degree strictly more than 2 that could be recognised that there is a vertical stacking next to it. Hence now the ordering has to done say only with respect to $y$ ?
Please let me know how can I code this idea or any other package etc deals with a similar idea or reference.

I might have misunderstood something, but if not the following is a working solution for a single shelf.
A shelf of boxes is a set of triples, (x, y, n).
The order relation, <<, on the set is then defined as:
a << b <=> a.x < b.x or a.x = b.x and a.y > b.y
Assumptions:
Two boxes cannot be in the same position, that is, a.x = b.x -> not a.y = b.y
This can be trivially implemented in many programming languages. For example (using a pseudogeneric syntax):
boxes = [ ..., (x, y, n), ... ]
boxes.sort((a,b) =>
if a.x < b.x return a_is_before_b
if a.x = b.x and a.y > b.y return a_is_before_b
return a_is_after_b
)
The above is encoded below in javascript.
function print(lob) {
text = "";
for ( i = 0; i < lob.length; i++ ) {
text += '<br>' + 'Box ' + lob[i][2] + ' at (' + lob[i][0] + ', ' + lob[i][1] + ')'
}
return text;
}
boxes = [ [ 11.5, 3.5, 9 ], [ 9.5, 3.5, 8 ], [ 8.5, 3.5, 7 ], [ 4.5, 1.5, 4 ], [ 7.5, 2.5, 6 ], [ 4.5, 5.5, 2 ], [ 4.5, 3.5, 3 ], [ 7.5, 4.5, 5 ], [ 2.5, 3.5, 1 ] ];
function onload() {
document.getElementById("print1").innerHTML = print(boxes);
boxes.sort( (a, b) => { if ( a[0] < b[0] ) return -1; if ( a[0] == b[0] && a[1] > b[1] ) return -1; return 1 } );
document.getElementById("print2").innerHTML = print(boxes);
}
<body onload="onload()">
List of boxes before sort:
<div id="print1"></div>
<br>
List of boxes after sort:
<div id="print2"></div>
</body>

Related

how to fast judge two double set intersect or not?

I want to fast judge two double set intersect or not.
problem
The element in set can be all range. The element in set are not ordered. Each set have 100,000+ element.
If exist a double a from set A, a double b from set B, a and b is very close,for example abs(a-b)<1e-6, we say set A and B intersect.
My way
calculate the range(lower bound and upper bound) of set_A and set_B
O(n), n is set's size
calculate range intersection rang_intersect of range_A and range_B
O(1)
if rang_intersect empty two set not intersect.
O(1)
if range_intersect not empty, find sub_set_A from set_A which in the range_intersect, find sub_set_B from set_B which in the range_intersect
O(n)
sort sub_set_A and sub_set_B
O(mlogm) m is sub_set_A's size
tranvers sub_set_A_sorted and sub_set_B_sorted by two pointer. find if exist element close, if exist two set intersect, if not, two set not intersect.
O(m)
My way can works, but I wonder if I can do faster.
Appendix
Why I want this:
Actually I am face a problem to judge two point set A & B collision or not. Each point p in point set have a double coordinate x,y,z. If exist a point a from point set A, a point b from point set B, a and b's coordinate very close, we say point set A and B collision.
In 3d case, we can define the order of point by first compare x then compare y, last compare z.
We can define the close that if all dimension's coordinate is close , the two point close.
This problem can convert to the problem above.
Some idea by gridding the space:
Let's take the point (1.2, 2.4, 3.6) with minimial distance required 1.
We may say that this point "touches" 8 unit cubes of R^3
[
(1.0, 2.0, 3.5)
(1.0, 2.0, 4.0)
(1.0, 2.5, 3.5) // 1 < 1.2 < 1.5
(1.0, 2.5, 4.0) // 2 < 2.4 < 2.5
(1.5, 2.0, 3.5) // 3.5 < 3.6 < 4
(1.5, 2.0, 4.0)
(1.5, 2.5, 3.5)
(1.5, 2.5, 4.0)
]
If two points are close to each other, their will be connected by some of their cube.
y
^
|
3 +---+---+
| | |
2.5+-------+---+---+
| a | | c | b |
2 +---+---+---+---+--->x
1 1.5 2
In example above in 2D plan, a is (1.2, 2.4).
Say b is (2.5, 2.4). b will touch the square (2,2), but a does not.
So they are not connected (indeed the min distance possible is (2.5-1.5===1).
Say c is (2.45, 2.4). c touches the square (1.5, 2). So is a. We check.
The main idea is to associate to each point its 8 cubes.
We can associate a uniq hash to each cube: the top level coordinate. e.g "{x}-{y}-{z}"
To check if A intersects B:
we build for each point of A its 8 hashes and store them in a hashmap: hash->point
for each point of B, we build the hashes, and if one of those exist in the hashmap we check if the corresponding points are in relation
Now consider
y
^
|
3 +---+---+
| a2| |
2.5+-------+
| a1| |
2 +---+---+
1 1.5 2
a2 and a1 's hashes will overlap on squares (1,2) and (1,2.5). So the hashmap is actually hash->points.
This implies that worst case could be O(n^2) if all the points land into the same cubes. Hopefully in reality they won't?
Below a code with irrelevant data:
(put 10**4 to avoid freezing the ui)
function roundEps (c, nth) {
const eps = 10**-nth
const r = (c % eps)
const v = (r >= eps / 2) ? [c-r+eps/2, c-r+eps] : [c-r, c-r+eps/2]
return v.map(x => x.toFixed(nth + 1))
}
function buildHashes (p, nth) {
return p.reduce((hashes, c) => {
const out = []
hashes.forEach(hash => {
const [l, u] = roundEps(c, nth)
out.push(`${hash},${l}`, `${hash},${u}`)
})
return out
},[''])
}
function buildMap (A, nth) {
const hashToPoints = new Map()
A.forEach(p => {
const hashes = buildHashes(p, nth)
hashes.forEach(hash => {
const v = hashToPoints.get(hash) || []
v.push(p)
hashToPoints.set(hash, v)
})
})
return hashToPoints
}
function intersects (m, b, nth, R) {
let processed = new Set()
return buildHashes(b, nth).some(hash => {
if (!m.has(hash)) return
const pts = m.get(hash)
if (processed.has(pts)) return
processed.add(pts)
return pts.some(p => R(p, b))
})
}
function d (a, b) {
return a.reduce((dist, x, i) => {
return Math.max(dist, Math.abs(x-b[i]))
}, 0)
}
function checkIntersection (A, B, nth=2) {
const m = buildMap(A, nth)
return B.some(b => intersects(m, b, nth, (a,b) => d(a, b) < 10**(-nth)))
}
// ephemeral testing :)
/*
function test () {
const assert = require('assert')
function testRound () {
assert.deepEqual(roundEps(127.857, 2), ['127.855', '127.860'])
assert.deepEqual(roundEps(127.853, 2), ['127.850', '127.855'])
assert.deepEqual(roundEps(127.855, 2), ['127.855', '127.860'])
}
function testD () {
assert.strictEqual(d([1,2,3],[5,1,2]), 4)
assert.strictEqual(d([1,2,3],[0,1,2]), 1)
}
function testCheckIntersection () {
{
const A = [[1.213,2.178,1.254],[0.002,1.231,2.695]]
const B = [[1.213,2.178,1.254],[0.002,1.231,2.695]]
assert(checkIntersection(A, B))
}
{
const A = [[1.213,2.178,1.254],[0.002,1.231,2.695]]
const B = [[10,20,30]]
assert(!checkIntersection(A, B))
}
{
const A = [[0,0,0]]
const B = [[0,0,0.06]]
assert(!checkIntersection(A, B, 2))
}
{
const A = [[0,0,0.013]]
const B = [[0,0,0.006]]
assert(checkIntersection(A, B, 2))
}
}
testRound()
testD()
testCheckIntersection()
}*/
const A = []
const B = []
for (let i = 0; i < 10**4; ++i) {
A.push([Math.random(), Math.random(), Math.random()])
B.push([Math.random(), Math.random(), Math.random()])
}
console.time('start')
console.log('intersect? ', checkIntersection(A, B, 6))
console.timeEnd('start')

Cutting algorithm of two dimensional board

I have problem with my homework.
Given a board of dimensions m x n is given, cut this board into rectangular pieces with the best total price. A matrix gives the price for each possible board size up through the original, uncut board.
Consider a 2 x 2 board with the price matrix:
3 4
3 6
We have a constant cost for each cutting for example 1.
Piece of length 1 x 1 is worth 3.
Horizontal piece of length 1 x 2 is worth 4.
Vertical piece of length 1 x 2 is worth 3.
Whole board is worth 6.
For this example, the optimal profit is 9, because we cut board into 1 x 1 pieces. Each piece is worth 3 and we done a 3 cut, so 4 x 3 - 3 x 1 = 9.
Second example:
1 2
3 4
Now I have to consider all the solutions:
4 1x1 pieces is worth 4x1 - (cost of cutting) 3x1 = 1
2 horizontal 1x2 is worth 2x2 - (cost of cutting) 1x1 = 3
2 vertical 1x2 is worth 3x2 - (cost of cutting) 1x1 = 5 -> best optimal profit
1 horizontal 1x2 + 2 x (1x1) pieces is worth 2 + 2 - (cost of cutting) 2 = 2
1 vertical 1x2 + 2 x (1x1) pieces is worth 3 + 2 - (cost of cutting) 2 = 3
I've read a lot about rod cutting algorithm but I don't have any idea how to bite this problem.
Do you have any ideas?
I did this in Python. The algorithm is
best_val = value of current board
check each horizontal and vertical cut for better value
for cut point <= half the current dimension (if none, return initial value)
recur on the two boards formed
if sum of values > best_val
... best_val = that sum
... record cut point and direction
return result: best_val, cut point, and direction
I'm not sure what you'll want for return values; I gave back the best value and tree of boards. For your second example, this is
(5, [[2, 1], [2, 1]])
Code, with debugging traces (indent and the labeled prints):
indent = ""
indent_len = 3
value = [[1, 2],
[3, 4]]
def best_cut(high, wide):
global indent
print indent, "ENTER", high, wide
indent += " " * indent_len
best_val = value[high-1][wide-1]
print indent, "Default", best_val
cut_vert = None
cut_val = best_val
cut_list = []
# Check horizontal cuts
for h_cut in range(1, 1 + high // 2):
print indent, "H_CUT", h_cut
cut_val1, cut_list1 = best_cut(h_cut, wide)
cut_val2, cut_list2 = best_cut(high - h_cut, wide)
cut_val = cut_val1 + cut_val2
if cut_val > best_val:
cut_list = [cut_list1, cut_list2]
print indent, "NEW H", h_cut, cut_val, cut_list
best_val = cut_val
cut_vert = False
best_h = h_cut
# Check vertical cuts
for v_cut in range(1, 1 + wide // 2):
print indent, "V_CUT", v_cut
cut_val1, cut_list1 = best_cut(high, v_cut)
cut_val2, cut_list2 = best_cut(high, wide - v_cut)
cut_val = cut_val1 + cut_val2
if cut_val > best_val:
cut_list = [cut_list1, cut_list2]
print indent, "NEW V", v_cut, cut_val, cut_list
best_val = cut_val
cut_vert = True
best_v = v_cut
# Return result of best cut
# Remember to subtract the cut cost
if cut_vert is None:
result = best_val , [high, wide]
elif cut_vert:
result = best_val-1, cut_list
else:
result = best_val-1, cut_list
indent = indent[indent_len:]
print indent, "LEAVE", cut_vert, result
return result
print best_cut(2, 2)
Output (profit and cut sizes) for each of the two tests:
(9, [[[1, 1], [1, 1]], [[1, 1], [1, 1]]])
(5, [[2, 1], [2, 1]])
Let f(h,w) represent the best total price achievable for a board with height h and width w with cutting price c. Then
f(h,w) = max(
price_matrix(h, w),
f(i, w) + f(h - i, w) - c,
f(h, j) + f(h, w - j) - c
)
for i = 1 to floor(h / 2)
for j = 1 to floor(w / 2)
Here's a bottom-up example in JavaScript that returns the filled table given the price matrix. The answer would be in the bottom right corner.
function f(prices, cost){
var m = new Array(prices.length);
for (let i=0; i<prices.length; i++)
m[i] = [];
for (let h=0; h<prices.length; h++){
for (let w=0; w<prices[0].length; w++){
m[h][w] = prices[h][w];
if (h == 0 && w == 0)
continue;
for (let i=1; i<(h+1>>1)+1; i++)
m[h][w] = Math.max(
m[h][w],
m[i-1][w] + m[h-i][w] - cost
);
for (let i=1; i<(w+1>>1)+1; i++)
m[h][w] = Math.max(
m[h][w],
m[h][i-1] + m[h][w-i] - cost
);
}
}
return m;
}
$('#submit').click(function(){
let prices = JSON.parse($('#input').val());
let result = f(prices, 1);
let str = result.map(line => JSON.stringify(line)).join('<br>');
$('#output').html(str);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="input">[[3, 4],
[3, 6]]</textarea>
<p><button type="button" id="submit">Submit</button></p>
<div id="output"><div>
Some thoughts on the problem rather than an answer:
It was a long time ago i studied dynamic programming, but i wrote up the following pseudo code which is think is O(n^2):
// 'Board'-class not included
val valueOfBoards: HashMap<Board, int>
fun cutBoard(b: Board, value: int) : int {
if (b.isEmpty()) return 0
if (valueOfBoards[b] > value) {
return 0;
} else {
valueOfBoards[b] = value
}
int maxValue = Integer.MIN_VALUE
for (Board piece : b.getPossiblePieces()) {
val (cuttingCost, smallerBoard) = b.cutOffPiece(piece)
val valueGained: int = piece.getPrice() - cuttingCost
maxValue = Max(maxValue, valueGained + cutBoard(smallerBoard, value + valueGained))
}
return maxValue;
}
The board class is not trivially implemented, here is some elaboration:
// returns all boards which fits in the current board
// for the initial board this will be width*height subboards
board.getPossiblePieces()
// returns a smaller board and the cutting cost of the cut
// I can see this becoming complex, depends on how one chooses to represent the board.
board.cutOffPiece(piece: Board)
It is not clear to me at the moment if cutOffPiece() breaks the algorithm in that you do not know how to optimally cut. I think since the algorithm will proceed from larger pieces to smaller pieces at some point it will be fine.
I tried to solve the re computation of sub problems (identical boards) by storing results in something like HashMap<Board, price> and comparing the new board with the stored best price before proceeding.
According to your answers I've prepared bottom-up and top-down implementation.
Bottom-up:
function bottomUp($high, $wide, $matrix){
$m = [];
for($h = 0; $h < $high; $h++){
for($w = 0; $w < $wide; $w++){
$m[$h][$w] = $matrix[$h][$w];
if($h == 0 && $w == 0){
continue;
}
for($i = 1; $i < ($h + 1 >> 1) + 1; $i++){
$m[$h][$w] = max(
$m[$h][$w],
$m[$i - 1][$w] + $m[$h - $i][$w] - CUT_COST
);
}
for($i = 1; $i < ($w + 1 >> 1) + 1; $i++){
$m[$h][$w] = max(
$m[$h][$w],
$m[$h][$i - 1] + $m[$h][$w - $i] - CUT_COST
);
}
}
}
return $m[$high-1][$wide-1];
}
Top-down:
function getBestCut($high, $wide, $matrix){
global $checked;
if(isset($checked[$high][$wide])){
return $checked[$high][$wide];
}
$bestVal = $matrix[$high-1][$wide-1];
$cutVert = CUT_VERT_NONE;
$cutVal = $bestVal;
$cutList = [];
for($hCut = 1; $hCut < 1 + floor($high/2); $hCut++){
$result1 = getBestCut($hCut, $wide, $matrix);
$cutVal1 = $result1[0];
$cutList1 = $result1[1];
$result2 = getBestCut($high - $hCut, $wide, $matrix);
$cutVal2 = $result2[0];
$cutList2 = $result2[1];
$cutVal = $cutVal1 + $cutVal2;
if($cutVal > $bestVal){
$cutList = [$cutList1, $cutList2];
$bestVal = $cutVal;
$cutVert = CUT_VERT_FALSE;
$bestH = $hCut;
}
$checked[$hCut][$wide] = $result1;
$checked[$high - $hCut][$wide] = $result2;
}
for($vCut = 1; $vCut < 1 + floor($wide/2); $vCut++){
$result1 = getBestCut($hCut, $vCut, $matrix);
$cutVal1 = $result1[0];
$cutList1 = $result1[1];
$result2 = getBestCut($high, $wide - $vCut, $matrix);
$cutVal2 = $result2[0];
$cutList2 = $result2[1];
$cutVal = $cutVal1 + $cutVal2;
if($cutVal > $bestVal){
$cutList = [$cutList1, $cutList2];
$bestVal = $cutVal;
$cutVert = CUT_VERT_TRUE;
$bestH = $vCut;
}
$checked[$hCut][$vCut] = $result1;
$checked[$high][$wide - $vCut] = $result2;
}
if($cutVert == CUT_VERT_NONE){
$result = [$bestVal, [$high, $wide]];
}else if($cutVert == CUT_VERT_TRUE){
$result = [$bestVal - CUT_COST, $cutList];
}else{
$result = [$bestVal - CUT_COST, $cutList];
}
return $result;
}
Please tell me are they correct implementation of this method?
I wonder if time complexity is O(m^2*n^2) in top-down method?

Maximum number achievable by converting two adjacent x to one (x+1)

Given a sequence of N integers where 1 <= N <= 500 and the numbers are between 1 and 50. In a step any two adjacent equal numbers x x can be replaced with a single x + 1. What is the maximum number achievable by such steps.
For example if given 2 3 1 1 2 2 then the maximum possible is 4:
2 3 1 1 2 2 ---> 2 3 2 2 2 ---> 2 3 3 2 ---> 2 4 2.
It is evident that I should try to do better than the maximum number available in the sequence. But I can't figure out a good algorithm.
Each substring of the input can make at most one single number (invariant: the log base two of the sum of two to the power of each entry). For every x, we can find the set of substrings that can make x. For each x, this is (1) every occurrence of x (2) the union of two contiguous substrings that can make x - 1. The resulting algorithm is O(N^2)-time.
An algorithm could work like this:
Convert the input to an array where every element has a frequency attribute, collapsing repeated consecutive values in the input into one single node. For example, this input:
1 2 2 4 3 3 3 3
Would be represented like this:
{val: 1, freq: 1} {val: 2, freq: 2} {val: 4, freq: 1} {val: 3, freq: 4}
Then find local minima nodes, like the node (3 3 3 3) in 1 (2 2) 4 (3 3 3 3) 4, i.e. nodes whose neighbours both have higher values. For those local minima that have an even frequency, "lift" those by applying the step. Repeat this until no such local minima (with even frequency) exist any more.
Start of the recursive part of the algorithm:
At both ends of the array, work inwards to "lift" values as long as the more inner neighbour has a higher value. With this rule, the following:
1 2 2 3 5 4 3 3 3 1 1
will completely resolve. First from the left side inward:
1 4 5 4 3 3 3 1 1
Then from the right side:
1 4 6 3 2
Note that when there is an odd frequency (like for the 3s above), there will be a "remainder" that cannot be incremented. The remainder should in this rule always be left on the outward side, so to maximise the potential towards the inner part of the array.
At this point the remaining local minima have odd frequencies. Applying the step to such a node will always leave a "remainder" (like above) with the original value. This remaining node can appear anywhere, but it only makes sense to look at solutions where this remainder is on the left side or the right side of the lift (not in the middle). So for example:
4 1 1 1 1 1 2 3 4
Can resolve to one of these:
4 2 2 1 2 3 4
Or:
4 1 2 2 2 3 4
The 1 in either second or fourth position, is the above mentioned "remainder". Obviously, the second way of resolving is more promising in this example. In general, the choice is obvious when on one side there is a value that is too high to merge with, like the left-most 4 is too high for five 1 values to get to. The 4 is like a wall.
When the frequency of the local minimum is one, there is nothing we can do with it. It actually separates the array in a left and right side that do not influence each other. The same is true for the remainder element discussed above: it separates the array into two parts that do not influence each other.
So the next step in the algorithm is to find such minima (where the choice is obvious), apply that kind of step and separate the problem into two distinct problems which should be solved recursively (from the top). So in the last example, the following two problems would be solved separately:
4
2 2 3 4
Then the best of both solutions will count as the overall solution. In this case that is 5.
The most challenging part of the algorithm is to deal with those local minima for which the choice of where to put the remainder is not obvious. For instance;
3 3 1 1 1 1 1 2 3
This can go to either:
3 3 2 2 1 2 3
3 3 1 2 2 2 3
In this example the end result is the same for both options, but in bigger arrays it would be less and less obvious. So here both options have to be investigated. In general you can have many of them, like 2 in this example:
3 1 1 1 2 3 1 1 1 1 1 3
Each of these two minima has two options. This seems like to explode into too many possibilities for larger arrays. But it is not that bad. The algorithm can take opposite choices in neighbouring minima, and go alternating like this through the whole array. This way alternating sections are favoured, and get the most possible value drawn into them, while the other sections are deprived of value. Now the algorithm turns the tables, and toggles all choices so that the sections that were previously favoured are now deprived, and vice versa. The solution of both these alternatives is derived by resolving each section recursively, and then comparing the two "grand" solutions to pick the best one.
Snippet
Here is a live JavaScript implementation of the above algorithm.
Comments are provided which hopefully should make it readable.
"use strict";
function Node(val, freq) {
// Immutable plain object
return Object.freeze({
val: val,
freq: freq || 1, // Default frequency is 1.
// Max attainable value when merged:
reduced: val + (freq || 1).toString(2).length - 1
});
}
function compress(a) {
// Put repeated elements in a single node
var result = [], i, j;
for (i = 0; i < a.length; i = j) {
for (j = i + 1; j < a.length && a[j] == a[i]; j++);
result.push(Node(a[i], j - i));
}
return result;
}
function decompress(a) {
// Expand nodes into separate, repeated elements
var result = [], i, j;
for (i = 0; i < a.length; i++) {
for (j = 0; j < a[i].freq; j++) {
result.push(a[i].val);
}
}
return result;
}
function str(a) {
return decompress(a).join(' ');
}
function unstr(s) {
s = s.replace(/\D+/g, ' ').trim();
return s.length ? compress(s.split(/\s+/).map(Number)) : [];
}
/*
The function merge modifies an array in-place, performing a "step" on
the indicated element.
The array will get an element with an incremented value
and decreased frequency, unless a join occurs with neighboring
elements with the same value: then the frequencies are accumulated
into one element. When the original frequency was odd there will
be a "remainder" element in the modified array as well.
*/
function merge(a, i, leftWards, stats) {
var val = a[i].val+1,
odd = a[i].freq % 2,
newFreq = a[i].freq >> 1,
last = i;
// Merge with neighbouring nodes of same value:
if ((!odd || !leftWards) && a[i+1] && a[i+1].val === val) {
newFreq += a[++last].freq;
}
if ((!odd || leftWards) && i && a[i-1].val === val) {
newFreq += a[--i].freq;
}
// Replace nodes
a.splice(i, last-i+1, Node(val, newFreq));
if (odd) a.splice(i+leftWards, 0, Node(val-1));
// Update statistics and trace: this is not essential to the algorithm
if (stats) {
stats.total_applied_merges++;
if (stats.trace) stats.trace.push(str(a));
}
return i;
}
/* Function Solve
Parameters:
a: The compressed array to be reduced via merges. It is changed in-place
and should not be relied on after the call.
stats: Optional plain object that will be populated with execution statistics.
Return value:
The array after the best merges were applied to achieve the highest
value, which is stored in the maxValue custom property of the array.
*/
function solve(a, stats) {
var maxValue, i, j, traceOrig, skipLeft, skipRight, sections, goLeft,
b, choice, alternate;
if (!a.length) return a;
if (stats && stats.trace) {
traceOrig = stats.trace;
traceOrig.push(stats.trace = [str(a)]);
}
// Look for valleys of even size, and "lift" them
for (i = 1; i < a.length - 1; i++) {
if (a[i-1].val > a[i].val && a[i].val < a[i+1].val && (a[i].freq % 2) < 1) {
// Found an even valley
i = merge(a, i, false, stats);
if (i) i--;
}
}
// Check left-side elements with always increasing values
for (i = 0; i < a.length-1 && a[i].val < a[i+1].val; i++) {
if (a[i].freq > 1) i = merge(a, i, false, stats) - 1;
};
// Check right-side elements with always increasing values, right-to-left
for (j = a.length-1; j > 0 && a[j-1].val > a[j].val; j--) {
if (a[j].freq > 1) j = merge(a, j, true, stats) + 1;
};
// All resolved?
if (i == j) {
while (a[i].freq > 1) merge(a, i, true, stats);
a.maxValue = a[i].val;
} else {
skipLeft = i;
skipRight = a.length - 1 - j;
// Look for other valleys (odd sized): they will lead to a split into sections
sections = [];
for (i = a.length - 2 - skipRight; i > skipLeft; i--) {
if (a[i-1].val > a[i].val && a[i].val < a[i+1].val) {
// Odd number of elements: if more than one, there
// are two ways to merge them, but maybe
// one of both possibilities can be excluded.
goLeft = a[i+1].val > a[i].reduced;
if (a[i-1].val > a[i].reduced || goLeft) {
if (a[i].freq > 1) i = merge(a, i, goLeft, stats) + goLeft;
// i is the index of the element which has become a 1-sized valley
// Split off the right part of the array, and store the solution
sections.push(solve(a.splice(i--), stats));
}
}
}
if (sections.length) {
// Solve last remaining section
sections.push(solve(a, stats));
sections.reverse();
// Combine the solutions of all sections into one
maxValue = sections[0].maxValue;
for (i = sections.length - 1; i >= 0; i--) {
maxValue = Math.max(sections[i].maxValue, maxValue);
}
} else {
// There is no more valley that can be resolved without branching into two
// directions. Look for the remaining valleys.
sections = [];
b = a.slice(0); // take copy
for (choice = 0; choice < 2; choice++) {
if (choice) a = b; // restore from copy on second iteration
alternate = choice;
for (i = a.length - 2 - skipRight; i > skipLeft; i--) {
if (a[i-1].val > a[i].val && a[i].val < a[i+1].val) {
// Odd number of elements
alternate = !alternate
i = merge(a, i, alternate, stats) + alternate;
sections.push(solve(a.splice(i--), stats));
}
}
// Solve last remaining section
sections.push(solve(a, stats));
}
sections.reverse(); // put in logical order
// Find best section:
maxValue = sections[0].maxValue;
for (i = sections.length - 1; i >= 0; i--) {
maxValue = Math.max(sections[i].maxValue, maxValue);
}
for (i = sections.length - 1; i >= 0 && sections[i].maxValue < maxValue; i--);
// Which choice led to the highest value (choice = 0 or 1)?
choice = (i >= sections.length / 2)
// Discard the not-chosen version
sections = sections.slice(choice * sections.length/2);
}
// Reconstruct the solution from the sections.
a = [].concat.apply([], sections);
a.maxValue = maxValue;
}
if (traceOrig) stats.trace = traceOrig;
return a;
}
function randomValues(len) {
var a = [];
for (var i = 0; i < len; i++) {
// 50% chance for a 1, 25% for a 2, ... etc.
a.push(Math.min(/\.1*/.exec(Math.random().toString(2))[0].length,5));
}
return a;
}
// I/O
var inputEl = document.querySelector('#inp');
var randEl = document.querySelector('#rand');
var lenEl = document.querySelector('#len');
var goEl = document.querySelector('#go');
var outEl = document.querySelector('#out');
goEl.onclick = function() {
// Get the input and structure it
var a = unstr(inputEl.value),
stats = {
total_applied_merges: 0,
trace: a.length < 100 ? [] : undefined
};
// Apply algorithm
a = solve(a, stats);
// Output results
var output = {
value: a.maxValue,
compact: str(a),
total_applied_merges: stats.total_applied_merges,
trace: stats.trace || 'no trace produced (input too large)'
};
outEl.textContent = JSON.stringify(output, null, 4);
}
randEl.onclick = function() {
// Get input (count of numbers to generate):
len = lenEl.value;
// Generate
var a = randomValues(len);
// Output
inputEl.value = a.join(' ');
// Simulate click to find the solution immediately.
goEl.click();
}
// Tests
var tests = [
' ', '',
'1', '1',
'1 1', '2',
'2 2 1 2 2', '3 1 3',
'3 2 1 1 2 2 3', '5',
'3 2 1 1 2 2 3 1 1 1 1 3 2 2 1 1 2', '6',
'3 1 1 1 3', '3 2 1 3',
'2 1 1 1 2 1 1 1 2 1 1 1 1 1 2', '3 1 2 1 4 1 2',
'3 1 1 2 1 1 1 2 3', '4 2 1 2 3',
'1 4 2 1 1 1 1 1 1 1', '1 5 1',
];
var res;
for (var i = 0; i < tests.length; i+=2) {
var res = str(solve(unstr(tests[i])));
if (res !== tests[i+1]) throw 'Test failed: ' + tests[i] + ' returned ' + res + ' instead of ' + tests[i+1];
}
Enter series (space separated):<br>
<input id="inp" size="60" value="2 3 1 1 2 2"><button id="go">Solve</button>
<br>
<input id="len" size="4" value="30"><button id="rand">Produce random series of this size and solve</button>
<pre id="out"></pre>
As you can see the program produces a reduced array with the maximum value included. In general there can be many derived arrays that have this maximum; only one is given.
An O(n*m) time and space algorithm is possible, where, according to your stated limits, n <= 500 and m <= 58 (consider that even for a billion elements, m need only be about 60, representing the largest element ± log2(n)). m is representing the possible numbers 50 + floor(log2(500)):
Consider the condensed sequence, s = {[x, number of x's]}.
If M[i][j] = [num_j,start_idx] where num_j represents the maximum number of contiguous js ending at index i of the condensed sequence; start_idx, the index where the sequence starts or -1 if it cannot join earlier sequences; then we have the following relationship:
M[i][j] = [s[i][1] + M[i-1][j][0], M[i-1][j][1]]
when j equals s[i][0]
j's greater than s[i][0] but smaller than or equal to s[i][0] + floor(log2(s[i][1])), represent converting pairs and merging with an earlier sequence if applicable, with a special case after the new count is odd:
When M[i][j][0] is odd, we do two things: first calculate the best so far by looking back in the matrix to a sequence that could merge with M[i][j] or its paired descendants, and then set a lower bound in the next applicable cells in the row (meaning a merge with an earlier sequence cannot happen via this cell). The reason this works is that:
if s[i + 1][0] > s[i][0], then s[i + 1] could only possibly pair with the new split section of s[i]; and
if s[i + 1][0] < s[i][0], then s[i + 1] might generate a lower j that would combine with the odd j from M[i], potentially making a longer sequence.
At the end, return the largest entry in the matrix, max(j + floor(log2(num_j))), for all j.
JavaScript code (counterexamples would be welcome; the limit on the answer is set at 7 for convenient visualization of the matrix):
function f(str){
var arr = str.split(/\s+/).map(Number);
var s = [,[arr[0],0]];
for (var i=0; i<arr.length; i++){
if (s[s.length - 1][0] == arr[i]){
s[s.length - 1][1]++;
} else {
s.push([arr[i],1]);
}
}
var M = [new Array(8).fill([0,0])],
best = 0;
for (var i=1; i<s.length; i++){
M[i] = new Array(8).fill([0,i]);
var temp = s[i][1],
temp_odd,
temp_start,
odd = false;
for (var j=s[i][0]; temp>0; j++){
var start_idx = odd ? temp_start : M[i][j-1][1];
if (start_idx != -1 && M[start_idx - 1][j][0]){
temp += M[start_idx - 1][j][0];
start_idx = M[start_idx - 1][j][1];
}
if (!odd){
M[i][j] = [temp,start_idx];
temp_odd = temp;
} else {
M[i][j] = [temp_odd,-1];
temp_start = start_idx;
}
if (!odd && temp & 1 && temp > 1){
odd = true;
temp_start = start_idx;
}
best = Math.max(best,j + Math.floor(Math.log2(temp)));
temp >>= 1;
temp_odd >>= 1;
}
}
return [arr, s, best, M];
}
// I/O
var button = document.querySelector('button');
var input = document.querySelector('input');
var pre = document.querySelector('pre');
button.onclick = function() {
var val = input.value;
var result = f(val);
var text = '';
for (var i=0; i<3; i++){
text += JSON.stringify(result[i]) + '\n\n';
}
for (var i in result[3]){
text += JSON.stringify(result[3][i]) + '\n';
}
pre.textContent = text;
}
<input value ="2 2 3 3 2 2 3 3 5">
<button>Solve</button>
<pre></pre>
Here's a brute force solution:
function findMax(array A, int currentMax)
for each pair (i, i+1) of indices for which A[i]==A[i+1] do
currentMax = max(A[i]+1, currentMax)
replace A[i],A[i+1] by a single number A[i]+1
currentMax = max(currentMax, findMax(A, currentMax))
end for
return currentMax
Given the array A, let currentMax=max(A[0], ..., A[n])
print findMax(A, currentMax)
The algorithm terminates because in each recursive call the array shrinks by 1.
It's also clear that it is correct: we try out all possible replacement sequences.
The code is extremely slow when the array is large and there's lots of options regarding replacements, but actually works reasonbly fast on arrays with small number of replaceable pairs. (I'll try to quantify the running time in terms of the number of replaceable pairs.)
A naive working code in Python:
def findMax(L, currMax):
for i in range(len(L)-1):
if L[i] == L[i+1]:
L[i] += 1
del L[i+1]
currMax = max(currMax, L[i])
currMax = max(currMax, findMax(L, currMax))
L[i] -= 1
L.insert(i+1, L[i])
return currMax
# entry point
if __name__ == '__main__':
L1 = [2, 3, 1, 1, 2, 2]
L2 = [2, 3, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2]
print findMax(L1, max(L1))
print findMax(L2, max(L2))
The result of the first call is 4, as expected.
The result of the second call is 5 as expected; the sequence that gives the result: 2,3,1,1,2,2,2,2,2,2,2,2, -> 2,3,1,1,3,2,2,2,2,2,2 -> 2,3,1,1,3,3,2,2,2,2, -> 2,3,1,1,3,3,3,2,2 -> 2,3,1,1,3,3,3,3 -> 2,3,1,1,4,3, -> 2,3,1,1,4,4 -> 2,3,1,1,5

Maximum sum of path from left column to right column

I'm trying to calculate the maximum sum that can be achieved in going from left column to right column in a grid. Allowed movements are up, down, right. I've implemented this solution (it's Breadth First Search) :
for(int i=1; i<=n; i++) {
Queue<Position> q = new LinkedList<Position>();
q.add(new Position(i, 1));
dp[i][1] = map[i][1];
while(!q.isEmpty()) {
Position node = q.poll();
visited[node.n][node.m] = 1;
if(dp[node.n][node.m] > max) {
max = dp[node.n][node.m];
}
if(visited[node.n-1][node.m] != 1 && node.n != 1 && dp[node.n-1][node.m] < dp[node.n][node.m] + map[node.n-1][node.m] && map[node.n-1][node.m] != -1) {
dp[node.n-1][node.m] = dp[node.n][node.m] + map[node.n-1][node.m];
q.add(new Position(node.n-1, node.m));
}
if(visited[node.n+1][node.m] != 1 && node.n != n && dp[node.n +1][node.m] < dp[node.n][node.m] + map[node.n+1][node.m] && map[node.n+1][node.m] != -1) {
dp[node.n +1][node.m] = dp[node.n][node.m] + map[node.n+1][node.m];
q.add(new Position(node.n + 1, node.m));
}
if(visited[node.n][node.m+1] != 1 && node.m != m && dp[node.n][node.m+1] < dp[node.n][node.m] + map[node.n][node.m+1] && map[node.n][node.m+1] != -1) {
dp[node.n][node.m+1] = dp[node.n][node.m] + map[node.n][node.m+1];
q.add(new Position(node.n, node.m+1));
}
}
}
static class Position {
int n, m;
public Position(int row, int column) {
this.n = row;
this.m = column;
}
}
Example Input:
-1 4 5 1
2 -1 2 4
3 3 -1 3
4 2 1 2
The problem with my solution is it should reach 2 (in last row 2nd column) by following 4->3->3->2 but my solution put 2 in visited state so it won't check it. And if I remove visited array, it will get trapped in infinite loop of up, down, up, down on any cell.
Edit : Each point can be visited only once.
This problem can be solved with a linear programming approach, but there is a small twist because you cannot visit each cell more than once but the movements can actually take you to that condition.
To solve the issue you can however note that in a given position (x, y) you either
just arrived at (x, y) from (x-1, y) and therefore you are allowed to go up, down or right (unless you're on the edges, of course)
arrived at (x, y) from (x, y-1) (i.e. from above) and then you're allowed only to go down or right
arrived at (x, y) from (x, y+1) (i.e. from below) and then you're allowed only to go up or right
This translates directly in the following recursive-memoized solution (code is in Python):
matrix = [[-1, 4, 5, 1],
[ 2,-1, 2, 4],
[ 3, 3,-1, 3],
[ 4, 2, 1, 2]]
rows = len(matrix)
cols = len(matrix[0])
cache = {}
def maxsum(dir, x, y):
key = (dir, x, y)
if key in cache: return cache[key]
base = matrix[y][x]
if x < cols-1:
best = base + maxsum("left", x+1, y)
else:
best = base
if dir != "above" and y > 0:
best = max(best, base + maxsum("below", x, y-1))
if dir != "below" and y < rows-1:
best = max(best, base + maxsum("above", x, y+1))
cache[key] = best
return best
print(max(maxsum("left", 0, y) for y in range(rows)))
If you are not allowed to step over a negative value (even if that would guarantee a bigger sum) the changes are trivial (and you need to specify what to return if there are no paths going from left column to right column).

Algorithm for iterating over an outward spiral on a discrete 2D grid from the origin

For example, here is the shape of intended spiral (and each step of the iteration)
y
|
|
16 15 14 13 12
17 4 3 2 11
-- 18 5 0 1 10 --- x
19 6 7 8 9
20 21 22 23 24
|
|
Where the lines are the x and y axes.
Here would be the actual values the algorithm would "return" with each iteration (the coordinates of the points):
[0,0],
[1,0], [1,1], [0,1], [-1,1], [-1,0], [-1,-1], [0,-1], [1,-1],
[2,-1], [2,0], [2,1], [2,2], [1,2], [0,2], [-1,2], [-2,2], [-2,1], [-2,0]..
etc.
I've tried searching, but I'm not exactly sure what to search for exactly, and what searches I've tried have come up with dead ends.
I'm not even sure where to start, other than something messy and inelegant and ad-hoc, like creating/coding a new spiral for each layer.
Can anyone help me get started?
Also, is there a way that can easily switch between clockwise and counter-clockwise (the orientation), and which direction to "start" the spiral from? (the rotation)
Also, is there a way to do this recursively?
My application
I have a sparse grid filled with data points, and I want to add a new data point to the grid, and have it be "as close as possible" to a given other point.
To do that, I'll call grid.find_closest_available_point_to(point), which will iterate over the spiral given above and return the first position that is empty and available.
So first, it'll check point+[0,0] (just for completeness's sake). Then it'll check point+[1,0]. Then it'll check point+[1,1]. Then point+[0,1], etc. And return the first one for which the position in the grid is empty (or not occupied already by a data point).
There is no upper bound to grid size.
There's nothing wrong with direct, "ad-hoc" solution. It can be clean enough too.
Just notice that spiral is built from segments. And you can get next segment from current one rotating it by 90 degrees. And each two rotations, length of segment grows by 1.
edit Illustration, those segments numbered
... 11 10
7 7 7 7 6 10
8 3 3 2 6 10
8 4 . 1 6 10
8 4 5 5 5 10
8 9 9 9 9 9
// (di, dj) is a vector - direction in which we move right now
int di = 1;
int dj = 0;
// length of current segment
int segment_length = 1;
// current position (i, j) and how much of current segment we passed
int i = 0;
int j = 0;
int segment_passed = 0;
for (int k = 0; k < NUMBER_OF_POINTS; ++k) {
// make a step, add 'direction' vector (di, dj) to current position (i, j)
i += di;
j += dj;
++segment_passed;
System.out.println(i + " " + j);
if (segment_passed == segment_length) {
// done with current segment
segment_passed = 0;
// 'rotate' directions
int buffer = di;
di = -dj;
dj = buffer;
// increase segment length if necessary
if (dj == 0) {
++segment_length;
}
}
}
To change original direction, look at original values of di and dj. To switch rotation to clockwise, see how those values are modified.
Here's a stab at it in C++, a stateful iterator.
class SpiralOut{
protected:
unsigned layer;
unsigned leg;
public:
int x, y; //read these as output from next, do not modify.
SpiralOut():layer(1),leg(0),x(0),y(0){}
void goNext(){
switch(leg){
case 0: ++x; if(x == layer) ++leg; break;
case 1: ++y; if(y == layer) ++leg; break;
case 2: --x; if(-x == layer) ++leg; break;
case 3: --y; if(-y == layer){ leg = 0; ++layer; } break;
}
}
};
Should be about as efficient as it gets.
This is the javascript solution based on the answer at
Looping in a spiral
var x = 0,
y = 0,
delta = [0, -1],
// spiral width
width = 6,
// spiral height
height = 6;
for (i = Math.pow(Math.max(width, height), 2); i>0; i--) {
if ((-width/2 < x && x <= width/2)
&& (-height/2 < y && y <= height/2)) {
console.debug('POINT', x, y);
}
if (x === y
|| (x < 0 && x === -y)
|| (x > 0 && x === 1-y)){
// change direction
delta = [-delta[1], delta[0]]
}
x += delta[0];
y += delta[1];
}
fiddle: http://jsfiddle.net/N9gEC/18/
This problem is best understood by analyzing how changes coordinates of spiral corners. Consider this table of first 8 spiral corners (excluding origin):
x,y | dx,dy | k-th corner | N | Sign |
___________________________________________
1,0 | 1,0 | 1 | 1 | +
1,1 | 0,1 | 2 | 1 | +
-1,1 | -2,0 | 3 | 2 | -
-1,-1 | 0,-2 | 4 | 2 | -
2,-1 | 3,0 | 5 | 3 | +
2,2 | 0,3 | 6 | 3 | +
-2,2 | -4,0 | 7 | 4 | -
-2,-2 | 0,-4 | 8 | 4 | -
By looking at this table we can calculate X,Y of k-th corner given X,Y of (k-1) corner:
N = INT((1+k)/2)
Sign = | +1 when N is Odd
| -1 when N is Even
[dx,dy] = | [N*Sign,0] when k is Odd
| [0,N*Sign] when k is Even
[X(k),Y(k)] = [X(k-1)+dx,Y(k-1)+dy]
Now when you know coordinates of k and k+1 spiral corner you can get all data points in between k and k+1 by simply adding 1 or -1 to x or y of last point.
Thats it.
good luck.
I would solve it using some math. Here is Ruby code (with input and output):
(0..($*.pop.to_i)).each do |i|
j = Math.sqrt(i).round
k = (j ** 2 - i).abs - j
p = [k, -k].map {|l| (l + j ** 2 - i - (j % 2)) * 0.5 * (-1) ** j}.map(&:to_i)
puts "p => #{p[0]}, #{p[1]}"
end
E.g.
$ ruby spiral.rb 10
p => 0, 0
p => 1, 0
p => 1, 1
p => 0, 1
p => -1, 1
p => -1, 0
p => -1, -1
p => 0, -1
p => 1, -1
p => 2, -1
p => 2, 0
And golfed version:
p (0..$*.pop.to_i).map{|i|j=Math.sqrt(i).round;k=(j**2-i).abs-j;[k,-k].map{|l|(l+j**2-i-j%2)*0.5*(-1)**j}.map(&:to_i)}
Edit
First try to approach the problem functionally. What do you need to know, at each step, to get to the next step?
Focus on plane's first diagonal x = y. k tells you how many steps you must take before touching it: negative values mean you have to move abs(k) steps vertically, while positive mean you have to move k steps horizontally.
Now focus on the length of the segment you're currently in (spiral's vertices - when the inclination of segments change - are considered as part of the "next" segment). It's 0 the first time, then 1 for the next two segments (= 2 points), then 2 for the next two segments (= 4 points), etc. It changes every two segments and each time the number of points part of that segments increase. That's what j is used for.
Accidentally, this can be used for getting another bit of information: (-1)**j is just a shorthand to "1 if you're decreasing some coordinate to get to this step; -1 if you're increasing" (Note that only one coordinate is changed at each step). Same holds for j%2, just replace 1 with 0 and -1 with 1 in this case. This mean they swap between two values: one for segments "heading" up or right and one for those going down or left.
This is a familiar reasoning, if you're used to functional programming: the rest is just a little bit of simple math.
It can be done in a fairly straightforward way using recursion. We just need some basic 2D vector math and tools for generating and mapping over (possibly infinite) sequences:
// 2D vectors
const add = ([x0, y0]) => ([x1, y1]) => [x0 + x1, y0 + y1];
const rotate = θ => ([x, y]) => [
Math.round(x * Math.cos(θ) - y * Math.sin(θ)),
Math.round(x * Math.sin(θ) + y * Math.cos(θ))
];
// Iterables
const fromGen = g => ({ [Symbol.iterator]: g });
const range = n => [...Array(n).keys()];
const map = f => it =>
fromGen(function*() {
for (const v of it) {
yield f(v);
}
});
And now we can express a spiral recursively by generating a flat line, plus a rotated (flat line, plus a rotated (flat line, plus a rotated ...)):
const spiralOut = i => {
const n = Math.floor(i / 2) + 1;
const leg = range(n).map(x => [x, 0]);
const transform = p => add([n, 0])(rotate(Math.PI / 2)(p));
return fromGen(function*() {
yield* leg;
yield* map(transform)(spiralOut(i + 1));
});
};
Which produces an infinite list of the coordinates you're interested in. Here's a sample of the contents:
const take = n => it =>
fromGen(function*() {
for (let v of it) {
if (--n < 0) break;
yield v;
}
});
const points = [...take(5)(spiralOut(0))];
console.log(points);
// => [[0,0],[1,0],[1,1],[0,1],[-1,1]]
You can also negate the rotation angle to go in the other direction, or play around with the transform and leg length to get more complex shapes.
For example, the same technique works for inward spirals as well. It's just a slightly different transform, and a slightly different scheme for changing the length of the leg:
const empty = [];
const append = it1 => it2 =>
fromGen(function*() {
yield* it1;
yield* it2;
});
const spiralIn = ([w, h]) => {
const leg = range(w).map(x => [x, 0]);
const transform = p => add([w - 1, 1])(rotate(Math.PI / 2)(p));
return w * h === 0
? empty
: append(leg)(
fromGen(function*() {
yield* map(transform)(spiralIn([h - 1, w]));
})
);
};
Which produces (this spiral is finite, so we don't need to take some arbitrary number):
const points = [...spiralIn([3, 3])];
console.log(points);
// => [[0,0],[1,0],[2,0],[2,1],[2,2],[1,2],[0,2],[0,1],[1,1]]
Here's the whole thing together as a live snippet if you want play around with it:
// 2D vectors
const add = ([x0, y0]) => ([x1, y1]) => [x0 + x1, y0 + y1];
const rotate = θ => ([x, y]) => [
Math.round(x * Math.cos(θ) - y * Math.sin(θ)),
Math.round(x * Math.sin(θ) + y * Math.cos(θ))
];
// Iterables
const fromGen = g => ({ [Symbol.iterator]: g });
const range = n => [...Array(n).keys()];
const map = f => it =>
fromGen(function*() {
for (const v of it) {
yield f(v);
}
});
const take = n => it =>
fromGen(function*() {
for (let v of it) {
if (--n < 0) break;
yield v;
}
});
const empty = [];
const append = it1 => it2 =>
fromGen(function*() {
yield* it1;
yield* it2;
});
// Outward spiral
const spiralOut = i => {
const n = Math.floor(i / 2) + 1;
const leg = range(n).map(x => [x, 0]);
const transform = p => add([n, 0])(rotate(Math.PI / 2)(p));
return fromGen(function*() {
yield* leg;
yield* map(transform)(spiralOut(i + 1));
});
};
// Test
{
const points = [...take(5)(spiralOut(0))];
console.log(JSON.stringify(points));
}
// Inward spiral
const spiralIn = ([w, h]) => {
const leg = range(w).map(x => [x, 0]);
const transform = p => add([w - 1, 1])(rotate(Math.PI / 2)(p));
return w * h === 0
? empty
: append(leg)(
fromGen(function*() {
yield* map(transform)(spiralIn([h - 1, w]));
})
);
};
// Test
{
const points = [...spiralIn([3, 3])];
console.log(JSON.stringify(points));
}
Here is a Python implementation based on the answer by #mako.
def spiral_iterator(iteration_limit=999):
x = 0
y = 0
layer = 1
leg = 0
iteration = 0
yield 0, 0
while iteration < iteration_limit:
iteration += 1
if leg == 0:
x += 1
if (x == layer):
leg += 1
elif leg == 1:
y += 1
if (y == layer):
leg += 1
elif leg == 2:
x -= 1
if -x == layer:
leg += 1
elif leg == 3:
y -= 1
if -y == layer:
leg = 0
layer += 1
yield x, y
Running this code:
for x, y in spiral_iterator(10):
print(x, y)
Yields:
0 0
1 0
1 1
0 1
-1 1
-1 0
-1 -1
0 -1
1 -1
2 -1
2 0
Try searching for either parametric or polar equations. Both are suitable to plotting spirally things. Here's a page that has plenty of examples, with pictures (and equations). It should give you some more ideas of what to look for.
I've done pretty much the same thin as a training exercise, with some differences in the output and the spiral orientation, and with an extra requirement, that the functions spatial complexity has to be O(1).
After think for a while I came to the idea that by knowing where does the spiral start and the position I was calculating the value for, I could simplify the problem by subtracting all the complete "circles" of the spiral, and then just calculate a simpler value.
Here is my implementation of that algorithm in ruby:
def print_spiral(n)
(0...n).each do |y|
(0...n).each do |x|
printf("%02d ", get_value(x, y, n))
end
print "\n"
end
end
def distance_to_border(x, y, n)
[x, y, n - 1 - x, n - 1 - y].min
end
def get_value(x, y, n)
dist = distance_to_border(x, y, n)
initial = n * n - 1
(0...dist).each do |i|
initial -= 2 * (n - 2 * i) + 2 * (n - 2 * i - 2)
end
x -= dist
y -= dist
n -= dist * 2
if y == 0 then
initial - x # If we are in the upper row
elsif y == n - 1 then
initial - n - (n - 2) - ((n - 1) - x) # If we are in the lower row
elsif x == n - 1 then
initial - n - y + 1# If we are in the right column
else
initial - 2 * n - (n - 2) - ((n - 1) - y - 1) # If we are in the left column
end
end
print_spiral 5
This is not exactly the thing you asked for, but I believe it'll help you to think your problem
I had a similar problem, but I didn't want to loop over the entire spiral each time to find the next new coordinate. The requirement is that you know your last coordinate.
Here is what I came up with with a lot of reading up on the other solutions:
function getNextCoord(coord) {
// required info
var x = coord.x,
y = coord.y,
level = Math.max(Math.abs(x), Math.abs(y));
delta = {x:0, y:0};
// calculate current direction (start up)
if (-x === level)
delta.y = 1; // going up
else if (y === level)
delta.x = 1; // going right
else if (x === level)
delta.y = -1; // going down
else if (-y === level)
delta.x = -1; // going left
// check if we need to turn down or left
if (x > 0 && (x === y || x === -y)) {
// change direction (clockwise)
delta = {x: delta.y,
y: -delta.x};
}
// move to next coordinate
x += delta.x;
y += delta.y;
return {x: x,
y: y};
}
coord = {x: 0, y: 0}
for (i = 0; i < 40; i++) {
console.log('['+ coord.x +', ' + coord.y + ']');
coord = getNextCoord(coord);
}
Still not sure if it is the most elegant solution. Perhaps some elegant maths could remove some of the if statements. Some limitations would be needing some modification to change spiral direction, doesn't take into account non-square spirals and can't spiral around a fixed coordinate.
I have an algorithm in java that outputs a similar output to yours, except that it prioritizes the number on the right, then the number on the left.
public static String[] rationals(int amount){
String[] numberList=new String[amount];
int currentNumberLeft=0;
int newNumberLeft=0;
int currentNumberRight=0;
int newNumberRight=0;
int state=1;
numberList[0]="("+newNumberLeft+","+newNumberRight+")";
boolean direction=false;
for(int count=1;count<amount;count++){
if(direction==true&&newNumberLeft==state){direction=false;state=(state<=0?(-state)+1:-state);}
else if(direction==false&&newNumberRight==state){direction=true;}
if(direction){newNumberLeft=currentNumberLeft+sign(state);}else{newNumberRight=currentNumberRight+sign(state);}
currentNumberLeft=newNumberLeft;
currentNumberRight=newNumberRight;
numberList[count]="("+newNumberLeft+","+newNumberRight+")";
}
return numberList;
}
Here's the algorithm. It rotates clockwise, but could easily rotate anticlockwise, with a few alterations. I made it in just under an hour.
// spiral_get_value(x,y);
sx = argument0;
sy = argument1;
a = max(sqrt(sqr(sx)),sqrt(sqr(sy)));
c = -b;
d = (b*2)+1;
us = (sy==c and sx !=c);
rs = (sx==b and sy !=c);
bs = (sy==b and sx !=b);
ls = (sx==c and sy !=b);
ra = rs*((b)*2);
ba = bs*((b)*4);
la = ls*((b)*6);
ax = (us*sx)+(bs*-sx);
ay = (rs*sy)+(ls*-sy);
add = ra+ba+la+ax+ay;
value = add+sqr(d-2)+b;
return(value);`
It will handle any x / y values (infinite).
It's written in GML (Game Maker Language), but the actual logic is sound in any programming language.
The single line algorithm only has 2 variables (sx and sy) for the x and y inputs. I basically expanded brackets, a lot. It makes it easier for you to paste it into notepad and change 'sx' for your x argument / variable name and 'sy' to your y argument / variable name.
`// spiral_get_value(x,y);
sx = argument0;
sy = argument1;
value = ((((sx==max(sqrt(sqr(sx)),sqrt(sqr(sy))) and sy !=(-1*max(sqrt(sqr(sx)),sqrt(sqr(sy))))))*((max(sqrt(sqr(sx)),sqrt(sqr(sy))))*2))+(((sy==max(sqrt(sqr(sx)),sqrt(sqr(sy))) and sx !=max(sqrt(sqr(sx)),sqrt(sqr(sy)))))*((max(sqrt(sqr(sx)),sqrt(sqr(sy))))*4))+(((sx==(-1*max(sqrt(sqr(sx)),sqrt(sqr(sy)))) and sy !=max(sqrt(sqr(sx)),sqrt(sqr(sy)))))*((max(sqrt(sqr(sx)),sqrt(sqr(sy))))*6))+((((sy==(-1*max(sqrt(sqr(sx)),sqrt(sqr(sy)))) and sx !=(-1*max(sqrt(sqr(sx)),sqrt(sqr(sy))))))*sx)+(((sy==max(sqrt(sqr(sx)),sqrt(sqr(sy))) and sx !=max(sqrt(sqr(sx)),sqrt(sqr(sy)))))*-sx))+(((sx==max(sqrt(sqr(sx)),sqrt(sqr(sy))) and sy !=(-1*max(sqrt(sqr(sx)),sqrt(sqr(sy))))))*sy)+(((sx==(-1*max(sqrt(sqr(sx)),sqrt(sqr(sy)))) and sy !=max(sqrt(sqr(sx)),sqrt(sqr(sy)))))*-sy))+sqr(((max(sqrt(sqr(sx)),sqrt(sqr(sy)))*2)+1)-2)+max(sqrt(sqr(sx)),sqrt(sqr(sy)));
return(value);`
I know the reply is awfully late :D but i hope it helps future visitors.

Resources