From cd338fa2bc69c88b50bfa6ab0eff3ac26e769ec9 Mon Sep 17 00:00:00 2001 From: Google Java Core Libraries Date: Fri, 22 Apr 2022 10:50:00 -0700 Subject: [PATCH] Adds rotate() for arrays of ints. RELNOTES=Adds rotate() for arrays of ints. PiperOrigin-RevId: 443706002 --- .../google/common/primitives/IntsTest.java | 97 +++++++++++++++++++ .../com/google/common/primitives/Ints.java | 46 +++++++++ .../google/common/primitives/IntsTest.java | 97 +++++++++++++++++++ .../com/google/common/primitives/Ints.java | 46 +++++++++ 4 files changed, 286 insertions(+) diff --git a/android/guava-tests/test/com/google/common/primitives/IntsTest.java b/android/guava-tests/test/com/google/common/primitives/IntsTest.java index 3aaed3a7d19d..3c3ce8abd55d 100644 --- a/android/guava-tests/test/com/google/common/primitives/IntsTest.java +++ b/android/guava-tests/test/com/google/common/primitives/IntsTest.java @@ -342,6 +342,103 @@ public void testReverseIndexed() { testReverse(new int[] {-1, 1, -2, 2}, 1, 3, new int[] {-1, -2, 1, 2}); } + private static void testRotate(int[] input, int distance, int[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Ints.rotate(input, distance); + assertThat(input).isEqualTo(expectedOutput); + } + + private static void testRotate( + int[] input, int distance, int fromIndex, int toIndex, int[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Ints.rotate(input, distance, fromIndex, toIndex); + assertThat(input).isEqualTo(expectedOutput); + } + + public void testRotate() { + testRotate(new int[] {}, -1, new int[] {}); + testRotate(new int[] {}, 0, new int[] {}); + testRotate(new int[] {}, 1, new int[] {}); + + testRotate(new int[] {1}, -2, new int[] {1}); + testRotate(new int[] {1}, -1, new int[] {1}); + testRotate(new int[] {1}, 0, new int[] {1}); + testRotate(new int[] {1}, 1, new int[] {1}); + testRotate(new int[] {1}, 2, new int[] {1}); + + testRotate(new int[] {1, 2}, -3, new int[] {2, 1}); + testRotate(new int[] {1, 2}, -1, new int[] {2, 1}); + testRotate(new int[] {1, 2}, -2, new int[] {1, 2}); + testRotate(new int[] {1, 2}, 0, new int[] {1, 2}); + testRotate(new int[] {1, 2}, 1, new int[] {2, 1}); + testRotate(new int[] {1, 2}, 2, new int[] {1, 2}); + testRotate(new int[] {1, 2}, 3, new int[] {2, 1}); + + testRotate(new int[] {1, 2, 3}, -5, new int[] {3, 1, 2}); + testRotate(new int[] {1, 2, 3}, -4, new int[] {2, 3, 1}); + testRotate(new int[] {1, 2, 3}, -3, new int[] {1, 2, 3}); + testRotate(new int[] {1, 2, 3}, -2, new int[] {3, 1, 2}); + testRotate(new int[] {1, 2, 3}, -1, new int[] {2, 3, 1}); + testRotate(new int[] {1, 2, 3}, 0, new int[] {1, 2, 3}); + testRotate(new int[] {1, 2, 3}, 1, new int[] {3, 1, 2}); + testRotate(new int[] {1, 2, 3}, 2, new int[] {2, 3, 1}); + testRotate(new int[] {1, 2, 3}, 3, new int[] {1, 2, 3}); + testRotate(new int[] {1, 2, 3}, 4, new int[] {3, 1, 2}); + testRotate(new int[] {1, 2, 3}, 5, new int[] {2, 3, 1}); + + testRotate(new int[] {1, 2, 3, 4}, -9, new int[] {2, 3, 4, 1}); + testRotate(new int[] {1, 2, 3, 4}, -5, new int[] {2, 3, 4, 1}); + testRotate(new int[] {1, 2, 3, 4}, -1, new int[] {2, 3, 4, 1}); + testRotate(new int[] {1, 2, 3, 4}, 0, new int[] {1, 2, 3, 4}); + testRotate(new int[] {1, 2, 3, 4}, 1, new int[] {4, 1, 2, 3}); + testRotate(new int[] {1, 2, 3, 4}, 5, new int[] {4, 1, 2, 3}); + testRotate(new int[] {1, 2, 3, 4}, 9, new int[] {4, 1, 2, 3}); + + testRotate(new int[] {1, 2, 3, 4, 5}, -6, new int[] {2, 3, 4, 5, 1}); + testRotate(new int[] {1, 2, 3, 4, 5}, -4, new int[] {5, 1, 2, 3, 4}); + testRotate(new int[] {1, 2, 3, 4, 5}, -3, new int[] {4, 5, 1, 2, 3}); + testRotate(new int[] {1, 2, 3, 4, 5}, -1, new int[] {2, 3, 4, 5, 1}); + testRotate(new int[] {1, 2, 3, 4, 5}, 0, new int[] {1, 2, 3, 4, 5}); + testRotate(new int[] {1, 2, 3, 4, 5}, 1, new int[] {5, 1, 2, 3, 4}); + testRotate(new int[] {1, 2, 3, 4, 5}, 3, new int[] {3, 4, 5, 1, 2}); + testRotate(new int[] {1, 2, 3, 4, 5}, 4, new int[] {2, 3, 4, 5, 1}); + testRotate(new int[] {1, 2, 3, 4, 5}, 6, new int[] {5, 1, 2, 3, 4}); + } + + public void testRotateIndexed() { + testRotate(new int[] {}, 0, 0, 0, new int[] {}); + + testRotate(new int[] {1}, 0, 0, 1, new int[] {1}); + testRotate(new int[] {1}, 1, 0, 1, new int[] {1}); + testRotate(new int[] {1}, 1, 1, 1, new int[] {1}); + + // Rotate the central 5 elements, leaving the ends as-is + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -6, 1, 6, new int[] {0, 2, 3, 4, 5, 1, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -1, 1, 6, new int[] {0, 2, 3, 4, 5, 1, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 0, 1, 6, new int[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 5, 1, 6, new int[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 14, 1, 6, new int[] {0, 2, 3, 4, 5, 1, 6}); + + // Rotate the first three elements + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -2, 0, 3, new int[] {2, 0, 1, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -1, 0, 3, new int[] {1, 2, 0, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 0, 0, 3, new int[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 1, 0, 3, new int[] {2, 0, 1, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 2, 0, 3, new int[] {1, 2, 0, 3, 4, 5, 6}); + + // Rotate the last four elements + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -6, 3, 7, new int[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -5, 3, 7, new int[] {0, 1, 2, 4, 5, 6, 3}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -4, 3, 7, new int[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -3, 3, 7, new int[] {0, 1, 2, 6, 3, 4, 5}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -2, 3, 7, new int[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -1, 3, 7, new int[] {0, 1, 2, 4, 5, 6, 3}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 0, 3, 7, new int[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 1, 3, 7, new int[] {0, 1, 2, 6, 3, 4, 5}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 2, 3, 7, new int[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 3, 3, 7, new int[] {0, 1, 2, 4, 5, 6, 3}); + } + public void testSortDescending() { testSortDescending(new int[] {}, new int[] {}); testSortDescending(new int[] {1}, new int[] {1}); diff --git a/android/guava/src/com/google/common/primitives/Ints.java b/android/guava/src/com/google/common/primitives/Ints.java index e07bdd88d93e..8d6a01b31ed5 100644 --- a/android/guava/src/com/google/common/primitives/Ints.java +++ b/android/guava/src/com/google/common/primitives/Ints.java @@ -510,6 +510,52 @@ public static void reverse(int[] array, int fromIndex, int toIndex) { } } + /** + * Performs a right rotation of {@code array} of "distance" places, so that the first element is + * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance + * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Ints.asList(array), + * distance)}, but is considerably faster and avoids allocation and garbage collection. + * + *

The provided "distance" may be negative, which will rotate left. + */ + public static void rotate(int[] array, int distance) { + rotate(array, distance, 0, array.length); + } + + /** + * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code + * toIndex} exclusive. This is equivalent to {@code + * Collections.rotate(Ints.asList(array).subList(fromIndex, toIndex), distance)}, but is + * considerably faster and avoids allocations and garbage collection. + * + *

The provided "distance" may be negative, which will rotate left. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + */ + public static void rotate(int[] array, int distance, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + if (array.length <= 1) { + return; + } + + int length = toIndex - fromIndex; + // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many + // places left to rotate. + int m = -distance % length; + m = (m < 0) ? m + length : m; + // The current index of what will become the first element of the rotated section. + int newFirstIndex = m + fromIndex; + if (newFirstIndex == fromIndex) { + return; + } + + reverse(array, fromIndex, newFirstIndex); + reverse(array, newFirstIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + /** * Returns an array containing each value of {@code collection}, converted to a {@code int} value * in the manner of {@link Number#intValue}. diff --git a/guava-tests/test/com/google/common/primitives/IntsTest.java b/guava-tests/test/com/google/common/primitives/IntsTest.java index 3aaed3a7d19d..3c3ce8abd55d 100644 --- a/guava-tests/test/com/google/common/primitives/IntsTest.java +++ b/guava-tests/test/com/google/common/primitives/IntsTest.java @@ -342,6 +342,103 @@ public void testReverseIndexed() { testReverse(new int[] {-1, 1, -2, 2}, 1, 3, new int[] {-1, -2, 1, 2}); } + private static void testRotate(int[] input, int distance, int[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Ints.rotate(input, distance); + assertThat(input).isEqualTo(expectedOutput); + } + + private static void testRotate( + int[] input, int distance, int fromIndex, int toIndex, int[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Ints.rotate(input, distance, fromIndex, toIndex); + assertThat(input).isEqualTo(expectedOutput); + } + + public void testRotate() { + testRotate(new int[] {}, -1, new int[] {}); + testRotate(new int[] {}, 0, new int[] {}); + testRotate(new int[] {}, 1, new int[] {}); + + testRotate(new int[] {1}, -2, new int[] {1}); + testRotate(new int[] {1}, -1, new int[] {1}); + testRotate(new int[] {1}, 0, new int[] {1}); + testRotate(new int[] {1}, 1, new int[] {1}); + testRotate(new int[] {1}, 2, new int[] {1}); + + testRotate(new int[] {1, 2}, -3, new int[] {2, 1}); + testRotate(new int[] {1, 2}, -1, new int[] {2, 1}); + testRotate(new int[] {1, 2}, -2, new int[] {1, 2}); + testRotate(new int[] {1, 2}, 0, new int[] {1, 2}); + testRotate(new int[] {1, 2}, 1, new int[] {2, 1}); + testRotate(new int[] {1, 2}, 2, new int[] {1, 2}); + testRotate(new int[] {1, 2}, 3, new int[] {2, 1}); + + testRotate(new int[] {1, 2, 3}, -5, new int[] {3, 1, 2}); + testRotate(new int[] {1, 2, 3}, -4, new int[] {2, 3, 1}); + testRotate(new int[] {1, 2, 3}, -3, new int[] {1, 2, 3}); + testRotate(new int[] {1, 2, 3}, -2, new int[] {3, 1, 2}); + testRotate(new int[] {1, 2, 3}, -1, new int[] {2, 3, 1}); + testRotate(new int[] {1, 2, 3}, 0, new int[] {1, 2, 3}); + testRotate(new int[] {1, 2, 3}, 1, new int[] {3, 1, 2}); + testRotate(new int[] {1, 2, 3}, 2, new int[] {2, 3, 1}); + testRotate(new int[] {1, 2, 3}, 3, new int[] {1, 2, 3}); + testRotate(new int[] {1, 2, 3}, 4, new int[] {3, 1, 2}); + testRotate(new int[] {1, 2, 3}, 5, new int[] {2, 3, 1}); + + testRotate(new int[] {1, 2, 3, 4}, -9, new int[] {2, 3, 4, 1}); + testRotate(new int[] {1, 2, 3, 4}, -5, new int[] {2, 3, 4, 1}); + testRotate(new int[] {1, 2, 3, 4}, -1, new int[] {2, 3, 4, 1}); + testRotate(new int[] {1, 2, 3, 4}, 0, new int[] {1, 2, 3, 4}); + testRotate(new int[] {1, 2, 3, 4}, 1, new int[] {4, 1, 2, 3}); + testRotate(new int[] {1, 2, 3, 4}, 5, new int[] {4, 1, 2, 3}); + testRotate(new int[] {1, 2, 3, 4}, 9, new int[] {4, 1, 2, 3}); + + testRotate(new int[] {1, 2, 3, 4, 5}, -6, new int[] {2, 3, 4, 5, 1}); + testRotate(new int[] {1, 2, 3, 4, 5}, -4, new int[] {5, 1, 2, 3, 4}); + testRotate(new int[] {1, 2, 3, 4, 5}, -3, new int[] {4, 5, 1, 2, 3}); + testRotate(new int[] {1, 2, 3, 4, 5}, -1, new int[] {2, 3, 4, 5, 1}); + testRotate(new int[] {1, 2, 3, 4, 5}, 0, new int[] {1, 2, 3, 4, 5}); + testRotate(new int[] {1, 2, 3, 4, 5}, 1, new int[] {5, 1, 2, 3, 4}); + testRotate(new int[] {1, 2, 3, 4, 5}, 3, new int[] {3, 4, 5, 1, 2}); + testRotate(new int[] {1, 2, 3, 4, 5}, 4, new int[] {2, 3, 4, 5, 1}); + testRotate(new int[] {1, 2, 3, 4, 5}, 6, new int[] {5, 1, 2, 3, 4}); + } + + public void testRotateIndexed() { + testRotate(new int[] {}, 0, 0, 0, new int[] {}); + + testRotate(new int[] {1}, 0, 0, 1, new int[] {1}); + testRotate(new int[] {1}, 1, 0, 1, new int[] {1}); + testRotate(new int[] {1}, 1, 1, 1, new int[] {1}); + + // Rotate the central 5 elements, leaving the ends as-is + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -6, 1, 6, new int[] {0, 2, 3, 4, 5, 1, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -1, 1, 6, new int[] {0, 2, 3, 4, 5, 1, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 0, 1, 6, new int[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 5, 1, 6, new int[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 14, 1, 6, new int[] {0, 2, 3, 4, 5, 1, 6}); + + // Rotate the first three elements + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -2, 0, 3, new int[] {2, 0, 1, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -1, 0, 3, new int[] {1, 2, 0, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 0, 0, 3, new int[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 1, 0, 3, new int[] {2, 0, 1, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 2, 0, 3, new int[] {1, 2, 0, 3, 4, 5, 6}); + + // Rotate the last four elements + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -6, 3, 7, new int[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -5, 3, 7, new int[] {0, 1, 2, 4, 5, 6, 3}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -4, 3, 7, new int[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -3, 3, 7, new int[] {0, 1, 2, 6, 3, 4, 5}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -2, 3, 7, new int[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -1, 3, 7, new int[] {0, 1, 2, 4, 5, 6, 3}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 0, 3, 7, new int[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 1, 3, 7, new int[] {0, 1, 2, 6, 3, 4, 5}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 2, 3, 7, new int[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 3, 3, 7, new int[] {0, 1, 2, 4, 5, 6, 3}); + } + public void testSortDescending() { testSortDescending(new int[] {}, new int[] {}); testSortDescending(new int[] {1}, new int[] {1}); diff --git a/guava/src/com/google/common/primitives/Ints.java b/guava/src/com/google/common/primitives/Ints.java index b63b0f946e7f..7ef8ff1dc310 100644 --- a/guava/src/com/google/common/primitives/Ints.java +++ b/guava/src/com/google/common/primitives/Ints.java @@ -512,6 +512,52 @@ public static void reverse(int[] array, int fromIndex, int toIndex) { } } + /** + * Performs a right rotation of {@code array} of "distance" places, so that the first element is + * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance + * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Ints.asList(array), + * distance)}, but is considerably faster and avoids allocation and garbage collection. + * + *

The provided "distance" may be negative, which will rotate left. + */ + public static void rotate(int[] array, int distance) { + rotate(array, distance, 0, array.length); + } + + /** + * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code + * toIndex} exclusive. This is equivalent to {@code + * Collections.rotate(Ints.asList(array).subList(fromIndex, toIndex), distance)}, but is + * considerably faster and avoids allocations and garbage collection. + * + *

The provided "distance" may be negative, which will rotate left. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + */ + public static void rotate(int[] array, int distance, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + if (array.length <= 1) { + return; + } + + int length = toIndex - fromIndex; + // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many + // places left to rotate. + int m = -distance % length; + m = (m < 0) ? m + length : m; + // The current index of what will become the first element of the rotated section. + int newFirstIndex = m + fromIndex; + if (newFirstIndex == fromIndex) { + return; + } + + reverse(array, fromIndex, newFirstIndex); + reverse(array, newFirstIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + /** * Returns an array containing each value of {@code collection}, converted to a {@code int} value * in the manner of {@link Number#intValue}.