circular_list.dart 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import 'package:quiver/iterables.dart';
  2. class CircularList<T> {
  3. List<T?> _array;
  4. int _length = 0;
  5. int _startIndex = 0;
  6. Function(int num)? onTrimmed;
  7. Function(T? item) _prepareItem;
  8. CircularList(int maxLength, Function(T? item) prepareItem,
  9. [T? Function(int index)? initialValueGenerator])
  10. : _array = (initialValueGenerator == null)
  11. ? List<T?>.filled(maxLength, null)
  12. : List<T?>.generate(maxLength, initialValueGenerator),
  13. _prepareItem = prepareItem;
  14. // Gets the cyclic index for the specified regular index. The cyclic index can then be used on the
  15. // backing array to get the element associated with the regular index.
  16. int _getCyclicIndex(int index) {
  17. return (_startIndex + index) % _array.length;
  18. }
  19. int get maxLength {
  20. return _array.length;
  21. }
  22. set maxLength(int value) {
  23. if (value <= 0)
  24. throw ArgumentError.value(
  25. value, 'value', 'maxLength can\'t be negative!');
  26. if (value == _array.length) return;
  27. // Reconstruct array, starting at index 0. Only transfer values from the
  28. // indexes 0 to length.
  29. final newArray = List<T?>.generate(
  30. value,
  31. (index) =>
  32. index < _array.length ? _array[_getCyclicIndex(index)] : null);
  33. _startIndex = 0;
  34. _array = newArray;
  35. }
  36. int get length {
  37. return _length;
  38. }
  39. set length(int value) {
  40. if (value > _length) {
  41. for (int i = length; i < value; i++) {
  42. _array[i] = null;
  43. }
  44. }
  45. _length = value;
  46. }
  47. void forEach(void Function(T? item, int index) callback,
  48. [bool includeBuffer = false]) {
  49. final len = includeBuffer ? _array.length : _length;
  50. for (int i = 0; i < len; i++) {
  51. callback(_array[_getCyclicIndex(i)], i);
  52. }
  53. }
  54. T? operator [](int index) {
  55. return _array[_getCyclicIndex(index)];
  56. }
  57. operator []=(int index, T? value) {
  58. _array[_getCyclicIndex(index)] = value;
  59. }
  60. void clear() {
  61. _startIndex = 0;
  62. _length = 0;
  63. }
  64. void addNew() {
  65. _prepareItem(_array[_getCyclicIndex(_length)]);
  66. if (_length == _array.length) {
  67. _startIndex++;
  68. if (_startIndex == _array.length) {
  69. _startIndex = 0;
  70. }
  71. onTrimmed?.call(1);
  72. } else {
  73. _length++;
  74. }
  75. }
  76. T? recycle() {
  77. if (length != maxLength) {
  78. throw Exception('Can only recycle when the buffer is full');
  79. }
  80. _startIndex = ++_startIndex & maxLength;
  81. return _array[_getCyclicIndex(length - 1)];
  82. }
  83. /// Removes and returns the last value on the list
  84. T pop() {
  85. return _array[_getCyclicIndex(_length-- - 1)]!;
  86. }
  87. /// Deletes and/or inserts items at a particular index (in that order).
  88. void splice(int start, int deleteCount, List<T> items) {
  89. // delete items
  90. if (deleteCount > 0) {
  91. for (int i = start; i < _length - deleteCount; i++)
  92. _array[_getCyclicIndex(i)] = _array[_getCyclicIndex(i + deleteCount)];
  93. length -= deleteCount;
  94. }
  95. if (items.length != 0) {
  96. // add items
  97. for (int i = _length - 1; i >= start; i--)
  98. _array[_getCyclicIndex(i + items.length)] = _array[_getCyclicIndex(i)];
  99. for (int i = 0; i < items.length; i++)
  100. _array[_getCyclicIndex(start + i)] = items[i];
  101. }
  102. // Adjust length as needed
  103. if (_length + items.length > _array.length) {
  104. int countToTrim = _length + items.length - _array.length;
  105. _startIndex += countToTrim;
  106. length = _array.length;
  107. onTrimmed?.call(countToTrim);
  108. } else {
  109. _length += items.length;
  110. }
  111. }
  112. void trimStart(int count) {
  113. if (count > _length) count = _length;
  114. // TODO: perhaps bug in original code, this does not clamp the value of startIndex
  115. _startIndex += count;
  116. _length -= count;
  117. onTrimmed?.call(count);
  118. }
  119. void shiftElements(int start, int count, int offset) {
  120. if (count < 0) return;
  121. if (start < 0 || start >= _length)
  122. throw Exception('Start argument is out of range');
  123. if (start + offset < 0)
  124. throw Exception('Can not shift elements in list beyond index 0');
  125. if (offset > 0) {
  126. for (var i = count - 1; i >= 0; i--) {
  127. this[start + i + offset] = this[start + i];
  128. }
  129. var expandListBy = (start + count + offset) - _length;
  130. if (expandListBy > 0) {
  131. _length += expandListBy;
  132. while (_length > _array.length) {
  133. length--;
  134. _startIndex++;
  135. onTrimmed?.call(1);
  136. }
  137. }
  138. } else {
  139. for (var i = 0; i < count; i++) {
  140. this[start + i + offset] = this[start + i];
  141. }
  142. }
  143. }
  144. bool get isFull => length == maxLength;
  145. List<T> toList() {
  146. return List<T>.generate(length, (index) => this[index]!);
  147. }
  148. }