Skip to content

Commit

Permalink
Optimize #rotate! (#11198)
Browse files Browse the repository at this point in the history
  • Loading branch information
HertzDevil committed Sep 29, 2021
1 parent 9a3f938 commit c172bd9
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 38 deletions.
9 changes: 4 additions & 5 deletions spec/std/array_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
91 changes: 91 additions & 0 deletions spec/std/bit_array_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
43 changes: 43 additions & 0 deletions spec/std/slice_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
43 changes: 43 additions & 0 deletions spec/std/static_array_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
34 changes: 2 additions & 32 deletions src/array.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
69 changes: 69 additions & 0 deletions src/bit_array.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
# ```
Expand Down
1 change: 1 addition & 0 deletions src/indexable/mutable.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand Down
36 changes: 35 additions & 1 deletion src/slice.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit c172bd9

Please sign in to comment.