Skip to content

Commit

Permalink
Add util functions to move elements in slices
Browse files Browse the repository at this point in the history
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
fxamacker committed Mar 4, 2025
1 parent b3fd53d commit 1fa0272
Show file tree
Hide file tree
Showing 2 changed files with 346 additions and 0 deletions.
78 changes: 78 additions & 0 deletions slice_utils.go
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
}
268 changes: 268 additions & 0 deletions slice_utils_test.go
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])
})
}

0 comments on commit 1fa0272

Please sign in to comment.