github.com/blend/go-sdk@v1.20220411.3/collections/ring_buffer.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package collections
     9  
    10  import (
    11  	"fmt"
    12  	"strings"
    13  )
    14  
    15  const (
    16  	ringBufferMinimumGrow     = 4
    17  	ringBufferGrowFactor      = 200
    18  	ringBufferDefaultCapacity = 4
    19  )
    20  
    21  // NewRingBuffer creates a new, empty, RingBuffer.
    22  func NewRingBuffer() *RingBuffer {
    23  	return &RingBuffer{
    24  		array: make([]interface{}, ringBufferDefaultCapacity),
    25  		head:  0,
    26  		tail:  0,
    27  		size:  0,
    28  	}
    29  }
    30  
    31  // NewRingBufferWithCapacity creates a new ringbuffer with a given capacity.
    32  func NewRingBufferWithCapacity(capacity int) *RingBuffer {
    33  	return &RingBuffer{
    34  		array: make([]interface{}, capacity),
    35  		head:  0,
    36  		tail:  0,
    37  		size:  0,
    38  	}
    39  }
    40  
    41  // NewRingBufferFromValues creates a new ring buffer out of a slice.
    42  func NewRingBufferFromValues(values []interface{}) *RingBuffer {
    43  	return &RingBuffer{
    44  		array: values,
    45  		head:  0,
    46  		tail:  len(values) - 1,
    47  		size:  len(values),
    48  	}
    49  }
    50  
    51  var (
    52  	_ Queue = (*RingBuffer)(nil)
    53  )
    54  
    55  // RingBuffer is a fifo buffer that is backed by a pre-allocated array, instead of allocating
    56  // a whole new node object for each element (which saves GC churn).
    57  // Enqueue can be O(n), Dequeue can be O(1).
    58  type RingBuffer struct {
    59  	array []interface{}
    60  	head  int
    61  	tail  int
    62  	size  int
    63  }
    64  
    65  // Len returns the length of the ring buffer (as it is currently populated).
    66  // Actual memory footprint may be different.
    67  func (rb *RingBuffer) Len() (len int) {
    68  	return rb.size
    69  }
    70  
    71  // Capacity returns the total size of the ring bufffer, including empty elements.
    72  func (rb *RingBuffer) Capacity() int {
    73  	return len(rb.array)
    74  }
    75  
    76  // Clear removes all objects from the RingBuffer.
    77  func (rb *RingBuffer) Clear() {
    78  	if rb.head < rb.tail {
    79  		arrayClear(rb.array, rb.head, rb.size)
    80  	} else {
    81  		arrayClear(rb.array, rb.head, len(rb.array)-rb.head)
    82  		arrayClear(rb.array, 0, rb.tail)
    83  	}
    84  
    85  	rb.head = 0
    86  	rb.tail = 0
    87  	rb.size = 0
    88  }
    89  
    90  // Enqueue adds an element to the "back" of the RingBuffer.
    91  func (rb *RingBuffer) Enqueue(object interface{}) {
    92  	if rb.size == len(rb.array) {
    93  		newCapacity := int(len(rb.array) * int(ringBufferGrowFactor/100))
    94  		if newCapacity < (len(rb.array) + ringBufferMinimumGrow) {
    95  			newCapacity = len(rb.array) + ringBufferMinimumGrow
    96  		}
    97  		rb.setCapacity(newCapacity)
    98  	}
    99  
   100  	rb.array[rb.tail] = object
   101  	rb.tail = (rb.tail + 1) % len(rb.array)
   102  	rb.size++
   103  }
   104  
   105  // Dequeue removes the first (oldest) element from the RingBuffer.
   106  func (rb *RingBuffer) Dequeue() interface{} {
   107  	if rb.size == 0 {
   108  		return nil
   109  	}
   110  
   111  	removed := rb.array[rb.head]
   112  	rb.head = (rb.head + 1) % len(rb.array)
   113  	rb.size--
   114  
   115  	return removed
   116  }
   117  
   118  // DequeueBack removes the last (newest) element from the RingBuffer.
   119  func (rb *RingBuffer) DequeueBack() interface{} {
   120  	if rb.size == 0 {
   121  		return nil
   122  	}
   123  
   124  	// tail is the
   125  	var removed interface{}
   126  	if rb.tail == 0 {
   127  		removed = rb.array[len(rb.array)-1]
   128  		rb.tail = len(rb.array) - 1
   129  	} else {
   130  		removed = rb.array[rb.tail-1]
   131  		rb.tail = rb.tail - 1
   132  	}
   133  	rb.size--
   134  	return removed
   135  }
   136  
   137  // Peek returns but does not remove the first element.
   138  func (rb *RingBuffer) Peek() interface{} {
   139  	if rb.size == 0 {
   140  		return nil
   141  	}
   142  	return rb.array[rb.head]
   143  }
   144  
   145  // PeekBack returns but does not remove the last element.
   146  func (rb *RingBuffer) PeekBack() interface{} {
   147  	if rb.size == 0 {
   148  		return nil
   149  	}
   150  	if rb.tail == 0 {
   151  		return rb.array[len(rb.array)-1]
   152  	}
   153  	return rb.array[rb.tail-1]
   154  }
   155  
   156  func (rb *RingBuffer) setCapacity(capacity int) {
   157  	newArray := make([]interface{}, capacity)
   158  	if rb.size > 0 {
   159  		if rb.head < rb.tail {
   160  			arrayCopy(rb.array, rb.head, newArray, 0, rb.size)
   161  		} else {
   162  			arrayCopy(rb.array, rb.head, newArray, 0, len(rb.array)-rb.head)
   163  			arrayCopy(rb.array, 0, newArray, len(rb.array)-rb.head, rb.tail)
   164  		}
   165  	}
   166  	rb.array = newArray
   167  	rb.head = 0
   168  	if rb.size == capacity {
   169  		rb.tail = 0
   170  	} else {
   171  		rb.tail = rb.size
   172  	}
   173  }
   174  
   175  // trimExcess resizes the buffer to better fit the contents.
   176  func (rb *RingBuffer) trimExcess() {
   177  	threshold := float64(len(rb.array)) * 0.9
   178  	if rb.size < int(threshold) {
   179  		rb.setCapacity(rb.size)
   180  	}
   181  }
   182  
   183  // Contents returns the ring buffer, in order, as a slice.
   184  func (rb *RingBuffer) Contents() []interface{} {
   185  	newArray := make([]interface{}, rb.size)
   186  
   187  	if rb.size == 0 {
   188  		return newArray
   189  	}
   190  
   191  	if rb.head < rb.tail {
   192  		arrayCopy(rb.array, rb.head, newArray, 0, rb.size)
   193  		arrayClear(rb.array, rb.head, rb.size)
   194  	} else {
   195  		arrayCopy(rb.array, rb.head, newArray, 0, len(rb.array)-rb.head)
   196  		arrayClear(rb.array, rb.head, len(rb.array)-rb.head)
   197  		arrayCopy(rb.array, 0, newArray, len(rb.array)-rb.head, rb.tail)
   198  		arrayClear(rb.array, 0, rb.tail)
   199  	}
   200  
   201  	return newArray
   202  }
   203  
   204  // Drain clears the buffer and removes the contents.
   205  func (rb *RingBuffer) Drain() []interface{} {
   206  	newArray := make([]interface{}, rb.size)
   207  
   208  	if rb.size == 0 {
   209  		return newArray
   210  	}
   211  
   212  	if rb.head < rb.tail {
   213  		arrayCopy(rb.array, rb.head, newArray, 0, rb.size)
   214  	} else {
   215  		arrayCopy(rb.array, rb.head, newArray, 0, len(rb.array)-rb.head)
   216  		arrayCopy(rb.array, 0, newArray, len(rb.array)-rb.head, rb.tail)
   217  	}
   218  
   219  	rb.head = 0
   220  	rb.tail = 0
   221  	rb.size = 0
   222  
   223  	return newArray
   224  }
   225  
   226  // Each calls the consumer for each element in the buffer.
   227  func (rb *RingBuffer) Each(consumer func(value interface{})) {
   228  	if rb.size == 0 {
   229  		return
   230  	}
   231  
   232  	if rb.head < rb.tail {
   233  		for cursor := rb.head; cursor < rb.tail; cursor++ {
   234  			consumer(rb.array[cursor])
   235  		}
   236  	} else {
   237  		for cursor := rb.head; cursor < len(rb.array); cursor++ {
   238  			consumer(rb.array[cursor])
   239  		}
   240  		for cursor := 0; cursor < rb.tail; cursor++ {
   241  			consumer(rb.array[cursor])
   242  		}
   243  	}
   244  }
   245  
   246  // Consume calls the consumer for each element in the buffer, while also dequeueing that entry.
   247  func (rb *RingBuffer) Consume(consumer func(value interface{})) {
   248  	if rb.size == 0 {
   249  		return
   250  	}
   251  
   252  	len := rb.Len()
   253  	for i := 0; i < len; i++ {
   254  		consumer(rb.Dequeue())
   255  	}
   256  }
   257  
   258  // EachUntil calls the consumer for each element in the buffer with a stopping condition in head=>tail order.
   259  func (rb *RingBuffer) EachUntil(consumer func(value interface{}) bool) {
   260  	if rb.size == 0 {
   261  		return
   262  	}
   263  
   264  	if rb.head < rb.tail {
   265  		for cursor := rb.head; cursor < rb.tail; cursor++ {
   266  			if !consumer(rb.array[cursor]) {
   267  				return
   268  			}
   269  		}
   270  	} else {
   271  		for cursor := rb.head; cursor < len(rb.array); cursor++ {
   272  			if !consumer(rb.array[cursor]) {
   273  				return
   274  			}
   275  		}
   276  		for cursor := 0; cursor < rb.tail; cursor++ {
   277  			if !consumer(rb.array[cursor]) {
   278  				return
   279  			}
   280  		}
   281  	}
   282  }
   283  
   284  // ReverseEachUntil calls the consumer for each element in the buffer with a stopping condition in tail=>head order.
   285  func (rb *RingBuffer) ReverseEachUntil(consumer func(value interface{}) bool) {
   286  	if rb.size == 0 {
   287  		return
   288  	}
   289  
   290  	if rb.head < rb.tail {
   291  		for cursor := rb.tail - 1; cursor >= rb.head; cursor-- {
   292  			if !consumer(rb.array[cursor]) {
   293  				return
   294  			}
   295  		}
   296  	} else {
   297  		for cursor := rb.tail; cursor > 0; cursor-- {
   298  			if !consumer(rb.array[cursor]) {
   299  				return
   300  			}
   301  		}
   302  		for cursor := len(rb.array) - 1; cursor >= rb.head; cursor-- {
   303  			if !consumer(rb.array[cursor]) {
   304  				return
   305  			}
   306  		}
   307  	}
   308  }
   309  
   310  func (rb *RingBuffer) String() string {
   311  	var values []string
   312  	for _, elem := range rb.Contents() {
   313  		values = append(values, fmt.Sprintf("%v", elem))
   314  	}
   315  	return strings.Join(values, " <= ")
   316  }
   317  
   318  func arrayClear(source []interface{}, index, length int) {
   319  	for x := 0; x < length; x++ {
   320  		absoluteIndex := x + index
   321  		source[absoluteIndex] = nil
   322  	}
   323  }
   324  
   325  func arrayCopy(source []interface{}, sourceIndex int, destination []interface{}, destinationIndex, length int) {
   326  	for x := 0; x < length; x++ {
   327  		from := sourceIndex + x
   328  		to := destinationIndex + x
   329  
   330  		destination[to] = source[from]
   331  	}
   332  }