Skip to content

Commit 14d831d

Browse files
Added Hilbert distance finding in a Hilbert curve of a 2d plane.
1 parent 5449ae6 commit 14d831d

File tree

2 files changed

+161
-0
lines changed

2 files changed

+161
-0
lines changed

numerics/hilbert/hilbert.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
Copyright 2014 Workiva, LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
/*
18+
Package Hilbert is designed to allow consumers to find the Hilbert
19+
distance on the Hilbert curve if given a 2 dimensional coordinate.
20+
This could be useful for hashing or constructing a Hilbert R-Tree.
21+
Algorithm taken from here:
22+
23+
http://en.wikipedia.org/wiki/Hilbert_curve
24+
25+
This expects coordinates in the range [0, 0] to [MaxInt32, MaxInt32].
26+
Using negative values for x and y will have undefinied behavior.
27+
28+
Benchmarks:
29+
BenchmarkEncode-8 10000000 181 ns/op
30+
BenchmarkDecode-8 10000000 191 ns/op
31+
*/
32+
package hilbert
33+
34+
// n defines the maximum power of 2 that can define a bound,
35+
// this is the value for 2-d space if you want to support
36+
// all hilbert ids with a single integer variable
37+
const n = 1 << 31
38+
39+
func boolToInt(value bool) int32 {
40+
if value {
41+
return int32(1)
42+
}
43+
44+
return int32(0)
45+
}
46+
47+
func rotate(n, rx, ry int32, x, y *int32) {
48+
if ry == 0 {
49+
if rx == 1 {
50+
*x = n - 1 - *x
51+
*y = n - 1 - *y
52+
}
53+
54+
t := *x
55+
*x = *y
56+
*y = t
57+
}
58+
}
59+
60+
// Encode will encode the provided x and y coordinates into a Hilbert
61+
// distance.
62+
func Encode(x, y int32) int64 {
63+
var rx, ry int32
64+
var d int64
65+
for s := int32(n / 2); s > 0; s /= 2 {
66+
rx = boolToInt(x&s > 0)
67+
ry = boolToInt(y&s > 0)
68+
d += int64(int64(s) * int64(s) * int64(((3 * rx) ^ ry)))
69+
rotate(s, rx, ry, &x, &y)
70+
}
71+
72+
return d
73+
}
74+
75+
// Decode will decode the provided Hilbert distance into a corresponding
76+
// x and y value, respectively.
77+
func Decode(h int64) (int32, int32) {
78+
var ry, rx int64
79+
var x, y int32
80+
t := h
81+
82+
for s := int64(1); s < int64(n); s *= 2 {
83+
rx = 1 & (t / 2)
84+
ry = 1 & (t ^ rx)
85+
rotate(int32(s), int32(rx), int32(ry), &x, &y)
86+
x += int32(s * rx)
87+
y += int32(s * ry)
88+
t /= 4
89+
}
90+
91+
return x, y
92+
}

numerics/hilbert/hilbert_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
Copyright 2014 Workiva, LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package hilbert
17+
18+
import (
19+
"math"
20+
"testing"
21+
22+
"github.com/stretchr/testify/assert"
23+
)
24+
25+
func TestHilbert(t *testing.T) {
26+
h := Encode(0, 0)
27+
x, y := Decode(h)
28+
assert.Equal(t, int64(0), h)
29+
assert.Equal(t, int32(0), x)
30+
assert.Equal(t, int32(0), y)
31+
32+
h = Encode(1, 0)
33+
x, y = Decode(h)
34+
assert.Equal(t, int64(3), h)
35+
assert.Equal(t, int32(1), x)
36+
assert.Equal(t, int32(0), y)
37+
38+
h = Encode(1, 1)
39+
x, y = Decode(h)
40+
assert.Equal(t, int64(2), h)
41+
assert.Equal(t, int32(1), x)
42+
assert.Equal(t, int32(1), y)
43+
44+
h = Encode(0, 1)
45+
x, y = Decode(h)
46+
assert.Equal(t, int64(1), h)
47+
assert.Equal(t, int32(0), x)
48+
assert.Equal(t, int32(1), y)
49+
}
50+
51+
func TestHilbertAtMaxRange(t *testing.T) {
52+
x, y := int32(math.MaxInt32), int32(math.MaxInt32)
53+
h := Encode(x, y)
54+
resultx, resulty := Decode(h)
55+
assert.Equal(t, x, resultx)
56+
assert.Equal(t, y, resulty)
57+
}
58+
59+
func BenchmarkEncode(b *testing.B) {
60+
for i := 0; i < b.N; i++ {
61+
Encode(int32(i), int32(i))
62+
}
63+
}
64+
65+
func BenchmarkDecode(b *testing.B) {
66+
for i := 0; i < b.N; i++ {
67+
Decode(int64(i))
68+
}
69+
}

0 commit comments

Comments
 (0)