diff --git a/array_data_slab.go b/array_data_slab.go index 00ec6c1..930db90 100644 --- a/array_data_slab.go +++ b/array_data_slab.go @@ -187,7 +187,12 @@ func (a *ArrayDataSlab) Split(storage SlabStorage) (Slab, Slab, error) { leftSize += elemSize } - // Construct right slab + // Split elements + var rightElements []Storable + a.elements, rightElements = split(a.elements, leftCount) + + // Create right slab + sID, err := storage.GenerateSlabID(a.header.slabID.address) if err != nil { // Wrap err as external error (if needed) because err is returned by SlabStorage interface. @@ -199,20 +204,18 @@ func (a *ArrayDataSlab) Split(storage SlabStorage) (Slab, Slab, error) { ), ) } - rightSlabCount := len(a.elements) - leftCount + rightSlab := &ArrayDataSlab{ header: ArraySlabHeader{ slabID: sID, size: arrayDataSlabPrefixSize + dataSize - leftSize, - count: uint32(rightSlabCount), + count: uint32(len(rightElements)), }, - next: a.next, + next: a.next, + elements: rightElements, } - rightSlab.elements = slices.Clone(a.elements[leftCount:]) - // Modify left (original) slab - a.elements = slices.Delete(a.elements, leftCount, len(a.elements)) a.header.size = arrayDataSlabPrefixSize + leftSize a.header.count = uint32(leftCount) a.next = rightSlab.header.slabID @@ -222,7 +225,7 @@ func (a *ArrayDataSlab) Split(storage SlabStorage) (Slab, Slab, error) { func (a *ArrayDataSlab) Merge(slab Slab) error { rightSlab := slab.(*ArrayDataSlab) - a.elements = append(a.elements, rightSlab.elements...) + a.elements = merge(a.elements, rightSlab.elements) a.header.size = a.header.size + rightSlab.header.size - arrayDataSlabPrefixSize a.header.count += rightSlab.header.count a.next = rightSlab.next @@ -237,6 +240,7 @@ func (a *ArrayDataSlab) LendToRight(slab Slab) error { count := a.header.count + rightSlab.header.count size := a.header.size + rightSlab.header.size + oldLeftCount := a.header.count leftCount := a.header.count leftSize := a.header.size @@ -252,22 +256,15 @@ func (a *ArrayDataSlab) LendToRight(slab Slab) error { leftCount-- } - // Update the right slab - - // Prepend elements from the left slab to the right slab - rightSlab.elements = slices.Insert( - rightSlab.elements, - 0, - a.elements[leftCount:]..., - ) + // Move elements + moveCount := oldLeftCount - leftCount + a.elements, rightSlab.elements = lendToRight(a.elements, rightSlab.elements, int(moveCount)) + // Update right slab rightSlab.header.size = size - leftSize rightSlab.header.count = count - leftCount // Update left slab - // NOTE: clear to prevent memory leak - clear(a.elements[leftCount:]) - a.elements = a.elements[:leftCount] a.header.size = leftSize a.header.count = leftCount @@ -281,6 +278,7 @@ func (a *ArrayDataSlab) BorrowFromRight(slab Slab) error { count := a.header.count + rightSlab.header.count size := a.header.size + rightSlab.header.size + oldLeftCount := a.header.count leftCount := a.header.count leftSize := a.header.size @@ -300,20 +298,15 @@ func (a *ArrayDataSlab) BorrowFromRight(slab Slab) error { leftCount++ } - rightStartIndex := leftCount - a.header.count + // Move elements + moveCount := leftCount - oldLeftCount + a.elements, rightSlab.elements = borrowFromRight(a.elements, rightSlab.elements, int(moveCount)) // Update left slab - a.elements = append(a.elements, rightSlab.elements[:rightStartIndex]...) a.header.size = leftSize a.header.count = leftCount // Update right slab - // TODO: copy elements to front instead? - // NOTE: prevent memory leak - for i := range rightStartIndex { - rightSlab.elements[i] = nil - } - rightSlab.elements = rightSlab.elements[rightStartIndex:] rightSlab.header.size = size - leftSize rightSlab.header.count = count - leftCount diff --git a/array_metadata_slab.go b/array_metadata_slab.go index 456a809..3c39c76 100644 --- a/array_metadata_slab.go +++ b/array_metadata_slab.go @@ -631,7 +631,7 @@ func (a *ArrayMetaDataSlab) Merge(slab Slab) error { leftSlabChildrenCount := len(a.childrenHeaders) rightSlab := slab.(*ArrayMetaDataSlab) - a.childrenHeaders = append(a.childrenHeaders, rightSlab.childrenHeaders...) + a.childrenHeaders = merge(a.childrenHeaders, rightSlab.childrenHeaders) a.header.size += rightSlab.header.size - arrayMetaDataSlabPrefixSize a.header.count += rightSlab.header.count @@ -661,7 +661,11 @@ func (a *ArrayMetaDataSlab) Split(storage SlabStorage) (Slab, Slab, error) { leftCount += a.childrenHeaders[i].count } - // Construct right slab + // Split childrenHeaders + var rightChildrenHeaders []ArraySlabHeader + a.childrenHeaders, rightChildrenHeaders = split(a.childrenHeaders, leftChildrenCount) + + // Create right slab sID, err := storage.GenerateSlabID(a.header.slabID.address) if err != nil { // Wrap err as external error (if needed) because err is returned by SlabStorage interface. @@ -676,10 +680,9 @@ func (a *ArrayMetaDataSlab) Split(storage SlabStorage) (Slab, Slab, error) { size: a.header.size - uint32(leftSize), count: a.header.count - leftCount, }, + childrenHeaders: rightChildrenHeaders, } - rightSlab.childrenHeaders = slices.Clone(a.childrenHeaders[leftChildrenCount:]) - rightSlab.childrenCountSum = make([]uint32, len(rightSlab.childrenHeaders)) countSum := uint32(0) for i := range rightSlab.childrenCountSum { @@ -688,7 +691,6 @@ func (a *ArrayMetaDataSlab) Split(storage SlabStorage) (Slab, Slab, error) { } // Modify left (original)slab - a.childrenHeaders = a.childrenHeaders[:leftChildrenCount] a.childrenCountSum = a.childrenCountSum[:leftChildrenCount] a.header.count = leftCount a.header.size = arrayMetaDataSlabPrefixSize + uint32(leftSize) @@ -699,15 +701,13 @@ func (a *ArrayMetaDataSlab) Split(storage SlabStorage) (Slab, Slab, error) { func (a *ArrayMetaDataSlab) LendToRight(slab Slab) error { rightSlab := slab.(*ArrayMetaDataSlab) - childrenHeadersLen := len(a.childrenHeaders) + len(rightSlab.childrenHeaders) - leftChildrenHeadersLen := childrenHeadersLen / 2 + oldLeftChildrenHeaderCount := len(a.childrenHeaders) + childrenHeaderCount := len(a.childrenHeaders) + len(rightSlab.childrenHeaders) + leftChildrenHeaderCount := childrenHeaderCount / 2 - // Prepend child headers from the left slab to the right slab headers - rightSlab.childrenHeaders = slices.Insert( - rightSlab.childrenHeaders, - 0, - a.childrenHeaders[leftChildrenHeadersLen:]..., - ) + // Move elements + moveCount := oldLeftChildrenHeaderCount - leftChildrenHeaderCount + a.childrenHeaders, rightSlab.childrenHeaders = lendToRight(a.childrenHeaders, rightSlab.childrenHeaders, moveCount) // Rebuild right slab childrenCountSum rightSlab.childrenCountSum = make([]uint32, len(rightSlab.childrenHeaders)) @@ -725,41 +725,40 @@ func (a *ArrayMetaDataSlab) LendToRight(slab Slab) error { rightSlab.header.size = arrayMetaDataSlabPrefixSize + uint32(len(rightSlab.childrenHeaders))*arraySlabHeaderSize // Update left slab (original) - a.childrenHeaders = a.childrenHeaders[:leftChildrenHeadersLen] - a.childrenCountSum = a.childrenCountSum[:leftChildrenHeadersLen] + a.childrenCountSum = a.childrenCountSum[:leftChildrenHeaderCount] a.header.count = 0 for i := range a.childrenHeaders { a.header.count += a.childrenHeaders[i].count } - a.header.size = arrayMetaDataSlabPrefixSize + uint32(leftChildrenHeadersLen)*arraySlabHeaderSize + a.header.size = arrayMetaDataSlabPrefixSize + uint32(leftChildrenHeaderCount)*arraySlabHeaderSize return nil } func (a *ArrayMetaDataSlab) BorrowFromRight(slab Slab) error { - originalLeftSlabCountSum := a.header.count - originalLeftSlabHeaderLen := len(a.childrenHeaders) + oldLeftSlabCountSum := a.header.count + oldLeftChildrenHeaderCount := len(a.childrenHeaders) rightSlab := slab.(*ArrayMetaDataSlab) - childrenHeadersLen := len(a.childrenHeaders) + len(rightSlab.childrenHeaders) - leftSlabHeaderLen := childrenHeadersLen / 2 - rightSlabHeaderLen := childrenHeadersLen - leftSlabHeaderLen + childrenHeaderCount := len(a.childrenHeaders) + len(rightSlab.childrenHeaders) + leftChildrenHeaderCount := childrenHeaderCount / 2 - // Update left slab (original) - a.childrenHeaders = append(a.childrenHeaders, rightSlab.childrenHeaders[:leftSlabHeaderLen-len(a.childrenHeaders)]...) + // Move elements + moveCount := leftChildrenHeaderCount - oldLeftChildrenHeaderCount + a.childrenHeaders, rightSlab.childrenHeaders = borrowFromRight(a.childrenHeaders, rightSlab.childrenHeaders, moveCount) - countSum := originalLeftSlabCountSum - for i := originalLeftSlabHeaderLen; i < len(a.childrenHeaders); i++ { + // Update left slab (original) + countSum := oldLeftSlabCountSum + for i := oldLeftChildrenHeaderCount; i < len(a.childrenHeaders); i++ { countSum += a.childrenHeaders[i].count a.childrenCountSum = append(a.childrenCountSum, countSum) } a.header.count = countSum - a.header.size = arrayMetaDataSlabPrefixSize + uint32(leftSlabHeaderLen)*arraySlabHeaderSize + a.header.size = arrayMetaDataSlabPrefixSize + uint32(leftChildrenHeaderCount)*arraySlabHeaderSize // Update right slab - rightSlab.childrenHeaders = rightSlab.childrenHeaders[len(rightSlab.childrenHeaders)-rightSlabHeaderLen:] rightSlab.childrenCountSum = rightSlab.childrenCountSum[:len(rightSlab.childrenHeaders)] countSum = uint32(0) @@ -768,7 +767,7 @@ func (a *ArrayMetaDataSlab) BorrowFromRight(slab Slab) error { rightSlab.childrenCountSum[i] = countSum } rightSlab.header.count = countSum - rightSlab.header.size = arrayMetaDataSlabPrefixSize + uint32(rightSlabHeaderLen)*arraySlabHeaderSize + rightSlab.header.size = arrayMetaDataSlabPrefixSize + uint32(len(rightSlab.childrenHeaders))*arraySlabHeaderSize return nil } diff --git a/map_elements_hashkey.go b/map_elements_hashkey.go index a769880..bb003a7 100644 --- a/map_elements_hashkey.go +++ b/map_elements_hashkey.go @@ -427,13 +427,10 @@ func (e *hkeyElements) Merge(elems elements) error { return NewSlabMergeError(fmt.Errorf("cannot merge elements of different types (%T, %T)", e, elems)) } - e.hkeys = append(e.hkeys, rElems.hkeys...) - e.elems = append(e.elems, rElems.elems...) + e.hkeys = merge(e.hkeys, rElems.hkeys) + e.elems = merge(e.elems, rElems.elems) e.size += rElems.Size() - hkeyElementsPrefixSize - // Set merged elements to nil to prevent memory leak. - clear(rElems.elems) - return nil } @@ -461,19 +458,22 @@ func (e *hkeyElements) Split() (elements, elements, error) { leftSize += elemSize } - // Create right slab elements - rightElements := &hkeyElements{level: e.level} - - rightElements.hkeys = slices.Clone(e.hkeys[leftCount:]) + // Split elements + var rightKeys []Digest + e.hkeys, rightKeys = split(e.hkeys, leftCount) - rightElements.elems = slices.Clone(e.elems[leftCount:]) + var rightElems []element + e.elems, rightElems = split(e.elems, leftCount) - rightElements.size = dataSize - leftSize + hkeyElementsPrefixSize + // Create right slab + rightElements := &hkeyElements{ + level: e.level, + hkeys: rightKeys, + elems: rightElems, + size: dataSize - leftSize + hkeyElementsPrefixSize, + } - e.hkeys = e.hkeys[:leftCount] - // NOTE: prevent memory leak - clear(e.elems[leftCount:]) - e.elems = e.elems[:leftCount] + // Update left slab e.size = hkeyElementsPrefixSize + leftSize return e, rightElements, nil @@ -494,6 +494,7 @@ func (e *hkeyElements) LendToRight(re elements) error { size := e.Size() + rightElements.Size() - hkeyElementsPrefixSize*2 + oldLeftCount := len(e.elems) leftCount := len(e.elems) leftSize := e.Size() - hkeyElementsPrefixSize @@ -509,16 +510,15 @@ func (e *hkeyElements) LendToRight(re elements) error { leftCount-- } - // Update the right elements - rightElements.hkeys = slices.Insert(rightElements.hkeys, 0, e.hkeys[leftCount:]...) - rightElements.elems = slices.Insert(rightElements.elems, 0, e.elems[leftCount:]...) + // Move elements + moveCount := oldLeftCount - leftCount + e.hkeys, rightElements.hkeys = lendToRight(e.hkeys, rightElements.hkeys, moveCount) + e.elems, rightElements.elems = lendToRight(e.elems, rightElements.elems, moveCount) + + // Update right slab rightElements.size = size - leftSize + hkeyElementsPrefixSize // Update left slab - // NOTE: prevent memory leak - clear(e.elems[leftCount:]) - e.hkeys = e.hkeys[:leftCount] - e.elems = e.elems[:leftCount] e.size = hkeyElementsPrefixSize + leftSize return nil @@ -539,6 +539,7 @@ func (e *hkeyElements) BorrowFromRight(re elements) error { size := e.Size() + rightElements.Size() - hkeyElementsPrefixSize*2 + oldLeftCount := len(e.elems) leftCount := len(e.elems) leftSize := e.Size() - hkeyElementsPrefixSize @@ -558,21 +559,15 @@ func (e *hkeyElements) BorrowFromRight(re elements) error { leftCount++ } - rightStartIndex := leftCount - len(e.elems) + // Move elements + moveCount := leftCount - oldLeftCount + e.hkeys, rightElements.hkeys = borrowFromRight(e.hkeys, rightElements.hkeys, moveCount) + e.elems, rightElements.elems = borrowFromRight(e.elems, rightElements.elems, moveCount) - // Update left elements - e.hkeys = append(e.hkeys, rightElements.hkeys[:rightStartIndex]...) - e.elems = append(e.elems, rightElements.elems[:rightStartIndex]...) + // Update left slab e.size = leftSize + hkeyElementsPrefixSize // Update right slab - // TODO: copy elements to front instead? - // NOTE: prevent memory leak - for i := range rightStartIndex { - rightElements.elems[i] = nil - } - rightElements.hkeys = rightElements.hkeys[rightStartIndex:] - rightElements.elems = rightElements.elems[rightStartIndex:] rightElements.size = size - leftSize + hkeyElementsPrefixSize return nil diff --git a/map_metadata_slab.go b/map_metadata_slab.go index aa4a7a0..8c28870 100644 --- a/map_metadata_slab.go +++ b/map_metadata_slab.go @@ -590,7 +590,7 @@ func (m *MapMetaDataSlab) updateChildrenHeadersAfterMerge( func (m *MapMetaDataSlab) Merge(slab Slab) error { rightSlab := slab.(*MapMetaDataSlab) - m.childrenHeaders = append(m.childrenHeaders, rightSlab.childrenHeaders...) + m.childrenHeaders = merge(m.childrenHeaders, rightSlab.childrenHeaders) m.header.size += rightSlab.header.size - mapMetaDataSlabPrefixSize return nil @@ -611,19 +611,21 @@ func (m *MapMetaDataSlab) Split(storage SlabStorage) (Slab, Slab, error) { return nil, nil, wrapErrorfAsExternalErrorIfNeeded(err, fmt.Sprintf("failed to generate slab ID for address 0x%x", m.SlabID().address)) } - // Construct right slab + // Split children headers + var rightChildrenHeaders []MapSlabHeader + m.childrenHeaders, rightChildrenHeaders = split(m.childrenHeaders, leftChildrenCount) + + // Create right slab rightSlab := &MapMetaDataSlab{ header: MapSlabHeader{ slabID: sID, size: m.header.size - uint32(leftSize), - firstKey: m.childrenHeaders[leftChildrenCount].firstKey, + firstKey: rightChildrenHeaders[0].firstKey, }, + childrenHeaders: rightChildrenHeaders, } - rightSlab.childrenHeaders = slices.Clone(m.childrenHeaders[leftChildrenCount:]) - // Modify left (original) slab - m.childrenHeaders = m.childrenHeaders[:leftChildrenCount] m.header.size = mapMetaDataSlabPrefixSize + uint32(leftSize) return m, rightSlab, nil @@ -632,24 +634,21 @@ func (m *MapMetaDataSlab) Split(storage SlabStorage) (Slab, Slab, error) { func (m *MapMetaDataSlab) LendToRight(slab Slab) error { rightSlab := slab.(*MapMetaDataSlab) - childrenHeadersLen := len(m.childrenHeaders) + len(rightSlab.childrenHeaders) - leftChildrenHeadersLen := childrenHeadersLen / 2 - rightChildrenHeadersLen := childrenHeadersLen - leftChildrenHeadersLen + oldLeftChildrenHeaderCount := len(m.childrenHeaders) + childrenHeaderCount := len(m.childrenHeaders) + len(rightSlab.childrenHeaders) + leftChildrenHeaderCount := childrenHeaderCount / 2 + rightChildrenHeaderCount := childrenHeaderCount - leftChildrenHeaderCount - // Update right slab childrenHeaders by prepending borrowed children headers - rightSlab.childrenHeaders = slices.Insert( - rightSlab.childrenHeaders, - 0, - m.childrenHeaders[leftChildrenHeadersLen:]...) + // Move children headers + moveCount := oldLeftChildrenHeaderCount - leftChildrenHeaderCount + m.childrenHeaders, rightSlab.childrenHeaders = lendToRight(m.childrenHeaders, rightSlab.childrenHeaders, moveCount) - // Update right slab header - rightSlab.header.size = mapMetaDataSlabPrefixSize + uint32(rightChildrenHeadersLen)*mapSlabHeaderSize + // Update right slab + rightSlab.header.size = mapMetaDataSlabPrefixSize + uint32(rightChildrenHeaderCount)*mapSlabHeaderSize rightSlab.header.firstKey = rightSlab.childrenHeaders[0].firstKey // Update left slab (original) - m.childrenHeaders = m.childrenHeaders[:leftChildrenHeadersLen] - - m.header.size = mapMetaDataSlabPrefixSize + uint32(leftChildrenHeadersLen)*mapSlabHeaderSize + m.header.size = mapMetaDataSlabPrefixSize + uint32(leftChildrenHeaderCount)*mapSlabHeaderSize return nil } @@ -658,19 +657,20 @@ func (m *MapMetaDataSlab) BorrowFromRight(slab Slab) error { rightSlab := slab.(*MapMetaDataSlab) - childrenHeadersLen := len(m.childrenHeaders) + len(rightSlab.childrenHeaders) - leftSlabHeaderLen := childrenHeadersLen / 2 - rightSlabHeaderLen := childrenHeadersLen - leftSlabHeaderLen + oldLeftChildrenHeaderCount := len(m.childrenHeaders) + childrenHeaderCount := len(m.childrenHeaders) + len(rightSlab.childrenHeaders) + leftChildrenHeaderCount := childrenHeaderCount / 2 + rightChildrenHeaderCount := childrenHeaderCount - leftChildrenHeaderCount - // Update left slab (original) - m.childrenHeaders = append(m.childrenHeaders, rightSlab.childrenHeaders[:leftSlabHeaderLen-len(m.childrenHeaders)]...) + // Move children headers + moveCount := leftChildrenHeaderCount - oldLeftChildrenHeaderCount + m.childrenHeaders, rightSlab.childrenHeaders = borrowFromRight(m.childrenHeaders, rightSlab.childrenHeaders, moveCount) - m.header.size = mapMetaDataSlabPrefixSize + uint32(leftSlabHeaderLen)*mapSlabHeaderSize + // Update left slab (original) + m.header.size = mapMetaDataSlabPrefixSize + uint32(leftChildrenHeaderCount)*mapSlabHeaderSize // Update right slab - rightSlab.childrenHeaders = rightSlab.childrenHeaders[len(rightSlab.childrenHeaders)-rightSlabHeaderLen:] - - rightSlab.header.size = mapMetaDataSlabPrefixSize + uint32(rightSlabHeaderLen)*mapSlabHeaderSize + rightSlab.header.size = mapMetaDataSlabPrefixSize + uint32(rightChildrenHeaderCount)*mapSlabHeaderSize rightSlab.header.firstKey = rightSlab.childrenHeaders[0].firstKey return nil diff --git a/slice_utils.go b/slice_utils.go new file mode 100644 index 0000000..d8ee657 --- /dev/null +++ b/slice_utils.go @@ -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 +} diff --git a/slice_utils_test.go b/slice_utils_test.go new file mode 100644 index 0000000..b13bce5 --- /dev/null +++ b/slice_utils_test.go @@ -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]) + }) +}