mirror of
https://github.com/androidx/media.git
synced 2025-05-14 02:59:52 +08:00
Add shuffle order interface and default implementation.
These classes maintain a shuffled order of indices allowing to query the next, previous, first, and last indices. And also support inserting and removing elements without changing the shuffled order of the rest. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=164116287
This commit is contained in:
parent
cb00b0209f
commit
3c6ad40481
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.source;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder;
|
||||
import com.google.android.exoplayer2.source.ShuffleOrder.UnshuffledShuffleOrder;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Unit test for {@link ShuffleOrder}.
|
||||
*/
|
||||
public final class ShuffleOrderTest extends TestCase {
|
||||
|
||||
public static final long RANDOM_SEED = 1234567890L;
|
||||
|
||||
public void testDefaultShuffleOrder() {
|
||||
assertShuffleOrderCorrectness(new DefaultShuffleOrder(0, RANDOM_SEED), 0);
|
||||
assertShuffleOrderCorrectness(new DefaultShuffleOrder(1, RANDOM_SEED), 1);
|
||||
assertShuffleOrderCorrectness(new DefaultShuffleOrder(5, RANDOM_SEED), 5);
|
||||
for (int initialLength = 0; initialLength < 4; initialLength++) {
|
||||
for (int insertionPoint = 0; insertionPoint <= initialLength; insertionPoint += 2) {
|
||||
testCloneAndInsert(new DefaultShuffleOrder(initialLength, RANDOM_SEED), insertionPoint, 0);
|
||||
testCloneAndInsert(new DefaultShuffleOrder(initialLength, RANDOM_SEED), insertionPoint, 1);
|
||||
testCloneAndInsert(new DefaultShuffleOrder(initialLength, RANDOM_SEED), insertionPoint, 5);
|
||||
}
|
||||
}
|
||||
testCloneAndRemove(new DefaultShuffleOrder(5, RANDOM_SEED), 0);
|
||||
testCloneAndRemove(new DefaultShuffleOrder(5, RANDOM_SEED), 2);
|
||||
testCloneAndRemove(new DefaultShuffleOrder(5, RANDOM_SEED), 4);
|
||||
testCloneAndRemove(new DefaultShuffleOrder(1, RANDOM_SEED), 0);
|
||||
}
|
||||
|
||||
public void testUnshuffledShuffleOrder() {
|
||||
assertShuffleOrderCorrectness(new UnshuffledShuffleOrder(0), 0);
|
||||
assertShuffleOrderCorrectness(new UnshuffledShuffleOrder(1), 1);
|
||||
assertShuffleOrderCorrectness(new UnshuffledShuffleOrder(5), 5);
|
||||
for (int initialLength = 0; initialLength < 4; initialLength++) {
|
||||
for (int insertionPoint = 0; insertionPoint <= initialLength; insertionPoint += 2) {
|
||||
testCloneAndInsert(new UnshuffledShuffleOrder(initialLength), insertionPoint, 0);
|
||||
testCloneAndInsert(new UnshuffledShuffleOrder(initialLength), insertionPoint, 1);
|
||||
testCloneAndInsert(new UnshuffledShuffleOrder(initialLength), insertionPoint, 5);
|
||||
}
|
||||
}
|
||||
testCloneAndRemove(new UnshuffledShuffleOrder(5), 0);
|
||||
testCloneAndRemove(new UnshuffledShuffleOrder(5), 2);
|
||||
testCloneAndRemove(new UnshuffledShuffleOrder(5), 4);
|
||||
testCloneAndRemove(new UnshuffledShuffleOrder(1), 0);
|
||||
}
|
||||
|
||||
public void testUnshuffledShuffleOrderIsUnshuffled() {
|
||||
ShuffleOrder shuffleOrder = new UnshuffledShuffleOrder(5);
|
||||
assertEquals(0, shuffleOrder.getFirstIndex());
|
||||
assertEquals(4, shuffleOrder.getLastIndex());
|
||||
for (int i = 0; i < 4; i++) {
|
||||
assertEquals(i + 1, shuffleOrder.getNextIndex(i));
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertShuffleOrderCorrectness(ShuffleOrder shuffleOrder, int length) {
|
||||
assertEquals(length, shuffleOrder.getLength());
|
||||
if (length == 0) {
|
||||
assertEquals(C.INDEX_UNSET, shuffleOrder.getFirstIndex());
|
||||
assertEquals(C.INDEX_UNSET, shuffleOrder.getLastIndex());
|
||||
} else {
|
||||
int[] indices = new int[length];
|
||||
indices[0] = shuffleOrder.getFirstIndex();
|
||||
assertEquals(C.INDEX_UNSET, shuffleOrder.getPreviousIndex(indices[0]));
|
||||
for (int i = 1; i < length; i++) {
|
||||
indices[i] = shuffleOrder.getNextIndex(indices[i - 1]);
|
||||
assertEquals(indices[i - 1], shuffleOrder.getPreviousIndex(indices[i]));
|
||||
for (int j = 0; j < i; j++) {
|
||||
assertTrue(indices[i] != indices[j]);
|
||||
}
|
||||
}
|
||||
assertEquals(indices[length - 1], shuffleOrder.getLastIndex());
|
||||
assertEquals(C.INDEX_UNSET, shuffleOrder.getNextIndex(indices[length - 1]));
|
||||
for (int i = 0; i < length; i++) {
|
||||
assertTrue(indices[i] >= 0 && indices[i] < length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void testCloneAndInsert(ShuffleOrder shuffleOrder, int position, int count) {
|
||||
ShuffleOrder newOrder = shuffleOrder.cloneAndInsert(position, count);
|
||||
assertShuffleOrderCorrectness(newOrder, shuffleOrder.getLength() + count);
|
||||
// Assert all elements still have the relative same order
|
||||
for (int i = 0; i < shuffleOrder.getLength(); i++) {
|
||||
int expectedNextIndex = shuffleOrder.getNextIndex(i);
|
||||
if (expectedNextIndex != C.INDEX_UNSET && expectedNextIndex >= position) {
|
||||
expectedNextIndex += count;
|
||||
}
|
||||
int newNextIndex = newOrder.getNextIndex(i < position ? i : i + count);
|
||||
while (newNextIndex >= position && newNextIndex < position + count) {
|
||||
newNextIndex = newOrder.getNextIndex(newNextIndex);
|
||||
}
|
||||
assertEquals(expectedNextIndex, newNextIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void testCloneAndRemove(ShuffleOrder shuffleOrder, int position) {
|
||||
ShuffleOrder newOrder = shuffleOrder.cloneAndRemove(position);
|
||||
assertShuffleOrderCorrectness(newOrder, shuffleOrder.getLength() - 1);
|
||||
// Assert all elements still have the relative same order
|
||||
for (int i = 0; i < shuffleOrder.getLength(); i++) {
|
||||
if (i == position) {
|
||||
continue;
|
||||
}
|
||||
int expectedNextIndex = shuffleOrder.getNextIndex(i);
|
||||
if (expectedNextIndex == position) {
|
||||
expectedNextIndex = shuffleOrder.getNextIndex(expectedNextIndex);
|
||||
}
|
||||
if (expectedNextIndex != C.INDEX_UNSET && expectedNextIndex >= position) {
|
||||
expectedNextIndex--;
|
||||
}
|
||||
int newNextIndex = newOrder.getNextIndex(i < position ? i : i - 1);
|
||||
assertEquals(expectedNextIndex, newNextIndex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.source;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Shuffled order of indices.
|
||||
*/
|
||||
public interface ShuffleOrder {
|
||||
|
||||
/**
|
||||
* The default {@link ShuffleOrder} implementation for random shuffle order.
|
||||
*/
|
||||
class DefaultShuffleOrder implements ShuffleOrder {
|
||||
|
||||
private final Random random;
|
||||
private final int[] shuffled;
|
||||
private final int[] indexInShuffled;
|
||||
|
||||
/**
|
||||
* Creates an instance with a specified length.
|
||||
*
|
||||
* @param length The length of the shuffle order.
|
||||
*/
|
||||
public DefaultShuffleOrder(int length) {
|
||||
this(length, new Random());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance with a specified length and the specified random seed. Shuffle orders of
|
||||
* the same length initialized with the same random seed are guaranteed to be equal.
|
||||
*
|
||||
* @param length The length of the shuffle order.
|
||||
* @param randomSeed A random seed.
|
||||
*/
|
||||
public DefaultShuffleOrder(int length, long randomSeed) {
|
||||
this(length, new Random(randomSeed));
|
||||
}
|
||||
|
||||
private DefaultShuffleOrder(int length, Random random) {
|
||||
this(createShuffledList(length, random), random);
|
||||
}
|
||||
|
||||
private DefaultShuffleOrder(int[] shuffled, Random random) {
|
||||
this.shuffled = shuffled;
|
||||
this.random = random;
|
||||
this.indexInShuffled = new int[shuffled.length];
|
||||
for (int i = 0; i < shuffled.length; i++) {
|
||||
indexInShuffled[shuffled[i]] = i;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return shuffled.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNextIndex(int index) {
|
||||
int shuffledIndex = indexInShuffled[index];
|
||||
return ++shuffledIndex < shuffled.length ? shuffled[shuffledIndex] : C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPreviousIndex(int index) {
|
||||
int shuffledIndex = indexInShuffled[index];
|
||||
return --shuffledIndex >= 0 ? shuffled[shuffledIndex] : C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLastIndex() {
|
||||
return shuffled.length > 0 ? shuffled[shuffled.length - 1] : C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFirstIndex() {
|
||||
return shuffled.length > 0 ? shuffled[0] : C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShuffleOrder cloneAndInsert(int insertionIndex, int insertionCount) {
|
||||
int[] insertionPoints = new int[insertionCount];
|
||||
int[] insertionValues = new int[insertionCount];
|
||||
for (int i = 0; i < insertionCount; i++) {
|
||||
insertionPoints[i] = random.nextInt(shuffled.length + 1);
|
||||
int swapIndex = random.nextInt(i + 1);
|
||||
insertionValues[i] = insertionValues[swapIndex];
|
||||
insertionValues[swapIndex] = i + insertionIndex;
|
||||
}
|
||||
Arrays.sort(insertionPoints);
|
||||
int[] newShuffled = new int[shuffled.length + insertionCount];
|
||||
int indexInOldShuffled = 0;
|
||||
int indexInInsertionList = 0;
|
||||
for (int i = 0; i < shuffled.length + insertionCount; i++) {
|
||||
if (indexInInsertionList < insertionCount
|
||||
&& indexInOldShuffled == insertionPoints[indexInInsertionList]) {
|
||||
newShuffled[i] = insertionValues[indexInInsertionList++];
|
||||
} else {
|
||||
newShuffled[i] = shuffled[indexInOldShuffled++];
|
||||
if (newShuffled[i] >= insertionIndex) {
|
||||
newShuffled[i] += insertionCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new DefaultShuffleOrder(newShuffled, new Random(random.nextLong()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShuffleOrder cloneAndRemove(int removalIndex) {
|
||||
int[] newShuffled = new int[shuffled.length - 1];
|
||||
boolean foundRemovedElement = false;
|
||||
for (int i = 0; i < shuffled.length; i++) {
|
||||
if (shuffled[i] == removalIndex) {
|
||||
foundRemovedElement = true;
|
||||
} else {
|
||||
newShuffled[foundRemovedElement ? i - 1 : i] = shuffled[i] > removalIndex
|
||||
? shuffled[i] - 1 : shuffled[i];
|
||||
}
|
||||
}
|
||||
return new DefaultShuffleOrder(newShuffled, new Random(random.nextLong()));
|
||||
}
|
||||
|
||||
private static int[] createShuffledList(int length, Random random) {
|
||||
int[] shuffled = new int[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
int swapIndex = random.nextInt(i + 1);
|
||||
shuffled[i] = shuffled[swapIndex];
|
||||
shuffled[swapIndex] = i;
|
||||
}
|
||||
return shuffled;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link ShuffleOrder} implementation which does not shuffle.
|
||||
*/
|
||||
final class UnshuffledShuffleOrder implements ShuffleOrder {
|
||||
|
||||
private final int length;
|
||||
|
||||
/**
|
||||
* Creates an instance with a specified length.
|
||||
*
|
||||
* @param length The length of the shuffle order.
|
||||
*/
|
||||
public UnshuffledShuffleOrder(int length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNextIndex(int index) {
|
||||
return ++index < length ? index : C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPreviousIndex(int index) {
|
||||
return --index >= 0 ? index : C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLastIndex() {
|
||||
return length > 0 ? length - 1 : C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFirstIndex() {
|
||||
return length > 0 ? 0 : C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShuffleOrder cloneAndInsert(int insertionIndex, int insertionCount) {
|
||||
return new UnshuffledShuffleOrder(length + insertionCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShuffleOrder cloneAndRemove(int removalIndex) {
|
||||
return new UnshuffledShuffleOrder(length - 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns length of shuffle order.
|
||||
*/
|
||||
int getLength();
|
||||
|
||||
/**
|
||||
* Returns the next index in the shuffle order.
|
||||
*
|
||||
* @param index An index.
|
||||
* @return The index after {@code index}, or {@link C#INDEX_UNSET} if {@code index} is the last
|
||||
* element.
|
||||
*/
|
||||
int getNextIndex(int index);
|
||||
|
||||
/**
|
||||
* Returns the previous index in the shuffle order.
|
||||
*
|
||||
* @param index An index.
|
||||
* @return The index before {@code index}, or {@link C#INDEX_UNSET} if {@code index} is the first
|
||||
* element.
|
||||
*/
|
||||
int getPreviousIndex(int index);
|
||||
|
||||
/**
|
||||
* Returns the last index in the shuffle order, or {@link C#INDEX_UNSET} if the shuffle order is
|
||||
* empty.
|
||||
*/
|
||||
int getLastIndex();
|
||||
|
||||
/**
|
||||
* Returns the first index in the shuffle order, or {@link C#INDEX_UNSET} if the shuffle order is
|
||||
* empty.
|
||||
*/
|
||||
int getFirstIndex();
|
||||
|
||||
/**
|
||||
* Return a copy of the shuffle order with newly inserted elements.
|
||||
*
|
||||
* @param insertionIndex The index in the unshuffled order at which elements are inserted.
|
||||
* @param insertionCount The number of elements inserted at {@code insertionIndex}.
|
||||
* @return A copy of this {@link ShuffleOrder} with newly inserted elements.
|
||||
*/
|
||||
ShuffleOrder cloneAndInsert(int insertionIndex, int insertionCount);
|
||||
|
||||
/**
|
||||
* Return a copy of the shuffle order with one element removed.
|
||||
*
|
||||
* @param removalIndex The index of the element in the unshuffled order which is to be removed.
|
||||
* @return A copy of this {@link ShuffleOrder} without the removed element.
|
||||
*/
|
||||
ShuffleOrder cloneAndRemove(int removalIndex);
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user