diff --git a/spec/std/array_spec.cr b/spec/std/array_spec.cr index 075f7e7d5ca4..2b9fa73bcbf3 100644 --- a/spec/std/array_spec.cr +++ b/spec/std/array_spec.cr @@ -1980,11 +1980,10 @@ describe "Array" do describe "rotate" do it "rotate!" do a = [1, 2, 3] - a.rotate!; a.should eq([2, 3, 1]) - a.rotate!; a.should eq([3, 1, 2]) - a.rotate!; a.should eq([1, 2, 3]) - a.rotate!; a.should eq([2, 3, 1]) - a.rotate!.should eq(a) + a.rotate!.should be(a); a.should eq([2, 3, 1]) + a.rotate!.should be(a); a.should eq([3, 1, 2]) + a.rotate!.should be(a); a.should eq([1, 2, 3]) + a.rotate!.should be(a); a.should eq([2, 3, 1]) end it "rotate" do diff --git a/spec/std/bit_array_spec.cr b/spec/std/bit_array_spec.cr index 055e02f092c6..920c54efce38 100644 --- a/spec/std/bit_array_spec.cr +++ b/spec/std/bit_array_spec.cr @@ -15,6 +15,18 @@ private def assert_no_unused_bits(ba : BitArray, *, file = __FILE__, line = __LI end end +private def assert_rotates!(from : BitArray, n : Int, to : BitArray, *, file = __FILE__, line = __LINE__) + from.rotate!(n).should eq(from), file: file, line: line + from.should eq(to), file: file, line: line + assert_no_unused_bits from, file: file, line: line +end + +private def assert_rotates!(from : BitArray, to : BitArray, *, file = __FILE__, line = __LINE__) + from.rotate!.should eq(from), file: file, line: line + from.should eq(to), file: file, line: line + assert_no_unused_bits from, file: file, line: line +end + describe "BitArray" do it "has size" do ary = BitArray.new(100) @@ -372,6 +384,85 @@ describe "BitArray" do ary.count { |b| b }.should eq(2) end + describe "#rotate!" do + it "rotates empty BitArray" do + assert_rotates! from_int(0, 0), from_int(0, 0) + assert_rotates! from_int(0, 0), 0, from_int(0, 0) + assert_rotates! from_int(0, 0), 1, from_int(0, 0) + assert_rotates! from_int(0, 0), -1, from_int(0, 0) + end + + it "rotates short BitArray" do + assert_rotates! from_int(5, 0b10011), from_int(5, 0b00111) + assert_rotates! from_int(5, 0b10011), 0, from_int(5, 0b10011) + assert_rotates! from_int(5, 0b10011), 1, from_int(5, 0b00111) + assert_rotates! from_int(5, 0b10011), 2, from_int(5, 0b01110) + assert_rotates! from_int(5, 0b10011), 3, from_int(5, 0b11100) + assert_rotates! from_int(5, 0b10011), 4, from_int(5, 0b11001) + assert_rotates! from_int(5, 0b10011), 5, from_int(5, 0b10011) + assert_rotates! from_int(5, 0b10011), 6, from_int(5, 0b00111) + assert_rotates! from_int(5, 0b10011), -1, from_int(5, 0b11001) + assert_rotates! from_int(5, 0b10011), -2, from_int(5, 0b11100) + assert_rotates! from_int(5, 0b10011), -3, from_int(5, 0b01110) + assert_rotates! from_int(5, 0b10011), -4, from_int(5, 0b00111) + + ba = from_int(5, 0b10011) + assert_rotates! ba, from_int(5, 0b00111) + assert_rotates! ba, from_int(5, 0b01110) + assert_rotates! ba, 2, from_int(5, 0b11001) + + ba = from_int(32, 0b11000101_00011111_11000001_00011101_u32) + assert_rotates! ba, 5, from_int(32, 0b10100011_11111000_00100011_10111000_u32) + assert_rotates! ba, -8, from_int(32, 0b10111000_10100011_11111000_00100011_u32) + assert_rotates! ba, 45, from_int(32, 0b01111111_00000100_01110111_00010100_u32) + end + + it "rotates medium BitArray" do + ba = from_int(64, 0b11001100_00101001_01111010_10110001_10111111_00100101_11101100_10101010_u64) + assert_rotates! ba, from_int(64, 0b10011000_01010010_11110101_01100011_01111110_01001011_11011001_01010101_u64) + assert_rotates! ba, 10, from_int(64, 0b01001011_11010101_10001101_11111001_00101111_01100101_01010110_01100001_u64) + assert_rotates! ba, 51, from_int(64, 0b10110011_00001010_01011110_10101100_01101111_11001001_01111011_00101010_u64) + assert_rotates! ba, -40, from_int(64, 0b10101100_01101111_11001001_01111011_00101010_10110011_00001010_01011110_u64) + assert_rotates! ba, 128, from_int(64, 0b10101100_01101111_11001001_01111011_00101010_10110011_00001010_01011110_u64) + assert_rotates! ba, 97, from_int(64, 0b01010101_01100110_00010100_10111101_01011000_11011111_10010010_11110110_u64) + end + + it "rotates large BitArray" do + ba = BitArray.new(200) + ba[0] = ba[2] = ba[5] = ba[11] = ba[64] = ba[103] = ba[193] = ba[194] = true + + ba.rotate! + ba2 = BitArray.new(200) + ba2[199] = ba2[1] = ba2[4] = ba2[10] = ba2[63] = ba2[102] = ba2[192] = ba2[193] = true + ba.should eq(ba2) + assert_no_unused_bits ba + + ba.rotate!(21) + ba2 = BitArray.new(200) + ba2[178] = ba2[180] = ba2[183] = ba2[189] = ba2[42] = ba2[81] = ba2[171] = ba2[172] = true + ba.should eq(ba2) + assert_no_unused_bits ba + + ba.rotate!(192) + ba2 = BitArray.new(200) + ba2[186] = ba2[188] = ba2[191] = ba2[197] = ba2[50] = ba2[89] = ba2[179] = ba2[180] = true + ba.should eq(ba2) + assert_no_unused_bits ba + + ba.rotate!(50) + ba2 = BitArray.new(200) + ba2[136] = ba2[138] = ba2[141] = ba2[147] = ba2[0] = ba2[39] = ba2[129] = ba2[130] = true + ba.should eq(ba2) + assert_no_unused_bits ba + + ba.rotate!(123) + ba2 = BitArray.new(200) + ba2[13] = ba2[15] = ba2[18] = ba2[24] = ba2[77] = ba2[116] = ba2[6] = ba2[7] = true + ba.should eq(ba2) + assert_no_unused_bits ba + end + end + it "raises when out of bounds" do ary = BitArray.new(10) expect_raises IndexError do diff --git a/spec/std/slice_spec.cr b/spec/std/slice_spec.cr index c883a5f4a6c4..9273608d051f 100644 --- a/spec/std/slice_spec.cr +++ b/spec/std/slice_spec.cr @@ -505,6 +505,49 @@ describe "Slice" do a.to_unsafe.should eq(b.to_unsafe) end + describe "rotate!" do + it do + a = Slice[1, 2, 3] + a.rotate!.to_unsafe.should eq(a.to_unsafe); a.should eq(Slice[2, 3, 1]) + a.rotate!.to_unsafe.should eq(a.to_unsafe); a.should eq(Slice[3, 1, 2]) + a.rotate!.to_unsafe.should eq(a.to_unsafe); a.should eq(Slice[1, 2, 3]) + a.rotate!.to_unsafe.should eq(a.to_unsafe); a.should eq(Slice[2, 3, 1]) + end + + it { a = Slice[1, 2, 3]; a.rotate!(0); a.should eq(Slice[1, 2, 3]) } + it { a = Slice[1, 2, 3]; a.rotate!(1); a.should eq(Slice[2, 3, 1]) } + it { a = Slice[1, 2, 3]; a.rotate!(2); a.should eq(Slice[3, 1, 2]) } + it { a = Slice[1, 2, 3]; a.rotate!(3); a.should eq(Slice[1, 2, 3]) } + it { a = Slice[1, 2, 3]; a.rotate!(4); a.should eq(Slice[2, 3, 1]) } + it { a = Slice[1, 2, 3]; a.rotate!(3001); a.should eq(Slice[2, 3, 1]) } + it { a = Slice[1, 2, 3]; a.rotate!(-1); a.should eq(Slice[3, 1, 2]) } + it { a = Slice[1, 2, 3]; a.rotate!(-3001); a.should eq(Slice[3, 1, 2]) } + + it do + a = Slice(Int32).new(50) { |i| i } + a.rotate!(5) + a.should eq(Slice[5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 1, 2, 3, 4]) + end + + it do + a = Slice(Int32).new(50) { |i| i } + a.rotate!(-5) + a.should eq(Slice[45, 46, 47, 48, 49, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44]) + end + + it do + a = Slice(Int32).new(50) { |i| i } + a.rotate!(20) + a.should eq(Slice[20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) + end + + it do + a = Slice(Int32).new(50) { |i| i } + a.rotate!(-20) + a.should eq(Slice[30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]) + end + end + it "creates empty slice" do slice = Slice(Int32).empty slice.empty?.should be_true diff --git a/spec/std/static_array_spec.cr b/spec/std/static_array_spec.cr index 4c359cdebd57..2fe7f7d35468 100644 --- a/spec/std/static_array_spec.cr +++ b/spec/std/static_array_spec.cr @@ -174,6 +174,49 @@ describe "StaticArray" do a.should be_a(StaticArray(Int32, 3)) end + describe "rotate!" do + it do + a = StaticArray[1, 2, 3] + a.rotate!; a.should eq(StaticArray[2, 3, 1]) + a.rotate!; a.should eq(StaticArray[3, 1, 2]) + a.rotate!; a.should eq(StaticArray[1, 2, 3]) + a.rotate!; a.should eq(StaticArray[2, 3, 1]) + end + + it { a = StaticArray[1, 2, 3]; a.rotate!(0); a.should eq(StaticArray[1, 2, 3]) } + it { a = StaticArray[1, 2, 3]; a.rotate!(1); a.should eq(StaticArray[2, 3, 1]) } + it { a = StaticArray[1, 2, 3]; a.rotate!(2); a.should eq(StaticArray[3, 1, 2]) } + it { a = StaticArray[1, 2, 3]; a.rotate!(3); a.should eq(StaticArray[1, 2, 3]) } + it { a = StaticArray[1, 2, 3]; a.rotate!(4); a.should eq(StaticArray[2, 3, 1]) } + it { a = StaticArray[1, 2, 3]; a.rotate!(3001); a.should eq(StaticArray[2, 3, 1]) } + it { a = StaticArray[1, 2, 3]; a.rotate!(-1); a.should eq(StaticArray[3, 1, 2]) } + it { a = StaticArray[1, 2, 3]; a.rotate!(-3001); a.should eq(StaticArray[3, 1, 2]) } + + it do + a = StaticArray(Int32, 50).new { |i| i } + a.rotate!(5) + a.should eq(StaticArray[5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 1, 2, 3, 4]) + end + + it do + a = StaticArray(Int32, 50).new { |i| i } + a.rotate!(-5) + a.should eq(StaticArray[45, 46, 47, 48, 49, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44]) + end + + it do + a = StaticArray(Int32, 50).new { |i| i } + a.rotate!(20) + a.should eq(StaticArray[20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) + end + + it do + a = StaticArray(Int32, 50).new { |i| i } + a.rotate!(-20) + a.should eq(StaticArray[30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]) + end + end + it "updates value" do a = StaticArray(Int32, 3).new { |i| i + 1 } a.update(1) { |x| x * 2 } diff --git a/src/array.cr b/src/array.cr index b067afb24a1e..23d1c2961859 100644 --- a/src/array.cr +++ b/src/array.cr @@ -1389,38 +1389,8 @@ class Array(T) end # :inherit: - def rotate!(n = 1) : self - return self if size == 0 - n %= size - - if n == 0 - elsif n == 1 - tmp = self[0] - @buffer.move_from(@buffer + n, size - n) - self[-1] = tmp - elsif n == (size - 1) - tmp = self[-1] - (@buffer + size - n).move_from(@buffer, n) - self[0] = tmp - elsif n <= SMALL_ARRAY_SIZE - tmp_buffer = uninitialized StaticArray(T, SMALL_ARRAY_SIZE) - tmp_buffer.to_unsafe.copy_from(@buffer, n) - @buffer.move_from(@buffer + n, size - n) - (@buffer + size - n).copy_from(tmp_buffer.to_unsafe, n) - elsif size - n <= SMALL_ARRAY_SIZE - tmp_buffer = uninitialized StaticArray(T, SMALL_ARRAY_SIZE) - tmp_buffer.to_unsafe.copy_from(@buffer + n, size - n) - (@buffer + size - n).move_from(@buffer, n) - @buffer.copy_from(tmp_buffer.to_unsafe, size - n) - elsif n <= size // 2 - tmp = self[0..n] - @buffer.move_from(@buffer + n, size - n) - (@buffer + size - n).copy_from(tmp.to_unsafe, n) - else - tmp = self[n..-1] - (@buffer + size - n).move_from(@buffer, n) - @buffer.copy_from(tmp.to_unsafe, size - n) - end + def rotate!(n : Int = 1) : self + to_unsafe_slice.rotate!(n) self end diff --git a/src/bit_array.cr b/src/bit_array.cr index 6f88cf2513f2..57bb91f52231 100644 --- a/src/bit_array.cr +++ b/src/bit_array.cr @@ -265,6 +265,75 @@ struct BitArray clear_unused_bits end + # :inherit: + def rotate!(n : Int = 1) : self + return self if size <= 1 + n %= size + return self if n == 0 + + if size % 8 == 0 && n % 8 == 0 + to_slice.rotate!(n // 8) + elsif size <= 32 + @bits[0] = (@bits[0] >> n) | (@bits[0] << (size - n)) + clear_unused_bits + elsif n <= 32 + temp = @bits[0] + malloc_size = self.malloc_size + (malloc_size - 1).times do |i| + @bits[i] = (@bits[i] >> n) | (@bits[i + 1] << (32 - n)) + end + + end_sub_index = (size - 1) % 32 + 1 + if n <= end_sub_index + # n = 3: (bit patterns here are little-endian) + # + # ........ ........ ........ .....CBA -> ........ ........ ........ ........ + # ........ ........ ........ ........ -> cba..... ........ ........ ........ + # 00000000 00000000 00000000 000edcba -> 00000000 00000000 00000000 000CBAed + @bits[malloc_size - 1] = (@bits[malloc_size - 1] >> n) | (temp << (end_sub_index - n)) + else + # n = 7: + # + # ........ ........ ........ .GFEDCBA -> ........ ........ ........ ........ + # ........ ........ ........ ........ -> BAedcba. ........ ........ ........ + # 00000000 00000000 00000000 000edcba -> 00000000 00000000 00000000 000GFEDC + @bits[malloc_size - 2] |= temp << (32 + end_sub_index - n) + @bits[malloc_size - 1] = temp << (end_sub_index - n) + end + + clear_unused_bits + elsif n >= size - 32 + n = size - n + malloc_size = self.malloc_size + + end_sub_index = (size - 1) % 32 + 1 + if n <= end_sub_index + # n = 3: + # + # ........ ........ ........ ........ -> ........ ........ ........ .....CBA + # 00000000 00000000 00000000 000CBA.. -> 00000000 00000000 00000000 000..... + temp = @bits[malloc_size - 1] >> (end_sub_index - n) + else + # n = 7: + # + # BA...... ........ ........ ........ -> ........ ........ ........ .GFEDCBA + # 00000000 00000000 00000000 000GFEDC -> 00000000 00000000 00000000 000..... + temp = (@bits[malloc_size - 1] << (n - end_sub_index)) | (@bits[malloc_size - 2] >> (32 + end_sub_index - n)) + end + + (malloc_size - 1).downto(1) do |i| + @bits[i] = (@bits[i] << n) | (@bits[i - 1] >> (32 - n)) + end + @bits[0] = (@bits[0] << n) | temp + + clear_unused_bits + else + super + end + + self + end + # Creates a string representation of self. # # ``` diff --git a/src/indexable/mutable.cr b/src/indexable/mutable.cr index c2f200fb0c7a..0872f8f7534a 100644 --- a/src/indexable/mutable.cr +++ b/src/indexable/mutable.cr @@ -200,6 +200,7 @@ module Indexable::Mutable(T) def rotate!(n : Int = 1) : self return self if size <= 1 n %= size + return self if n == 0 # juggling algorithm size.gcd(n).times do |i| diff --git a/src/slice.cr b/src/slice.cr index 88bd690a7f70..70401b941197 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -311,9 +311,43 @@ struct Slice(T) # Raises if this slice is read-only. def rotate!(n : Int = 1) : self check_writable - super + + return self if size == 0 + n %= size + + if n == 0 + elsif n == 1 + tmp = self[0] + @pointer.move_from(@pointer + n, size - n) + self[-1] = tmp + elsif n == (size - 1) + tmp = self[-1] + (@pointer + size - n).move_from(@pointer, n) + self[0] = tmp + elsif n <= SMALL_SLICE_SIZE + tmp_buffer = uninitialized T[SMALL_SLICE_SIZE] + tmp_buffer.to_unsafe.copy_from(@pointer, n) + @pointer.move_from(@pointer + n, size - n) + (@pointer + size - n).copy_from(tmp_buffer.to_unsafe, n) + elsif size - n <= SMALL_SLICE_SIZE + tmp_buffer = uninitialized T[SMALL_SLICE_SIZE] + tmp_buffer.to_unsafe.copy_from(@pointer + n, size - n) + (@pointer + size - n).move_from(@pointer, n) + @pointer.copy_from(tmp_buffer.to_unsafe, size - n) + elsif n <= size // 2 + tmp = self[...n].dup + @pointer.move_from(@pointer + n, size - n) + (@pointer + size - n).copy_from(tmp.to_unsafe, n) + else + tmp = self[n..].dup + (@pointer + size - n).move_from(@pointer, n) + @pointer.copy_from(tmp.to_unsafe, size - n) + end + self end + private SMALL_SLICE_SIZE = 16 # same as Array::SMALL_ARRAY_SIZE + # :inherit: # # Raises if this slice is read-only. diff --git a/src/static_array.cr b/src/static_array.cr index 1082d2196932..0f9b058c33ef 100644 --- a/src/static_array.cr +++ b/src/static_array.cr @@ -158,6 +158,12 @@ struct StaticArray(T, N) StaticArray(U, N).new { |i| yield to_unsafe[i], offset + i } end + # :inherit: + def rotate!(n : Int = 1) : self + to_slice.rotate!(n) + self + end + # Returns a slice that points to the elements of this static array. # Changes made to the returned slice also affect this static array. #