circular_list.dart 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. class CircularList<T> {
  2. CircularList(int maxLength) : _array = List<T?>.filled(maxLength, null);
  3. late List<T?> _array;
  4. var _length = 0;
  5. var _startIndex = 0;
  6. // Gets the cyclic index for the specified regular index. The cyclic index can then be used on the
  7. // backing array to get the element associated with the regular index.
  8. @pragma('vm:prefer-inline')
  9. int _getCyclicIndex(int index) {
  10. return (_startIndex + index) % _array.length;
  11. }
  12. int get maxLength {
  13. return _array.length;
  14. }
  15. set maxLength(int value) {
  16. if (value <= 0) {
  17. throw ArgumentError.value(value, 'value', "maxLength can't be negative!");
  18. }
  19. if (value == _array.length) return;
  20. // Reconstruct array, starting at index 0. Only transfer values from the
  21. // indexes 0 to length.
  22. final newArray = List<T?>.generate(
  23. value,
  24. (index) => index < _array.length ? _array[_getCyclicIndex(index)] : null,
  25. );
  26. _startIndex = 0;
  27. _array = newArray;
  28. }
  29. int get length {
  30. return _length;
  31. }
  32. set length(int value) {
  33. if (value > _length) {
  34. for (int i = length; i < value; i++) {
  35. _array[i] = null;
  36. }
  37. }
  38. _length = value;
  39. }
  40. void forEach(void Function(T item) callback) {
  41. final length = _length;
  42. for (int i = 0; i < length; i++) {
  43. callback(_array[_getCyclicIndex(i)]!);
  44. }
  45. }
  46. T operator [](int index) {
  47. if (index >= length || index < 0) {
  48. throw RangeError.range(index, 0, length - 1);
  49. }
  50. return _array[_getCyclicIndex(index)]!;
  51. }
  52. operator []=(int index, T value) {
  53. if (index >= length || index < 0) {
  54. throw RangeError.range(index, 0, length - 1);
  55. }
  56. _array[_getCyclicIndex(index)] = value;
  57. }
  58. void clear() {
  59. _startIndex = 0;
  60. _length = 0;
  61. }
  62. void pushAll(Iterable<T> items) {
  63. for (var element in items) {
  64. push(element);
  65. }
  66. }
  67. void push(T value) {
  68. _array[_getCyclicIndex(_length)] = value;
  69. if (_length == _array.length) {
  70. _startIndex++;
  71. if (_startIndex == _array.length) {
  72. _startIndex = 0;
  73. }
  74. } else {
  75. _length++;
  76. }
  77. }
  78. /// Removes and returns the last value on the list
  79. T pop() {
  80. return _array[_getCyclicIndex(_length-- - 1)]!;
  81. }
  82. /// Deletes item at [index].
  83. void remove(int index, [int count = 1]) {
  84. if (count > 0) {
  85. if (index + count >= _length) {
  86. count = _length - index;
  87. }
  88. for (var i = index; i < _length - count; i++) {
  89. _array[_getCyclicIndex(i)] = _array[_getCyclicIndex(i + count)];
  90. }
  91. length -= count;
  92. }
  93. }
  94. /// Inserts [item] at [index].
  95. void insert(int index, T item) {
  96. if (index < 0 || index > _length) {
  97. throw RangeError.range(index, 0, _length);
  98. }
  99. if (index == _length) {
  100. return push(item);
  101. }
  102. if (index == 0 && _length >= _array.length) {
  103. // when something is inserted at index 0 and the list is full then
  104. // the new value immediately gets removed => nothing changes
  105. return;
  106. }
  107. for (var i = _length - 1; i >= index; i--) {
  108. _array[_getCyclicIndex(i + 1)] = _array[_getCyclicIndex(i)];
  109. }
  110. _array[_getCyclicIndex(index)] = item;
  111. if (_length >= _array.length) {
  112. _startIndex += 1;
  113. } else {
  114. _length++;
  115. }
  116. }
  117. /// Inserts [items] at [index] in order.
  118. void insertAll(int index, List<T> items) {
  119. for (var i = items.length - 1; i >= 0; i--) {
  120. insert(index, items[i]);
  121. // when the list is full then we have to move the index down
  122. // as newly inserted values remove values with a lower index
  123. if (_length >= _array.length) {
  124. index--;
  125. if (index < 0) {
  126. return;
  127. }
  128. }
  129. }
  130. }
  131. void trimStart(int count) {
  132. if (count > _length) count = _length;
  133. _startIndex += count;
  134. _startIndex %= _array.length;
  135. _length -= count;
  136. }
  137. void shiftElements(int start, int count, int offset) {
  138. if (count < 0) return;
  139. if (start < 0 || start >= _length) {
  140. throw Exception('Start argument is out of range');
  141. }
  142. if (start + offset < 0) {
  143. throw Exception('Can not shift elements in list beyond index 0');
  144. }
  145. if (offset > 0) {
  146. for (var i = count - 1; i >= 0; i--) {
  147. this[start + i + offset] = this[start + i];
  148. }
  149. var expandListBy = (start + count + offset) - _length;
  150. if (expandListBy > 0) {
  151. _length += expandListBy;
  152. while (_length > _array.length) {
  153. length--;
  154. _startIndex++;
  155. }
  156. }
  157. } else {
  158. for (var i = 0; i < count; i++) {
  159. this[start + i + offset] = this[start + i];
  160. }
  161. }
  162. }
  163. void replaceWith(List<T> replacement) {
  164. var copyStart = 0;
  165. if (replacement.length > maxLength) {
  166. copyStart = replacement.length - maxLength;
  167. }
  168. final copyLength = replacement.length - copyStart;
  169. for (var i = 0; i < copyLength; i++) {
  170. _array[i] = replacement[copyStart + i];
  171. }
  172. _startIndex = 0;
  173. _length = copyLength;
  174. }
  175. bool get isFull => length == maxLength;
  176. List<T> toList() {
  177. return List<T>.generate(length, (index) => this[index]);
  178. }
  179. }