From c1d55b92960933569860610d1548e6d715a87fce Mon Sep 17 00:00:00 2001 From: Dale Weiler Date: Sat, 4 Jun 2022 04:31:55 -0400 Subject: [PATCH 1/3] add max heap implementation to slice package --- core/slice/heap/heap.odin | 232 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 core/slice/heap/heap.odin diff --git a/core/slice/heap/heap.odin b/core/slice/heap/heap.odin new file mode 100644 index 000000000..893e77b42 --- /dev/null +++ b/core/slice/heap/heap.odin @@ -0,0 +1,232 @@ +/* + Copyright 2022 Dale Weiler . + Made available under Odin's BSD-3 license. + + List of contributors: + Dale Weiler: Initial implementation +*/ + +// Package implements a generic max heap in-place on a slice for any type. +package heap + +/* + Constructs a max heap in slice given by data with comparator. A max heap is + a range of elements which has the following properties: + + 1. With N = len(data), for all 0 < i < N, data[(i - 1) / 2] does not compare + less than data[i]. + + 2. A new element can be added using push in O(log n) time. + + 3. The first element can be removed using pop in O(log n) time. + + The comparator compares elements of type T and can be used to construct a + max heap (less than) or min heap (greater than) for T. +*/ +make :: proc(data: []$T, compare: $C) { + // amoritize length lookup + length := len(data) + if length <= 1 do return + + // start from data parent, no need to consider children + for start := (length - 2) / 2; start >= 0; start -= 1 { + sift_down(data, compare, start); + start -= 1; + } +} + +/* + Inserts the element at the position len(data)-1 into the max heap with + comparator. + + At most log(N) comparisons where N = len(data) will be performed. +*/ +push :: proc(data: []$T, compare: $C) { + sift_up(data, compare) +} + +/* + Swaps the value in position data[0] and the value in data[len(data)-1] and + makes subrange [0, len(data)-1) into a heap. This has the effect of removing + the first element from the heap. + + At most 2 * log(N) comparisons where N = len(data) will be performed. +*/ +pop :: proc(data: []$T, compare: $C) { + length := len(data) + if length <= 1 do return + + last := length + + // create a hole at 0 + top := data[0] + hole := floyd_sift_down(data, compare) + last -= 1 + + if hole == last { + data[hole] = top + } else { + data[hole] = data[last] + hole += 1 + data[last] = top + sift_up(data[:hole], compare) + } +} + +/* + Converts the max heap into a sorted range in ascending order. The resulting + slice will no longer be a heap after this. + + At most 2 * N * log(N) comparisons where N = len(data) will be performed. +*/ +sort :: proc(data: []$T, compare: $C) { + for n := len(data); n >= 1; n -= 1 { + pop(data[:n], compare) + } +} + +/* + Examines the slice and finds the largest range which is a max-heap. Elements + are compared with user-supplied comparison procedure. + + This returns the upper bound of the largest range in the slice which is a + max heap. That is, the last index for which data is a max heap. + + At most O(n) comparisons where N = len(data) will be performed. +*/ +is_heap_until :: proc(data: []$T, compare: $C) -> int { + length := len(data) + a := 0 + b := 1 + for b < length { + if compare(data[a], data[b]) { + return b + } + b += 1 + if b == length || compare(data[a], data[b]) { + return b + } + a += 1 + b = 2 * a + 1 + } + return length +} + +/* + Checks if a given slice is a max heap. + + At most O(n) comparisons where N = len(data) will be performed. +*/ +is_heap :: #force_inline proc(data: []$T, compare: $C) -> bool { + return is_heap_until(data, compare) == len(data) +} + +@(private="file") +floyd_sift_down :: proc(data: []$T, compare: $C) -> int { + length := len(data) + assert(length >= 2) + + hole := 0 + child := 0 + index := 0 + for { + index += child + 1 + child = 2 * child + 1 + if child + 1 < length && compare(data[index], data[index + 1]) { + child += 1 + index += 1 + } + + data[hole] = data[index] + hole = index + + if child > (length - 2) / 2 { + return hole + } + } + + unreachable() +} + +@(private="file") +sift_down :: proc(data: []$T, compare: $C, start: int) { + start := start + child := start + + // amoritize length lookup + length := len(data) + + // left child of start is at 2 * start + 1 + // right child of start is at 2 * start + 2 + if length < 2 || (length - 2) / 2 < child { + return + } + + child = 2 * child + 1 + + if child + 1 < length && compare(data[child], data[child + 1]) { + // right child exists and is greater than left child + child += 1 + } + + // check if in heap order + if compare(data[child], data[start]) { + // start is larger than its largest child + return + } + + top := data[start] + for { + // not in heap order, swap parent with its largest child + data[start] = data[child] + start = child + + if (length - 2) / 2 < child { + break + } + + // recompute child based off updated parent + child = 2 * child + 1 + + if child + 1 < length && compare(data[child], data[child + 1]) { + // right child exists and is greater than left child + child += 1 + } + + // check if we are in heap order + if compare(data[child], top) { + break + } + } + + data[start] = top +} + +@(private="file") +sift_up :: proc(data: []$T, compare: $C) { + // amoritize length lookup + length := len(data) + + if length <= 1 do return + + last := length + length = (length - 2) / 2 + index := length + last -= 1 + if compare(data[index], data[last]) { + top := data[last] + for { + data[last] = data[index] + last = index + if length == 0 { + break + } + length = (length - 1) / 2 + index = length + if !compare(data[index], top) { + break + } + } + data[last] = top + } +} \ No newline at end of file From a996cfc5361e6872eaf993c041cece40570c3bc8 Mon Sep 17 00:00:00 2001 From: Dale Weiler Date: Sat, 4 Jun 2022 04:47:52 -0400 Subject: [PATCH 2/3] fix --- core/slice/heap/heap.odin | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/slice/heap/heap.odin b/core/slice/heap/heap.odin index 893e77b42..bcaadf708 100644 --- a/core/slice/heap/heap.odin +++ b/core/slice/heap/heap.odin @@ -30,8 +30,7 @@ make :: proc(data: []$T, compare: $C) { // start from data parent, no need to consider children for start := (length - 2) / 2; start >= 0; start -= 1 { - sift_down(data, compare, start); - start -= 1; + sift_down(data, compare, start) } } From ff9d0583920dcd76bc47b580a52fce852b48fc66 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 12 Jun 2022 17:25:42 +0100 Subject: [PATCH 3/3] Minor changes to `core:slice/heap`; add to examples/all --- core/slice/heap/heap.odin | 48 +++++++++++++++++++------------------- examples/all/all_main.odin | 2 ++ 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/core/slice/heap/heap.odin b/core/slice/heap/heap.odin index bcaadf708..0a3f53efb 100644 --- a/core/slice/heap/heap.odin +++ b/core/slice/heap/heap.odin @@ -23,14 +23,14 @@ package heap The comparator compares elements of type T and can be used to construct a max heap (less than) or min heap (greater than) for T. */ -make :: proc(data: []$T, compare: $C) { +make :: proc(data: []$T, less: proc(a, b: T) -> bool) { // amoritize length lookup length := len(data) if length <= 1 do return // start from data parent, no need to consider children for start := (length - 2) / 2; start >= 0; start -= 1 { - sift_down(data, compare, start) + sift_down(data, less, start) } } @@ -40,8 +40,8 @@ make :: proc(data: []$T, compare: $C) { At most log(N) comparisons where N = len(data) will be performed. */ -push :: proc(data: []$T, compare: $C) { - sift_up(data, compare) +push :: proc(data: []$T, less: proc(a, b: T) -> bool) { + sift_up(data, less) } /* @@ -51,7 +51,7 @@ push :: proc(data: []$T, compare: $C) { At most 2 * log(N) comparisons where N = len(data) will be performed. */ -pop :: proc(data: []$T, compare: $C) { +pop :: proc(data: []$T, less: proc(a, b: T) -> bool) { length := len(data) if length <= 1 do return @@ -59,7 +59,7 @@ pop :: proc(data: []$T, compare: $C) { // create a hole at 0 top := data[0] - hole := floyd_sift_down(data, compare) + hole := floyd_sift_down(data, less) last -= 1 if hole == last { @@ -68,7 +68,7 @@ pop :: proc(data: []$T, compare: $C) { data[hole] = data[last] hole += 1 data[last] = top - sift_up(data[:hole], compare) + sift_up(data[:hole], less) } } @@ -78,9 +78,9 @@ pop :: proc(data: []$T, compare: $C) { At most 2 * N * log(N) comparisons where N = len(data) will be performed. */ -sort :: proc(data: []$T, compare: $C) { +sort :: proc(data: []$T, less: proc(a, b: T) -> bool) { for n := len(data); n >= 1; n -= 1 { - pop(data[:n], compare) + pop(data[:n], less) } } @@ -93,16 +93,16 @@ sort :: proc(data: []$T, compare: $C) { At most O(n) comparisons where N = len(data) will be performed. */ -is_heap_until :: proc(data: []$T, compare: $C) -> int { +is_heap_until :: proc(data: []$T, less: proc(a, b: T) -> bool) -> int { length := len(data) a := 0 b := 1 for b < length { - if compare(data[a], data[b]) { + if less(data[a], data[b]) { return b } b += 1 - if b == length || compare(data[a], data[b]) { + if b == length || less(data[a], data[b]) { return b } a += 1 @@ -116,12 +116,12 @@ is_heap_until :: proc(data: []$T, compare: $C) -> int { At most O(n) comparisons where N = len(data) will be performed. */ -is_heap :: #force_inline proc(data: []$T, compare: $C) -> bool { - return is_heap_until(data, compare) == len(data) +is_heap :: #force_inline proc(data: []$T, less: proc(a, b: T) -> bool) -> bool { + return is_heap_until(data, less) == len(data) } @(private="file") -floyd_sift_down :: proc(data: []$T, compare: $C) -> int { +floyd_sift_down :: proc(data: []$T, less: proc(a, b: T) -> bool) -> int { length := len(data) assert(length >= 2) @@ -131,7 +131,7 @@ floyd_sift_down :: proc(data: []$T, compare: $C) -> int { for { index += child + 1 child = 2 * child + 1 - if child + 1 < length && compare(data[index], data[index + 1]) { + if child + 1 < length && less(data[index], data[index + 1]) { child += 1 index += 1 } @@ -148,7 +148,7 @@ floyd_sift_down :: proc(data: []$T, compare: $C) -> int { } @(private="file") -sift_down :: proc(data: []$T, compare: $C, start: int) { +sift_down :: proc(data: []$T, less: proc(a, b: T) -> bool, start: int) { start := start child := start @@ -163,13 +163,13 @@ sift_down :: proc(data: []$T, compare: $C, start: int) { child = 2 * child + 1 - if child + 1 < length && compare(data[child], data[child + 1]) { + if child + 1 < length && less(data[child], data[child + 1]) { // right child exists and is greater than left child child += 1 } // check if in heap order - if compare(data[child], data[start]) { + if less(data[child], data[start]) { // start is larger than its largest child return } @@ -187,13 +187,13 @@ sift_down :: proc(data: []$T, compare: $C, start: int) { // recompute child based off updated parent child = 2 * child + 1 - if child + 1 < length && compare(data[child], data[child + 1]) { + if child + 1 < length && less(data[child], data[child + 1]) { // right child exists and is greater than left child child += 1 } // check if we are in heap order - if compare(data[child], top) { + if less(data[child], top) { break } } @@ -202,7 +202,7 @@ sift_down :: proc(data: []$T, compare: $C, start: int) { } @(private="file") -sift_up :: proc(data: []$T, compare: $C) { +sift_up :: proc(data: []$T, less: proc(a, b: T) -> bool) { // amoritize length lookup length := len(data) @@ -212,7 +212,7 @@ sift_up :: proc(data: []$T, compare: $C) { length = (length - 2) / 2 index := length last -= 1 - if compare(data[index], data[last]) { + if less(data[index], data[last]) { top := data[last] for { data[last] = data[index] @@ -222,7 +222,7 @@ sift_up :: proc(data: []$T, compare: $C) { } length = (length - 1) / 2 index = length - if !compare(data[index], top) { + if !less(data[index], top) { break } } diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 1ab242305..9f347fad3 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -98,6 +98,7 @@ import reflect "core:reflect" import runtime "core:runtime" import simd "core:simd" import slice "core:slice" +import slice_heap "core:slice/heap" import sort "core:sort" import strconv "core:strconv" import strings "core:strings" @@ -195,6 +196,7 @@ _ :: reflect _ :: runtime _ :: simd _ :: slice +_ :: slice_heap _ :: sort _ :: strconv _ :: strings