diff --git a/spec/std/deque_spec.cr b/spec/std/deque_spec.cr index e1bc24bd4ed0..9aad0b1e53cb 100644 --- a/spec/std/deque_spec.cr +++ b/spec/std/deque_spec.cr @@ -37,6 +37,22 @@ end private alias RecursiveDeque = Deque(RecursiveDeque) +# Yields multiple forms of `Deque{}`, `Deque{0}`, `Deque{0, 1}`, `Deque{0, 1, 2}`, ..., +# `Deque{0, 1, 2, ..., max_size - 1}`. All deques have *max_size* as their +# capacity; each deque is yielded *max_size* times with a different start +# position in the internal buffer. Every deque is a fresh instance. +private def each_queue_repr(max_size, &) + (0..max_size).each do |size| + max_size.times do |i| + x = Deque(Int32).new(max_size, 0) + x.rotate!(i) + x.pop(max_size - size) + x.fill &.itself + yield x + end + end +end + describe "Deque" do describe "implementation" do it "works the same as array" do @@ -242,6 +258,44 @@ describe "Deque" do a.concat((4..1000)) a.should eq(Deque.new((1..1000).to_a)) end + + it "concats indexable" do + each_queue_repr(16) do |a| + size = a.size + b = Array.new(10, &.+(size)) + a.concat(b).should be(a) + a.should eq(Deque.new(size + 10, &.itself)) + end + + each_queue_repr(16) do |a| + size = a.size + b = Slice.new(10, &.+(size)) + a.concat(b).should be(a) + a.should eq(Deque.new(size + 10, &.itself)) + end + + each_queue_repr(16) do |a| + size = a.size + b = StaticArray(Int32, 10).new(&.+(size)) + a.concat(b).should be(a) + a.should eq(Deque.new(size + 10, &.itself)) + end + + each_queue_repr(16) do |a| + size = a.size + b = Deque.new(10, &.+(size)) + a.concat(b).should be(a) + a.should eq(Deque.new(size + 10, &.itself)) + end + end + + it "concats itself" do + each_queue_repr(16) do |a| + size = a.size + a.concat(a).should be(a) + a.should eq(Deque.new((0...size).to_a * 2)) + end + end end describe "delete" do diff --git a/src/deque.cr b/src/deque.cr index 2f8efe6ebd01..d2154bb462e6 100644 --- a/src/deque.cr +++ b/src/deque.cr @@ -179,7 +179,65 @@ class Deque(T) end # Appends the elements of *other* to `self`, and returns `self`. - def concat(other : Enumerable(T)) + # + # ``` + # deq = Deque{"a", "b"} + # deq.concat(Deque{"c", "d"}) + # deq # => Deque{"a", "b", "c", "d"} + # ``` + def concat(other : Indexable) : self + other_size = other.size + + resize_if_cant_insert(other_size) + + index = @start + @size + index -= @capacity if index >= @capacity + concat_indexable(other, index) + + @size += other_size + + self + end + + private def concat_indexable(other : Deque, index) + Deque.half_slices(other) do |slice| + index = concat_indexable(slice, index) + end + end + + private def concat_indexable(other : Array | StaticArray, index) + concat_indexable(Slice.new(other.to_unsafe, other.size), index) + end + + private def concat_indexable(other : Slice, index) + if index + other.size <= @capacity + # there is enough space after the last element; one copy will suffice + (@buffer + index).copy_from(other.to_unsafe, other.size) + index += other.size + index == @capacity ? 0 : index + else + # copy the first half of *other* to the end of the buffer, and then the + # remaining half to the start, which must be available after a call to + # `#resize_if_cant_insert` + first_half_size = @capacity - index + second_half_size = other.size - first_half_size + (@buffer + index).copy_from(other.to_unsafe, first_half_size) + @buffer.copy_from(other.to_unsafe + first_half_size, second_half_size) + second_half_size + end + end + + private def concat_indexable(other, index) + appender = (@buffer + index).appender + buffer_end = @buffer + @capacity + other.each do |elem| + appender << elem + appender = @buffer.appender if appender.pointer == buffer_end + end + end + + # :ditto: + def concat(other : Enumerable(T)) : self other.each do |x| push x end @@ -593,6 +651,14 @@ class Deque(T) @capacity * 2 end + private def calculate_new_capacity(new_size) + new_capacity = @capacity == 0 ? INITIAL_CAPACITY : @capacity + while new_capacity < new_size + new_capacity *= 2 + end + new_capacity + end + # behaves like `resize_if_cant_insert(1)` private def resize_if_cant_insert if @size >= @capacity @@ -600,6 +666,13 @@ class Deque(T) end end + private def resize_if_cant_insert(insert_size) + new_capacity = calculate_new_capacity(@size + insert_size) + if new_capacity > @capacity + resize_to_capacity(new_capacity) + end + end + private def resize_to_capacity(capacity) old_capacity, @capacity = @capacity, capacity