I have an array of integers representing a deck of cards with 52 cards in it, with numbers ranging from 1-52 to represent the cards. I am trying to write a method which will take two positions within the array as parameters to divide the array into three blocks. Block 1 is all the values below the first position, block 2 is all the values lying between the two positions(inclusive of the values at position 1 and 2) and block 3 is all the values lying above the second position. I then want to switch the positions of blocks 1 and 3 within the array.
So for an array with the values of:
1,2,3,4,5,6,7,8,9,10,11,12
Setting positionOne(1), positionTwo(9) would give the array:
11,12,2,3,4,5,6,7,8,9,10,1
Here's what I have so far, which almost works but I think has bugs in it:
public void switchPositions(int pos1, int pos2) {
int[] newCards = new int[cards.length];
int sizeChunkA = 0;
int sizeChunkC = 0;
int sizeChunkB = 0;
int counter = 0;
for(int i = pos2+1; i<cards.length; i++) {
sizeChunkC++;
}
for(int i = 0; i<pos1; i++) {
sizeChunkA++;
}
for(int i = pos1; i<=pos2; i++) {
sizeChunkB++;
}
for(int i = 1; i<=sizeChunkC; i++) {
newCards[counter] = cards[pos2+i];
counter++;
}
for(int i=pos1; i<=pos2; i++) {
newCards[counter] = cards[i];
counter++;
}
for(int i=0; i<sizeChunkA; i++) {
newCards[counter] = cards[i];
counter++;
}
cards = newCards;
}
Is there a better way to do this?
Instead of loops use Arrays.copyOfRange and System.arraycopy:
// make a copy of the first section before overwriting it
int[] copy = Arrays.copyOfRange (inputArray, from, to);
// overwrite the first section with the second section
System.arraysCopy(inputArray, sourcePosition, inputArray, from, copy.length);
// copy the original content of the first section to the second section
System.arraysCopy(copy, 0, inputArray, sourcePosition, copy.length);
You have to change the indices according to your requirements.
I would approach it this way.
Create three arraylists for your three temp blocks.
Iterate through your array starting at 0-> position 1. Moving the values into your first array list.
Iterate through your starting at position 1 and ending at position 2. Moving all values into the sec0nd array list.
Then iterate through your array starting at position 2+1 through to the end.
Combine the three array lists.
There is a bit clearer of a way to do this. If you want to code it yourself rather than just use the existing Arrays.copyOfRange (perhaps if you would like to do it in place rather than generating a new array).
int lengthOfDeck = cards.length;
int[] newCards = new int[lengthOfDeck];
for (int i = 1; i <= lengthOfDeck; i++) {
if (i >=pos1 && i <=pos2) {
newCards[i-1]=i;
}
else if (i < pos1) {
newCards[i-1]= i + pos2;
}
else {
newCards[i-1] = i - pos2;
}
}
What about approach without using additional memory?!
Main idea, is that if you want to shift array to the offs positions right or left you can do it in place with 2 full for loops. I give you example:
Initial array {1,2,3,4,5}; we want to move it 2 positions right (i.e. offs=2), and have result {4,5,1,2,3}
1st loop for reverse all elements: {5,4,3,2,1}
2nd loop for reverse first offs elements: {4,5,3,2,1}
3rd loop for reverse other elements: {4,5,1,2,3}.
That's all! To solve your task, you have to do it twice, but second time you have to reduce array's length to not move last elements.
This is example:
public static void main(String... args) throws IOException {
int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
replace(arr, 1, 9);
// arr = [11, 12, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1]
}
public static void replace(int[] arr, int pos1, int pos2) {
rotate(arr, arr.length, -pos1);
rotate(arr, arr.length - pos1, -pos2);
}
// offs > 0 - to the right; offs < 0 - to the left
private static void rotate(int[] arr, int length, int offs) {
offs = offs >= length ? length % offs : offs;
length = Math.min(arr.length, length);
for (int i = 0, j = length - 1; i < j; i++, j--)
swap(arr, i, j);
for (int i = 0, j = offs > 0 ? offs - 1 : length + offs - 1; i < j; i++, j--)
swap(arr, i, j);
for (int i = offs > 0 ? offs : length + offs, j = length - 1; i < j; i++, j--)
swap(arr, i, j);
}
private static void swap(int[] a, int i, int j) {
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
According to the performance. You have 4 full for loops with one swap in each of them. So performance is O(n), without using additional memory.
Thanks to Eran for his tip, here is a more adapted method for your needs:
public int[] splitArray(int[] i, int position1, int position2) {
position2++;
int[] piece1 = Arrays.copyOfRange(i, 0, position1);
int[] piece2 = Arrays.copyOfRange(i, position1, position2);
int[] piece3 = Arrays.copyOfRange(i, position2, i.length);
System.arraycopy(piece3, 0, i, 0, piece3.length);
System.arraycopy(piece2, 0, i, piece3.length, piece2.length);
System.arraycopy(piece1, 0, i, piece3.length+piece2.length, piece1.length);
return i;
}
Related
I have a function public static int countBaad(int[] hs) that takes in an input array and I'm supposed to find how many numbers are smaller than the ones ahead of it.
For instance,
if hs = [7,3,5,4,1] the answer would be 2 because the pairs that violate the order are 3 and 5 and 3 and 4, since 3 is smaller than them and should've been ahead of them.
if hs = [8,5,6,7,2,1] the answer would be 3 because 5 is smaller than 6 and 7, giving us 2, and since 6 is also smaller than 7, we would get a total of 3 wrong pairs
Here is my current code using the merge sort approach:
public static int countBaad(int[] hs){
return mergeSort(hs, hs.length);
}
public static int mergeSort(int[] a, int n) {
if (n < 2) {
return n;
}
int mid = n / 2;
int[] l = new int[mid];
int[] r = new int[n - mid];
for (int i = 0; i < mid; i++) {
l[i] = a[i];
}
for (int i = mid; i < n; i++) {
r[i - mid] = a[i];
}
mergeSort(l, mid);
mergeSort(r, n - mid);
return merge(a, l, r, mid, n - mid);
}
public static int merge(int[] a, int[] l, int[] r, int left, int right) {
int size = 0;
int i = 0, j = 0, k = 0;
while (i < left && j < right) {
if (l[i] <= r[j]) {
a[k++] = l[i++];
size++;
}
else {
a[k++] = r[j++];
size++;
}
}
while (i < left) {
a[k++] = l[i++];
size++;
}
while (j < right) {
a[k++] = r[j++];
size++;
}
return size;
}
This code gives me the incorrect output after I put in arrays
hs = [7,3,5,4,1] returns 5
hs = [8,5,6,7,2,1] returns 6
What am I doing wrong here, can anyone please correct me?
What your code is currently doing is attempting a sort and then simply returning the size of the sorted array (big surprise, given the aptly named size variable).
Basically you are sorting in descending order and your specification calls for the result to be how many numbers were smaller than those appearing later in the array.
However, in merge you are actually adding to size regardless of their values.
Then, you're only returning the 'size' result of the final merge, not that of the sorting steps required inbetween.
And finally, perhaps the elephant in the room, is that you're performing a (unnecessary) sort as a side effect, but ignoring it completely.
Long story short, the code is too complicated and error prone for what it is supposed to do.
Here's a simple double for loop that achieves the desired outcome:
public static int countBaad(int[] hs){
int count = 0;
for(int i = 0; i < hs.length; i++) {
for(int j = i+1; j < hs.length; j++) {
//compare the i'th position with all subsequent positions
int current = hs[i];
int other = hs[j];
if(current < other) {
System.out.println("Found bad number pair: ("+current+","+other+")");
count++;
}
}
}
return count;
}
System.out.println(countBaad(new int[]{7,3,5,4,1}));
//prints:
//Found bad number pair: (3,5)
//Found bad number pair: (3,4)
//2
System.out.println(countBaad(new int[]{8,5,6,7,2,1}));
//prints:
//Found bad number pair: (5,6)
//Found bad number pair: (5,7)
//Found bad number pair: (6,7)
//3
This is much more succinct and free from side effects.
Edit:
Fixing the mergeSort code, with extra sysout logging to illustrate the algorithm:
public static int mergeSort(int[] a, int n) {
if(n==1) {
//No sorting required, so the result should be 0.
return 0;
}
int mid = n / 2;
int[] l = new int[mid];
int[] r = new int[n - mid];
//'splitting the array' loops are just arraycopy, so
// should use the native implementation:
System.arraycopy(a, 0, l, 0, mid);
if(n - mid >= 0) System.arraycopy(a, mid, r, 0, n - mid);
//add the results from all merges, not just the last one
int result = 0;
result += mergeSort(l, mid);
result += mergeSort(r, n - mid);
result += merge(a, l, r); //there is no need to pass in the array lengths
return result;
}
public static int merge(int[] a, int[] l, int[] r) {
System.out.println("Merging "+Arrays.toString(l)+" and "+Arrays.toString(r));
int size = 0;
int lIdx = 0, rIdx = 0, aIdx = 0;
while (lIdx < l.length && rIdx < r.length) {
if (l[lIdx] >= r[rIdx]) {
a[aIdx++] = l[lIdx++];
//size++; //no: left was already bigger than right
}
else {
//take from the right.
//This number is bigger than all the numbers remaining on the left.
for(int tempIdx = lIdx;tempIdx<l.length;tempIdx++) {
//this loop is for illustration only
System.out.println(" Found bad pair: (" + l[tempIdx] + "," + r[rIdx] + ")");
}
size+=l.length-lIdx;
a[aIdx++] = r[rIdx++];
}
}
//while (lIdx < left) { //NOTE that you had this condition incorrectly reversed resulting in bad merge
// a[aIdx++] = l[lIdx++];
// size++; //no, no comparisons are taking place here
//}
//while (rIdx < right) { //NOTE that you had this condition incorrectly reversed, resulting in bad merge
// a[aIdx++] = r[rIdx++];
// size++; //no, no comparisons are taking place here
//}
//we can also replace the above two loops with arraycopy
// which will perform better on large arrays
if(lIdx < left) {
System.arraycopy(l, lIdx, a, aIdx, l.length-lIdx);
}
if(rIdx < right) {
System.arraycopy(r, rIdx, a, aIdx, r.length-rIdx);
}
return size;
}
Since you value performance, you should use System.arraycopy where possible. I have also renamed the loop variables to make the code easier to understand.
System.out.println(countBaad(new int[]{7,3,5,4,1}));
//prints:
//Merging [7] and [3]
//Merging [4] and [1]
//Merging [5] and [4, 1]
//Merging [7, 3] and [5, 4, 1]
// Found bad pair: (3,5)
// Found bad pair: (3,4)
//2
System.out.println(countBaad(new int[]{8,5,6,7,2,1}));
//prints:
//Merging [5] and [6]
// Found bad pair: (5,6)
//Merging [8] and [6, 5]
//Merging [2] and [1]
//Merging [7] and [2, 1]
//Merging [8, 6, 5] and [7, 2, 1]
// Found bad pair: (6,7)
// Found bad pair: (5,7)
//3
Edit #2
To remove the side effects (sort) from this method, the input array can be copied, for example with a simple call to Arrays.copyOf(hs, hs.length); and passing in the result instead of the original.
I've been working on a merge sort algorithm, which I want to use to sort a string array eventually.
The theory is that I sort an array containing the IDs that I want to sort in the same order as the main array which is a string, then sort the ID array and perform the same actions on the string array, theoretically sorting them both the same way.
For example, the array of ID values could be [1,2,3,4,5], and the main array would look something like ["Name, 1","Name, 2"]...
When I run the algorithm without including any of the second array parts it works correctly and this is the output:
[0, 1, 1, 10, 12, 16, 18, 24, 26, 53, 53, 53, 100]
But as soon as I try to add the second array operations, I get an exception saying:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
at s4001175.ct5057.flight.Booker.merge(Booker.java:250)
at s4001175.ct5057.flight.Booker.mergeSort(Booker.java:236)
at s4001175.ct5057.flight.Booker.mergeSort(Booker.java:232)
at s4001175.ct5057.flight.Booker.mergeSort(Booker.java:232)
at s4001175.ct5057.flight.FlightManagement.passengerStatus(FlightManagement.java:92)
at s4001175.ct5057.flight.FlightManagement.mainMenu(FlightManagement.java:53)
at s4001175.ct5057.flight.FlightManagement.main(FlightManagement.java:27)
Code:
public void mergeSort(int[] flightIDArr, int arrLength, String[] actualArr) {
// Flattened array of bookings to 1d array
String[] flatArray = flattenArray(readBookings());
// If array does not need to be split as length is already 1
if (arrLength < 2) {
return;
}
// Middle of the array
int mid = arrLength / 2;
//Left array of length of half of the array
int[] left = new int[mid];
// Other half of split array, with arrLength-mid size to ensure that it works even if the array is an odd length
int[] right = new int[arrLength - mid];
//Mirrored version of previous lines but for string array of what we actually want to sort (booking info)
String[] actualLeft = new String[mid];
// Same as above but right side
String[] actualRight = new String[arrLength - mid];
// for the elements in left array
for (int i = 0; i < mid; i++) {
// filling the left arrays with the info from the full array
left[i] = flightIDArr[i];
actualLeft[i] = flatArray[i];
}
// for elements in right array
for (int i = mid; i < arrLength; i++) {
// filling the right arrays with the info from the full array
right[i - mid] = flightIDArr[i];
actualRight[i - mid] = flatArray[i];
}
//recursively calls the mergesort algorithm to split the arrays as small as possible
mergeSort(left, mid, actualLeft);
mergeSort(right, arrLength - mid, actualRight);
//re merges the split arrays
merge(flightIDArr,actualArr, left, right, mid, arrLength - mid, actualLeft,actualRight);
}
//Method to merge all the split arrays
public void merge(
int[] flightIDArr, String[] actualArr, int[] leftArr, int[] rightArr, int left, int right, String[] actualLeft, String[] actualRight) {
//setting up variables for looping
int i = 0, j = 0, k = 0;
//
while (i < left && j < right) {
if (leftArr[i] <= rightArr[j]) {
flightIDArr[k++] = leftArr[i++];
actualArr[k++] = actualLeft[i++];
}
else {
flightIDArr[k++] = rightArr[j++];
actualArr[k++] = actualRight[j++];
}
}
while (i < left) {
flightIDArr[k++] = leftArr[i++];
actualArr[k++] = actualLeft[i++];
}
while (j < right) {
flightIDArr[k++] = rightArr[j++];
actualArr[k++] = actualRight[j++];
}
// System.out.println(Arrays.toString(actualArr));
System.out.println(Arrays.toString(flightIDArr));
}
I've tried running through the program using the debugger in IntelliJ, and when the exception is produced to begin with at line 250, these are the values of the variables involved:
leftArr: 0:0
actualLeft:
0:
"Booking Number: 1
Flight Number: 000
Passenger Name: Test Name
Seat Number: 1
Seat Class: First"
So it looks like when this stage in the code is reached it is trying to set values in these arrays outside of their range, and yet this does not occur when running the algorithm without the second array involved, despite seemingly the same situation occurring on line 249, where
flightIDArr[k++] = leftArr[i++];
is called
Edit for new code:
Using the proposed answer I have changed my code, which seems to remove the error however the sorting no longer works correctly, and some numbers are noteven included:
The correct output:
[0, 1, 1, 10, 12, 16, 18, 24, 26, 53, 53, 53, 100]
Vs the current output with the new code:
[0, 1, 1, 1, 12, 16, 18, 18, 24, 53, 53, 53, 100]
As you can see there are now duplicate and missing values from the original dataset.
Code:
public void mergeSort(int[] flightIDArr, int arrLength, String[] actualArr) {
// Flattened array of bookings to 1d array
String[] flatArray = flattenArray(readBookings());
// If array does not need to be split as length is already 1
if (arrLength < 2) {
return;
}
// Middle of the array
int mid = arrLength / 2;
//Left array of length of half of the array
int[] left = new int[mid];
// Other half of split array, with arrLength-mid size to ensure that it works even if the array is an odd length
int[] right = new int[arrLength - mid];
//Mirrored version of previous lines but for string array of what we actually want to sort (booking info)
String[] actualLeft = new String[mid];
// Same as above but right side
String[] actualRight = new String[arrLength - mid];
// for the elements in left array
for (int i = 0; i < mid; i++) {
// filling the left arrays with the info from the full array
left[i] = flightIDArr[i];
actualLeft[i] = flatArray[i];
}
// for elements in right array
for (int i = mid; i < arrLength; i++) {
// filling the right arrays with the info from the full array
right[i - mid] = flightIDArr[i];
actualRight[i - mid] = flatArray[i];
}
//recursively calls the mergesort algorithm to split the arrays as small as possible
mergeSort(left, mid, actualLeft);
mergeSort(right, arrLength - mid, actualRight);
//re merges the split arrays
merge(flightIDArr,actualArr, left, right, mid, arrLength - mid, actualLeft,actualRight);
}
//Method to merge all the split arrays
public void merge(
int[] flightIDArr, String[] actualArr, int[] leftArr, int[] rightArr, int left, int right, String[] actualLeft, String[] actualRight) {
//setting up variables for looping
int i = 0, j = 0, k = 0;
//
while (i < left && j < right) {
if (leftArr[i] <= rightArr[j]) {
flightIDArr[k] = leftArr[i];
actualArr[k++] = actualLeft[i++];
}
else {
flightIDArr[k] = rightArr[j];
actualArr[k++] = actualRight[i++];
}
}
while (i < left) {
flightIDArr[k] = leftArr[i];
actualArr[k++] = actualLeft[i++];
}
while (j < right) {
flightIDArr[k] = rightArr[j];
actualArr[k++] = actualRight[j++];
}
// System.out.println(Arrays.toString(ActualArr));
System.out.println(Arrays.toString(flightIDArr));
// System.out.println(" Actual Array: \n \n " +(Arrays.toString (actualArr)));
}
When you use k++ j++ etc. you increase the variable. You do this for both the left and the right array. But you probably only want to do it once, not twice.
You want to replace code like this:
if (leftArr[i] <= rightArr[j]) {
flightIDArr[k++] = leftArr[i++];
actualArr[k++] = actualLeft[i++];
}
With this:
if (leftArr[i] <= rightArr[j]) {
flightIDArr[k] = leftArr[i];
actualArr[k++] = actualLeft[i++];
}
Or this:
if (leftArr[i] <= rightArr[j]) {
flightIDArr[k] = leftArr[i];
actualArr[k] = actualLeft[i];
k++; i++;
}
The title may be a bit confusing so here's an instance. I have two arrays:
int [] scores;
scores = new int[5]; //(5,7,10,3,6)
int [] places;
places = new int[5]; //(1,2,3,4,5)
I need to somehow sort the second array (I can't change the first one), so it represents the highness of elements in the first array. 10 is the highest so its place has to be 1st, 3 is the lowest so its place has to be 5th.
After the sorting second array should look like this:
places = {4,2,1,5,3};
Here's my code, and I need some help to make it work the way it should.
do {
for (int i = 0; i < 5; i++) {
for (int j = 1; j < 5; j++) {
if (scores[i] < scores[j]) {
temp = places[i];
places[i] = places[j];
places[j] = temp;
flag = true;
} else {
flag = false;
}
}
}
} while (flag);
Thanks in advance
#Korashen adviced a pretty good solution,
Another way:
assume all the values of scores are different and positive, you can make a copy of the array,sort it, and by subtaction to know the indexes,
in your example:
before sorting :
scores = (5,7,10,3,6)
after sorting :
scores_sorted = (3,5,6,7,10)
the value of places will be by the following rule:
if(scores_sorted[i]-scores[j] == 0)
places[i] = j
full example:
int[] scores = new int[]{5, 7, 10, 3, 6};
int[] scores_sorted = scores.clone();
int[] places = new int[]{0,1,2,3,4};
sort(scores_sorted);
for(int i=0;i<5;++i){
for(int j=0;j<5;++j){
if(scores_sorted[i]-scores[j] == 0){
places[i] = j;
}
}
}
You can use any sorting algorithm over the places but, instead comparing the places, compare the scores indexed by places.
Here is the modified quickSort:
static int partition(int[] iarray, int[] varray, int begin, int end) {
int pivot = end;
int counter = begin;
for (int i = begin; i < end; i++) {
if (varray[iarray[i]] < varray[iarray[pivot]]) {
int temp = iarray[counter];
iarray[counter] = iarray[i];
iarray[i] = temp;
counter++;
}
}
int temp = iarray[pivot];
iarray[pivot] = iarray[counter];
iarray[counter] = temp;
return counter;
}
public static void quickSort(int[] iarray, int[] varray, int begin, int end) {
if (end <= begin) return;
int pivot = partition(iarray, varray, begin, end);
quickSort(iarray, varray, begin, pivot - 1);
quickSort(iarray, varray, pivot + 1, end);
}
The only change is add the varray argument and the comparison iarray[i] < iarray[pivot] to varray[iarray[i]] < varray[iarray[pivot]].
NOTE: places must be numbers from 0 to n - 1.
If places are keys instead indexes, you need an intermediate Map to convert the varray[iarray[i]] to varray[real_index_of.get(iarray[i])].
A running example could be:
int[] scores = new int[]{5, 7, 10, 3, 6};
int[] places = new int[]{0, 1, 2, 3, 4};
quickSort(places, scores, 0, places.length - 1);
System.out.println(Arrays.stream(scores).mapToObj(Integer::toString).collect(joining(", ")));
System.out.println(Arrays.stream(places).mapToObj(Integer::toString).collect(joining(", ")));
With output:
5, 7, 10, 3, 6
3, 0, 4, 1, 2
(your output is wrong since 5 is the second lowest value)
I've been working on this code for a few days with no luck because of a stack overflow error during recursion.
The problem states that our pivot is always the 0th element and through recursion we can sort an array by either swapping or creating new arrays and copy them into the original array. I chose to create 2 arrays since it is easier for me to understand but once I reach the recursive step i get this error.
I traced the algorithm and realized that the pivot element is always being placed in the lower array and may be the problem.
enter code here
public static void main(String[] args) {
int[] arr = new int[] { 12, 7, 5, 8, 4, 6, 11, 15 };
quicksort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
private static int[] quicksort(int[] arr, int middle) {
// Base case
if (arr.length == 1) {
return arr;
}
int[] upper = new int[arr.length];
int[] lower = new int[arr.length];
int upperIndex = 0, lowerIndex = 0;
// Put the elements into their respective pivots
for (int i = 0; i < arr.length; i++) {
if (arr[i] <= middle) {
lower[lowerIndex++] = arr[i];
} else {
upper[upperIndex++] = arr[i];
}
}
// Recurse, and sort the pivots
lower = quicksort(lower, lower[0]);
upper = quicksort(upper, upper[0]);
// Combine lower, middle, and upper back into one array
System.arraycopy(lower, 0, arr, 0, lowerIndex);
arr[lowerIndex + 1] = middle;
System.arraycopy(upper, 0, arr, lowerIndex + 2, upperIndex);
return arr;
}
The result should be a sorted array in ascending order.
As mentioned in Quimby's answer, both lower and upper are set to size arr.length. The code needs use a third parameter indicating the actual length, such as
quicksort(lower, lower[0], lowerIndex);
quicksort(upper, upper[0], upperIndex);
Since middle will end up in lower, if the data is already sorted, than at each level of recursion, all of the data ends up in lower, with zero elements in upper, resulting in an infinite recursion that is only stopped by stack overflow.
Since you're using separate arrays for the quicksort, you might want to consider a 3 way partition:
public static void quicksort(int[] arr, int pivot, int size) {
// Base case
if (size < 2) {
return;
}
int[] upper = new int[size];
int[] middle = new int[size];
int[] lower = new int[size];
int upperIndex = 0, middleIndex = 0, lowerIndex = 0;
// Put the elements into their respective arrays
for (int i = 0; i < size; i++) {
if (arr[i] < pivot) {
lower[lowerIndex++] = arr[i];
} else if (arr[i] == pivot){
middle[middleIndex++] = arr[i];
} else {
upper[upperIndex++] = arr[i];
}
}
// sort lower and upper
quicksort(lower, lower[0], lowerIndex);
quicksort(upper, upper[0], upperIndex);
// Combine lower, middle, and upper back into one array
System.arraycopy(lower, 0, arr, 0, lowerIndex);
System.arraycopy(middle, 0, arr, lowerIndex, middleIndex);
System.arraycopy(upper, 0, arr, lowerIndex+middleIndex, upperIndex);
}
Your upper and lower arrays don't seem to shrink at all, so the arr.length == 1 is never true. Easier implementation is to use indices and only the original array.
As part of a school project, I need to write a function that will take an integer N and return a two-dimensional array of every permutation of the array {0, 1, ..., N-1}. The declaration would look like public static int[][] permutations(int N).
The algorithm described at http://www.usna.edu/Users/math/wdj/book/node156.html is how I've decided to implement this.
I wrestled for quite a while with arrays and arrays of ArrayLists and ArrayLists of ArrayLists, but so far I've been frustrated, especially trying to convert a 2d ArrayList to a 2d array.
So I wrote it in javascript. This works:
function allPermutations(N) {
// base case
if (N == 2) return [[0,1], [1,0]];
else {
// start with all permutations of previous degree
var permutations = allPermutations(N-1);
// copy each permutation N times
for (var i = permutations.length*N-1; i >= 0; i--) {
if (i % N == 0) continue;
permutations.splice(Math.floor(i/N), 0, permutations[Math.floor(i/N)].slice(0));
}
// "weave" next number in
for (var i = 0, j = N-1, d = -1; i < permutations.length; i++) {
// insert number N-1 at index j
permutations[i].splice(j, 0, N-1);
// index j is N-1, N-2, N-3, ... , 1, 0; then 0, 1, 2, ... N-1; then N-1, N-2, etc.
j += d;
// at beginning or end of the row, switch weave direction
if (j < 0 || j >= N) {
d *= -1;
j += d;
}
}
return permutations;
}
}
So what's the best strategy to port that to Java? Can I do it with just primitive arrays? Do I need an array of ArrayLists? Or an ArrayList of ArrayLists? Or is there some other data type that's better? Whatever I use, I need to be able to convert it back into a an array of primitive arrays.
Maybe's there a better algorithm that would simplify this for me...
Thank you in advance for your advice!
As you know the number of permutations beforehand (it's N!) and also you want/have to return an int[][] I would go for an array directly. You can declare it right at the beginning with correct dimensions and return it at the end. Thus you don't have to worry about converting it afterwards at all.
Since you pretty much had it completed on your own in javascript, I'll go ahead and give you the Java code for implementing Steinhaus' permutation algorithm. I basically just ported your code to Java, leaving as much of it the same as I could, including comments.
I tested it up to N = 7. I tried to have it calculate N = 8, but it's been running for almost 10 minutes already on a 2 GHz Intel Core 2 Duo processor, and still going, lol.
I'm sure if you really worked at it you could speed this up significantly, but even then you're probably only going to be able to squeeze maybe a couple more N-values out of it, unless of course you have access to a supercomputer ;-).
Warning - this code is correct, NOT robust. If you need it robust, which you usually don't for homework assignments, then that would be an exercise left to you. I would also recommend implementing it using Java Collections, simply because it would be a great way to learn the in's and out's of the Collections API.
There's several "helper" methods included, including one to print a 2d array. Enjoy!
Update: N = 8 took 25 minutes, 38 seconds.
Edit: Fixed N == 1 and N == 2.
public class Test
{
public static void main (String[] args)
{
printArray (allPermutations (8));
}
public static int[][] allPermutations (int N)
{
// base case
if (N == 2)
{
return new int[][] {{1, 2}, {2, 1}};
}
else if (N > 2)
{
// start with all permutations of previous degree
int[][] permutations = allPermutations (N - 1);
for (int i = 0; i < factorial (N); i += N)
{
// copy each permutation N - 1 times
for (int j = 0; j < N - 1; ++j)
{
// similar to javascript's array.splice
permutations = insertRow (permutations, i, permutations [i]);
}
}
// "weave" next number in
for (int i = 0, j = N - 1, d = -1; i < permutations.length; ++i)
{
// insert number N at index j
// similar to javascript's array.splice
permutations = insertColumn (permutations, i, j, N);
// index j is N-1, N-2, N-3, ... , 1, 0; then 0, 1, 2, ... N-1; then N-1, N-2, etc.
j += d;
// at beginning or end of the row, switch weave direction
if (j < 0 || j > N - 1)
{
d *= -1;
j += d;
}
}
return permutations;
}
else
{
throw new IllegalArgumentException ("N must be >= 2");
}
}
private static void arrayDeepCopy (int[][] src, int srcRow, int[][] dest,
int destRow, int numOfRows)
{
for (int row = 0; row < numOfRows; ++row)
{
System.arraycopy (src [srcRow + row], 0, dest [destRow + row], 0,
src[row].length);
}
}
public static int factorial (int n)
{
return n == 1 ? 1 : n * factorial (n - 1);
}
private static int[][] insertColumn (int[][] src, int rowIndex,
int columnIndex, int columnValue)
{
int[][] dest = new int[src.length][0];
for (int i = 0; i < dest.length; ++i)
{
dest [i] = new int [src[i].length];
}
arrayDeepCopy (src, 0, dest, 0, src.length);
int numOfColumns = src[rowIndex].length;
int[] rowWithExtraColumn = new int [numOfColumns + 1];
System.arraycopy (src [rowIndex], 0, rowWithExtraColumn, 0, columnIndex);
System.arraycopy (src [rowIndex], columnIndex, rowWithExtraColumn,
columnIndex + 1, numOfColumns - columnIndex);
rowWithExtraColumn [columnIndex] = columnValue;
dest [rowIndex] = rowWithExtraColumn;
return dest;
}
private static int[][] insertRow (int[][] src, int rowIndex,
int[] rowElements)
{
int srcRows = src.length;
int srcCols = rowElements.length;
int[][] dest = new int [srcRows + 1][srcCols];
arrayDeepCopy (src, 0, dest, 0, rowIndex);
arrayDeepCopy (src, rowIndex, dest, rowIndex + 1, src.length - rowIndex);
System.arraycopy (rowElements, 0, dest [rowIndex], 0, rowElements.length);
return dest;
}
public static void printArray (int[][] array)
{
for (int row = 0; row < array.length; ++row)
{
for (int col = 0; col < array[row].length; ++col)
{
System.out.print (array [row][col] + " ");
}
System.out.print ("\n");
}
System.out.print ("\n");
}
}
The java arrays are not mutable (in the sense, you cannot change their length). For direct translation of this recursive algorithm you probably want to use List interface (and probably LinkedList implementation as you want put numbers in the middle). That is List<List<Integer>>.
Beware the factorial grows rapidly: for N = 13, there is 13! permutations that is 6 227 020 800. But I guess you need to run it for only small values.
The algorithm above is quite complex, my solution would be:
create List<int[]> to hold all permutations
create one array of size N and fill it with identity ({1,2,3,...,N})
program function that in place creates next permutation in lexicographical ordering
repeat this until you get the identity again:
put a copy of the array at the end of the list
call the method to get next permutation.
If your program just needs to output all permutations, I would avoid to store them and just print them right away.
The algorithm to compute next permutation can be found on internet. Here for example
Use whatever you want, arrays or lists, but don't convert them - it just makes it harder. I can't tell what's better, probably I'd go for ArrayList<int[]>, since the outer List allows me to add the permutation easily and the inner array is good enough. That's just a matter of taste (but normally prefer lists, since they're much more flexible).
As per Howard's advice, I decided I didn't want to use anything but the primitive array type. The algorithm I initially picked was a pain to implement in Java, so thanks to stalker's advice, I went with the lexicographic-ordered algorithm described at Wikipedia. Here's what I ended up with:
public static int[][] generatePermutations(int N) {
int[][] a = new int[factorial(N)][N];
for (int i = 0; i < N; i++) a[0][i] = i;
for (int i = 1; i < a.length; i++) {
a[i] = Arrays.copyOf(a[i-1], N);
int k, l;
for (k = N - 2; a[i][k] >= a[i][k+1]; k--);
for (l = N - 1; a[i][k] >= a[i][l]; l--);
swap(a[i], k, l);
for (int j = 1; k+j < N-j; j++) swap(a[i], k+j, N-j);
}
return a;
}
private static void swap(int[] is, int k, int l) {
int tmp_k = is[k];
int tmp_l = is[l];
is[k] = tmp_l;
is[l] = tmp_k;
}