Разное

Задача 1

Поменяйте местами значения двух целочисленных переменных. Найдите два способа: с использованием промежуточной переменной и без.

Классическая задача с классическими решениями. Первое и самое простое решение — воспользоваться третей переменной в качестве буфера:

int i = 10;

int j = 20;


int buf;


buf = i;

i = j;

j = buf;


System.out.println("i = " + i + ", j = " + j); //Выведет i = 20, j = 10

Второй способ также широко известен, но обычно никто сам до него не додумывается, просто откуда-то узнают, что так тоже можно. Выглядит он так:

int i = 10;

int j = 20;


i = i ^ j;

j = i ^ j;

i = i ^ j;


System.out.println("i = " + i + ", j = " + j); //Выведет i = 20, j = 10

Суть этого способа в нетривиальном использовании побитовой операции "исключающее или". В первой инструкции, использующей оператор ^, мы в переменную i записываем своеобразную "сумму" двух переменных. А точнее информацию о том, какие биты в них совпадают, а какие нет. Далее из этой общей информации мы вычисляем исходные переменные в нужном нам порядке.

Абсолютно такой же по сути алгоритм можно написать без "изысков" в виде оператора ^, а с самой обычной арифметикой:

int i = 10;

int j = 20;

i = i + j;

j = i - j;

i = i - j;


System.out.println("i = " + i + ", j = " + j); //Выведет i = 20, j = 10

В этом примере точно такая же логика, только она не завуалирована оператором ^.

  • А именно, сначала мы в i записываем сумму двух переменных, т.е. 30.

  • Затем в j записываем разницу между суммой и j. Теперь в j у нас 10. Та самая 10, которая изначально была в i.

  • В конце мы в i записываем разность суммы (30) и j (теперь уже 10), которая составляет 20 — первичное значение j.

Задача 2

Напишите метод со следующей сигнатурой:

public static double sqrt(double x);

Метод принимает аргумент типа double со значением в диапазоне от 1 до 1_000_000 и возвращает арифметический квадратный корень этого аргумента. Пользоваться как стандартной, так и сторонними библиотеками, разумеется, нельзя.

У задачи есть несколько решений, которые отличаются ресурсоёмкостью, точностью и другими характеристиками. Предлагается начать с алгоритма, который перебирает варианты, возводя их в квадрат и сравнивая с исходным аргументом. По мере перебора ответ всё больше уточняется. Назовём его алгоритмом приближения.

Для решения задачи достаточно вычислить квадратный корень с точностью в 4 цифры после запятой.

Общий принцип перебора вариантов покажем на примере числа 3.

  • Нам точно известно, что арифметический квадратный корень из этого числа лежит в промежутке между 0 и 3.

  • 0 — мы будем считать минимумом, а 3 — максимумом.

  • Сложим 0 и 3, полученную сумму разделим пополам, получим 1,5.

  • Возведём 1,5 в квадрат и получим 2,25. Число 2,25 очевидно меньше, чем 3.

  • Таким образом, также совершенно очевидно, что корень из 3 не может быть меньше, чем 1,5. А значит мы можем обновить наш минимум. Теперь он равен 1,5. И мы знаем, что наш ответ лежит где-то между 1,5 и 3.

  • Сложим минимум с максимумом и разделим пополам: (1,5 + 3) / 2 = 2,25.

  • Возведём 2,25 в квадрат и получим 5,0625. Число 5,0625 очевидно больше, чем 3.

  • Таким образом, также совершенно очевидно, что корень из 3 не может быть больше, чем 2,25. А значит мы можем обновить наш максимум. Теперь он равен 2,25. И мы знаем, что наш ответ лежит между 1,5 и 2,25.

Круг, как видите замкнулся, и основная часть алгоритма ясна. Разумеется, нужно проверить входные данные и определиться с тем, как мы будем ограничивать точность. Но это уже не самая интересная часть задачи.

В простейшем виде алгоритм на Java может выглядеть так:

public static double sqrt(double x) {

double min = 0;
double max = x;

double approx = 0;
double approxSqr = 0;

do {
approx = (min + max) / 2;
approxSqr = approx * approx;

if (approxSqr == x) {
return approx;

} else if (approxSqr > x) {
max = approx;

} else if (approxSqr < x) {
min = approx;
}

} while (!isPreciseEnough(max, x));

return max;
}

public static boolean isPreciseEnough(double max, double x) {

double sqr = max * max;
double roundSqr = sqr - sqr % 0.0001;

return roundSqr - x == 0;
}

Подобный алгоритм поиска путём приближения имеет свои недостатки, которые, впрочем, перекрываются его простотой.

Если мы выйдем за пределы условия задачи и передадим нашему методу sqrt значение от 0 до 1, то алгоритм уйдёт в бесконечный цикл. В качестве дополнительного задания подумайте, как может быть реализован аналогичный алгоритм для чисел в диапазоне от 0 до 1.

Задача 3

Напишите метод со следующей сигнатурой:

public static int getDecimalPointPos(double x);

Метод принимает аргумент типа double со значением в диапазоне от 1 до 1_000_000 и возвращает позицию запятой (точки) в этом вещественном числе.

У задачи есть несколько решений, принципиально можно различить два подхода: математический и нематематический.

Математический

Позиция запятой десятичной дроби полностью определяется целой частью дробного числа. Если число меньше 10, то то запятая стоит в позиции 2. Если число от 10 до 100 (не включительно), то запятая стоит в 3 позиции и т.д.

В простейшем виде решение может быть таким:

public static int getDecimalPointPos(double x) {


int pos = 0;

boolean isInRange;


do {


isInRange = x >= Math.pow(10, pos) && x < Math.pow(10, ++pos);


} while (!isInRange);


return pos + 1;

}

Нематематический

В общем-то ничего не мешает посмотреть на число как на строку символов. Разрезать эту строку на подстроки по символу "точка" и выяснить длину первой подстроки.

Подобное решение может выглядеть так:

public static int getDecimalPointPos(double x) {


String number = x + "";

String integerPart = number.split("\\.")[0];


return integerPart.length() + 1;

}

В данном решении стоит обратить внимание на то, как приходится экранировать точку в регулярном выражении, чтобы символ воспринимался как просто "точка". Кроме того, для наглядности код написан довольно многострочно. Его можно "сжать" в одну строку:

public static int getDecimalPointPos(double x) {

return (x + "").split("\\.")[0].length() + 1;

}

Но такой вариант, конечно, совершенно нечитаем.

Задача 4

Напишите метод со следующей сигнатурой:

public static String generatePassword();

Метод не принимает аргументов. В методе генерируется пароль для пользователя. Пароль соответствует следующим требованиям:

  • Длина пароля — 10 символов.

  • Все буквы, задействованные в пароле, должны быть латинскими, но должна присутствовать как минимум одна заглавная буква.

  • В пароле должна присутствовать как минимум одна цифра.

  • В пароле должен присутствовать как минимум один небуквенный/нецифровой символ, имеющийся на клавиатуре (знак препинания, математической операции и т.п.)

Чтобы решить эту задачу, нужно знать позиции символов в таблице символов. Затем с помощью генератора псевдослучайных чисел можно без трудна сгенерировать все нужные символы. Чтобы увидеть диапазон нужных нам символов и их номеров в таблице, запустите следующий код:

char a = '0';

int i = '0';


while (a <= 'z') {

System.out.println(a++ + " " + i++);

}

Зная позиции символов, мы можем выполнить все требования задачи. Например, так:

public static String generatePassword() {

List<Character> passwordCharacters = new ArrayList<>(10);


//Добавим одну строчную букву

passwordCharacters.add((char) (new Random().nextInt(26) + 97));

//Добавим одну заглавную букву

passwordCharacters.add((char) (new Random().nextInt(26) + 65));

//Добавим одну цифру

passwordCharacters.add((char) (new Random().nextInt(10) + 48));

//Добавим один небуквенный/нецифровой символ

passwordCharacters.add((char) (new Random().nextInt(7) + 58));


//Добавим 6 случайных символов

for (int i = 0; i < 6; i++) {

passwordCharacters.add((char) (new Random().nextInt(75) + 48));

}


//Перетасуем список

Collections.shuffle(passwordCharacters);


//Переделаем список в строку

String result = passwordCharacters.stream()

.map(String::valueOf)

.collect(Collectors.joining());


return result;

}

Задача 5

Если выполнить приведение типов следующим образом:

int i = 1000;

byte j = (byte) i;


System.out.println(j);

то значение переменной j будет равным -24, что отобразится на консоли.

Напишите код, который бы вычислял результат приведения int к byte математически, без собственно приведения.

Число 1000 в двоичной системе в переменной типа int выглядит следующим образом:

0000 0000 0000 0000 0000 0011 1110 1000

Приведение этого числа к типу byte приведёт к тому, что от указанной последовательности из 32 бит останутся только 8 последних, так как byte в Java восьмибитный:

1110 1000

Математически такого эффекта можно добиться, если взять остаток от деления исходного числа (в нашем случае 1000) на 28 :

1000 % 28 = 232

Если бы в Java в типе byte числа больше 127 не были отданы под отрицательные числа, то этот ответ был бы окончательным. Более того, если результат такого взятия остатка меньше или равен 127, то ответ получен.

Но в нашем случае результат 232 не может быть окончательным, так как он выпадает из диапазона byte от -128 до +127.

И тем не менее, в двоичной записи число 232 выглядит так, как нам нужно:

1110 1000

Итак, поскольку это число больше 127, а значит его первый бит равен 1, то в Java оно должно быть интерпретировано, как отрицательное число, и нам нужно выполнить ещё одну операцию, а именно вычесть из этого десятичного числа 256:

232 - 256 = -24

Окончательный ответ получен.

Таким образом алгоритм математического вычисления результата приведения int к byte в Java может выглядеть так:

static int calculateCast(int i) {


if (i % 256 > 127) {

return i % 256 - 256;

} else {

return i % 256;

}

}