I'm having trouble showing how to ensure recursively decreasing functions on a tree class in Dafny. I have the following definitions which verify.
class RoseTree {
var NodeType: int
var id: string
var children: array<RoseTree>
ghost var nodeSet: set<RoseTree>
constructor(nt: int, id: string, children: array<RoseTree>)
ensures forall x :: 0 <= x < children.Length ==> children[x].nodeSet <= this.nodeSet
ensures forall x :: 0 <= x < this.children.Length ==> this.children[x].nodeSet <= this.nodeSet
{
this.NodeType := nt;
this.id := id;
this.children := children;
if children.Length == 0 {
this.nodeSet := {this};
}else{
this.nodeSet := {this}+childrenNodeSet(children);
}
}
}
function setRosePick(s: set<set<RoseTree>>): set<RoseTree>
requires s != {}
{
var x :| x in s; x
}
function setUnion(setosets: set<set<RoseTree>>) : set<RoseTree>
decreases setosets
{
if setosets == {} then {} else
var x := setRosePick(setosets);
assert x <= x + setUnion(setosets-{x});
x + setUnion(setosets-{x})
}
lemma setUnionDef(s: set<set<RoseTree>>, y: set<RoseTree>)
requires y in s
ensures setUnion(s) == y + setUnion(s - {y})
{
var x := setRosePick(s);
if y == x {
}else{
calc {
setUnion(s);
==
x + setUnion(s - {x});
== {setUnionDef(s - {x}, y); }
x + y + setUnion(s - {x} - {y});
== { assert s - {x} - {y} == s - {y} - {x}; }
y + x + setUnion(s - {y} - {x});
== {setUnionDef(s - {y}, x); }
y + setUnion(s - {y});
}
}
}
lemma setUnionReturns(s: set<set<RoseTree>>)
ensures s == {} ==> setUnion(s) == {}
ensures s != {} ==> forall x :: x in s ==> x <= setUnion(s)
{
if s == {} {
assert setUnion(s) == {};
} else {
forall x | x in s
ensures x <= setUnion(s)
{
setUnionDef(s, x);
assert x <= x + setUnion(s-{x});
}
}
}
function childNodeSets(children: array<RoseTree>): set<set<RoseTree>>
reads children
reads set x | 0 <= x < children.Length :: children[x]
{
set x | 0 <= x < children.Length :: children[x].nodeSet
}
function childNodeSetsPartial(children: array<RoseTree>, index: int): set<set<RoseTree>>
requires 0 <= index < children.Length
reads children
reads set x | index <= x < children.Length :: children[x]
{
set x | index <= x < children.Length :: children[x].nodeSet
}
function childrenNodeSet(children: array<RoseTree>): set<RoseTree>
reads children
reads set x | 0 <= x < children.Length :: children[x]
ensures forall x :: x in childNodeSets(children) ==> x <= childrenNodeSet(children)
ensures forall i :: 0 <= i < children.Length ==> children[i].nodeSet <= childrenNodeSet(children)
{
var y := childNodeSets(children);
setUnionReturns(y);
setUnion(y)
}
In particular I'm trying to define the height function for the tree.
function height(node: RoseTree):nat
reads node
reads node.children
reads set x | 0 <= x < node.children.Length :: node.children[x]
decreases node.nodeSet
{
if node.children.Length == 0 then 1 else 1 + maxChildHeight(node, node.children,node.children.Length-1,0)
}
function maxChildHeight(node: RoseTree, children: array<RoseTree>, index: nat, best: nat) : nat
reads node
reads node.children
reads set x | 0 <= x < node.children.Length :: node.children[x]
requires children == node.children
requires 0 <= index < children.Length
ensures forall x :: 0 <= x <= index < children.Length ==> maxChildHeight(node, children, index, best) >= height(children[x])
decreases node.nodeSet - setUnion(childNodeSetsPartial(children, index)), 1
{
if index == 0 then best else if height(children[index]) >= best then maxChildHeight(node, children, index-1, height(children[index])) else maxChildHeight(node, children, index-1, best)
}
I though it should be possible to show that the nodeSet of the node will be a subset of its parent node or that the union of child node sets will be a subset of the parent node, and thus both functions will terminate. My decreases expressions don't prove it to dafny and I'm not quite sure how to proceed. Is there another way to prove termination or can I fix these decrease statements?
Also, do all instances of a class have the constructor ensure statements applied implicitly or only if explicitly constructed using the constructor?
Edit: updated definitions of childNodeSetsPartial and maxChildHeight
to recurse downward. It still doesn't verify.
Defining mutable linked heap-allocated data structures in Dafny is not very common except as an exercise. So you should consider whether a datatype would serve you better, as in
datatype RoseTree = Node(children: seq<RoseTree>)
function height(r: RoseTree): int
{
if r.children == [] then
1
else
var c := set i | 0 <= i < |r.children| :: height(r.children[i]);
assert height(r.children[0]) in c;
assert c != {};
SetMax(c) + 1
}
If you insist on mutable linked heap-allocated data structures, then there is a standard idiom for doing that. Please read sections 0 and 1 of these lecture notes and check out the modern version of the example code here.
Applying this idiom to your code, we get the following.
class RoseTree {
var NodeType: int
var id: string
var children: array<RoseTree>
ghost var repr: set<object>
predicate Valid()
reads this, repr
decreases repr
{
&& this in repr
&& children in repr
&& (forall i | 0 <= i < children.Length ::
children[i] in repr
&& children[i].repr <= repr
&& this !in children[i].repr
&& children[i].Valid())
}
constructor(nt: int, id: string, children: array<RoseTree>)
requires forall i | 0 <= i < children.Length :: children[i].Valid()
ensures Valid()
{
this.NodeType := nt;
this.id := id;
this.children := children;
this.repr := {this, children} +
(set i | 0 <= i < children.Length :: children[i]) +
(set x, i | 0 <= i < children.Length && x in children[i].repr :: x);
}
}
function SetMax(s: set<int>): int
requires s != {}
ensures forall x | x in s :: SetMax(s) >= x
{
var x :| x in s;
if s == {x} then
x
else
var y := SetMax(s - {x});
assert forall z | z in s :: z == x || (z in (s - {x}) && y >= z);
if x > y then x else y
}
function height(node: RoseTree): nat
requires node.Valid()
reads node.repr
{
if node.children.Length == 0 then
1
else
var c := set i | 0 <= i < node.children.Length :: height(node.children[i]);
assert height(node.children[0]) in c;
assert c != {};
SetMax(c) + 1
}
do all instances of a class have the constructor ensure statements applied implicitly or only if explicitly constructed using the constructor?
I'm not sure if I understand this question. I think the answer is "no", though. Since a class might have multiple constructors with different postconditions.
Related
I am working on the following proof and the invariant result_val is proved with an induction strategy on i using begin as the base case.
The sup case is trying to prove true which holds trivially using Frama-C 24.0. But when I switch to 25.0, it tries to prove a seemingly more complicated condition, which looks closer to a correct inductive inference because it did the weakest precondition computation explicitly.
However, all SMT solvers I tried cannot prove the condition generated by Frama-C 25.0.
I am a bit worried about the correctness of version 24.0's result because using true as the inductive proof goal seems to be unlikely. Can anyone hint to me at what happened? Is that a bug in 24.0 or just some difference in the implementation?
#include <stdbool.h>
#define SIZE 1000
bool data[SIZE] ;
/*#
logic integer count(integer begin, integer end)=
begin >= end ? 0 : (data[begin]==true) ? count(begin+1, end)+1 : count(begin+1, end);
*/
/*#
requires SIZE > begin >= 0;
requires SIZE >= end >= 0;
requires begin <= end;
assigns \nothing;
ensures \result == count(begin, end);
*/
unsigned int occurrences_of(int begin, int end)
{
unsigned int result = 0;
/*#
loop invariant i_bound: begin <= i <= end;
loop invariant result_bound: 0 <= result <= i-begin;
loop invariant result_val: result == count(begin, i);
loop assigns i, result;
loop variant end-i;
*/
for (unsigned int i = begin; i < end; ++i){
result += (data[i] == true) ? 1 : 0;
}
return result;
}
Below is the result from Frama-c 24.0
Proof:
Goal Invariant 'result_val' (preserved) (Induction: proved)
+ Goal Induction (Base) (proved)
+ Goal Induction (Induction (sup)) (proved)
+ Goal Induction (Induction (inf)) (proved)
Qed.
--------------------------------------------------------------------------------
Goal Induction (Induction (sup)):
Prove: true.
Below is the result from Frama-c 25.0
--------------------------------------------------------------------------------
Proof:
Goal Invariant 'result_val' (preserved) (Induction: pending)
+ Goal Induction (Base) (proved)
+ Goal Induction (Induction (sup)) (pending)
+ Goal Induction (Induction (inf)) (proved)
End.
--------------------------------------------------------------------------------
Goal Induction (Induction (sup)):
Let x_0 = to_uint32(end#L1).
Let x_1 = to_uint32(tmp#L12).
Let x_2 = data#L1[i#L6].
Let x_3 = result#L6.
Let x_4 = result#L13.
Let x_5 = to_uint32(1 + i#L6).
Assume {
Have: begin#L1 < i#L6.
Have: i#L6 <= end#L1.
Have: i#L6 < x_0.
Have: 0 <= x_3.
Have: x_5 <= end#L1.
Have: begin#L1 <= x_5.
Have: (begin#L1 + x_3) <= i#L6.
Have: (begin#L1 + x_4) <= x_5.
Have: is_uint32(i#L6).
Have: is_bool(x_2).
Have: is_uint32(x_3).
Have: if (x_2 = 1) then (tmp#L12 = 1) else (tmp#L12 = 0).
Have: forall i_0 : Z. let x_6 = L_count(data#L1, begin#L1, i_0) in
let x_7 = to_uint32(1 + i_0) in let x_8 = to_uint32(x_1 + x_6) in
let x_9 = data#L1[i_0] in ((i_0 <= end#L1) -> ((begin#L1 <= i_0) ->
((i_0 < i#L6) -> ((i_0 < x_0) -> ((0 <= x_6) -> ((x_7 <= end#L1) ->
((begin#L1 <= x_7) -> (((begin#L1 + x_6) <= i_0) ->
(((begin#L1 + x_8) <= x_7) -> (is_uint32(i_0) -> (is_bool(x_9) ->
(is_uint32(x_6) ->
((if (x_9 = 1) then (tmp#L12 = 1) else (tmp#L12 = 0)) ->
(L_count(data#L1, begin#L1, x_7) = x_8)))))))))))))).
[...]
Stmt { L6: }
Stmt { tmp = tmp_0; }
Stmt { L12: result = x_4; }
Stmt { L13: }
}
Prove: L_count(data#L1, begin#L1, x_5) = x_4.
Goal id: typed_occurrences_of_loop_invariant_result_val_preserved
Short id: occurrences_of_loop_invariant_result_val_preserved
--------------------------------------------------------------------------------
Prover Alt-Ergo 2.4.2: Timeout (Qed:52ms) (10s).
A bug on the typing of the induction tactic was indeed fixed between Frama-C 24 and 25 (https://git.frama-c.com/pub/frama-c/-/commit/6058453cce2715f7dcf9027767559f95fb3b1679). And the symptom was indeed that the tactic could generate ill-typed formulas with true instead of a term.
Proving this example in not that easy. For two main reasons:
the function and the definition work in the opposite directions,
the definition does not have an optimal expression for reasoning.
However, one can write a lemma function to solve the problem:
#include <stdbool.h>
#define SIZE 1000
bool data[SIZE] ;
/*#
logic integer count(integer begin, integer end)=
begin >= end ? 0 : ((data[begin]==true) ? count(begin+1, end)+1 : count(begin+1, end));
*/
/*# ghost
/# requires begin < end ;
assigns \nothing ;
ensures count(begin, end) == ((data[end-1]==true) ? count(begin, end-1)+1 : count(begin, end-1));
#/
void lemma(bool* d, int begin, int end){
/# loop invariant begin <= i < end ;
loop invariant count(i, end) == ((data[end-1]==true) ? count(i, end-1)+1 : count(i, end-1));
loop assigns i ;
loop variant i - begin ;
#/
for(int i = end-1 ; i > begin ; i--);
}
*/
/*#
requires SIZE > begin >= 0;
requires SIZE >= end >= 0;
requires begin <= end;
assigns \nothing;
ensures \result == count(begin, end);
*/
unsigned int occurrences_of(int begin, int end)
{
unsigned int result = 0;
/*#
loop invariant i_bound: begin <= i <= end;
loop invariant result_bound: 0 <= result <= i-begin;
loop invariant result_val: result == count(begin, i);
loop assigns i, result;
loop variant end-i;
*/
for (unsigned int i = begin; i < end; ++i){
result += (data[i] == true) ? 1 : 0;
//# ghost lemma(data, begin, i+1);
}
return result;
}
I'd suggest to use the following definition:
/*#
logic integer count(integer begin, integer end)=
begin >= end ? 0 : ((data[end-1]==true) ? 1 : 0) + count(begin, end-1);
*/
It works in the same direction as the function and avoids the duplication of the term count(begin, end-1) which makes reasoning easier.
The following is based off of Secure Foundations's dafny implementation of a Dynamic Array.
I'm trying to create a test method that when calling push_back, invokes extend_buffer. This requires a prefix: calling push_back enough times to fill it up so the next time it's called, the buffer is extended. With a default size of 16, the prefix would consist of calling push_back 15 times. I find that if I make the call 15 times it verifies, but if I instead try to call in a for loop, I get the error call may violate context's modifies clause.
class Vector<T> {
static const DEFAULT_SIZE := 16
var buffer : array<T>
var capacity : int
var size : int
predicate Valid()
reads this, buffer
{
capacity >= DEFAULT_SIZE
&& capacity as int == buffer.Length
&& 0 <= size < capacity
}
method extend_buffer(value: T)
requires Valid()
ensures Valid()
ensures fresh(buffer)
ensures size as int < capacity as int - 1
ensures forall i : int :: 0 <= i < old(size) ==> buffer[i] == old(buffer[i])
ensures size == old(size)
ensures capacity == old(capacity) as int * 2
ensures buffer.Length == old(buffer.Length) * 2
modifies this`capacity, this`buffer
{
var old_buffer := this.buffer;
var old_size := this.capacity;
capacity := old_size * 2;
buffer := newArrayFill(capacity, value);
var i:= 0;
while i < old_size
invariant Valid();
invariant capacity > old_size;
invariant i < capacity;
invariant i < old_size;
invariant fresh(buffer)
invariant size < capacity - 1;
invariant size == old(size)
invariant capacity == old(capacity) * 2
invariant forall k : int :: 0 <= k < i ==> buffer[k] == old_buffer[k] == old(buffer[k])
{
buffer[i] := old_buffer[i];
if i == old_size - 1 {
break;
}
i := i + 1;
}
}
method push_back(value:T)
requires Valid()
ensures Valid();
ensures old(size as int) < buffer.Length
ensures buffer[old(size)] == value
ensures size == old(size) + 1
ensures if old(size) + 1 == old(capacity) then fresh(buffer) else buffer == old(buffer)
ensures forall i : int :: 0 <= i < old(size) ==> buffer[i] == old(buffer[i])
ensures forall i : int :: size <= i < old(buffer.Length) ==> buffer[i] == old(buffer[i])
ensures if size == old(capacity) then fresh(buffer) else !fresh(buffer) && buffer == old(buffer)
modifies this, this.buffer, this`size
{
if (size + 1 == capacity)
{
extend_buffer(value);
}
buffer[size] := value;
size := size + 1;
}
method {:extern "Extern", "newArrayFill"} newArrayFill<T>(n: int, t: T) returns (ar: array<T>)
ensures ar.Length == n as int
ensures forall i | 0 <= i < n :: ar[i] == t
ensures fresh(ar)
constructor(default_val:T)
ensures Valid()
ensures fresh(buffer)
ensures size == 0
ensures capacity == DEFAULT_SIZE
ensures capacity as int == buffer.Length
{
size := 0;
capacity := DEFAULT_SIZE;
new;
buffer := newArrayFill(DEFAULT_SIZE, default_val);
}
}
method push_back_should_extend()
{
var arr := new Vector(0);
label L:
var oracleValue := 7;
for i : int := 0 to arr.capacity - 1
invariant arr.Valid()
{
arr.push_back(oracleValue);
}
}
I'm assuming the issue arises from push_back claiming it modifies this, which I feel is overstating what it actually modifies, but when I remove this, the error just moves to the call to extend_buffer.
Interestingly, adding in the following twostate:
twostate predicate sameBuffer(v: Vector)
reads v`buffer
{
v.buffer == old(v.buffer)
}
and then adding invariant sameBuffer#L(arr) to push_back_should_extend's for loop also doesn't verify, even though it's clear from ensures if old(size) + 1 == old(capacity) then fresh(buffer) else !fresh(buffer) && buffer == old(buffer) that the memory for buffer doesn't change throughout the prefix.
I feel this could be addressed by an inductive lemma, but while I understand the form of lemma's, I lack the ability to apply and derive them.
Adding
invariant fresh(arr.buffer)
to the for loop seems to fix it. Does that do what you want?
I have a bottleneck in my code in expressions like any(x >= b | x == y) for a large x.
I'd like to avoid the allocation x >= b | x == y. I've found that it's easy to write a function for particular cases.
SEXP eval_any_or2(SEXP x, SEXP b, SEXP y) {
R_xlen_t N = xlength(x);
if (xlength(y) != N || xlength(b) != 1) {
error("Wrong lengths.");
}
const int *xp = INTEGER(x);
const int *yp = INTEGER(y);
const int *bp = INTEGER(b);
bool o = false;
for (R_xlen_t i = 0; i < N; ++i) {
if (xp[i] >= bp[0] || xp[i] == yp[i]) {
o = true;
break;
}
}
SEXP ans = PROTECT(allocVector(LGLSXP, 1));
LOGICAL(ans)[0] = o ? TRUE : FALSE;
UNPROTECT(1);
return ans;
}
However, for clarity I'd like to keep as much of the natural syntax as possible, like any_or(x >= b, x == y). So I'd like to be able to detect whether a call is of the form <vector> <operator> <vector> when <operator> is one of the standard binary operators, and each <vector> is of equal length vectors length 1. Something like this:
any_or2 <- function(expr1, expr2) {
sexp1 <- substitute(expr1)
sexp2 <- substitute(expr2)
if (!is_binary_sexp(sexp1) || !is_binary_sexp(sexp2) {
# fall through to just basic R
return(any(expr1 | expr2))
}
# In C
eval_any_or2(...) # either the substituted expression or x,y,b
}
I've attempted the following C function which detects whether a substituted expression/call is a binary expression, but (a) I'm having trouble detecting whether the operator is a binary operator and (b) getting the vectors from the expression (x, y, b in the example) to use later (either in the same C function or as passed to a C function like the one above).
#define return_false SEXP ans = PROTECT(allocVector(LGLSXP, 1)); \
LOGICAL(ans)[0] = FALSE; \
UNPROTECT(1); \
return ans; \
SEXP is_binary_sexp(SEXP sx) {
if (TYPEOF(sx) != LANGSXP) {
return_false
}
// does it have three elements?
int len = 0;
SEXP el, nxt;
for (nxt = sx; nxt != R_NilValue || len > 4; el = CAR(nxt), nxt = CDR(nxt)) {
len++;
}
if (len != 3) {
return_false;
}
if (TYPEOF(CAR(sx)) != SYMSXP) {
return_false;
}
SEXP ans = PROTECT(allocVector(LGLSXP, 1));
LOGICAL(ans)[0] = TRUE;
UNPROTECT(1);
return ans;
}
In R I would write something like:
is_binary_sexp_R <- function(sexprA) {
# sexprA is the result of substitute()
is.call(sexprA) &&
length(sexprA) == 3L &&
match(as.character(sexprA[[1]]), c("!=", "==", "<=", ">=", "<", ">"), nomatch = 0L) &&
is.name(lhs <- sexprA[[2L]])
}
but I'd like to do as much as possible in C.
I've been looking at the use of lemmas in Dafny but am finding it hard to understand and obviously the below example doesn't verify, quite possibly because Dafny doesn't see the induction or something like a lemma to prove some property of count? Basically, I don't know how or what I need to define to help convince Dafny that counting is inductive and a thing etc. Some of the ensures and invariants specifications are not necessary, but that's not the point. btw, this was easier in Spec#.
function count(items: seq<int>, item: int): nat
decreases |items|
{
if |items| == 0 then 0 else
(if items[|items| - 1] == item then 1 else 0)
+ count( items[..(|items| - 1)], item )
}
method occurences(items: array<int>, item: int) returns (r: nat)
requires items != null
ensures r <= items.Length
// some number of occurences of item
ensures r > 0 ==> exists k: nat :: k < items.Length
&& items[k] == item
// no occurences of item
ensures r == 0 ==> forall k: nat :: k < items.Length
==> items[k] != item
ensures r == count( items[..], item )
{
var i: nat := 0;
var num: nat := 0;
while i < items.Length
// i is increasing and there could be elements that match
invariant num <= i <= items.Length
invariant num > 0 ==> exists k: nat :: k < i
&& items[k] == item
invariant num == 0 ==> forall k: nat :: k < i
==> items[k] != item
invariant num == old(num) + 1 || num == old(num)
invariant num == count( items[..i], item )
{
if items[i] == item
{ num := num + 1; }
i := i + 1;
}
return num;
}
I would use a definition of count based around a multiset, then everything works:
function count(items: seq<int>, item: int): nat
decreases |items|
{
multiset(items)[item]
}
method occurences(items: array<int>, item: int) returns (r: nat)
requires items != null
ensures r <= items.Length
// some number of occurences of item
ensures r > 0 ==> exists k: nat :: k < items.Length
&& items[k] == item
// no occurences of item
ensures r == 0 ==> forall k: nat :: k < items.Length
==> items[k] != item
ensures r == count(items[..], item)
{
var i: nat := 0;
var num: nat := 0;
while i < items.Length
// i is increasing and there could be elements that match
invariant num <= i <= items.Length
invariant num > 0 ==> exists k: nat :: k < i
&& items[k] == item
invariant num == 0 ==> forall k: nat :: k < i
==> items[k] != item
invariant num == old(num) + 1 || num == old(num)
invariant num == count(items[..i], item)
{
if items[i] == item
{ num := num + 1; }
i := i + 1;
}
assert items[..i] == items[..];
r := num;
}
I would also like to suggest two alternative approaches, and another solution to your original design.
Without changing the implementation, I personally would probably write the specification like this:
function count(items: seq<int>, item: int): nat
decreases |items|
{
multiset(items)[item]
}
method occurences(items: array<int>, item: int) returns (num: nat)
requires items != null
ensures num <= items.Length
ensures num == 0 <==> item !in items[..]
ensures num == count(items[..], item)
{
num := 0;
var i: nat := 0;
while i < items.Length
invariant num <= i <= items.Length
invariant num == 0 <==> item !in items[..i]
invariant num == count(items[..i], item)
{
if items[i] == item
{ num := num + 1; }
i := i + 1;
}
assert items[..i] == items[..];
}
If I were to decide on the implementation too then I would write it like this:
method occurences(items: array<int>, item: int) returns (num: nat)
requires items != null
ensures num == multiset(items[..])[item]
{
num := multiset(items[..])[item];
}
There is a way to get the original to verify by adding an extra assertion. NB. I think that "old" doesn't do what you think it does in a loop invariant.
function count(items: seq<int>, item: int): nat
decreases |items|
{
if |items| == 0 then 0 else
(if items[|items|-1] == item then 1 else 0)
+ count(items[..|items|-1], item )
}
method occurences(items: array<int>, item: int) returns (r: nat)
requires items != null
ensures r <= items.Length
// some number of occurences of item
ensures r > 0 ==> exists k: nat :: k < items.Length
&& items[k] == item
// no occurences of item
ensures r == 0 ==> forall k: nat :: k < items.Length
==> items[k] != item
ensures r == count( items[..], item )
{
var i: nat := 0;
var num:nat := 0;
while i < items.Length
invariant num <= i <= items.Length
invariant num > 0 ==> exists k: nat :: k < i
&& items[k] == item
invariant num == 0 ==> forall k: nat :: k < i
==> items[k] != item
invariant num == count(items[..i], item)
{
assert items[..i+1] == items[..i] + [items[i]];
if items[i] == item
{ num := num + 1; }
i := i + 1;
}
assert items[..i] == items[..];
r := num;
}
I've got the following grid of numbers centered around 0 and increasing in spiral. I need an algorithm which would receive number in spiral and return x; y - numbers of moves how to get to that number from 0. For example for number 9 it would return -2; -1. For 4 it would be 1; 1.
25|26|... etc.
24| 9|10|11|12
23| 8| 1| 2|13
22| 7| 0| 3|14
21| 6| 5| 4|15
20|19|18|17|16
This spiral can be slightly changed if it would help the algorithm to be better.
Use whatever language you like. I would really appreciate mathematical explanation.
Thank you.
First we need to determine which cycle (distance from center) and sector (north, east, south or west) we are in. Then we can determine the exact position of the number.
The first numbers in each cycle is as follows: 1, 9, 25
This is a quadratic sequence: first(n) = (2n-1)^2 = 4n^2 - 4n + 1
The inverse of this is the cycle-number: cycle(i) = floor((sqrt(i) + 1) / 2)
The length of a cycle is: length(n) = first(n+1) - first(n) = 8n
The sector will then be:
sector(i) = floor(4 * (i - first(cycle(i))) / length(cycle(i)))
Finally, to get the position, we need to extrapolate from the position of the first number in the cycle and sector.
To put it all together:
def first(cycle):
x = 2 * cycle - 1
return x * x
def cycle(index):
return (isqrt(index) + 1)//2
def length(cycle):
return 8 * cycle
def sector(index):
c = cycle(index)
offset = index - first(c)
n = length(c)
return 4 * offset / n
def position(index):
c = cycle(index)
s = sector(index)
offset = index - first(c) - s * length(c) // 4
if s == 0: #north
return -c, -c + offset + 1
if s == 1: #east
return -c + offset + 1, c
if s == 2: #south
return c, c - offset - 1
# else, west
return c - offset - 1, -c
def isqrt(x):
"""Calculates the integer square root of a number"""
if x < 0:
raise ValueError('square root not defined for negative numbers')
n = int(x)
if n == 0:
return 0
a, b = divmod(n.bit_length(), 2)
x = 2**(a+b)
while True:
y = (x + n//x)//2
if y >= x:
return x
x = y
Example:
>>> position(9)
(-2, -1)
>>> position(4)
(1, 1)
>>> position(123456)
(-176, 80)
Do you mean something like this? I did not implement any algorithm and the code can be written better but it works - that's always a start :) Just change the threshold value for whatever you wish and you'll get the result.
static int threshold=14, x=0, y=0;
public static void main(String[] args) {
int yChange=1, xChange=1, count=0;
while( !end(count) ){
for (int i = 0; i < yChange; i++) {
if( end(count) )return;
count++;
y--;
}
yChange++;
for (int i = 0; i < xChange; i++) {
if( end(count) )return;
count++;
x++;
}
xChange++;
for (int i = 0; i < yChange; i++) {
if( end(count) )return;
count++;
y++;
}
yChange++;
for (int i = 0; i < xChange; i++) {
if( end(count) )return;
count++;
x--;
}
xChange++;
}
}
public static boolean end(int count){
if(count<threshold){
return false;
}else{
System.out.println("count: "+count+", x: "+x+", y: "+y);
return true;
}
}