Страницы

Поиск по вопросам

понедельник, 4 февраля 2019 г.

Как правильно сделать циклический сдвиг в одномерном массиве?

Я новичок в java и у меня возник данный вопрос. Опишу ситуацию: есть двумерный массив типа int 9 на 9, и есть метод, который делает циклический сдвиг в одномерном массиве на заданное количество элементов.
Первое что я сделал это инициализировал первую строку двумерного массива числами от 1 до 9.
private static final int MAX = 9; private static int[][] baseTable = new int[MAX][MAX];
for (int b = 0; b < 9; b++) {
baseTable[0][b] = b + 1;
}
После чего я, сделав сдвиг на один элемент, записал то что получится в 4-ю строчку двумерного массива.
baseTable[3] = CyclicShift.shiftLeft(baseTable[0], 1);
Но тут и возникла проблема: когда функция возвращает сдвинутый массив и записывает его в 4-ю строчку почему-то сдвигается и нулевая строчка, и они, по сути, просто дублирую друг друга. Как я понимаю, они просто ссылаются на одно и тоже место в памяти, и тут возникает вопрос: почему нельзя таким образом проинициализировать строки в массиве?
Вот код метода shiftLeft
public static int[] shiftLeft(int[] a, int iter) { if (a != null) { for (int m = 0; m < iter; m++) { int tmp = a[0]; for (int i = 0; i < a.length - 1; i++) { a[i] = a[i + 1]; } a[a.length - 1] = tmp; } return a; } else { return null; } }
P.S. Проблема была решена сначала полным заполнением всего двумерного массива, а затем сортировкой только тех строк, которых мне нужно, и запись в них же. Но интересует именно механизм работы массива, и то, почему возникает такая ситуация.


Ответ

Тут всё довольно просто. Дело в том, что вы переставляете элементы в том же самом массиве, который и получаете в качестве источника.
Нужно определиться, что же все-таки должен делать ваш метод shiftLeft():
Переставлять элементы в том массиве, который получает Или возвращать новый массив, являющийся перестановкой старого.
Сейчас он делает и то, и другое. А еще вместо того, чтобы сразу перемещать элементы на N позиций, он делает N итераций — это не очень производительно.
Вот вариант, который создает новый массив и записывает в него значения со сдвигом, используя созданный как раз для этих целей метод System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
Реализация:
public class ArrayShift {
public static int[] shiftLeft(int[] a, int shift) { if (a != null) { int length = a.length; int[] b = new int[length]; // шаг 1 System.arraycopy(a, shift, b, 0, length - shift); // шаг 2 System.arraycopy(a, 0, b, length - shift, shift); return b; } else { return null; } } }
Вот как это работает.
Шаг 1:
array a: 0 1 2 3 4 5 ↓ ↓ ↓ ↓ array b: _ _ _ _ _ _
Шаг 2:
array a: 0 1 2 3 4 5 ↓ ↓ array b: 2 3 4 5 _ _
Несколько юнит-тестов:
import org.junit.Assert; import org.junit.Test;
public class ArrayShiftTest {
int[] source = {0, 1, 2, 3, 4, 5};
@Test public void testValid() { int[] expected = {2, 3, 4, 5, 0, 1}; Assert.assertArrayEquals(expected, ArrayShift.shiftLeft(source, 2)); }
public void testZero() { Assert.assertArrayEquals(source, ArrayShift.shiftLeft(source, 0)); }
/** * На отрицательных значениях метод сломается * */ @Test(expected = IndexOutOfBoundsException.class) public void testNegative() { ArrayShift.shiftLeft(source, -1); }
/** * На значениях выше длины массива — тоже * */ @Test(expected = IndexOutOfBoundsException.class) public void testExceedLength() { ArrayShift.shiftLeft(source, 10); } }
Если про юнит-тесты непонятно будет, спрашивайте.
Также подскажу вам три пункта, которые вы можете улучшить самостоятельно:
Сдвиг влево и вправо — одно и то же, можно задавать направление положительным и отрицательным значением параметра и объединить shiftLeft и shiftRight Если не объединять методы, то нужно что-то делать при отрицательных значениях Необходимо подумать о значениях, превышающих длину массива

Комментариев нет:

Отправить комментарий