-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add util functions to move elements in slices
This commit adds internal functions to move elements. The new functions can be used to simplify slab operations, such as Merge(), Split(), LendToRight(), and BorrowFromRight().
- Loading branch information
Showing
2 changed files
with
346 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/* | ||
* Atree - Scalable Arrays and Ordered Maps | ||
* | ||
* Copyright Flow Foundation | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package atree | ||
|
||
import "slices" | ||
|
||
// split splits s into two slices with left slice of leftCount length and right slice of remaining elements. | ||
// Returned left is resliced s, and returned right is new slice. | ||
func split[S ~[]E, E any](s S, leftCount int) (left S, right S) { | ||
_ = s[leftCount:] // bounds check | ||
|
||
right = slices.Clone(s[leftCount:]) | ||
left = slices.Delete(s, leftCount, len(s)) | ||
return left, right | ||
} | ||
|
||
// merge returnes concatenated left and right slices. | ||
// If left slice has sufficient capacity, returned slice is resliced to append elements from right. | ||
// Right slice is cleared. | ||
func merge[S ~[]E, E any](left, right S) S { | ||
left = append(left, right...) | ||
clear(right) | ||
return left | ||
} | ||
|
||
// lendToRight moves elements from tail of left slice to head of right slice. | ||
func lendToRight[S ~[]E, E any](left, right S, count int) (S, S) { | ||
leftIndex := len(left) - count | ||
|
||
_ = left[leftIndex:] // bounds check | ||
|
||
// Prepend elements from the tail of left slice to the head of right slice. | ||
right = slices.Insert( | ||
right, | ||
0, | ||
left[leftIndex:]..., | ||
) | ||
|
||
// Remove moved elements from left | ||
left = slices.Delete(left, leftIndex, len(left)) | ||
|
||
return left, right | ||
} | ||
|
||
// borrowFromRight moves elements from head of right slice to tail of left slice. | ||
func borrowFromRight[S ~[]E, E any](left, right S, count int) (S, S) { | ||
_ = right[:count] // bounds check | ||
|
||
// Append moved elements to left | ||
left = append(left, right[:count]...) | ||
|
||
// Move remaining elements in the right slice to the front | ||
right = slices.Insert( | ||
right[:0], | ||
0, | ||
right[count:]...) | ||
|
||
// Clear moved elements to prevent memory leak | ||
clear(right[len(right):cap(right)]) | ||
|
||
return left, right | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,268 @@ | ||
/* | ||
* Atree - Scalable Arrays and Ordered Maps | ||
* | ||
* Copyright Flow Foundation | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package atree | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestSliceSplit(t *testing.T) { | ||
t.Parallel() | ||
|
||
t.Run("count is out of range", func(t *testing.T) { | ||
var s []int | ||
assert.Panics(t, func() { _, _ = split(s, len(s)+1) }) | ||
|
||
s = []int{1} | ||
assert.Panics(t, func() { _, _ = split(s, len(s)+1) }) | ||
}) | ||
|
||
t.Run("empty slice", func(t *testing.T) { | ||
var s []int | ||
left, right := split(s, 0) | ||
require.Equal(t, 0, len(left)) | ||
require.Equal(t, 0, len(right)) | ||
}) | ||
|
||
t.Run("non-empty slice", func(t *testing.T) { | ||
|
||
// split []int{1, 2, 3, 4, 5} at 0 | ||
{ | ||
s := []int{1, 2, 3, 4, 5} | ||
left, right := split(s, 0) | ||
|
||
require.Equal(t, 0, len(left)) | ||
require.Equal(t, []int{0, 0, 0, 0, 0}, left[:cap(left)]) | ||
|
||
require.Equal(t, 5, len(right)) | ||
require.Equal(t, []int{1, 2, 3, 4, 5}, right) | ||
} | ||
|
||
// split []int{1, 2, 3, 4, 5} at 3 | ||
{ | ||
s := []int{1, 2, 3, 4, 5} | ||
p := &s[0] | ||
left, right := split(s, 3) | ||
|
||
require.Equal(t, 3, len(left)) | ||
require.Equal(t, []int{1, 2, 3}, left) | ||
require.Equal(t, []int{1, 2, 3, 0, 0}, left[:cap(left)]) | ||
require.Equal(t, p, &left[0]) | ||
|
||
require.Equal(t, 2, len(right)) | ||
require.Equal(t, []int{4, 5}, right) | ||
} | ||
|
||
// split []int{1, 2, 3, 4, 5} at len(s) | ||
{ | ||
s := []int{1, 2, 3, 4, 5} | ||
p := &s[0] | ||
left, right := split(s, 5) | ||
|
||
require.Equal(t, 5, len(left)) | ||
require.Equal(t, []int{1, 2, 3, 4, 5}, left) | ||
require.Equal(t, p, &left[0]) | ||
|
||
require.Equal(t, 0, len(right)) | ||
} | ||
}) | ||
} | ||
|
||
func TestSliceMerge(t *testing.T) { | ||
t.Parallel() | ||
|
||
t.Run("empty slices", func(t *testing.T) { | ||
s1 := []int{} | ||
s2 := []int{} | ||
s := merge(s1, s2) | ||
require.Equal(t, 0, len(s)) | ||
}) | ||
|
||
t.Run("empty left slice", func(t *testing.T) { | ||
s1 := []int{} | ||
s2 := []int{1, 2, 3} | ||
s := merge(s1, s2) | ||
require.Equal(t, 3, len(s)) | ||
require.Equal(t, []int{1, 2, 3}, s) | ||
}) | ||
|
||
t.Run("empty right slice", func(t *testing.T) { | ||
s1 := []int{1, 2, 3} | ||
p := &s1[0] | ||
s2 := []int{} | ||
s := merge(s1, s2) | ||
require.Equal(t, 3, len(s)) | ||
require.Equal(t, []int{1, 2, 3}, s) | ||
require.Equal(t, p, &s[0]) | ||
}) | ||
|
||
t.Run("non-empty slices", func(t *testing.T) { | ||
s1 := make([]int, 3, 5) | ||
s1[0] = 1 | ||
s1[1] = 2 | ||
s1[2] = 3 | ||
p := &s1[0] | ||
s2 := []int{4, 5} | ||
s := merge(s1, s2) | ||
require.Equal(t, 5, len(s)) | ||
require.Equal(t, []int{1, 2, 3, 4, 5}, s) | ||
require.Equal(t, p, &s[0]) | ||
}) | ||
} | ||
|
||
func TestSliceMoveFromLeftToRight(t *testing.T) { | ||
t.Parallel() | ||
|
||
t.Run("count is out of range", func(t *testing.T) { | ||
assert.Panics(t, func() { _, _ = lendToRight([]int{}, []int{0, 1, 2}, 1) }) | ||
|
||
assert.Panics(t, func() { _, _ = lendToRight([]int{0}, []int{0, 1, 2}, 2) }) | ||
}) | ||
|
||
t.Run("move 0 element", func(t *testing.T) { | ||
// left is empty slice | ||
{ | ||
s1 := []int{} | ||
s2 := []int{3, 4} | ||
p2 := &s2[0] | ||
s1b, s2b := lendToRight(s1, s2, 0) | ||
require.Equal(t, []int{}, s1b) | ||
require.Equal(t, []int{3, 4}, s2b) | ||
require.Equal(t, p2, &s2b[0]) | ||
} | ||
|
||
// left is non-empty slice | ||
{ | ||
s1 := []int{1, 2} | ||
p1 := &s1[0] | ||
s2 := []int{3, 4} | ||
p2 := &s2[0] | ||
s1b, s2b := lendToRight(s1, s2, 0) | ||
require.Equal(t, []int{1, 2}, s1b) | ||
require.Equal(t, p1, &s1b[0]) | ||
require.Equal(t, []int{3, 4}, s2b) | ||
require.Equal(t, p2, &s2b[0]) | ||
} | ||
}) | ||
|
||
t.Run("move all elements", func(t *testing.T) { | ||
// right is empty slice | ||
{ | ||
s1 := []int{1, 2, 3} | ||
oldlen := len(s1) | ||
s2 := []int{} | ||
s1b, s2b := lendToRight(s1, s2, len(s1)) | ||
require.Equal(t, []int{}, s1b) | ||
require.Equal(t, []int{0, 0, 0}, s1b[:oldlen]) | ||
require.Equal(t, []int{1, 2, 3}, s2b) | ||
} | ||
|
||
// right is non-empty slice | ||
{ | ||
s1 := []int{1, 2, 3} | ||
oldlen := len(s1) | ||
s2 := []int{4, 5} | ||
s1b, s2b := lendToRight(s1, s2, len(s1)) | ||
require.Equal(t, []int{}, s1b) | ||
require.Equal(t, []int{0, 0, 0}, s1b[:oldlen]) | ||
require.Equal(t, []int{1, 2, 3, 4, 5}, s2b) | ||
} | ||
}) | ||
|
||
t.Run("move some elements", func(t *testing.T) { | ||
s1 := []int{1, 2, 3} | ||
oldlen := len(s1) | ||
s2 := []int{4, 5} | ||
s1b, s2b := lendToRight(s1, s2, 2) | ||
require.Equal(t, []int{1}, s1b) | ||
require.Equal(t, []int{1, 0, 0}, s1b[:oldlen]) | ||
require.Equal(t, []int{2, 3, 4, 5}, s2b) | ||
}) | ||
} | ||
|
||
func TestSliceMoveFromRightToLeft(t *testing.T) { | ||
t.Parallel() | ||
|
||
t.Run("count is out of range", func(t *testing.T) { | ||
assert.Panics(t, func() { _, _ = borrowFromRight([]int{}, []int{}, 1) }) | ||
|
||
assert.Panics(t, func() { _, _ = borrowFromRight([]int{}, []int{1}, 2) }) | ||
}) | ||
|
||
t.Run("move 0 element", func(t *testing.T) { | ||
// right is empty slice | ||
{ | ||
s1 := []int{3, 4} | ||
s2 := []int{} | ||
s1b, s2b := borrowFromRight(s1, s2, 0) | ||
require.Equal(t, []int{3, 4}, s1b) | ||
require.Equal(t, []int{}, s2b) | ||
} | ||
|
||
// right is non-empty slice | ||
{ | ||
s1 := []int{1, 2, 3} | ||
p1 := &s1[0] | ||
s2 := []int{4, 5} | ||
p2 := &s2[0] | ||
s1b, s2b := borrowFromRight(s1, s2, 0) | ||
require.Equal(t, []int{1, 2, 3}, s1b) | ||
require.Equal(t, p1, &s1b[0]) | ||
require.Equal(t, []int{4, 5}, s2b) | ||
require.Equal(t, p2, &s2b[0]) | ||
} | ||
}) | ||
|
||
t.Run("move all element", func(t *testing.T) { | ||
// left is empty slice | ||
{ | ||
s1 := []int{} | ||
s2 := []int{1, 2, 3} | ||
oldlen := len(s2) | ||
s1b, s2b := borrowFromRight(s1, s2, len(s2)) | ||
require.Equal(t, []int{1, 2, 3}, s1b) | ||
require.Equal(t, []int{}, s2b) | ||
require.Equal(t, []int{0, 0, 0}, s2b[:oldlen]) | ||
} | ||
|
||
// left is non-empty slice | ||
{ | ||
s1 := []int{1, 2, 3} | ||
s2 := []int{4, 5} | ||
oldlen := len(s2) | ||
s1b, s2b := borrowFromRight(s1, s2, len(s2)) | ||
require.Equal(t, []int{1, 2, 3, 4, 5}, s1b) | ||
require.Equal(t, []int{}, s2b) | ||
require.Equal(t, []int{0, 0}, s2b[:oldlen]) | ||
} | ||
}) | ||
|
||
t.Run("move some element", func(t *testing.T) { | ||
s1 := []int{1, 2} | ||
s2 := []int{3, 4, 5} | ||
oldlen := len(s2) | ||
s1b, s2b := borrowFromRight(s1, s2, 2) | ||
require.Equal(t, []int{1, 2, 3, 4}, s1b) | ||
require.Equal(t, []int{5}, s2b) | ||
require.Equal(t, []int{5, 0, 0}, s2b[:oldlen]) | ||
}) | ||
} |