You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							613 lines
						
					
					
						
							13 KiB
						
					
					
				
			
		
		
	
	
							613 lines
						
					
					
						
							13 KiB
						
					
					
				| package roaring
 | |
| 
 | |
| import (
 | |
| 	"container/heap"
 | |
| 	"fmt"
 | |
| 	"runtime"
 | |
| 	"sync"
 | |
| )
 | |
| 
 | |
| var defaultWorkerCount = runtime.NumCPU()
 | |
| 
 | |
| type bitmapContainerKey struct {
 | |
| 	key    uint16
 | |
| 	idx    int
 | |
| 	bitmap *Bitmap
 | |
| }
 | |
| 
 | |
| type multipleContainers struct {
 | |
| 	key        uint16
 | |
| 	containers []container
 | |
| 	idx        int
 | |
| }
 | |
| 
 | |
| type keyedContainer struct {
 | |
| 	key       uint16
 | |
| 	container container
 | |
| 	idx       int
 | |
| }
 | |
| 
 | |
| type bitmapContainerHeap []bitmapContainerKey
 | |
| 
 | |
| func (h bitmapContainerHeap) Len() int           { return len(h) }
 | |
| func (h bitmapContainerHeap) Less(i, j int) bool { return h[i].key < h[j].key }
 | |
| func (h bitmapContainerHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }
 | |
| 
 | |
| func (h *bitmapContainerHeap) Push(x interface{}) {
 | |
| 	// Push and Pop use pointer receivers because they modify the slice's length,
 | |
| 	// not just its contents.
 | |
| 	*h = append(*h, x.(bitmapContainerKey))
 | |
| }
 | |
| 
 | |
| func (h *bitmapContainerHeap) Pop() interface{} {
 | |
| 	old := *h
 | |
| 	n := len(old)
 | |
| 	x := old[n-1]
 | |
| 	*h = old[0 : n-1]
 | |
| 	return x
 | |
| }
 | |
| 
 | |
| func (h bitmapContainerHeap) Peek() bitmapContainerKey {
 | |
| 	return h[0]
 | |
| }
 | |
| 
 | |
| func (h *bitmapContainerHeap) popIncrementing() (key uint16, container container) {
 | |
| 	k := h.Peek()
 | |
| 	key = k.key
 | |
| 	container = k.bitmap.highlowcontainer.containers[k.idx]
 | |
| 
 | |
| 	newIdx := k.idx + 1
 | |
| 	if newIdx < k.bitmap.highlowcontainer.size() {
 | |
| 		k = bitmapContainerKey{
 | |
| 			k.bitmap.highlowcontainer.keys[newIdx],
 | |
| 			newIdx,
 | |
| 			k.bitmap,
 | |
| 		}
 | |
| 		(*h)[0] = k
 | |
| 		heap.Fix(h, 0)
 | |
| 	} else {
 | |
| 		heap.Pop(h)
 | |
| 	}
 | |
| 
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (h *bitmapContainerHeap) Next(containers []container) multipleContainers {
 | |
| 	if h.Len() == 0 {
 | |
| 		return multipleContainers{}
 | |
| 	}
 | |
| 
 | |
| 	key, container := h.popIncrementing()
 | |
| 	containers = append(containers, container)
 | |
| 
 | |
| 	for h.Len() > 0 && key == h.Peek().key {
 | |
| 		_, container = h.popIncrementing()
 | |
| 		containers = append(containers, container)
 | |
| 	}
 | |
| 
 | |
| 	return multipleContainers{
 | |
| 		key,
 | |
| 		containers,
 | |
| 		-1,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func newBitmapContainerHeap(bitmaps ...*Bitmap) bitmapContainerHeap {
 | |
| 	// Initialize heap
 | |
| 	var h bitmapContainerHeap = make([]bitmapContainerKey, 0, len(bitmaps))
 | |
| 	for _, bitmap := range bitmaps {
 | |
| 		if !bitmap.IsEmpty() {
 | |
| 			key := bitmapContainerKey{
 | |
| 				bitmap.highlowcontainer.keys[0],
 | |
| 				0,
 | |
| 				bitmap,
 | |
| 			}
 | |
| 			h = append(h, key)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	heap.Init(&h)
 | |
| 
 | |
| 	return h
 | |
| }
 | |
| 
 | |
| func repairAfterLazy(c container) container {
 | |
| 	switch t := c.(type) {
 | |
| 	case *bitmapContainer:
 | |
| 		if t.cardinality == invalidCardinality {
 | |
| 			t.computeCardinality()
 | |
| 		}
 | |
| 
 | |
| 		if t.getCardinality() <= arrayDefaultMaxSize {
 | |
| 			return t.toArrayContainer()
 | |
| 		} else if c.(*bitmapContainer).isFull() {
 | |
| 			return newRunContainer16Range(0, MaxUint16)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return c
 | |
| }
 | |
| 
 | |
| func toBitmapContainer(c container) container {
 | |
| 	switch t := c.(type) {
 | |
| 	case *arrayContainer:
 | |
| 		return t.toBitmapContainer()
 | |
| 	case *runContainer16:
 | |
| 		if !t.isFull() {
 | |
| 			return t.toBitmapContainer()
 | |
| 		}
 | |
| 	}
 | |
| 	return c
 | |
| }
 | |
| 
 | |
| func appenderRoutine(bitmapChan chan<- *Bitmap, resultChan <-chan keyedContainer, expectedKeysChan <-chan int) {
 | |
| 	expectedKeys := -1
 | |
| 	appendedKeys := 0
 | |
| 	keys := make([]uint16, 0)
 | |
| 	containers := make([]container, 0)
 | |
| 	for appendedKeys != expectedKeys {
 | |
| 		select {
 | |
| 		case item := <-resultChan:
 | |
| 			if len(keys) <= item.idx {
 | |
| 				keys = append(keys, make([]uint16, item.idx-len(keys)+1)...)
 | |
| 				containers = append(containers, make([]container, item.idx-len(containers)+1)...)
 | |
| 			}
 | |
| 			keys[item.idx] = item.key
 | |
| 			containers[item.idx] = item.container
 | |
| 
 | |
| 			appendedKeys++
 | |
| 		case msg := <-expectedKeysChan:
 | |
| 			expectedKeys = msg
 | |
| 		}
 | |
| 	}
 | |
| 	answer := &Bitmap{
 | |
| 		roaringArray{
 | |
| 			make([]uint16, 0, expectedKeys),
 | |
| 			make([]container, 0, expectedKeys),
 | |
| 			make([]bool, 0, expectedKeys),
 | |
| 			false,
 | |
| 			nil,
 | |
| 		},
 | |
| 	}
 | |
| 	for i := range keys {
 | |
| 		if containers[i] != nil { // in case a resulting container was empty, see ParAnd function
 | |
| 			answer.highlowcontainer.appendContainer(keys[i], containers[i], false)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	bitmapChan <- answer
 | |
| }
 | |
| 
 | |
| // ParHeapOr computes the union (OR) of all provided bitmaps in parallel,
 | |
| // where the parameter "parallelism" determines how many workers are to be used
 | |
| // (if it is set to 0, a default number of workers is chosen)
 | |
| // ParHeapOr uses a heap to compute the union. For rare cases it might be faster than ParOr
 | |
| func ParHeapOr(parallelism int, bitmaps ...*Bitmap) *Bitmap {
 | |
| 
 | |
| 	bitmapCount := len(bitmaps)
 | |
| 	if bitmapCount == 0 {
 | |
| 		return NewBitmap()
 | |
| 	} else if bitmapCount == 1 {
 | |
| 		return bitmaps[0].Clone()
 | |
| 	}
 | |
| 
 | |
| 	if parallelism == 0 {
 | |
| 		parallelism = defaultWorkerCount
 | |
| 	}
 | |
| 
 | |
| 	h := newBitmapContainerHeap(bitmaps...)
 | |
| 
 | |
| 	bitmapChan := make(chan *Bitmap)
 | |
| 	inputChan := make(chan multipleContainers, 128)
 | |
| 	resultChan := make(chan keyedContainer, 32)
 | |
| 	expectedKeysChan := make(chan int)
 | |
| 
 | |
| 	pool := sync.Pool{
 | |
| 		New: func() interface{} {
 | |
| 			return make([]container, 0, len(bitmaps))
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	orFunc := func() {
 | |
| 		// Assumes only structs with >=2 containers are passed
 | |
| 		for input := range inputChan {
 | |
| 			c := toBitmapContainer(input.containers[0]).lazyOR(input.containers[1])
 | |
| 			for _, next := range input.containers[2:] {
 | |
| 				c = c.lazyIOR(next)
 | |
| 			}
 | |
| 			c = repairAfterLazy(c)
 | |
| 			kx := keyedContainer{
 | |
| 				input.key,
 | |
| 				c,
 | |
| 				input.idx,
 | |
| 			}
 | |
| 			resultChan <- kx
 | |
| 			pool.Put(input.containers[:0])
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	go appenderRoutine(bitmapChan, resultChan, expectedKeysChan)
 | |
| 
 | |
| 	for i := 0; i < parallelism; i++ {
 | |
| 		go orFunc()
 | |
| 	}
 | |
| 
 | |
| 	idx := 0
 | |
| 	for h.Len() > 0 {
 | |
| 		ck := h.Next(pool.Get().([]container))
 | |
| 		if len(ck.containers) == 1 {
 | |
| 			resultChan <- keyedContainer{
 | |
| 				ck.key,
 | |
| 				ck.containers[0],
 | |
| 				idx,
 | |
| 			}
 | |
| 			pool.Put(ck.containers[:0])
 | |
| 		} else {
 | |
| 			ck.idx = idx
 | |
| 			inputChan <- ck
 | |
| 		}
 | |
| 		idx++
 | |
| 	}
 | |
| 	expectedKeysChan <- idx
 | |
| 
 | |
| 	bitmap := <-bitmapChan
 | |
| 
 | |
| 	close(inputChan)
 | |
| 	close(resultChan)
 | |
| 	close(expectedKeysChan)
 | |
| 
 | |
| 	return bitmap
 | |
| }
 | |
| 
 | |
| // ParAnd computes the intersection (AND) of all provided bitmaps in parallel,
 | |
| // where the parameter "parallelism" determines how many workers are to be used
 | |
| // (if it is set to 0, a default number of workers is chosen)
 | |
| func ParAnd(parallelism int, bitmaps ...*Bitmap) *Bitmap {
 | |
| 	bitmapCount := len(bitmaps)
 | |
| 	if bitmapCount == 0 {
 | |
| 		return NewBitmap()
 | |
| 	} else if bitmapCount == 1 {
 | |
| 		return bitmaps[0].Clone()
 | |
| 	}
 | |
| 
 | |
| 	if parallelism == 0 {
 | |
| 		parallelism = defaultWorkerCount
 | |
| 	}
 | |
| 
 | |
| 	h := newBitmapContainerHeap(bitmaps...)
 | |
| 
 | |
| 	bitmapChan := make(chan *Bitmap)
 | |
| 	inputChan := make(chan multipleContainers, 128)
 | |
| 	resultChan := make(chan keyedContainer, 32)
 | |
| 	expectedKeysChan := make(chan int)
 | |
| 
 | |
| 	andFunc := func() {
 | |
| 		// Assumes only structs with >=2 containers are passed
 | |
| 		for input := range inputChan {
 | |
| 			c := input.containers[0].and(input.containers[1])
 | |
| 			for _, next := range input.containers[2:] {
 | |
| 				if c.getCardinality() == 0 {
 | |
| 					break
 | |
| 				}
 | |
| 				c = c.iand(next)
 | |
| 			}
 | |
| 
 | |
| 			// Send a nil explicitly if the result of the intersection is an empty container
 | |
| 			if c.getCardinality() == 0 {
 | |
| 				c = nil
 | |
| 			}
 | |
| 
 | |
| 			kx := keyedContainer{
 | |
| 				input.key,
 | |
| 				c,
 | |
| 				input.idx,
 | |
| 			}
 | |
| 			resultChan <- kx
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	go appenderRoutine(bitmapChan, resultChan, expectedKeysChan)
 | |
| 
 | |
| 	for i := 0; i < parallelism; i++ {
 | |
| 		go andFunc()
 | |
| 	}
 | |
| 
 | |
| 	idx := 0
 | |
| 	for h.Len() > 0 {
 | |
| 		ck := h.Next(make([]container, 0, 4))
 | |
| 		if len(ck.containers) == bitmapCount {
 | |
| 			ck.idx = idx
 | |
| 			inputChan <- ck
 | |
| 			idx++
 | |
| 		}
 | |
| 	}
 | |
| 	expectedKeysChan <- idx
 | |
| 
 | |
| 	bitmap := <-bitmapChan
 | |
| 
 | |
| 	close(inputChan)
 | |
| 	close(resultChan)
 | |
| 	close(expectedKeysChan)
 | |
| 
 | |
| 	return bitmap
 | |
| }
 | |
| 
 | |
| // ParOr computes the union (OR) of all provided bitmaps in parallel,
 | |
| // where the parameter "parallelism" determines how many workers are to be used
 | |
| // (if it is set to 0, a default number of workers is chosen)
 | |
| func ParOr(parallelism int, bitmaps ...*Bitmap) *Bitmap {
 | |
| 	var lKey uint16 = MaxUint16
 | |
| 	var hKey uint16 = 0
 | |
| 
 | |
| 	bitmapsFiltered := bitmaps[:0]
 | |
| 	for _, b := range bitmaps {
 | |
| 		if !b.IsEmpty() {
 | |
| 			bitmapsFiltered = append(bitmapsFiltered, b)
 | |
| 		}
 | |
| 	}
 | |
| 	bitmaps = bitmapsFiltered
 | |
| 
 | |
| 	for _, b := range bitmaps {
 | |
| 		lKey = minOfUint16(lKey, b.highlowcontainer.keys[0])
 | |
| 		hKey = maxOfUint16(hKey, b.highlowcontainer.keys[b.highlowcontainer.size()-1])
 | |
| 	}
 | |
| 
 | |
| 	if lKey == MaxUint16 && hKey == 0 {
 | |
| 		return New()
 | |
| 	} else if len(bitmaps) == 1 {
 | |
| 		return bitmaps[0]
 | |
| 	}
 | |
| 
 | |
| 	keyRange := hKey - lKey + 1
 | |
| 	if keyRange == 1 {
 | |
| 		// revert to FastOr. Since the key range is 0
 | |
| 		// no container-level aggregation parallelism is achievable
 | |
| 		return FastOr(bitmaps...)
 | |
| 	}
 | |
| 
 | |
| 	if parallelism == 0 {
 | |
| 		parallelism = defaultWorkerCount
 | |
| 	}
 | |
| 
 | |
| 	var chunkSize int
 | |
| 	var chunkCount int
 | |
| 	if parallelism*4 > int(keyRange) {
 | |
| 		chunkSize = 1
 | |
| 		chunkCount = int(keyRange)
 | |
| 	} else {
 | |
| 		chunkCount = parallelism * 4
 | |
| 		chunkSize = (int(keyRange) + chunkCount - 1) / chunkCount
 | |
| 	}
 | |
| 
 | |
| 	if chunkCount*chunkSize < int(keyRange) {
 | |
| 		// it's fine to panic to indicate an implementation error
 | |
| 		panic(fmt.Sprintf("invariant check failed: chunkCount * chunkSize < keyRange, %d * %d < %d", chunkCount, chunkSize, keyRange))
 | |
| 	}
 | |
| 
 | |
| 	chunks := make([]*roaringArray, chunkCount)
 | |
| 
 | |
| 	chunkSpecChan := make(chan parChunkSpec, minOfInt(maxOfInt(64, 2*parallelism), int(chunkCount)))
 | |
| 	chunkChan := make(chan parChunk, minOfInt(32, int(chunkCount)))
 | |
| 
 | |
| 	orFunc := func() {
 | |
| 		for spec := range chunkSpecChan {
 | |
| 			ra := lazyOrOnRange(&bitmaps[0].highlowcontainer, &bitmaps[1].highlowcontainer, spec.start, spec.end)
 | |
| 			for _, b := range bitmaps[2:] {
 | |
| 				ra = lazyIOrOnRange(ra, &b.highlowcontainer, spec.start, spec.end)
 | |
| 			}
 | |
| 
 | |
| 			for i, c := range ra.containers {
 | |
| 				ra.containers[i] = repairAfterLazy(c)
 | |
| 			}
 | |
| 
 | |
| 			chunkChan <- parChunk{ra, spec.idx}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for i := 0; i < parallelism; i++ {
 | |
| 		go orFunc()
 | |
| 	}
 | |
| 
 | |
| 	go func() {
 | |
| 		for i := 0; i < chunkCount; i++ {
 | |
| 			spec := parChunkSpec{
 | |
| 				start: uint16(int(lKey) + i*chunkSize),
 | |
| 				end:   uint16(minOfInt(int(lKey)+(i+1)*chunkSize-1, int(hKey))),
 | |
| 				idx:   int(i),
 | |
| 			}
 | |
| 			chunkSpecChan <- spec
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	chunksRemaining := chunkCount
 | |
| 	for chunk := range chunkChan {
 | |
| 		chunks[chunk.idx] = chunk.ra
 | |
| 		chunksRemaining--
 | |
| 		if chunksRemaining == 0 {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	close(chunkChan)
 | |
| 	close(chunkSpecChan)
 | |
| 
 | |
| 	containerCount := 0
 | |
| 	for _, chunk := range chunks {
 | |
| 		containerCount += chunk.size()
 | |
| 	}
 | |
| 
 | |
| 	result := Bitmap{
 | |
| 		roaringArray{
 | |
| 			containers:      make([]container, containerCount),
 | |
| 			keys:            make([]uint16, containerCount),
 | |
| 			needCopyOnWrite: make([]bool, containerCount),
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	resultOffset := 0
 | |
| 	for _, chunk := range chunks {
 | |
| 		copy(result.highlowcontainer.containers[resultOffset:], chunk.containers)
 | |
| 		copy(result.highlowcontainer.keys[resultOffset:], chunk.keys)
 | |
| 		copy(result.highlowcontainer.needCopyOnWrite[resultOffset:], chunk.needCopyOnWrite)
 | |
| 		resultOffset += chunk.size()
 | |
| 	}
 | |
| 
 | |
| 	return &result
 | |
| }
 | |
| 
 | |
| type parChunkSpec struct {
 | |
| 	start uint16
 | |
| 	end   uint16
 | |
| 	idx   int
 | |
| }
 | |
| 
 | |
| type parChunk struct {
 | |
| 	ra  *roaringArray
 | |
| 	idx int
 | |
| }
 | |
| 
 | |
| func (c parChunk) size() int {
 | |
| 	return c.ra.size()
 | |
| }
 | |
| 
 | |
| func parNaiveStartAt(ra *roaringArray, start uint16, last uint16) int {
 | |
| 	for idx, key := range ra.keys {
 | |
| 		if key >= start && key <= last {
 | |
| 			return idx
 | |
| 		} else if key > last {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	return ra.size()
 | |
| }
 | |
| 
 | |
| func lazyOrOnRange(ra1, ra2 *roaringArray, start, last uint16) *roaringArray {
 | |
| 	answer := newRoaringArray()
 | |
| 	length1 := ra1.size()
 | |
| 	length2 := ra2.size()
 | |
| 
 | |
| 	idx1 := parNaiveStartAt(ra1, start, last)
 | |
| 	idx2 := parNaiveStartAt(ra2, start, last)
 | |
| 
 | |
| 	var key1 uint16
 | |
| 	var key2 uint16
 | |
| 	if idx1 < length1 && idx2 < length2 {
 | |
| 		key1 = ra1.getKeyAtIndex(idx1)
 | |
| 		key2 = ra2.getKeyAtIndex(idx2)
 | |
| 
 | |
| 		for key1 <= last && key2 <= last {
 | |
| 
 | |
| 			if key1 < key2 {
 | |
| 				answer.appendCopy(*ra1, idx1)
 | |
| 				idx1++
 | |
| 				if idx1 == length1 {
 | |
| 					break
 | |
| 				}
 | |
| 				key1 = ra1.getKeyAtIndex(idx1)
 | |
| 			} else if key1 > key2 {
 | |
| 				answer.appendCopy(*ra2, idx2)
 | |
| 				idx2++
 | |
| 				if idx2 == length2 {
 | |
| 					break
 | |
| 				}
 | |
| 				key2 = ra2.getKeyAtIndex(idx2)
 | |
| 			} else {
 | |
| 				c1 := ra1.getFastContainerAtIndex(idx1, false)
 | |
| 
 | |
| 				answer.appendContainer(key1, c1.lazyOR(ra2.getContainerAtIndex(idx2)), false)
 | |
| 				idx1++
 | |
| 				idx2++
 | |
| 				if idx1 == length1 || idx2 == length2 {
 | |
| 					break
 | |
| 				}
 | |
| 
 | |
| 				key1 = ra1.getKeyAtIndex(idx1)
 | |
| 				key2 = ra2.getKeyAtIndex(idx2)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if idx2 < length2 {
 | |
| 		key2 = ra2.getKeyAtIndex(idx2)
 | |
| 		for key2 <= last {
 | |
| 			answer.appendCopy(*ra2, idx2)
 | |
| 			idx2++
 | |
| 			if idx2 == length2 {
 | |
| 				break
 | |
| 			}
 | |
| 			key2 = ra2.getKeyAtIndex(idx2)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if idx1 < length1 {
 | |
| 		key1 = ra1.getKeyAtIndex(idx1)
 | |
| 		for key1 <= last {
 | |
| 			answer.appendCopy(*ra1, idx1)
 | |
| 			idx1++
 | |
| 			if idx1 == length1 {
 | |
| 				break
 | |
| 			}
 | |
| 			key1 = ra1.getKeyAtIndex(idx1)
 | |
| 		}
 | |
| 	}
 | |
| 	return answer
 | |
| }
 | |
| 
 | |
| func lazyIOrOnRange(ra1, ra2 *roaringArray, start, last uint16) *roaringArray {
 | |
| 	length1 := ra1.size()
 | |
| 	length2 := ra2.size()
 | |
| 
 | |
| 	idx1 := 0
 | |
| 	idx2 := parNaiveStartAt(ra2, start, last)
 | |
| 
 | |
| 	var key1 uint16
 | |
| 	var key2 uint16
 | |
| 	if idx1 < length1 && idx2 < length2 {
 | |
| 		key1 = ra1.getKeyAtIndex(idx1)
 | |
| 		key2 = ra2.getKeyAtIndex(idx2)
 | |
| 
 | |
| 		for key1 <= last && key2 <= last {
 | |
| 			if key1 < key2 {
 | |
| 				idx1++
 | |
| 				if idx1 >= length1 {
 | |
| 					break
 | |
| 				}
 | |
| 				key1 = ra1.getKeyAtIndex(idx1)
 | |
| 			} else if key1 > key2 {
 | |
| 				ra1.insertNewKeyValueAt(idx1, key2, ra2.getContainerAtIndex(idx2))
 | |
| 				ra1.needCopyOnWrite[idx1] = true
 | |
| 				idx2++
 | |
| 				idx1++
 | |
| 				length1++
 | |
| 				if idx2 >= length2 {
 | |
| 					break
 | |
| 				}
 | |
| 				key2 = ra2.getKeyAtIndex(idx2)
 | |
| 			} else {
 | |
| 				c1 := ra1.getFastContainerAtIndex(idx1, true)
 | |
| 
 | |
| 				ra1.containers[idx1] = c1.lazyIOR(ra2.getContainerAtIndex(idx2))
 | |
| 				ra1.needCopyOnWrite[idx1] = false
 | |
| 				idx1++
 | |
| 				idx2++
 | |
| 				if idx1 >= length1 || idx2 >= length2 {
 | |
| 					break
 | |
| 				}
 | |
| 
 | |
| 				key1 = ra1.getKeyAtIndex(idx1)
 | |
| 				key2 = ra2.getKeyAtIndex(idx2)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if idx2 < length2 {
 | |
| 		key2 = ra2.getKeyAtIndex(idx2)
 | |
| 		for key2 <= last {
 | |
| 			ra1.appendCopy(*ra2, idx2)
 | |
| 			idx2++
 | |
| 			if idx2 >= length2 {
 | |
| 				break
 | |
| 			}
 | |
| 			key2 = ra2.getKeyAtIndex(idx2)
 | |
| 		}
 | |
| 	}
 | |
| 	return ra1
 | |
| }
 | |
| 
 |