/*
* Copyright 2012, Facebook, Inc.
*
* 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.facebook.LinkBench;
import java.util.Random;
/**
* Shuffler designed to make computing permutation and inverse easy
*/
public class InvertibleShuffler {
private final long[] params;
private final int shuffleGroups;
long n;
long nRoundedUp; // n rounded up to next multiple of shuffleGroups
long nRoundedDown; // n rounded down to next multiple of shuffleGroups
int minGroupSize;
public InvertibleShuffler(long seed, int shuffleGroups, long n) {
this(new Random(seed), shuffleGroups, n);
}
public InvertibleShuffler(Random rng, int shuffleGroups, long n) {
if (shuffleGroups > n) {
// Can't have more shuffle groups than items
shuffleGroups = (int)n;
}
this.shuffleGroups = shuffleGroups;
this.n = n;
this.params = new long[shuffleGroups];
this.minGroupSize = (int)n / shuffleGroups;
for (int i = 0; i < shuffleGroups; i++) {
// Positive long
params[i] = Math.abs(rng.nextInt(minGroupSize));
}
this.nRoundedDown = (n / shuffleGroups) * shuffleGroups;
this.nRoundedUp = n == nRoundedDown ? n : nRoundedDown + shuffleGroups;
}
public long permute(long i) {
return permute(i, false);
}
public long invertPermute(long i) {
return permute(i, true);
}
public long permute(long i, boolean inverse) {
if (i < 0 || i >= n) {
throw new IllegalArgumentException("Bad index to permute: " + i
+ ": out of range [0:" + (n - 1) + "]");
}
// Number of the group
int group = (int) (i % shuffleGroups);
// Whether this is a big or small group
boolean bigGroup = group < n % shuffleGroups;
// Calculate the (positive) rotation
long rotate = params[group];
if (inverse) {
// Reverse the rotation
if (bigGroup) {
rotate = minGroupSize + 1 - rotate;
} else {
rotate = minGroupSize - rotate;
}
assert(rotate >= 0);
}
long j = (i + shuffleGroups * rotate);
long result;
if (j < n) {
result = j;
} else {
// Depending on the group there might be different numbers of
// ids in the ring
if (bigGroup) {
result = j % nRoundedUp;
} else {
result = j % nRoundedDown;
}
if (result >= n) {
result = group;
}
}
assert(result % shuffleGroups == group);
return result;
}
}