diff --git a/Homework7/cache_fib#5.ser b/Homework7/cache_fib#5.ser new file mode 100644 index 0000000..cb3c166 Binary files /dev/null and b/Homework7/cache_fib#5.ser differ diff --git a/Homework7/cache_fib#6.ser b/Homework7/cache_fib#6.ser new file mode 100644 index 0000000..b8c95e0 Binary files /dev/null and b/Homework7/cache_fib#6.ser differ diff --git a/Homework7/pom.xml b/Homework7/pom.xml new file mode 100644 index 0000000..b4ff171 --- /dev/null +++ b/Homework7/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + + org.example + Homework7 + 1.0-SNAPSHOT + + + 21 + 21 + UTF-8 + + + \ No newline at end of file diff --git a/Homework7/src/main/java/Main.java b/Homework7/src/main/java/Main.java new file mode 100644 index 0000000..7783d55 --- /dev/null +++ b/Homework7/src/main/java/Main.java @@ -0,0 +1,39 @@ +import generator.RandomObjectGenerator; +import model.MyClass; +import model.MyRecord; +import proxy.CacheProxy; +import annotations.Cache; +import proxy.FibCalculator; + +public class Main { + public static void main(String[] args) { + // генератор объектов + RandomObjectGenerator rog = new RandomObjectGenerator(); + + var myClass = rog.nextObject(MyClass.class, "create"); //через фабрику + System.out.println("MyClass (factory): " + myClass); + + + var myRecord = rog.nextObject(MyRecord.class); //через конструктор + System.out.println("MyRecord (random): " + myRecord); + + // обычная реализация + FibCalculator original = new FibCalculator() { + @Override + @Cache(persist = true) + public long fib(int number) { + if (number <= 1) return number; + return fib(number - 1) + fib(number - 2); + } + }; + + // кэш-прокси + FibCalculator proxy = CacheProxy.create(original, FibCalculator.class); + + // вызовы с кэшированием + System.out.println("fib(5): " + proxy.fib(5)); + System.out.println("fib(6): " + proxy.fib(6)); + System.out.println("fib(5) again (from cache): " + proxy.fib(5)); + } + +} diff --git a/Homework7/src/main/java/annotations/Cache.java b/Homework7/src/main/java/annotations/Cache.java new file mode 100644 index 0000000..6e17ade --- /dev/null +++ b/Homework7/src/main/java/annotations/Cache.java @@ -0,0 +1,9 @@ +package annotations; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Cache { + boolean persist() default false; +} diff --git a/Homework7/src/main/java/annotations/Max.java b/Homework7/src/main/java/annotations/Max.java new file mode 100644 index 0000000..d1028d4 --- /dev/null +++ b/Homework7/src/main/java/annotations/Max.java @@ -0,0 +1,9 @@ +package annotations; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Max { + long value(); +} diff --git a/Homework7/src/main/java/annotations/Min.java b/Homework7/src/main/java/annotations/Min.java new file mode 100644 index 0000000..92f7566 --- /dev/null +++ b/Homework7/src/main/java/annotations/Min.java @@ -0,0 +1,9 @@ +package annotations; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Min { + long value(); +} diff --git a/Homework7/src/main/java/annotations/NotNull.java b/Homework7/src/main/java/annotations/NotNull.java new file mode 100644 index 0000000..9240b2f --- /dev/null +++ b/Homework7/src/main/java/annotations/NotNull.java @@ -0,0 +1,7 @@ +package annotations; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface NotNull {} diff --git a/Homework7/src/main/java/generator/RandomObjectGenerator.java b/Homework7/src/main/java/generator/RandomObjectGenerator.java new file mode 100644 index 0000000..e303e9a --- /dev/null +++ b/Homework7/src/main/java/generator/RandomObjectGenerator.java @@ -0,0 +1,55 @@ +package generator; + +import annotations.Max; +import annotations.Min; + +import java.lang.reflect.*; +import java.util.Random; + +public class RandomObjectGenerator { + private Random random = new Random(); + + public T nextObject(Class clazz) { + return nextObject(clazz, null); + } + + public T nextObject(Class clazz, String factoryMethodName) { + try { + if (factoryMethodName != null) { + Method method = clazz.getMethod(factoryMethodName); + return clazz.cast(method.invoke(null)); + } + + Constructor constructor = clazz.getDeclaredConstructors()[0]; + constructor.setAccessible(true); + + Parameter[] params = constructor.getParameters(); + Object[] args = new Object[params.length]; + + for (int i = 0; i < params.length; i++) { + args[i] = generateValue(params[i]); + } + + return clazz.cast(constructor.newInstance(args)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private Object generateValue(Parameter parameter) { + Class type = parameter.getType(); + AnnotatedElement element = parameter; + + if (type == int.class || type == Integer.class) { + long min = 0, max = 100; + if (element.isAnnotationPresent(Min.class)) + min = element.getAnnotation(Min.class).value(); + if (element.isAnnotationPresent(Max.class)) + max = element.getAnnotation(Max.class).value(); + return (int)(min + random.nextInt((int)(max - min + 1))); + } else if (type == String.class) { + return "str_" + random.nextInt(1000); + } + return null; + } +} diff --git a/Homework7/src/main/java/model/MyClass.java b/Homework7/src/main/java/model/MyClass.java new file mode 100644 index 0000000..c9e1816 --- /dev/null +++ b/Homework7/src/main/java/model/MyClass.java @@ -0,0 +1,29 @@ +package model; + +import annotations.Min; +import annotations.Max; +import annotations.NotNull; + +public class MyClass { + @NotNull + @Min(10) + @Max(99) + private Integer age; + + @NotNull + private String name; + + public MyClass(Integer age, String name) { + this.age = age; + this.name = name; + } + + public static MyClass create() { + return new MyClass(42, "FactoryCreated"); + } + + @Override + public String toString() { + return "MyClass{age=" + age + ", name='" + name + "'}"; + } +} diff --git a/Homework7/src/main/java/model/MyRecord.java b/Homework7/src/main/java/model/MyRecord.java new file mode 100644 index 0000000..5fa141c --- /dev/null +++ b/Homework7/src/main/java/model/MyRecord.java @@ -0,0 +1,13 @@ +package model; + +import annotations.*; + +public record MyRecord( + @NotNull + @Min(10) + @Max(50) + Integer count, + + @NotNull + String description +) {} diff --git a/Homework7/src/main/java/proxy/CacheProxy.java b/Homework7/src/main/java/proxy/CacheProxy.java new file mode 100644 index 0000000..79f5252 --- /dev/null +++ b/Homework7/src/main/java/proxy/CacheProxy.java @@ -0,0 +1,61 @@ +package proxy; + +import annotations.Cache; + +import java.io.*; +import java.lang.reflect.*; +import java.util.HashMap; +import java.util.Map; + +public class CacheProxy { + @SuppressWarnings("unchecked") + public static T create(T target, Class interf) { + return (T) Proxy.newProxyInstance( + interf.getClassLoader(), + new Class[]{interf}, + new CacheHandler(target) + ); + } + + private static class CacheHandler implements InvocationHandler { + private final Object target; + private final Map cache = new HashMap<>(); + + public CacheHandler(Object target) { + this.target = target; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (!method.isAnnotationPresent(Cache.class)) + return method.invoke(target, args); + + String key = method.getName() + "#" + (args != null ? args[0] : "null"); + Cache cacheAnn = method.getAnnotation(Cache.class); + + if (cache.containsKey(key)) { + return cache.get(key); + } + + File file = new File("cache_" + key + ".ser"); + if (cacheAnn.persist() && file.exists()) { + try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) { + Object result = ois.readObject(); + cache.put(key, result); + return result; + } + } + + Object result = method.invoke(target, args); + cache.put(key, result); + + if (cacheAnn.persist()) { + try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file))) { + oos.writeObject(result); + } + } + + return result; + } + } +} diff --git a/Homework7/src/main/java/proxy/FibCalculator.java b/Homework7/src/main/java/proxy/FibCalculator.java new file mode 100644 index 0000000..7f652c3 --- /dev/null +++ b/Homework7/src/main/java/proxy/FibCalculator.java @@ -0,0 +1,8 @@ +package proxy; + +import annotations.Cache; + +public interface FibCalculator { + @Cache(persist = true) + long fib(int number); +} diff --git a/Homework7/target/classes/Main$1.class b/Homework7/target/classes/Main$1.class new file mode 100644 index 0000000..569e733 Binary files /dev/null and b/Homework7/target/classes/Main$1.class differ diff --git a/Homework7/target/classes/Main.class b/Homework7/target/classes/Main.class new file mode 100644 index 0000000..c6010b9 Binary files /dev/null and b/Homework7/target/classes/Main.class differ diff --git a/Homework7/target/classes/annotations/Cache.class b/Homework7/target/classes/annotations/Cache.class new file mode 100644 index 0000000..6af088b Binary files /dev/null and b/Homework7/target/classes/annotations/Cache.class differ diff --git a/Homework7/target/classes/annotations/Max.class b/Homework7/target/classes/annotations/Max.class new file mode 100644 index 0000000..93d35d2 Binary files /dev/null and b/Homework7/target/classes/annotations/Max.class differ diff --git a/Homework7/target/classes/annotations/Min.class b/Homework7/target/classes/annotations/Min.class new file mode 100644 index 0000000..8606c80 Binary files /dev/null and b/Homework7/target/classes/annotations/Min.class differ diff --git a/Homework7/target/classes/annotations/NotNull.class b/Homework7/target/classes/annotations/NotNull.class new file mode 100644 index 0000000..7447acc Binary files /dev/null and b/Homework7/target/classes/annotations/NotNull.class differ diff --git a/Homework7/target/classes/generator/RandomObjectGenerator.class b/Homework7/target/classes/generator/RandomObjectGenerator.class new file mode 100644 index 0000000..32a8388 Binary files /dev/null and b/Homework7/target/classes/generator/RandomObjectGenerator.class differ diff --git a/Homework7/target/classes/model/MyClass.class b/Homework7/target/classes/model/MyClass.class new file mode 100644 index 0000000..cab0a03 Binary files /dev/null and b/Homework7/target/classes/model/MyClass.class differ diff --git a/Homework7/target/classes/model/MyRecord.class b/Homework7/target/classes/model/MyRecord.class new file mode 100644 index 0000000..cdedc9c Binary files /dev/null and b/Homework7/target/classes/model/MyRecord.class differ diff --git a/Homework7/target/classes/proxy/CacheProxy$CacheHandler.class b/Homework7/target/classes/proxy/CacheProxy$CacheHandler.class new file mode 100644 index 0000000..4b3a524 Binary files /dev/null and b/Homework7/target/classes/proxy/CacheProxy$CacheHandler.class differ diff --git a/Homework7/target/classes/proxy/CacheProxy.class b/Homework7/target/classes/proxy/CacheProxy.class new file mode 100644 index 0000000..232beef Binary files /dev/null and b/Homework7/target/classes/proxy/CacheProxy.class differ diff --git a/Homework7/target/classes/proxy/FibCalculator.class b/Homework7/target/classes/proxy/FibCalculator.class new file mode 100644 index 0000000..280e79e Binary files /dev/null and b/Homework7/target/classes/proxy/FibCalculator.class differ diff --git a/Task1/Atomic_Counter.java b/Task1/Atomic_Counter.java new file mode 100644 index 0000000..942cc3d --- /dev/null +++ b/Task1/Atomic_Counter.java @@ -0,0 +1,14 @@ +package Task1; +import java.util.concurrent.atomic.AtomicInteger; + +public class Atomic_Counter { + private final AtomicInteger count = new AtomicInteger(0); + public void increment() { + count.getAndIncrement(); + } + + + public int getCount() { + return count.get(); + } +} diff --git a/Task1/Client.java b/Task1/Client.java new file mode 100644 index 0000000..63611a5 --- /dev/null +++ b/Task1/Client.java @@ -0,0 +1,34 @@ +package Task1; +import java.io.*; +import java.net.ConnectException; +import java.net.Socket; +import java.util.Scanner; + + +public class Client { + private static final String HOST = "localhost"; + private static final int PORT = 55535; + + public static void main(String[] args) { + try (Scanner scanner = new Scanner(System.in)) { + while (true) { + System.out.print("Ваня: "); + String input = scanner.nextLine().trim(); + if (input.equalsIgnoreCase("exit")) break; + + try (Socket socket = new Socket(HOST, PORT); + ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); + ObjectInputStream in = new ObjectInputStream(socket.getInputStream())) { + out.writeObject(input); + out.flush(); + String response = (String) in.readObject(); + System.out.println("Сервер: " + response); + } catch (ConnectException e) { + System.out.println("Сервер перегружен. Ожидайте..."); + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + } + } + } + } +} \ No newline at end of file diff --git a/Task1/ComputerClubAnalytics.java b/Task1/ComputerClubAnalytics.java new file mode 100644 index 0000000..d513188 --- /dev/null +++ b/Task1/ComputerClubAnalytics.java @@ -0,0 +1,30 @@ +package Task1; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +public class ComputerClubAnalytics { + public static String calculateAverageSessionTime(List sessions) { + if (sessions == null || sessions.isEmpty()) { + return "0ч 0м"; + } + + Duration totalDuration = Duration.ZERO; + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd, HH:mm"); + + for (String session : sessions) { + String[] parts = session.split(" - "); + if (parts.length != 2) continue; + + LocalDateTime start = LocalDateTime.parse(parts[0], formatter); + LocalDateTime end = LocalDateTime.parse(parts[1], formatter); + totalDuration = totalDuration.plus(Duration.between(start, end)); + } + + Duration averageDuration = totalDuration.dividedBy(sessions.size()); + long hours = averageDuration.toHours(); + long minutes = averageDuration.toMinutes() % 60; + return hours + "ч " + minutes + "м"; + } +} \ No newline at end of file diff --git a/Task1/ReetnrantLock_Counter.java b/Task1/ReetnrantLock_Counter.java new file mode 100644 index 0000000..228ea92 --- /dev/null +++ b/Task1/ReetnrantLock_Counter.java @@ -0,0 +1,19 @@ +package Task1; +import java.util.concurrent.locks.ReentrantLock; + +public class ReetnrantLock_Counter { + private int count = 0; + private final ReentrantLock lock = new ReentrantLock(); + public void increment() { + lock.lock(); + try { + count++; + } finally { + lock.unlock(); + } + } + + public int getCount() { + return count; + } +} diff --git a/Task1/Semaphore_Counter.java b/Task1/Semaphore_Counter.java new file mode 100644 index 0000000..2d16fcc --- /dev/null +++ b/Task1/Semaphore_Counter.java @@ -0,0 +1,21 @@ +package Task1; +import java.util.concurrent.Semaphore; + +public class Semaphore_Counter { + private int count = 0; + private final Semaphore semaphore = new Semaphore(1); // Используем стандартный Semaphore + + public void increment() { + try { + semaphore.acquire(); // Метод acquire() из java.util.concurrent.Semaphore + count++; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + semaphore.release(); // Метод release() из java.util.concurrent.Semaphore + } + } + + public int getCount() { + return count; + } diff --git a/Task1/Server.java b/Task1/Server.java new file mode 100644 index 0000000..10ce961 --- /dev/null +++ b/Task1/Server.java @@ -0,0 +1,61 @@ +package Task1; +import java.io.*; +import java.net.*; +import java.util.HashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class Server { + private static final int PORT = 55535; + private static final int MAX_CONNECTIONS = 3; + private static final HashMap quotes = new HashMap<>(); + + static { + quotes.put("ошибка", "Не совершает ошибки, тот кто ничего не делает, то есть ты)"); + quotes.put("ошибки", "Не совершает ошибки, тот кто ничего не делает, то есть ты)"); + quotes.put("работай", "Сам работай"); + quotes.put("личности", "Не переходи на личности там, где их нет"); + quotes.put("оскорбления", "Если твои противники перешли на личные оскорбления, будь уверена – твоя победа не за горами"); + quotes.put("глупый", "А я тебе говорил, что ты глупый? Так вот, я забираю свои слова обратно... Ты просто бог идиотизма.."); + quotes.put("интеллект", "Чем ниже интеллект, тем громче оскорбления"); + } + + public static void main(String[] args) throws IOException { + ExecutorService pool = Executors.newFixedThreadPool(MAX_CONNECTIONS); + try (ServerSocket serverSocket = new ServerSocket(PORT)) { + System.out.println("Сервер запущен на порту " + PORT); + while (true) { + Socket clientSocket = serverSocket.accept(); + pool.execute(new ClientHandler(clientSocket)); + } + } + } + + private static class ClientHandler implements Runnable { + private final Socket clientSocket; + + ClientHandler(Socket socket) { + this.clientSocket = socket; + } + + @Override + public void run() { + try (ObjectInputStream in = new ObjectInputStream(clientSocket.getInputStream()); + ObjectOutputStream out = new ObjectOutputStream(clientSocket.getOutputStream())) { + String keyword = (String) in.readObject(); + String response = quotes.getOrDefault(keyword, "Цитата не найдена"); + out.writeObject(response); + out.flush(); + + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + } finally { + try { + clientSocket.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } +} \ No newline at end of file diff --git a/Task1/Synchronized_Counter.java b/Task1/Synchronized_Counter.java new file mode 100644 index 0000000..c15c93d --- /dev/null +++ b/Task1/Synchronized_Counter.java @@ -0,0 +1,13 @@ +package Task1; + + +public class Synchronized_Counter { + private int count = 0; + public synchronized void increment() { + count++; + } + + public int getCount() { + return count; + } +} diff --git a/Task1/Tests.java b/Task1/Tests.java new file mode 100644 index 0000000..38ee651 --- /dev/null +++ b/Task1/Tests.java @@ -0,0 +1,68 @@ +package Task1; +import org.junit.Test; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import static org.junit.Assert.assertEquals; + +public class Tests { + private static final int THREADS = 1000; + private static final int INCREMENTS_PER_THREAD = 1000; + private static final int EXPECTED_TOTAL = THREADS * INCREMENTS_PER_THREAD; + + @Test + public void testSynchronizedCounter() throws InterruptedException { + Synchronized_Counter counter = new Synchronized_Counter(); + runConcurrentTest(counter); + assertEquals(EXPECTED_TOTAL, counter.getCount()); + } + + + + @Test + public void testAtomicCounter() throws InterruptedException { + Atomic_Counter counter = new Atomic_Counter(); + runConcurrentTest(counter); + assertEquals(EXPECTED_TOTAL, counter.getCount()); + } + + + + @Test + public void testReentrantLockCounter() throws InterruptedException { + ReentrantLock_Counter counter = new ReentrantLock_Counter(); + runConcurrentTest(counter); + assertEquals(EXPECTED_TOTAL, counter.getCount()); + } + + + + @Test + public void testSemaphoreCounter() throws InterruptedException { + Semaphore_Counter counter = new Semaphore_Counter(); + runConcurrentTest(counter); + assertEquals(EXPECTED_TOTAL, counter.getCount()); + } + + + + private void runConcurrentTest(RunnableCounter counter) throws InterruptedException { + ExecutorService executor = Executors.newFixedThreadPool(THREADS); + for (int i = 0; i < THREADS; i++) { + executor.submit(() -> { + for (int j = 0; j < INCREMENTS_PER_THREAD; j++) { + counter.increment(); + } + }); + } + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.MINUTES); + } + + + + private interface RunnableCounter { + void increment(); + int getCount(); + } +} \ No newline at end of file diff --git a/Task10/OutputStreamComposition.java b/Task10/OutputStreamComposition.java new file mode 100644 index 0000000..aa5b25c --- /dev/null +++ b/Task10/OutputStreamComposition.java @@ -0,0 +1,18 @@ +package Task10; +import java.io.*; +import java.nio.file.*; + +public class OutputStreamComposition { + public static void main(String[] args) { + Path path = Paths.get("output.txt"); + try (OutputStream fileOutputStream = Files.newOutputStream(path); + BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); + OutputStreamWriter outputStreamWriter = new OutputStreamWriter(bufferedOutputStream, "UTF-8"); + PrintWriter printWriter = new PrintWriter(outputStreamWriter)) { + printWriter.println("Programming is learned by writing programs. – Brian Kernighan"); + } catch (IOException e) { + System.err.println("Произошла ошибка при записи в файл: " + e.getMessage()); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/Task2/FactorialCalculator.java b/Task2/FactorialCalculator.java new file mode 100644 index 0000000..f75484c --- /dev/null +++ b/Task2/FactorialCalculator.java @@ -0,0 +1,56 @@ +package Task2; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.*; + +public class FactorialCalculator { + + public static BigInteger computeFactorial(int n, int numThreads) throws InterruptedException, ExecutionException { + if (n < 0) { + throw new IllegalArgumentException("n must be non-negative"); + } + if (n == 0 || n == 1) { + return BigInteger.ONE; + } + + ExecutorService executor = Executors.newFixedThreadPool(numThreads); + List> futures = new ArrayList<>(); + + int chunkSize = n / numThreads; + int start = 1; + int end; + + for (int i = 0; i < numThreads; i++) { + end = (i == numThreads - 1) ? n : start + chunkSize - 1; + futures.add(executor.submit(new FactorialTask(start, end))); + start = end + 1; + } + BigInteger result = BigInteger.ONE; + for (Future future : futures) { + result = result.multiply(future.get()); + } + + executor.shutdown(); + return result; + } + + private static class FactorialTask implements Callable { + private final int start; + private final int end; + + FactorialTask(int start, int end) { + this.start = start; + this.end = end; + } + + @Override + public BigInteger call() { + BigInteger product = BigInteger.ONE; + for (int i = start; i <= end; i++) { + product = product.multiply(BigInteger.valueOf(i)); + } + return product; + } + } +} \ No newline at end of file diff --git a/Task2/FactorialCalculatorTests.java b/Task2/FactorialCalculatorTests.java new file mode 100644 index 0000000..469eb90 --- /dev/null +++ b/Task2/FactorialCalculatorTests.java @@ -0,0 +1,27 @@ +package Task2; +import org.junit.Test; +import java.math.BigInteger; +import static org.junit.Assert.assertEquals; + +public class FactorialCalculatorTest { + + @Test + public void testComputeFactorial() throws Exception { + assertEquals(BigInteger.valueOf(120), FactorialCalculator.computeFactorial(5, 2)); + assertEquals(BigInteger.valueOf(3628800), FactorialCalculator.computeFactorial(10, 4)); + } + + + + @Test(expected = IllegalArgumentException.class) + public void testNegativeInput() throws Exception { + FactorialCalculator.computeFactorial(-1, 2); + } + + + @Test + public void testEdgeCases() throws Exception { + assertEquals(BigInteger.ONE, FactorialCalculator.computeFactorial(0, 1)); + assertEquals(BigInteger.ONE, FactorialCalculator.computeFactorial(1, 3)); + } +} \ No newline at end of file diff --git a/Task2/FixedThreadPool.java b/Task2/FixedThreadPool.java new file mode 100644 index 0000000..151a426 --- /dev/null +++ b/Task2/FixedThreadPool.java @@ -0,0 +1,73 @@ +package Task2; +import java.util.ArrayDeque; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicBoolean; + + +class FixedThreadPool implements ThreadPool { + private final Thread[] threads; + private final Queue taskQueue = new ArrayDeque<>(); + private final AtomicBoolean isRunning = new AtomicBoolean(true); + + private FixedThreadPool(int numThreads) { + this.threads = new Thread[numThreads]; + } + + public static FixedThreadPool create(int numThreads) { + return new FixedThreadPool(numThreads); + } + + @Override + public void start() { + for (int i = 0; i < threads.length; i++) { + threads[i] = new Thread(() -> { + while (isRunning.get() || !taskQueue.isEmpty()) { + Runnable task; + synchronized (taskQueue) { + while (taskQueue.isEmpty() && isRunning.get()) { + try { + taskQueue.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + } + task = taskQueue.poll(); + } + if (task != null) { + task.run(); + } + } + }); + threads[i].start(); + } + } + + + @Override + public void execute(Runnable runnable) { + synchronized (taskQueue) { + if (!isRunning.get()) { + throw new IllegalStateException("Пул остановлен"); + } + taskQueue.offer(runnable); + taskQueue.notify(); + } + } + + + @Override + public void close() { + isRunning.set(false); + synchronized (taskQueue) { + taskQueue.notifyAll(); + } + for (Thread thread : threads) { + try { + thread.join(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } +} \ No newline at end of file diff --git a/Task2/FridayTheThirteenth.java b/Task2/FridayTheThirteenth.java new file mode 100644 index 0000000..a5681be --- /dev/null +++ b/Task2/FridayTheThirteenth.java @@ -0,0 +1,27 @@ +package Task2; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.temporal.TemporalAdjusters; +import java.util.ArrayList; +import java.util.List; + +public class FridayTheThirteenth { + public static List findFridayTheThirteenth(int year) { + List fridays = new ArrayList<>(); + for (int month = 1; month <= 12; month++) { + LocalDate date = LocalDate.of(year, month, 13); + if (date.getDayOfWeek() == DayOfWeek.FRIDAY) { + fridays.add(date.toString()); + } + } + return fridays; + } + + public static LocalDate findNextFridayTheThirteenth(LocalDate date) { + LocalDate nextDate = date.with(TemporalAdjusters.next(DayOfWeek.FRIDAY)); + while (nextDate.getDayOfMonth() != 13) { + nextDate = nextDate.with(TemporalAdjusters.next(DayOfWeek.FRIDAY)); + } + return nextDate; + } +} diff --git a/Task2/Main.java b/Task2/Main.java new file mode 100644 index 0000000..df432e2 --- /dev/null +++ b/Task2/Main.java @@ -0,0 +1,23 @@ +package Task2; +public class Main { // тут на примере чисел фибоначчи показываю, как просят + public static void main(String[] args) { + try (FixedThreadPool pool = FixedThreadPool.create(4)) { + pool.start(); + for (int i = 0; i < 30; i++) { + int num = i; + pool.execute(() -> { + long result = fibonacci(num); + System.out.printf("fib(%d) = %d (поток: %s)%n", + num, result, Thread.currentThread().getName()); + }); + } + } + } + + + + private static long fibonacci(int n) { + if (n <= 1) return n; + return fibonacci(n - 1) + fibonacci(n - 2); + } +} diff --git a/Task2/ThreadPool.java b/Task2/ThreadPool.java new file mode 100644 index 0000000..3707e8f --- /dev/null +++ b/Task2/ThreadPool.java @@ -0,0 +1,5 @@ +package Task2; +public interface ThreadPool extends AutoCloseable { + void start(); + void execute(Runnable runnable); +} diff --git a/Task3/CachingPersonDatabase.java b/Task3/CachingPersonDatabase.java new file mode 100644 index 0000000..d517a6e --- /dev/null +++ b/Task3/CachingPersonDatabase.java @@ -0,0 +1,65 @@ +package Task3; + +import java.util.*; + +public class CachingPersonDatabase implements PersonDatabase { + private final Map idIndex = new HashMap<>(); + private final Map> nameIndex = new HashMap<>(); + private final Map> addressIndex = new HashMap<>(); + private final Map> phoneIndex = new HashMap<>(); + + @Override + public synchronized void add(Person person) { + if (idIndex.containsKey(person.id())) { + return; + } + idIndex.put(person.id(), person); + addToIndex(nameIndex, person.name(), person); + addToIndex(addressIndex, person.address(), person); + addToIndex(phoneIndex, person.phoneNumber(), person); + } + + @Override + public synchronized void delete(int id) { + Person person = idIndex.remove(id); + if (person == null) { + return; + } + removeFromIndex(nameIndex, person.name(), person); + removeFromIndex(addressIndex, person.address(), person); + removeFromIndex(phoneIndex, person.phoneNumber(), person); + } + + @Override + public List findByName(String name) { + return getFromIndex(nameIndex, name); + } + + @Override + public List findByAddress(String address) { + return getFromIndex(addressIndex, address); + } + + @Override + public List findByPhone(String phone) { + return getFromIndex(phoneIndex, phone); + } + private synchronized void addToIndex(Map> index, String key, Person person) { + index.computeIfAbsent(key, k -> new ArrayList<>()).add(person); + } + + private synchronized void removeFromIndex(Map> index, String key, Person person) { + List list = index.get(key); + if (list != null) { + list.remove(person); + if (list.isEmpty()) { + index.remove(key); + } + } + } + + private synchronized List getFromIndex(Map> index, String key) { + List result = index.getOrDefault(key, Collections.emptyList()); + return new ArrayList<>(result); + } +} \ No newline at end of file diff --git a/Task3/CachingPersonDatabaseTests.java b/Task3/CachingPersonDatabaseTests.java new file mode 100644 index 0000000..1b49cd4 --- /dev/null +++ b/Task3/CachingPersonDatabaseTests.java @@ -0,0 +1,27 @@ +package Task3; +import org.junit.Test; +import static org.junit.Assert.*; +import java.util.List; + +public class CachingPersonDatabaseTest { + @Test + public void testAddAndFind() { + PersonDatabase db = new CachingPersonDatabase(); + Person person = new Person(1, "Alice", "Paris", "12345"); + db.add(person); + assertEquals(List.of(person), db.findByName("Alice")); + assertEquals(List.of(person), db.findByAddress("Paris")); + assertEquals(List.of(person), db.findByPhone("12345")); + } + + @Test + public void testDelete() { + PersonDatabase db = new CachingPersonDatabase(); + Person person = new Person(1, "Bob", "London", "67890"); + db.add(person); + db.delete(1); + assertTrue(db.findByName("Bob").isEmpty()); + assertTrue(db.findByAddress("London").isEmpty()); + assertTrue(db.findByPhone("67890").isEmpty()); + } +} \ No newline at end of file diff --git a/Task3/DateParser.java b/Task3/DateParser.java new file mode 100644 index 0000000..dea891e --- /dev/null +++ b/Task3/DateParser.java @@ -0,0 +1,30 @@ +package Task3; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Optional; + +public class DateParser { + public static Optional parseDate(String string) { + if (string == null || string.isEmpty()) { + return Optional.empty(); + } + + DateTimeFormatter[] formatters = { + DateTimeFormatter.ofPattern("yyyy-MM-dd"), + DateTimeFormatter.ofPattern("yyyy-M-d"), + DateTimeFormatter.ofPattern("M/d/yyyy"), + DateTimeFormatter.ofPattern("M/d/yy") + }; + + for (DateTimeFormatter formatter : formatters) { + try { + return Optional.of(LocalDate.parse(string, formatter)); + } catch (DateTimeParseException e) { + continue; + } + } + + return Optional.empty(); + } +} \ No newline at end of file diff --git a/Task3/Person.java b/Task3/Person.java new file mode 100644 index 0000000..ad3f7c7 --- /dev/null +++ b/Task3/Person.java @@ -0,0 +1,14 @@ +package Task3; + +import java.util.List; + +public record Person(int id, String name, String address, String phoneNumber) {} + +interface PersonDatabase { + void add(Person person); + void delete(int id); + + List findByName(String name); + List findByAddress(String address); + List findByPhone(String phone); +} \ No newline at end of file diff --git a/Task3_5/CachingPersonDatabaseWithReadWriteLock.java b/Task3_5/CachingPersonDatabaseWithReadWriteLock.java new file mode 100644 index 0000000..ce8919c --- /dev/null +++ b/Task3_5/CachingPersonDatabaseWithReadWriteLock.java @@ -0,0 +1,91 @@ +package Task3_5; + +import java.util.*; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class CachingPersonDatabaseWithReadWriteLock implements PersonDatabase { + private final Map idIndex = new HashMap<>(); + private final Map> nameIndex = new HashMap<>(); + private final Map> addressIndex = new HashMap<>(); + private final Map> phoneIndex = new HashMap<>(); + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + @Override + public void add(Person person) { + lock.writeLock().lock(); + try { + if (idIndex.containsKey(person.id())) return; + idIndex.put(person.id(), person); + addToIndex(nameIndex, person.name(), person); + addToIndex(addressIndex, person.address(), person); + addToIndex(phoneIndex, person.phoneNumber(), person); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public void delete(int id) { + lock.writeLock().lock(); + try { + Person person = idIndex.remove(id); + if (person == null) return; + + removeFromIndex(nameIndex, person.name(), person); + removeFromIndex(addressIndex, person.address(), person); + removeFromIndex(phoneIndex, person.phoneNumber(), person); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public List findByName(String name) { + lock.readLock().lock(); + try { + return new ArrayList<>(getFromIndex(nameIndex, name)); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public List findByAddress(String address) { + lock.readLock().lock(); + try { + return new ArrayList<>(getFromIndex(addressIndex, address)); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public List findByPhone(String phone) { + lock.readLock().lock(); + try { + return new ArrayList<>(getFromIndex(phoneIndex, phone)); + } finally { + lock.readLock().unlock(); + } + } + + + + private void addToIndex(Map> index, String key, Person person) { + index.computeIfAbsent(key, k -> new ArrayList<>()).add(person); + } + + private void removeFromIndex(Map> index, String key, Person person) { + List list = index.get(key); + if (list != null) { + list.remove(person); + if (list.isEmpty()) index.remove(key); + } + } + + private List getFromIndex(Map> index, String key) { + return index.getOrDefault(key, Collections.emptyList()); + } + +} \ No newline at end of file diff --git a/Task3_5/Person.java b/Task3_5/Person.java new file mode 100644 index 0000000..926ab6a --- /dev/null +++ b/Task3_5/Person.java @@ -0,0 +1,13 @@ +package Task3_5; +import java.util.List; + +public record Person(int id, String name, String address, String phoneNumber) {} + +interface PersonDatabase { + void add(Task3_5.Person person); + void delete(int id); + + List findByName(String name); + List findByAddress(String address); + List findByPhone(String phone); +} \ No newline at end of file diff --git a/Task4/MonteCarloPiMultiThread.java b/Task4/MonteCarloPiMultiThread.java new file mode 100644 index 0000000..bb97f4b --- /dev/null +++ b/Task4/MonteCarloPiMultiThread.java @@ -0,0 +1,31 @@ +package Task4; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicLong; + +public class MonteCarloPiMultiThread { + public static double calculatePi(long iterations, int threads) throws InterruptedException { + AtomicLong circleCount = new AtomicLong(0); + ExecutorService executor = Executors.newFixedThreadPool(threads); + long perThread = iterations / threads; + + for (int i = 0; i < threads; i++) { + executor.submit(() -> { + long localCount = 0; + ThreadLocalRandom random = ThreadLocalRandom.current(); + for (long j = 0; j < perThread; j++) { + double x = random.nextDouble(); + double y = random.nextDouble(); + if (x * x + y * y <= 1.0) { + localCount++; + } + } + circleCount.addAndGet(localCount); + }); + } + + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.HOURS); + return 4.0 * circleCount.get() / iterations; + } +} \ No newline at end of file diff --git a/Task4/MonteCarloPiSingleThread.java b/Task4/MonteCarloPiSingleThread.java new file mode 100644 index 0000000..6c62394 --- /dev/null +++ b/Task4/MonteCarloPiSingleThread.java @@ -0,0 +1,16 @@ +package Task4; +import java.util.concurrent.ThreadLocalRandom; + +public class MonteCarloPiSingleThread { + public static double calculatePi(long iterations) { + long circleCount = 0; + for (long i = 0; i < iterations; i++) { + double x = ThreadLocalRandom.current().nextDouble(); + double y = ThreadLocalRandom.current().nextDouble(); + if (x * x + y * y <= 1.0) { + circleCount++; + } + } + return 4.0 * circleCount / iterations; + } +} \ No newline at end of file diff --git a/Task4/PasswordValidator.java b/Task4/PasswordValidator.java new file mode 100644 index 0000000..1dbdcef --- /dev/null +++ b/Task4/PasswordValidator.java @@ -0,0 +1,6 @@ +package Task4; +public class PasswordValidator { + public static boolean validatePassword(String password) { + return password != null && password.matches(".*[~!@#$%^&*|].*"); + } +} \ No newline at end of file diff --git a/Task4/PiBenchmark.java b/Task4/PiBenchmark.java new file mode 100644 index 0000000..6598a84 --- /dev/null +++ b/Task4/PiBenchmark.java @@ -0,0 +1,26 @@ +package Task4; + +public class PiBenchmark { + public static void main(String[] args) throws InterruptedException { + int[] iterations = {10_000_000, 100_000_000, 1_000_000_000}; + int threads = Runtime.getRuntime().availableProcessors(); + + for (long n : iterations) { + long start = System.nanoTime(); + double piSingle = MonteCarloPiSingleThread.calculatePi(n); + long singleTime = System.nanoTime() - start; + + + start = System.nanoTime(); + double piMulti = MonteCarloPiMultiThread.calculatePi(n, threads); + long multiTime = System.nanoTime() - start; + + System.out.printf("Итераций: %,d%n", n); + System.out.printf("Однопоточное время: %.3f сек%n", singleTime / 1e9); + System.out.printf("Многопоточное время: %.3f сек%n", multiTime / 1e9); + System.out.printf("Ускорение: x%.2f%n", (double) singleTime / multiTime); + System.out.printf("Погрешность (single): %.6f%n", Math.abs(piSingle - Math.PI)); + System.out.printf("Погрешность (multi): %.6f%n%n", Math.abs(piMulti - Math.PI)); + } + } +} \ No newline at end of file diff --git a/Task5/LicensePlateValidator.java b/Task5/LicensePlateValidator.java new file mode 100644 index 0000000..c171a7a --- /dev/null +++ b/Task5/LicensePlateValidator.java @@ -0,0 +1,6 @@ +package Task5; +public class LicensePlateValidator { + public static boolean validateLicensePlate(String plate) { + return plate != null && plate.matches("[АВЕКМНОРСТУХ]\\d{3}[АВЕКМНОРСТУХ]{2}\\d{2,3}"); + } +} diff --git a/Task5_with_deadlock/Account.java b/Task5_with_deadlock/Account.java new file mode 100644 index 0000000..1373a86 --- /dev/null +++ b/Task5_with_deadlock/Account.java @@ -0,0 +1,24 @@ +package Task5_with_deadlock; + +public class Account { + private int cashBalance; + + public Account(int cashBalance) { + this.cashBalance = cashBalance; + } + + public void addMoney(int money) { + this.cashBalance += money; + } + + public boolean takeOffMoney(int money) { + if (this.cashBalance < money) return false; + this.cashBalance -= money; + return true; + } + + public int getCashBalance() { + return cashBalance; + } +} + diff --git a/Task5_with_deadlock/AccountMain.java b/Task5_with_deadlock/AccountMain.java new file mode 100644 index 0000000..40f5eee --- /dev/null +++ b/Task5_with_deadlock/AccountMain.java @@ -0,0 +1,24 @@ +package Task5_with_deadlock; + +public class AccountMain { + public static void main(String[] args) { + Account firstAccount = new Account(100_000); + Account secondAccount = new Account(100_000); + + Thread firstThread = new Thread(new AccountThread(firstAccount, secondAccount, 100)); + Thread secondThread = new Thread(new AccountThread(secondAccount, firstAccount, 100)); + + firstThread.start(); + secondThread.start(); + + try { + firstThread.join(); + secondThread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + System.out.println("First account balance: " + firstAccount.getCashBalance()); + System.out.println("Second account balance: " + secondAccount.getCashBalance()); + } +} diff --git a/Task5_with_deadlock/AccountThread.java b/Task5_with_deadlock/AccountThread.java new file mode 100644 index 0000000..80ec259 --- /dev/null +++ b/Task5_with_deadlock/AccountThread.java @@ -0,0 +1,26 @@ +package Task5_with_deadlock; + +public class AccountThread implements Runnable { + private final Account accountFrom; + private final Account accountTo; + private final int money; + + public AccountThread(Account accountFrom, Account accountTo, int money) { + this.accountFrom = accountFrom; + this.accountTo = accountTo; + this.money = money; + } + + @Override + public void run() { + for (int i = 0; i < 4000; i++) { + synchronized (accountFrom) { + synchronized (accountTo) { + if (accountFrom.takeOffMoney(money)) { + accountTo.addMoney(money); + } + } + } + } + } +} diff --git a/Task5_without_deadlock/Account.java b/Task5_without_deadlock/Account.java new file mode 100644 index 0000000..20f1dec --- /dev/null +++ b/Task5_without_deadlock/Account.java @@ -0,0 +1,23 @@ +package Task5_without_deadlock; +public class Account { + private int cashBalance; + + public Account(int cashBalance) { + this.cashBalance = cashBalance; + } + + public void addMoney(int money) { + this.cashBalance += money; + } + + public boolean takeOffMoney(int money) { + if (this.cashBalance < money) return false; + this.cashBalance -= money; + return true; + } + + public int getCashBalance() { + return cashBalance; + } +} + diff --git a/Task5_without_deadlock/AccountMain.java b/Task5_without_deadlock/AccountMain.java new file mode 100644 index 0000000..90aea79 --- /dev/null +++ b/Task5_without_deadlock/AccountMain.java @@ -0,0 +1,24 @@ +package Task5_without_deadlock; + +public class AccountMain { + public static void main(String[] args) { + Account firstAccount = new Account(100_000); + Account secondAccount = new Account(100_000); + + Thread firstThread = new Thread(new AccountThread(firstAccount, secondAccount, 100)); + Thread secondThread = new Thread(new AccountThread(secondAccount, firstAccount, 100)); + + firstThread.start(); + secondThread.start(); + + try { + firstThread.join(); + secondThread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + System.out.println("First account balance: " + firstAccount.getCashBalance()); + System.out.println("Second account balance: " + secondAccount.getCashBalance()); + } +} \ No newline at end of file diff --git a/Task5_without_deadlock/AccountThread.java b/Task5_without_deadlock/AccountThread.java new file mode 100644 index 0000000..14ba204 --- /dev/null +++ b/Task5_without_deadlock/AccountThread.java @@ -0,0 +1,29 @@ +package Task5_without_deadlock; + +public class AccountThread implements Runnable { + private final Account accountFrom; + private final Account accountTo; + private final int money; + + public AccountThread(Account accountFrom, Account accountTo, int money) { + this.accountFrom = accountFrom; + this.accountTo = accountTo; + this.money = money; + } + + @Override + public void run() { + for (int i = 0; i < 4000; i++) { + Account first = (accountFrom.hashCode() < accountTo.hashCode()) ? accountFrom : accountTo; + Account second = (accountFrom.hashCode() < accountTo.hashCode()) ? accountTo : accountFrom; + + synchronized (first) { + synchronized (second) { + if (accountFrom.takeOffMoney(money)) { + accountTo.addMoney(money); + } + } + } + } + } +} \ No newline at end of file diff --git a/Task6/BinaryStringValidator.java b/Task6/BinaryStringValidator.java new file mode 100644 index 0000000..31e3a7b --- /dev/null +++ b/Task6/BinaryStringValidator.java @@ -0,0 +1,14 @@ +package Task6; +public class BinaryStringValidator { + public static boolean validateThirdSymbol(String input) { + return input != null && input.matches("[01]{2}0.*"); + } + + public static boolean validateStartEnd(String input) { + return input != null && input.matches("([01]).*\\1"); + } + + public static boolean validateLength(String input) { + return input != null && input.matches("[01]{1,3}"); + } +} \ No newline at end of file diff --git a/Task7/DiskMap.java b/Task7/DiskMap.java new file mode 100644 index 0000000..dff83da --- /dev/null +++ b/Task7/DiskMap.java @@ -0,0 +1,102 @@ +package Task7; +import java.io.*; +import java.util.HashMap; +import java.util.Map; + +public class DiskMap implements Map { + private final File file; + private final Map map; + + public DiskMap(String filename) throws IOException { + this.file = new File(filename); + this.map = new HashMap<>(); + if (file.exists()) { + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + while ((line = reader.readLine()) != null) { + String[] parts = line.split("=", 2); + if (parts.length == 2) { + map.put(parts[0], parts[1]); + } + } + } + } + } + + @Override + public String put(String key, String value) { + String oldValue = map.put(key, value); + saveToFile(); + return oldValue; + } + + private void saveToFile() { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + for (Map.Entry entry : map.entrySet()) { + writer.write(entry.getKey() + "=" + entry.getValue()); + writer.newLine(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + public String get(Object key) { + return map.get(key); + } + + @Override + public String remove(Object key) { + String value = map.remove(key); + saveToFile(); + return value; + } + + @Override + public void putAll(Map m) { + map.putAll(m); + saveToFile(); + } + + @Override + public void clear() { + map.clear(); + saveToFile(); + } + + @Override + public java.util.Set keySet() { + return map.keySet(); + } + + @Override + public java.util.Collection values() { + return map.values(); + } + + @Override + public java.util.Set> entrySet() { + return map.entrySet(); + } +} \ No newline at end of file diff --git a/Task8/FileCloner.java b/Task8/FileCloner.java new file mode 100644 index 0000000..c2343ad --- /dev/null +++ b/Task8/FileCloner.java @@ -0,0 +1,35 @@ +package Task8; +import java.io.IOException; +import java.nio.file.*; + +public class FileCloner { + public static void cloneFile(Path path) throws IOException { + if (!Files.exists(path)) { + throw new IOException("Файл не существует: " + path); + } + + String fileName = path.getFileName().toString(); + String baseName = fileName.substring(0, fileName.lastIndexOf('.')); + String extension = fileName.substring(fileName.lastIndexOf('.')); + int copyNumber = 1; + Path newPath; + + do { + String newFileName = baseName + " – копия" + (copyNumber > 1 ? " (" + copyNumber + ")" : "") + extension; + newPath = path.getParent().resolve(newFileName); + copyNumber++; + } while (Files.exists(newPath)); + + Files.copy(path, newPath); + } + + public static void main(String[] args) { + try { + Path path = Paths.get("Tinkoff Bank Biggest Secret.txt"); + cloneFile(path); + System.out.println("Файл успешно скопирован."); + } catch (IOException e) { + System.err.println("Ошибка при копировании файла: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/Task9/FileFilters.java b/Task9/FileFilters.java new file mode 100644 index 0000000..9a9c118 --- /dev/null +++ b/Task9/FileFilters.java @@ -0,0 +1,42 @@ +package Task9; +import java.io.IOException; +import java.nio.file.*; + +public class FileFilters { + public static DirectoryStream.Filter regularFile = Files::isRegularFile; + public static DirectoryStream.Filter readable = Files::isReadable; + + public static DirectoryStream.Filter largerThan(long size) { + return path -> { + try { + return Files.size(path) > size; + } catch (IOException e) { + return false; + } + }; + } + + public static DirectoryStream.Filter magicNumber(int... magicBytes) { + return path -> { + try { + byte[] fileBytes = Files.readAllBytes(path); + for (int i = 0; i < magicBytes.length; i++) { + if (fileBytes[i] != (byte) magicBytes[i]) { + return false; + } + } + return true; + } catch (IOException e) { + return false; + } + }; + } + + public static DirectoryStream.Filter globMatches(String glob) { + return path -> path.getFileSystem().getPathMatcher("glob:" + glob).matches(path.getFileName()); + } + + public static DirectoryStream.Filter regexContains(String regex) { + return path -> path.getFileName().toString().matches(regex); + } +} \ No newline at end of file diff --git a/homework2/.gitkeep b/homework2/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/homework2/.gitkeep @@ -0,0 +1 @@ + diff --git a/homework2/Task1/.gitkeep b/homework2/Task1/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/homework2/Task1/.gitkeep @@ -0,0 +1 @@ + diff --git a/homework2/Task1/Addition.java b/homework2/Task1/Addition.java new file mode 100644 index 0000000..a7b5469 --- /dev/null +++ b/homework2/Task1/Addition.java @@ -0,0 +1,8 @@ +package Task1; + +public record Addition(Expr number1, Expr number2) implements Expr{ + @Override + public double evaluate() { + return number1.evaluate() + number2.evaluate(); + } +} \ No newline at end of file diff --git a/homework2/Task1/Constant.java b/homework2/Task1/Constant.java new file mode 100644 index 0000000..228e500 --- /dev/null +++ b/homework2/Task1/Constant.java @@ -0,0 +1,8 @@ +package Task1; + +public record Constant(Expr number) implements Expr { + @Override + public double evaluate() { + return number.evaluate(); + } +} \ No newline at end of file diff --git a/homework2/Task1/Exponent.java b/homework2/Task1/Exponent.java new file mode 100644 index 0000000..e62a620 --- /dev/null +++ b/homework2/Task1/Exponent.java @@ -0,0 +1,8 @@ +package Task1; + +public record Exponent(Expr number, Expr exponent) implements Expr { + @Override + public double evaluate() { + return Math.pow(number.evaluate(), exponent.evaluate()); + } +} diff --git a/homework2/Task1/Expr.java b/homework2/Task1/Expr.java new file mode 100644 index 0000000..5923be0 --- /dev/null +++ b/homework2/Task1/Expr.java @@ -0,0 +1,5 @@ +package Task1; + +public sealed interface Expr permits Constant, Negate, Exponent, Addition, Multiplication { + public double evaluate(); +} diff --git a/homework2/Task1/Multiplication.java b/homework2/Task1/Multiplication.java new file mode 100644 index 0000000..f01ab2a --- /dev/null +++ b/homework2/Task1/Multiplication.java @@ -0,0 +1,8 @@ +package Task1; + +public record Multiplication(Expr number1, Expr number2) implements Expr { + @Override + public double evaluate() { + return number1.evaluate() * number2.evaluate(); + } +} diff --git a/homework2/Task1/Negate.java b/homework2/Task1/Negate.java new file mode 100644 index 0000000..a4e6d58 --- /dev/null +++ b/homework2/Task1/Negate.java @@ -0,0 +1,8 @@ +package Task1; + +public record Negate(Expr number) implements Expr { + @Override + public double evaluate() { + return -number.evaluate(); + } +} \ No newline at end of file diff --git a/homework2/Task2/.gitkeep b/homework2/Task2/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/homework2/Task2/.gitkeep @@ -0,0 +1 @@ + diff --git a/homework2/Task2/DynamicArray.java b/homework2/Task2/DynamicArray.java new file mode 100644 index 0000000..892c556 --- /dev/null +++ b/homework2/Task2/DynamicArray.java @@ -0,0 +1,139 @@ +package Task2; + +public interface DynamicArray { + /** + * Возвращает количество элементов в массиве. + * + * @return количество элементов в массиве + */ + int size(); + + /** + * Проверяет, является ли массив пустым. + * + * @return true, если массив пуст, иначе false + */ + boolean isEmpty(); + + /** + * Проверяет, содержит ли массив указанный элемент. + * + * @param element элемент для проверки + * @return true, если элемент присутствует в массиве, иначе false + */ + boolean contains(int element); + + /** + * Добавляет указанный элемент в конец массива. + * + * @param e элемент для добавления + * @return true, если элемент был успешно добавлен + */ + boolean add(int e); + + /** + * Проверяет, содержит ли массив все элементы указанной коллекции. + * + * @param c коллекция для проверки + * @return true, если массив содержит все элементы коллекции, иначе false + */ + boolean containsAll(DynamicIntArray c); + + /** + * Добавляет все элементы указанной коллекции в конец массива. + * + * @param c коллекция элементов для добавления + * @return true, если массив был изменен как результат операции + */ + boolean addAll(DynamicIntArray c); + + /** + * Вставляет все элементы указанной коллекции в массив, начиная с указанного индекса. + * + * @param index индекс, начиная с которого вставляются элементы + * @param c коллекция элементов для добавления + * @return true, если массив был изменен как результат операции + */ + boolean addAll(int index, DynamicIntArray c); + + /** + * Удаляет из массива все элементы, присутствующие в указанной коллекции. + * + * @param c коллекция элементов для удаления + * @return true, если массив был изменен как результат операции + */ + boolean removeAll(DynamicIntArray c); + + /** + * Оставляет в массиве только те элементы, которые присутствуют в указанной коллекции. + * + * @param c коллекция элементов для оставления + * @return true, если массив был изменен как результат операции + */ + boolean retainAll(DynamicIntArray c); + + /** + * Сортирует элементы массива в естественном порядке. + */ + default void sort() { + // Default implementation can be provided here if needed + } + + /** + * Удаляет все элементы из массива. + */ + void clear(); + + /** + * Возвращает элемент по указанному индексу. + * + * @param index индекс элемента + * @return элемент по указанному индексу + * @throws IndexOutOfBoundsException если индекс выходит за пределы массива + */ + int get(int index); + + /** + * Заменяет элемент по указанному индексу на новый элемент. + * + * @param index индекс элемента для замены + * @param element новый элемент + * @return предыдущий элемент по указанному индексу + * @throws IndexOutOfBoundsException если индекс выходит за пределы массива + */ + int set(int index, int element); + + /** + * Вставляет элемент в массив по указанному индексу. + * + * @param index индекс для вставки элемента + * @param element элемент для вставки + * @throws IndexOutOfBoundsException если индекс выходит за пределы массива + */ + void add(int index, int element); + + /** + * Удаляет элемент по указанному индексу из массива. + * + * @param index индекс элемента для удаления + * @return удаленный элемент + * @throws IndexOutOfBoundsException если индекс выходит за пределы массива + */ + long remove(int index); + + /** + * Возвращает индекс первого вхождения указанного элемента в массиве. + * + * @param element элемент для поиска + * @return индекс первого вхождения элемента, или -1, если элемент не найден + */ + int indexOf(int element); + + /** + * Возвращает индекс последнего вхождения указанного элемента в массиве. + * + * @param element элемент для поиска + * @return индекс последнего вхождения элемента, или -1, если элемент не найден + */ + int lastIndexOf(int element); +} diff --git a/homework2/Task2/DynamicIntArray.java b/homework2/Task2/DynamicIntArray.java new file mode 100644 index 0000000..431869d --- /dev/null +++ b/homework2/Task2/DynamicIntArray.java @@ -0,0 +1,188 @@ +package Task2; + + +import java.util.Arrays; +public class DynamicIntArray implements DynamicArray { + private int size = 0; + private int i; + private int array[] = new int[1]; + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return (size == 0); + } + + @Override + public boolean contains(int element) { + return (indexOf(element) != -1); + } + + @Override + public boolean add(int e) { + if (size == array.length) { + array = Arrays.copyOf(array, array.length * 2); + } + array[size++] = e; + return true; + } + + @Override + public boolean containsAll(DynamicIntArray c) { + for (i = 0; i < size; i++) { + if (!c.contains(c.get(i))) { + return false; + } + } + return true; + } + + @Override + public boolean addAll(DynamicIntArray c) { + for (i = 0; i < c.size(); i++) { + add(c.get(i)); + } + return true; + } + + + + + public boolean addAll(int index, DynamicIntArray c) { + if (index < 0 || index > size) { + throw new IndexOutOfBoundsException("Invalid index: " + index); + } + + int numNewElements = c.size(); + if (size + numNewElements > array.length) { + int[] newArray = new int[array.length + numNewElements]; + System.arraycopy(array, 0, newArray, 0, index); + System.arraycopy(array, index, newArray, index + numNewElements, size - index); + array = newArray; + } else { + System.arraycopy(array, index, array, index + numNewElements, size - index); + } + + for (int i = 0; i < numNewElements; i++) { + array[index + i] = c.get(i); + } + size += numNewElements; + return true; + } + + @Override + public boolean removeAll(DynamicIntArray c) { + boolean modified = false; + for (int i = 0; i < size; i++) { + if (c.contains(array[i])) { + remove(i); + i--; + modified = true; + } + } + return modified; + } + + @Override + public boolean retainAll(DynamicIntArray c) { + boolean modified = false; + for (int i = 0; i < size; i++) { + if (!c.contains(array[i])) { + remove(i); + --i; + modified = true; + } + } + return modified; + } + + @Override + public void sort() { + Arrays.sort(array, 0, size); + } + + @Override + public void clear() { + size = 0; + } + + @Override + public int get(int index) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException("Invalid index: " + index); + } + return array[index]; + } + + @Override + public int set(int index, int element) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException("Invalid index: " + index); + } + int old = array[index]; + array[index] = element; + return old; + } + + @Override + public void add(int index, int element) { + if (index < 0 || index > size) { + throw new IndexOutOfBoundsException("Invalid index: " + index); + } + + if (size == array.length) { + int[] newArray = new int[array.length * 2]; + System.arraycopy(array, 0, newArray, 0, index); + System.arraycopy(array, index, newArray, index + 1, size - index); + array = newArray; + } else { + System.arraycopy(array, index, array, index + 1, size - index); + } + + array[index] = element; + size++; + } + + @Override + public long remove(int index) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException("Invalid index: " + index); + } + + int removedElement = array[index]; + + + for (int i = index; i < size - 1; i++) { + array[i] = array[i + 1]; + } + + size--; + return removedElement; + } + + @Override + public int indexOf(int value) { + for (int i = 0; i < size; i++) { + if (array[i] == value) { + return i; + } + } + return -1; + } + + @Override + public int lastIndexOf(int element) { + for (int i = size - 1; i >= 0; --i) { + if (array[i] == element) { + return i; + } + } + return -1; + } + + +} \ No newline at end of file diff --git a/homework2/Task3/.gitkeep b/homework2/Task3/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/homework2/Task3/.gitkeep @@ -0,0 +1 @@ + diff --git a/homework2/Task3/Connection.java b/homework2/Task3/Connection.java new file mode 100644 index 0000000..4ff1c50 --- /dev/null +++ b/homework2/Task3/Connection.java @@ -0,0 +1,5 @@ +package Task3; + +public interface Connection extends AutoCloseable { + void execute(String command) throws ConnectionException; +} \ No newline at end of file diff --git a/homework2/Task3/ConnectionException.java b/homework2/Task3/ConnectionException.java new file mode 100644 index 0000000..4dae6f2 --- /dev/null +++ b/homework2/Task3/ConnectionException.java @@ -0,0 +1,13 @@ +package Task3; + +public class ConnectionException extends RuntimeException { + public ConnectionException() { + + } + public ConnectionException(String message) { + super(message); + } + public ConnectionException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/homework2/Task3/ConnectionManager.java b/homework2/Task3/ConnectionManager.java new file mode 100644 index 0000000..0d53afd --- /dev/null +++ b/homework2/Task3/ConnectionManager.java @@ -0,0 +1,5 @@ +package Task3; + +public interface ConnectionManager { + Connection getConnection(); +} diff --git a/homework2/Task3/DefaultConnectionManager.java b/homework2/Task3/DefaultConnectionManager.java new file mode 100644 index 0000000..7543637 --- /dev/null +++ b/homework2/Task3/DefaultConnectionManager.java @@ -0,0 +1,12 @@ +package Task3; + +public class DefaultConnectionManager implements ConnectionManager { + @Override + public Connection getConnection() { + if (Math.random() < 0.5) { + return new StableConnection(); + } else { + return new FaultyConnection(); + } + } +} \ No newline at end of file diff --git a/homework2/Task3/FaultyConnection.java b/homework2/Task3/FaultyConnection.java new file mode 100644 index 0000000..b75905d --- /dev/null +++ b/homework2/Task3/FaultyConnection.java @@ -0,0 +1,16 @@ +package Task3; + +public class FaultyConnection implements Connection { + @Override + public void execute(String command) { + if (Math.random() < 0.5) { + throw new ConnectionException("FaultyConnection failed to execute command."); + } + System.out.println("Executing command: " + command); + } + + @Override + public void close() { + System.out.println("FaultyConnection closed."); + } +} \ No newline at end of file diff --git a/homework2/Task3/FaultyConnectionManager.java b/homework2/Task3/FaultyConnectionManager.java new file mode 100644 index 0000000..cefb843 --- /dev/null +++ b/homework2/Task3/FaultyConnectionManager.java @@ -0,0 +1,8 @@ +package Task3; + +public class FaultyConnectionManager implements ConnectionManager { + @Override + public Connection getConnection() { + return new FaultyConnection(); + } +} \ No newline at end of file diff --git a/homework2/Task3/PopularCommandExecutor.java b/homework2/Task3/PopularCommandExecutor.java new file mode 100644 index 0000000..d2882de --- /dev/null +++ b/homework2/Task3/PopularCommandExecutor.java @@ -0,0 +1,36 @@ +package Task3; + +public final class PopularCommandExecutor { + private final ConnectionManager manager; + private final int maxAttempts; + + public PopularCommandExecutor(ConnectionManager manager, int maxAttempts) { + this.manager = manager; + this.maxAttempts = maxAttempts; + } + + public void updatePackages() { + tryExecute("apt update && apt upgrade -y"); + } + + void tryExecute(String command) { + ConnectionException lastException = null; + + for (int attempt = 1; attempt <= maxAttempts; attempt++) { + try (Connection connection = manager.getConnection()) { + connection.execute(command); + return; // Успешное выполнение команды + } catch (ConnectionException e) { + lastException = e; + System.out.println("Attempt " + attempt + " failed: " + e.getMessage()); + } catch (Exception e) { + lastException = new ConnectionException("Unexpected error", e); + System.out.println("Attempt " + attempt + " failed: " + e.getMessage()); + } + } + + if (lastException != null) { + throw new ConnectionException("Failed to execute command after " + maxAttempts + " attempts", lastException); + } + } +} \ No newline at end of file diff --git a/homework2/Task3/StableConnection.java b/homework2/Task3/StableConnection.java new file mode 100644 index 0000000..11c2b72 --- /dev/null +++ b/homework2/Task3/StableConnection.java @@ -0,0 +1,13 @@ +package Task3; + +public class StableConnection implements Connection { + @Override + public void execute(String command) { + System.out.println("Executing command: " + command); + } + + @Override + public void close() { + System.out.println("StableConnection closed."); + } +} \ No newline at end of file diff --git a/homework2/Task4/.gitkeep b/homework2/Task4/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/homework2/Task4/.gitkeep @@ -0,0 +1 @@ + diff --git a/homework2/Task4/CallingInfo.java b/homework2/Task4/CallingInfo.java new file mode 100644 index 0000000..5e016de --- /dev/null +++ b/homework2/Task4/CallingInfo.java @@ -0,0 +1,3 @@ +package Task4; + +public record CallingInfo(String className, String methodName) {} \ No newline at end of file diff --git a/homework2/Task4/CallingInfoMain.java b/homework2/Task4/CallingInfoMain.java new file mode 100644 index 0000000..b7adb4d --- /dev/null +++ b/homework2/Task4/CallingInfoMain.java @@ -0,0 +1,20 @@ +package Task4; + +public class CallingInfoMain { + + public static CallingInfo callingInfo() { + Throwable throwable = new Throwable(); + StackTraceElement[] stackTrace = throwable.getStackTrace(); + StackTraceElement caller = stackTrace[1]; + String className = caller.getClassName(); + String methodName = caller.getMethodName(); + return new CallingInfo(className, methodName); + } + + + public static void main(String[] args) { + CallingInfo info = callingInfo(); + System.out.println("Class: " + info.className()); + System.out.println("Method: " + info.methodName()); + } +} \ No newline at end of file diff --git a/homework5/src/Task1/Atomic_Counter.java b/homework5/src/Task1/Atomic_Counter.java new file mode 100644 index 0000000..942cc3d --- /dev/null +++ b/homework5/src/Task1/Atomic_Counter.java @@ -0,0 +1,14 @@ +package Task1; +import java.util.concurrent.atomic.AtomicInteger; + +public class Atomic_Counter { + private final AtomicInteger count = new AtomicInteger(0); + public void increment() { + count.getAndIncrement(); + } + + + public int getCount() { + return count.get(); + } +} diff --git a/homework5/src/Task1/ReetnrantLock_Counter.java b/homework5/src/Task1/ReetnrantLock_Counter.java new file mode 100644 index 0000000..228ea92 --- /dev/null +++ b/homework5/src/Task1/ReetnrantLock_Counter.java @@ -0,0 +1,19 @@ +package Task1; +import java.util.concurrent.locks.ReentrantLock; + +public class ReetnrantLock_Counter { + private int count = 0; + private final ReentrantLock lock = new ReentrantLock(); + public void increment() { + lock.lock(); + try { + count++; + } finally { + lock.unlock(); + } + } + + public int getCount() { + return count; + } +} diff --git a/homework5/src/Task1/Semaphore_Counter.java b/homework5/src/Task1/Semaphore_Counter.java new file mode 100644 index 0000000..2d16fcc --- /dev/null +++ b/homework5/src/Task1/Semaphore_Counter.java @@ -0,0 +1,21 @@ +package Task1; +import java.util.concurrent.Semaphore; + +public class Semaphore_Counter { + private int count = 0; + private final Semaphore semaphore = new Semaphore(1); // Используем стандартный Semaphore + + public void increment() { + try { + semaphore.acquire(); // Метод acquire() из java.util.concurrent.Semaphore + count++; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + semaphore.release(); // Метод release() из java.util.concurrent.Semaphore + } + } + + public int getCount() { + return count; + } diff --git a/homework5/src/Task1/Synchronized_Counter.java b/homework5/src/Task1/Synchronized_Counter.java new file mode 100644 index 0000000..c15c93d --- /dev/null +++ b/homework5/src/Task1/Synchronized_Counter.java @@ -0,0 +1,13 @@ +package Task1; + + +public class Synchronized_Counter { + private int count = 0; + public synchronized void increment() { + count++; + } + + public int getCount() { + return count; + } +} diff --git a/homework5/src/Task1/Tests.java b/homework5/src/Task1/Tests.java new file mode 100644 index 0000000..38ee651 --- /dev/null +++ b/homework5/src/Task1/Tests.java @@ -0,0 +1,68 @@ +package Task1; +import org.junit.Test; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import static org.junit.Assert.assertEquals; + +public class Tests { + private static final int THREADS = 1000; + private static final int INCREMENTS_PER_THREAD = 1000; + private static final int EXPECTED_TOTAL = THREADS * INCREMENTS_PER_THREAD; + + @Test + public void testSynchronizedCounter() throws InterruptedException { + Synchronized_Counter counter = new Synchronized_Counter(); + runConcurrentTest(counter); + assertEquals(EXPECTED_TOTAL, counter.getCount()); + } + + + + @Test + public void testAtomicCounter() throws InterruptedException { + Atomic_Counter counter = new Atomic_Counter(); + runConcurrentTest(counter); + assertEquals(EXPECTED_TOTAL, counter.getCount()); + } + + + + @Test + public void testReentrantLockCounter() throws InterruptedException { + ReentrantLock_Counter counter = new ReentrantLock_Counter(); + runConcurrentTest(counter); + assertEquals(EXPECTED_TOTAL, counter.getCount()); + } + + + + @Test + public void testSemaphoreCounter() throws InterruptedException { + Semaphore_Counter counter = new Semaphore_Counter(); + runConcurrentTest(counter); + assertEquals(EXPECTED_TOTAL, counter.getCount()); + } + + + + private void runConcurrentTest(RunnableCounter counter) throws InterruptedException { + ExecutorService executor = Executors.newFixedThreadPool(THREADS); + for (int i = 0; i < THREADS; i++) { + executor.submit(() -> { + for (int j = 0; j < INCREMENTS_PER_THREAD; j++) { + counter.increment(); + } + }); + } + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.MINUTES); + } + + + + private interface RunnableCounter { + void increment(); + int getCount(); + } +} \ No newline at end of file diff --git a/homework5/src/Task2/FactorialCalculator.java b/homework5/src/Task2/FactorialCalculator.java new file mode 100644 index 0000000..f75484c --- /dev/null +++ b/homework5/src/Task2/FactorialCalculator.java @@ -0,0 +1,56 @@ +package Task2; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.*; + +public class FactorialCalculator { + + public static BigInteger computeFactorial(int n, int numThreads) throws InterruptedException, ExecutionException { + if (n < 0) { + throw new IllegalArgumentException("n must be non-negative"); + } + if (n == 0 || n == 1) { + return BigInteger.ONE; + } + + ExecutorService executor = Executors.newFixedThreadPool(numThreads); + List> futures = new ArrayList<>(); + + int chunkSize = n / numThreads; + int start = 1; + int end; + + for (int i = 0; i < numThreads; i++) { + end = (i == numThreads - 1) ? n : start + chunkSize - 1; + futures.add(executor.submit(new FactorialTask(start, end))); + start = end + 1; + } + BigInteger result = BigInteger.ONE; + for (Future future : futures) { + result = result.multiply(future.get()); + } + + executor.shutdown(); + return result; + } + + private static class FactorialTask implements Callable { + private final int start; + private final int end; + + FactorialTask(int start, int end) { + this.start = start; + this.end = end; + } + + @Override + public BigInteger call() { + BigInteger product = BigInteger.ONE; + for (int i = start; i <= end; i++) { + product = product.multiply(BigInteger.valueOf(i)); + } + return product; + } + } +} \ No newline at end of file diff --git a/homework5/src/Task2/FactorialCalculatorTests.java b/homework5/src/Task2/FactorialCalculatorTests.java new file mode 100644 index 0000000..469eb90 --- /dev/null +++ b/homework5/src/Task2/FactorialCalculatorTests.java @@ -0,0 +1,27 @@ +package Task2; +import org.junit.Test; +import java.math.BigInteger; +import static org.junit.Assert.assertEquals; + +public class FactorialCalculatorTest { + + @Test + public void testComputeFactorial() throws Exception { + assertEquals(BigInteger.valueOf(120), FactorialCalculator.computeFactorial(5, 2)); + assertEquals(BigInteger.valueOf(3628800), FactorialCalculator.computeFactorial(10, 4)); + } + + + + @Test(expected = IllegalArgumentException.class) + public void testNegativeInput() throws Exception { + FactorialCalculator.computeFactorial(-1, 2); + } + + + @Test + public void testEdgeCases() throws Exception { + assertEquals(BigInteger.ONE, FactorialCalculator.computeFactorial(0, 1)); + assertEquals(BigInteger.ONE, FactorialCalculator.computeFactorial(1, 3)); + } +} \ No newline at end of file diff --git a/homework5/src/Task3/CachingPersonDatabase.java b/homework5/src/Task3/CachingPersonDatabase.java new file mode 100644 index 0000000..d517a6e --- /dev/null +++ b/homework5/src/Task3/CachingPersonDatabase.java @@ -0,0 +1,65 @@ +package Task3; + +import java.util.*; + +public class CachingPersonDatabase implements PersonDatabase { + private final Map idIndex = new HashMap<>(); + private final Map> nameIndex = new HashMap<>(); + private final Map> addressIndex = new HashMap<>(); + private final Map> phoneIndex = new HashMap<>(); + + @Override + public synchronized void add(Person person) { + if (idIndex.containsKey(person.id())) { + return; + } + idIndex.put(person.id(), person); + addToIndex(nameIndex, person.name(), person); + addToIndex(addressIndex, person.address(), person); + addToIndex(phoneIndex, person.phoneNumber(), person); + } + + @Override + public synchronized void delete(int id) { + Person person = idIndex.remove(id); + if (person == null) { + return; + } + removeFromIndex(nameIndex, person.name(), person); + removeFromIndex(addressIndex, person.address(), person); + removeFromIndex(phoneIndex, person.phoneNumber(), person); + } + + @Override + public List findByName(String name) { + return getFromIndex(nameIndex, name); + } + + @Override + public List findByAddress(String address) { + return getFromIndex(addressIndex, address); + } + + @Override + public List findByPhone(String phone) { + return getFromIndex(phoneIndex, phone); + } + private synchronized void addToIndex(Map> index, String key, Person person) { + index.computeIfAbsent(key, k -> new ArrayList<>()).add(person); + } + + private synchronized void removeFromIndex(Map> index, String key, Person person) { + List list = index.get(key); + if (list != null) { + list.remove(person); + if (list.isEmpty()) { + index.remove(key); + } + } + } + + private synchronized List getFromIndex(Map> index, String key) { + List result = index.getOrDefault(key, Collections.emptyList()); + return new ArrayList<>(result); + } +} \ No newline at end of file diff --git a/homework5/src/Task3/CachingPersonDatabaseTests.java b/homework5/src/Task3/CachingPersonDatabaseTests.java new file mode 100644 index 0000000..1b49cd4 --- /dev/null +++ b/homework5/src/Task3/CachingPersonDatabaseTests.java @@ -0,0 +1,27 @@ +package Task3; +import org.junit.Test; +import static org.junit.Assert.*; +import java.util.List; + +public class CachingPersonDatabaseTest { + @Test + public void testAddAndFind() { + PersonDatabase db = new CachingPersonDatabase(); + Person person = new Person(1, "Alice", "Paris", "12345"); + db.add(person); + assertEquals(List.of(person), db.findByName("Alice")); + assertEquals(List.of(person), db.findByAddress("Paris")); + assertEquals(List.of(person), db.findByPhone("12345")); + } + + @Test + public void testDelete() { + PersonDatabase db = new CachingPersonDatabase(); + Person person = new Person(1, "Bob", "London", "67890"); + db.add(person); + db.delete(1); + assertTrue(db.findByName("Bob").isEmpty()); + assertTrue(db.findByAddress("London").isEmpty()); + assertTrue(db.findByPhone("67890").isEmpty()); + } +} \ No newline at end of file diff --git a/homework5/src/Task3/Person.java b/homework5/src/Task3/Person.java new file mode 100644 index 0000000..ad3f7c7 --- /dev/null +++ b/homework5/src/Task3/Person.java @@ -0,0 +1,14 @@ +package Task3; + +import java.util.List; + +public record Person(int id, String name, String address, String phoneNumber) {} + +interface PersonDatabase { + void add(Person person); + void delete(int id); + + List findByName(String name); + List findByAddress(String address); + List findByPhone(String phone); +} \ No newline at end of file diff --git a/homework5/src/Task3_5/CachingPersonDatabaseWithReadWriteLock.java b/homework5/src/Task3_5/CachingPersonDatabaseWithReadWriteLock.java new file mode 100644 index 0000000..ce8919c --- /dev/null +++ b/homework5/src/Task3_5/CachingPersonDatabaseWithReadWriteLock.java @@ -0,0 +1,91 @@ +package Task3_5; + +import java.util.*; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class CachingPersonDatabaseWithReadWriteLock implements PersonDatabase { + private final Map idIndex = new HashMap<>(); + private final Map> nameIndex = new HashMap<>(); + private final Map> addressIndex = new HashMap<>(); + private final Map> phoneIndex = new HashMap<>(); + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + @Override + public void add(Person person) { + lock.writeLock().lock(); + try { + if (idIndex.containsKey(person.id())) return; + idIndex.put(person.id(), person); + addToIndex(nameIndex, person.name(), person); + addToIndex(addressIndex, person.address(), person); + addToIndex(phoneIndex, person.phoneNumber(), person); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public void delete(int id) { + lock.writeLock().lock(); + try { + Person person = idIndex.remove(id); + if (person == null) return; + + removeFromIndex(nameIndex, person.name(), person); + removeFromIndex(addressIndex, person.address(), person); + removeFromIndex(phoneIndex, person.phoneNumber(), person); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public List findByName(String name) { + lock.readLock().lock(); + try { + return new ArrayList<>(getFromIndex(nameIndex, name)); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public List findByAddress(String address) { + lock.readLock().lock(); + try { + return new ArrayList<>(getFromIndex(addressIndex, address)); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public List findByPhone(String phone) { + lock.readLock().lock(); + try { + return new ArrayList<>(getFromIndex(phoneIndex, phone)); + } finally { + lock.readLock().unlock(); + } + } + + + + private void addToIndex(Map> index, String key, Person person) { + index.computeIfAbsent(key, k -> new ArrayList<>()).add(person); + } + + private void removeFromIndex(Map> index, String key, Person person) { + List list = index.get(key); + if (list != null) { + list.remove(person); + if (list.isEmpty()) index.remove(key); + } + } + + private List getFromIndex(Map> index, String key) { + return index.getOrDefault(key, Collections.emptyList()); + } + +} \ No newline at end of file diff --git a/homework5/src/Task3_5/Person.java b/homework5/src/Task3_5/Person.java new file mode 100644 index 0000000..926ab6a --- /dev/null +++ b/homework5/src/Task3_5/Person.java @@ -0,0 +1,13 @@ +package Task3_5; +import java.util.List; + +public record Person(int id, String name, String address, String phoneNumber) {} + +interface PersonDatabase { + void add(Task3_5.Person person); + void delete(int id); + + List findByName(String name); + List findByAddress(String address); + List findByPhone(String phone); +} \ No newline at end of file diff --git a/homework5/src/Task4/MonteCarloPiMultiThread.java b/homework5/src/Task4/MonteCarloPiMultiThread.java new file mode 100644 index 0000000..bb97f4b --- /dev/null +++ b/homework5/src/Task4/MonteCarloPiMultiThread.java @@ -0,0 +1,31 @@ +package Task4; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicLong; + +public class MonteCarloPiMultiThread { + public static double calculatePi(long iterations, int threads) throws InterruptedException { + AtomicLong circleCount = new AtomicLong(0); + ExecutorService executor = Executors.newFixedThreadPool(threads); + long perThread = iterations / threads; + + for (int i = 0; i < threads; i++) { + executor.submit(() -> { + long localCount = 0; + ThreadLocalRandom random = ThreadLocalRandom.current(); + for (long j = 0; j < perThread; j++) { + double x = random.nextDouble(); + double y = random.nextDouble(); + if (x * x + y * y <= 1.0) { + localCount++; + } + } + circleCount.addAndGet(localCount); + }); + } + + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.HOURS); + return 4.0 * circleCount.get() / iterations; + } +} \ No newline at end of file diff --git a/homework5/src/Task4/MonteCarloPiSingleThread.java b/homework5/src/Task4/MonteCarloPiSingleThread.java new file mode 100644 index 0000000..6c62394 --- /dev/null +++ b/homework5/src/Task4/MonteCarloPiSingleThread.java @@ -0,0 +1,16 @@ +package Task4; +import java.util.concurrent.ThreadLocalRandom; + +public class MonteCarloPiSingleThread { + public static double calculatePi(long iterations) { + long circleCount = 0; + for (long i = 0; i < iterations; i++) { + double x = ThreadLocalRandom.current().nextDouble(); + double y = ThreadLocalRandom.current().nextDouble(); + if (x * x + y * y <= 1.0) { + circleCount++; + } + } + return 4.0 * circleCount / iterations; + } +} \ No newline at end of file diff --git a/homework5/src/Task4/PiBenchmark.java b/homework5/src/Task4/PiBenchmark.java new file mode 100644 index 0000000..6598a84 --- /dev/null +++ b/homework5/src/Task4/PiBenchmark.java @@ -0,0 +1,26 @@ +package Task4; + +public class PiBenchmark { + public static void main(String[] args) throws InterruptedException { + int[] iterations = {10_000_000, 100_000_000, 1_000_000_000}; + int threads = Runtime.getRuntime().availableProcessors(); + + for (long n : iterations) { + long start = System.nanoTime(); + double piSingle = MonteCarloPiSingleThread.calculatePi(n); + long singleTime = System.nanoTime() - start; + + + start = System.nanoTime(); + double piMulti = MonteCarloPiMultiThread.calculatePi(n, threads); + long multiTime = System.nanoTime() - start; + + System.out.printf("Итераций: %,d%n", n); + System.out.printf("Однопоточное время: %.3f сек%n", singleTime / 1e9); + System.out.printf("Многопоточное время: %.3f сек%n", multiTime / 1e9); + System.out.printf("Ускорение: x%.2f%n", (double) singleTime / multiTime); + System.out.printf("Погрешность (single): %.6f%n", Math.abs(piSingle - Math.PI)); + System.out.printf("Погрешность (multi): %.6f%n%n", Math.abs(piMulti - Math.PI)); + } + } +} \ No newline at end of file diff --git a/homework5/src/Task5_with_deadlock/Account.java b/homework5/src/Task5_with_deadlock/Account.java new file mode 100644 index 0000000..1373a86 --- /dev/null +++ b/homework5/src/Task5_with_deadlock/Account.java @@ -0,0 +1,24 @@ +package Task5_with_deadlock; + +public class Account { + private int cashBalance; + + public Account(int cashBalance) { + this.cashBalance = cashBalance; + } + + public void addMoney(int money) { + this.cashBalance += money; + } + + public boolean takeOffMoney(int money) { + if (this.cashBalance < money) return false; + this.cashBalance -= money; + return true; + } + + public int getCashBalance() { + return cashBalance; + } +} + diff --git a/homework5/src/Task5_with_deadlock/AccountMain.java b/homework5/src/Task5_with_deadlock/AccountMain.java new file mode 100644 index 0000000..40f5eee --- /dev/null +++ b/homework5/src/Task5_with_deadlock/AccountMain.java @@ -0,0 +1,24 @@ +package Task5_with_deadlock; + +public class AccountMain { + public static void main(String[] args) { + Account firstAccount = new Account(100_000); + Account secondAccount = new Account(100_000); + + Thread firstThread = new Thread(new AccountThread(firstAccount, secondAccount, 100)); + Thread secondThread = new Thread(new AccountThread(secondAccount, firstAccount, 100)); + + firstThread.start(); + secondThread.start(); + + try { + firstThread.join(); + secondThread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + System.out.println("First account balance: " + firstAccount.getCashBalance()); + System.out.println("Second account balance: " + secondAccount.getCashBalance()); + } +} diff --git a/homework5/src/Task5_with_deadlock/AccountThread.java b/homework5/src/Task5_with_deadlock/AccountThread.java new file mode 100644 index 0000000..80ec259 --- /dev/null +++ b/homework5/src/Task5_with_deadlock/AccountThread.java @@ -0,0 +1,26 @@ +package Task5_with_deadlock; + +public class AccountThread implements Runnable { + private final Account accountFrom; + private final Account accountTo; + private final int money; + + public AccountThread(Account accountFrom, Account accountTo, int money) { + this.accountFrom = accountFrom; + this.accountTo = accountTo; + this.money = money; + } + + @Override + public void run() { + for (int i = 0; i < 4000; i++) { + synchronized (accountFrom) { + synchronized (accountTo) { + if (accountFrom.takeOffMoney(money)) { + accountTo.addMoney(money); + } + } + } + } + } +} diff --git a/homework5/src/Task5_without_deadlock/Account.java b/homework5/src/Task5_without_deadlock/Account.java new file mode 100644 index 0000000..20f1dec --- /dev/null +++ b/homework5/src/Task5_without_deadlock/Account.java @@ -0,0 +1,23 @@ +package Task5_without_deadlock; +public class Account { + private int cashBalance; + + public Account(int cashBalance) { + this.cashBalance = cashBalance; + } + + public void addMoney(int money) { + this.cashBalance += money; + } + + public boolean takeOffMoney(int money) { + if (this.cashBalance < money) return false; + this.cashBalance -= money; + return true; + } + + public int getCashBalance() { + return cashBalance; + } +} + diff --git a/homework5/src/Task5_without_deadlock/AccountMain.java b/homework5/src/Task5_without_deadlock/AccountMain.java new file mode 100644 index 0000000..90aea79 --- /dev/null +++ b/homework5/src/Task5_without_deadlock/AccountMain.java @@ -0,0 +1,24 @@ +package Task5_without_deadlock; + +public class AccountMain { + public static void main(String[] args) { + Account firstAccount = new Account(100_000); + Account secondAccount = new Account(100_000); + + Thread firstThread = new Thread(new AccountThread(firstAccount, secondAccount, 100)); + Thread secondThread = new Thread(new AccountThread(secondAccount, firstAccount, 100)); + + firstThread.start(); + secondThread.start(); + + try { + firstThread.join(); + secondThread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + System.out.println("First account balance: " + firstAccount.getCashBalance()); + System.out.println("Second account balance: " + secondAccount.getCashBalance()); + } +} \ No newline at end of file diff --git a/homework5/src/Task5_without_deadlock/AccountThread.java b/homework5/src/Task5_without_deadlock/AccountThread.java new file mode 100644 index 0000000..14ba204 --- /dev/null +++ b/homework5/src/Task5_without_deadlock/AccountThread.java @@ -0,0 +1,29 @@ +package Task5_without_deadlock; + +public class AccountThread implements Runnable { + private final Account accountFrom; + private final Account accountTo; + private final int money; + + public AccountThread(Account accountFrom, Account accountTo, int money) { + this.accountFrom = accountFrom; + this.accountTo = accountTo; + this.money = money; + } + + @Override + public void run() { + for (int i = 0; i < 4000; i++) { + Account first = (accountFrom.hashCode() < accountTo.hashCode()) ? accountFrom : accountTo; + Account second = (accountFrom.hashCode() < accountTo.hashCode()) ? accountTo : accountFrom; + + synchronized (first) { + synchronized (second) { + if (accountFrom.takeOffMoney(money)) { + accountTo.addMoney(money); + } + } + } + } + } +} \ No newline at end of file diff --git a/news-aggregator/README.md b/news-aggregator/README.md new file mode 100644 index 0000000..d905672 --- /dev/null +++ b/news-aggregator/README.md @@ -0,0 +1,52 @@ +# News Aggregator + Java консольное приложение для агрегации новостных статей из различных источников с использованием RestAPI. + +## Возможности + +- Настройка источников новостей (задание http-адресов и имён полей) +- Получение новостей с помощью RestAPI +- Сохранение статей в базу данных PostreSQL +- Просмотр новостей с возможностью фильтрации +- Консольный интерфейс пользователя +- Автоматическое получение и отображение новостей в отдельном потоке + +## Необходимые компоненты + +- Java 21 или новее +- Maven +- PostreSQL + + +## Использование + +Приложение предоставляет простой консольный интерфейс со следующими опциями: + + 0. Выход + 1. Настройка: Сколько отображать новостей + 2. Настройка: Сортировать по дате + 3. Настройка: Сортировать по источнику + 5. Получение свежих новостей: Собрать новости + 6. Показать последние новости + 7. Показать последние новости (со всеми информационными полями) + 8. Экспорт в CSV файл +10. Поиск по слову (везде) +11. Поиск только в заголовке +12. Поиск только в тексте +13. Поиск по дате +14. Поиск по категории +15. Поиск по источнику +16. Отображение Списка источников +17. Добавление заранее настроенных источников RBC и Lenta +18. Добавить свой источник +19. Удалить один из существующих источников +20. Аналитика и статистика по количеству новостей в разных категориях + + +## База данных + +Приложение использует PostreSQL для хранения новостных статей. Таблицы базы данных будут созданы автоматически при первом запуске приложения. + +## Зависимости + +- PostreSQL JDBC - База данных +- JUnit & Mockito - тестирование \ No newline at end of file diff --git a/news-aggregator/docker-compose.yml b/news-aggregator/docker-compose.yml new file mode 100644 index 0000000..dbd3b9c --- /dev/null +++ b/news-aggregator/docker-compose.yml @@ -0,0 +1,34 @@ +version: '3.8' +name: postgres + +services: + postgres: + image: postgres:15-alpine + container_name: postgres + environment: + - POSTGRES_USER=todo_user + - POSTGRES_PASSWORD=todo_pass + - POSTGRES_DB=tododb + - POSTGRES_HOST_AUTH_METHOD=trust + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U todo_user -d tododb"] + interval: 5s + timeout: 5s + retries: 5 + restart: unless-stopped + + init_user_postgres: + image: postgres:15-alpine + depends_on: + postgres: + condition: service_healthy + restart: "no" + command: > + bash -c "PGPASSWORD=todo_pass psql -h postgres -U todo_user -d tododb -c 'ALTER ROLE todo_user SUPERUSER'" + +volumes: + postgres_data: diff --git a/news-aggregator/export.csv b/news-aggregator/export.csv new file mode 100644 index 0000000..c5f5235 --- /dev/null +++ b/news-aggregator/export.csv @@ -0,0 +1,104 @@ +title, date,summary, url, keywords, video_or_foto_url, category, source, source_id +"В администрации Трампа прошли совещания после конфликта с Маском","2025-06-06 03:01:00.0","Дональд Трамп Фото: Brendan McDermid / Reuters Юлия Мискевич Сотрудники администрации президента США Дональда Трампа провели ряд экстренных совещаний по поводу публичного конфликта президента США с миллиардером Илоном Маском. Об этом сообщает Reuters. Как уточняют источники, на переговорах обсуждали последствия скандальных заявлений Маска в соцсети Х. Между главой Белого дома Дональдом Трампом и американским предпринимателем Илоном Маском произошел конфликт. В конце мая стало известно, что миллиардер принял решение покинуть свой пост в администрации президента, где он занимал должность главы Департамента государственной эффективности США (DOGE). Однако спустя несколько дней Маск резко раскритиковал законопроект Трампа о сокращении федеральных расходов, который тот назвал «единым, большим и красивым».","https://lenta.ru/news/2025/06/06/v-administratsii-trampa-proshli-soveschaniya-posle-konflikta-s-maskom/","администрации, трампа, маском, президента, reuters","https://icdn.lenta.ru/images/2025/06/06/03/20250606030322747/detail_38c62d8d2bbe0ec5449185c8ae7628f8.jpg","Политика","Lenta.ru","1848215" +"В России рассказали о возможном ответе на теракты Украины","2025-06-06 02:35:00.0","Фото: РИА Новости / РИА Новости Евгений Силаев Вооруженные силы (ВС) России в ответ на недавние теракты Украины могут нанести комбинированный удар по военным объектам и предприятиям военно-промышленного комплекса беспилотными летательными аппаратами (БПЛА), а также баллистическими ракетами типа «Искандер» и «Кинжал». О возможном ответе рассказал главный редактор журнала «Национальная оборона» Игорь Коротченко в беседе с РИА Новости. По его словам, при планировании удара возмездия будет учитываться возможность использования Украиной западных систем противовоздушной обороны (ПВО). «По заранее назначенным объектам на Украине могут быть применены как баллистические ракеты, включая, очевидно, комплексы 'Искандер-М' и 'Кинжал', а также, возможно, и иные ракетные комплексы, крылатые ракеты морского и воздушного базирования, при наличии, повторюсь, соответствующего политического решения», — подчеркнул Коротченко. Он уточнил, что целями ударов могут стать объекты железнодорожной, морской и иной логистики, используемые для поставки и транспортировки вооружения, а также места сборки вооружения и военной техники. Ранее украинские паблики сообщили о взлете стратегических бомбардировщиков в России. Также появились сведения о выходе в Черное море носителей ракет «Калибр». Однако официально информация не подтверждалась.","https://lenta.ru/news/2025/06/06/v-rossii-rasskazali-o-vozmozhnom-otvete-na-terakty-ukrainy/","также, россии, новости, могут, вооружения","https://icdn.lenta.ru/images/2025/06/06/02/20250606023605156/detail_6157c843f9db461ed5da413559801025.jpg","Спорт","Lenta.ru","1848209" +"Бывшего председателя Нижегородской гордумы объявили в розыск","2025-06-06 02:18:00.0","Фото: Freepik Юлия Мискевич Председатель городской думы Нижнего Новгорода в 2018-2020 годах Дмитрий Барыкин объявлен в розыск по уголовной статье. Об этом сообщает ТАСС со ссылкой на МВД России. Название статьи не уточняется. До этого в Москве заочно осудили главу «Бюро 17» Александрину Маркво за отмывание бюджетных денег. Ранее суд Донецкой народной республики (ДНР) утвердил обвинительное заключение по уголовному делу 33-летнего гражданина Великобритании Гэри Бонини.","https://lenta.ru/news/2025/06/06/eks-predsedatelya-nizhegorodskoy-gordumy-ob-yavili-v-rozysk/","розыск, юлия, бюро, дмитрий, гэри","https://icdn.lenta.ru/images/2025/06/06/02/20250606022828460/detail_24227d9cd28315635ca15384afb0fcc4.jpg","Общее","Lenta.ru","1848207" +"В Ставропольском крае подготовили партию дронов с повышенной грузоподъемностью","2025-06-06 02:02:02.0","Фото: Пресс-служба Южного военного округа Даниил Иринин Волонтеры и конструкторы из Кочубеевского района Ставропольского края подготовили новую партию FPV-дронов с повышенной грузоподъемностью для участников специальной военной операции (СВО). Об этом сообщили в Министерстве обороны (МО) России. Отмечается, что благодаря усиленной раме квадрокоптеры могут нести груз весом до четырех килограммов, а дальность полета составляет 28 километров. Часть деталей для дронов изготавливают на 3D-принтерах. «Перед отгрузкой FPV-дроны проходят этапы установки необходимого программного обеспечения и проверки всех систем. После завершения изготовления, партии направляются в подразделения Южного военного округа (ЮВО) в зону проведения СВО для боевого применения», — говорится в сообщении. Ранее в июне стало известно, что десантники группировки войск «Днепр» Вооруженных сил России в зоне СВО начали применять многоцелевые FPV-дроны «Заноза», способные доставлять боеприпасы, провиант или наносить удары.","https://lenta.ru/news/2025/06/06/v-stavropolskom-krae-podgotovili-partiyu-dronov-s-povyshennoy-gruzopod-emnostyu/","дронов, партию, южного, военного, россии","https://icdn.lenta.ru/images/2025/06/05/14/20250605142616529/detail_66a08eb1651f71482a2f399750ae93fb.jpg","СВО","Lenta.ru","1847844" +"Маск сделал репост видео с Трампом на вечеринке Эпштейна","2025-06-06 02:02:00.0","Илон Маск Фото: Nathan Howard / Reuters Марина Совина Американский предприниматель Илон Маск сделал репост видео с президентом США Дональдом Трампом на вечеринке миллиардера Джеффри Эпштейна. Видео опубликовано на странице бизнесмена в социальной сети X. Маск опубликовал публикацию другого пользователя с фрагментом репортажа телеканала MSNBC. На кадрах видно, как Трамп проводит время на вечеринке Эпштейна, которая прошла в 1992 году. Ранее предприниматель заявил, что Трамп фигурирует в деле против Эпштейна. По его словам, по этой причине файлы по делу публикуют не полностью. Также Маск поддержал идею об объявлении импичмента Трампу и передаче власти вице-президенту Джей-Ди Вэнсу. До этого миллиардер подверг резкой критике «большой, красивый» законопроект Трампа, назвав его «отвратительной мерзостью».","https://lenta.ru/news/2025/06/06/mask-sdelal-repost-video-s-trampom-na-vecherinke-epshteyna/","маск, эпштейна, видео, вечеринке, предприниматель","https://icdn.lenta.ru/images/2025/06/06/02/20250606021046282/detail_4f9f1fb9dabea29fc58dd324781c42c4.jpg","Политика","Lenta.ru","1848205" +"Учительницу арестовали за секс с 15-летним","2025-06-06 02:01:48.0","Кадр: ABC News Никита Савин В Австралии арестовали учительницу из штата Новый Южный Уэльс, которая совратила 15-летнего ученика. Об этом сообщает ABC News. За решеткой оказалась Карли Рэй, которая преподавала в школе города Лейк-Маккуори. По данным следствия, женщина некоторое время переписывалась со школьником в социальных сетях, а в октябре 2024 года занялась с ним сексом. Помимо изнасилования, учительницу обвинили в жестоком обращении с детьми, хранении материалов о насилии над детьми и подстрекательстве ребенка к незаконным сексуальным действиям. В среду, 4 июня, Рэй отказалась признать себя виновной в суде. Она также отвергла обвинения в попытке повлиять на ход следствия. Следующее заседание по ее делу назначено на 3 июля. Ранее в США арестовали учительницу из штата Канзас, которая напоила алкоголем и совратила двоих учеников. Женщина также пыталась заняться сексом с третьим пострадавшим. ","https://lenta.ru/news/2025/06/06/uchitelnitsu-arestovali-za-seks-s-15-letnim/","учительницу, которая, арестовали, детьми, совратила","https://icdn.lenta.ru/images/2025/06/05/13/20250605133820875/detail_d31b4b87676e5faa99b0fe9a94ec508b.jpg","Общее","Lenta.ru","1847771" +"Врач предупредила о последствиях позднего начала половой жизни","2025-06-06 02:01:36.0","Фото: Unsplash Александра Лисица Позднее начало половой жизни может иметь как позитивные, так и негативные последствия для здоровья, предупредила репродуктолог Альфия Сугурова. Такое мнение она выразила в беседе с изданием Ufacitynews. Позднее начало половой жизни, по словам Сугуровой, может привести к чувству неуверенности или неполноценности, нарушению социальных связей, особенно если человек испытывает давление со стороны сверстников. Однако для некоторых это может быть положительным опытом, так как они могут чувствовать себя более готовыми и уверенными, считает врач. Кроме того, при позднем начале сексуальной жизни человек может устанавливать более сильные эмоциональные связи и иметь меньший риск заболеваний, передающихся половым путем (ЗППП). В то же время отсутствие опыта может повысить риск травм или дискомфорта в процессе, уточнила Сугурова. При этом, продолжила репродуктолог, позднее начало половой жизни может повлиять на репродуктивное здоровье некоторых людей, особенно если у человека есть проблемы с фертильностью из-за других причин. Ранее психолог Елена Соловьева назвала лучший возраст для первых отношений. По ее словам, в возрасте от 18 до 25 лет должна случиться первая любовь, первые отношения.","https://lenta.ru/news/2025/06/06/vrach-predupredila-o-posledstviyah-pozdnego-nachala-polovoy-zhizni/","может, жизни, половой, позднее, начало","https://icdn.lenta.ru/images/2025/06/05/14/20250605141728205/detail_7e7b91d4cdbf375bac945186d3e97e64.jpg","Общее","Lenta.ru","1847823" +"Оборонные предприятия Франции забили тревогу из-за перевооружения страны в кредит","2025-06-06 01:57:00.0","Фото: Unsplash Александра Синицына Власти Франции задолжали своим предприятиям 8 миллиардов евро по оборонным заказам, Министерство Вооруженных сил страны обязалось оплатить всю сумму до конца года. Компании бьют тревогу в связи с тем, что государство перевооружается в кредит, передает телеканал BFMTV. По данным СМИ, это затрагивает военную технику, которую уже заказали у представителей оборонной промышленностей, но пока не оплатили. Беспокойство из-за неоплаченных счетов выразили несколько компаний, в том числе Airbus и Thales. В 2024 году парламент Франции принял закон о военном планировании, который включает финансирование перевооружения страны до 2030 года на сумму 413 миллиардов евро. Тем не менее «часть бюджета уже израсходована, поскольку 99 миллиардов евро пойдут на выполнение прежних обязательств». Предполагается, что на новые заказы может остаться не так много средств. Ранее Париж предоставил Украине бюджетные гарантии на 1,5 миллиарда евро для закупки вооружений у французских оборонных предприятий. До этого министр обороны Франции Себастьян Лекорню анонсировал поставки Киеву гаубиц Caesar и других вооружения на 200 миллионов евро.","https://lenta.ru/news/2025/06/06/oboronnye-predpriyatiya-frantsii-zabili-trevogu-iz-za-perevooruzheniya-strany-v-kredit/","евро, франции, миллиардов, страны, кредит","https://icdn.lenta.ru/images/2025/06/06/01/20250606015925677/detail_5d5f8948e9aa0607f36e11a8f355e903.jpg","Политика","Lenta.ru","1848204" +"В Раде заявили о постигшем Трампа проклятии Зеленского","2025-06-06 01:50:44.0","Фото: Brian Snyder / Reuters Евгений Силаев На президента США Дональда Трампа подействовало проклятие украинского лидера Владимира Зеленского, ситуация похожа на старые фильмы про ведьм. Об этом заявил депутат Верховной Рады Артем Дмитрук в своем Telegram-канале. «Центр проклятия в Киеве. Точнее на Банковой. За детьми прячется! Проклятие Банковой действует. И скоро его почувствуют даже те, кто считал себя неуязвимыми», — написал Дмитрук. Ранее между главой Белого дома Дональдом Трампом и американским предпринимателем Илоном Маском произошел конфликт. В конце мая стало известно, что миллиардер принял решение покинуть свой пост в администрации президента, где он занимал должность главы Департамента государственной эффективности США (DOGE).","https://lenta.ru/news/2025/06/06/v-rade-zayavili-o-postigshem-trampa-proklyatii-zelenskogo/","зеленского, банковой, дмитрук, трампа, проклятие","https://icdn.lenta.ru/images/2025/06/06/01/20250606015044824/detail_1ed8692bc9e2af2d6d695151afbbe732.jpg","Политика","Lenta.ru","1848203" +"Родителей лишили свободы за плохую чистку зубов их детей","2025-06-06 01:28:00.0","Фото: Sergey Bulkin / News.ru / Global Look Press Евгений Силаев В Швеции супружескую пару приговорили в восьми месяцам лишения свободы за плохую чистку зубов их детей. Об этом сообщило издание Aftonbladet. Расследование началось после посещения детьми стоматологической клиники в городе Бурос. У несовершеннолетних диагностировали глубокий кариес, приведший к необходимости удалить несколько зубов. Кроме того, у них также появились абсцессы, инфекции и воспаления. Прокурор София Брюнгельссон выступила за лишение супругов свободы, так как они не обеспечили уход за здоровьем детей. Обвиняемые отрицали свою вину. Суд встал на сторону обвинения и приговорил супругов к восьми месяцам лишения свободы. «Родители несут ответственность за заботу о своих детях, и я считаю, что тот тип пренебрежения, который мы здесь видим, является преступным», — подчеркнула Брюнгельссон. Ранее стало известно, что в Кунгуре осудят пару приемных родителей, которая дождалась 18-летия своей дочери и ограбила ее на улице.","https://lenta.ru/news/2025/06/06/roditeley-lishili-svobody-za-plohuyu-chistku-zubov-ih-detey/","свободы, зубов, детей, чистку, месяцам","https://icdn.lenta.ru/images/2025/06/06/01/20250606013817654/detail_7e6158112671ee9fa946992fb2924634.jpg","Общее","Lenta.ru","1848201" +"Число отравившихся в следовавшем в Анапу поезде детей выросло","2025-06-06 01:28:00.0","Фото: Наталья Селиверстова / РИА Новости Марина Совина Число отравившихся в поезде Екатеринбург – Анапа детей выросло до 22. Об этом сообщили в Министерстве здравоохранения Свердловской области. Отмечается, что 14 детей поместили в стационар. У них обнаружили норовирус второго генотипа. «По прибытии поезда в Анапу все дети были направлены в городскую детскую больницу для осмотра врачами-инфекционистами», — говорится в сообщении ведомства. До этого инфекцию подхватили восемь детей. Их высадили в Воронежской области и отправили в больницу вместе с одним сопровождающим. Ранее стало известно, что в Угличе Ярославской области десятки человек отравились на теплоходе «Михаил Булгаков» во время круиза. В итоге было возбуждено дело по части 1 статьи 236.","https://lenta.ru/news/2025/06/06/chislo-otravivshihsya-v-sledovavshem-v-anapu-poezde-detey-vyroslo/","детей, области, анапу, поезде, число","https://icdn.lenta.ru/images/2025/06/06/01/20250606013514591/detail_1e6dd33cbab4b8e4c889bc0834ba4c92.jpg","Общее","Lenta.ru","1848202" +"В Германии подняли истребители для сопровождения Ил-20","2025-06-06 01:04:00.0","Фото: Tobias Schwarz / Reuters Юлия Мискевич Истребители Военно-воздушных сил (ВВС) Германии подняли в небо для сопровождения российского самолета Ил-20. Об этом сообщает издание Focus. Уточняется, что российское воздушное судно двигалось в международном воздушном пространстве над Балтийским морем близ проведения учений НАТО. Кроме того, оно не выходило на связь. По информации Focus, это шестой по счету схожий инцидент в этом году. Ранее журнал Military Watch Magazine сообщил о том, что Украина присоединилась к платформе НАТО по прямому обмену данными с истребителями и системами ПВО альянса.","https://lenta.ru/news/2025/06/06/v-germanii-podnyali-istrebiteli-dlya-soprovozhdeniya-il-20/","focus, этом, сопровождения, истребители, подняли","https://icdn.lenta.ru/images/2025/06/06/01/20250606010941416/detail_46a4ca337646d8a265c0e2405660f95b.jpg","Общее","Lenta.ru","1848198" +"Заболевших в поезде детей поместили в стационар","2025-06-06 01:01:00.0","Фото: Alexander Legky / Global Look Press Евгений Силаев 14 детей, отравившихся в поезде Екатеринбург — Анапа, поместили в стационар, у них обнаружили норовирус. Об этом сообщил Минздрав Свердловской области в своем Telegram-канале. «По итогам обследования 14 детей, у которых при экспресс-тестировании выявлен норовирус 2 генотипа, оставлены под наблюдением в условиях стационара», — говорится в сообщении. Уточняется, что остальные дети находятся в изоляторе лагеря «Жемчужина» под медицинским наблюдением. В свою очередь, госпитализированные в Воронежской области пассажиры готовятся к выписке, после чего их привезут в лагерь. Ранее сообщалось, что в пассажирском поезде, следовавшем из Екатеринбурга в Анапу, произошла вспышка кишечной инфекции среди группы из 50 детей. Восьмерых пассажиров высадили в Воронежской области и отправили в больницу вместе с одним сопровождающим.","https://lenta.ru/news/2025/06/06/zabolevshih-v-poezde-detey-pomestili-v-statsionar/","детей, поезде, области, стационар, наблюдением","https://icdn.lenta.ru/images/2025/06/06/01/20250606010839739/detail_792ddbb46cd6ffd7c6f1ed02141b8f64.jpg","Общее","Lenta.ru","1848197" +"Женщины массово пожалели о пластике и стали возвращать природную внешность.","2025-06-06 00:01:00.0","","https://lenta.ru/extlink/2025/06/04/zhenschiny-massovo-pozhaleli-o-plastike-i-stali-vozvraschat-prirodnuyu-vneshnost/","пожалели, женщины, массово, пластике, возвращать","https://icdn.lenta.ru/images/2025/05/21/18/20250521181354894/detail_c8c3436f4117a37bf4e45300f9eb77cb.jpg","Общее","Lenta.ru","1836598" +"Женщины массово пожалели о пластике и стали возвращать природную внешность. Как это привело к новой опасности?","2025-06-06 00:01:00.0","Фото: Emma McIntyre / WireImage / Getty Images Дина Васильева В последнее десятилетие пухлые губы, заостренные скулы, крошечный нос и высокие брови считались чертами эталонной внешности. Тысячи женщин по всему миру ложились под нож или вводили под кожу филлеры, чтобы достичь «идеального» лица, которое соответствовало бы моде, продиктованной соцсетями. Но сегодня все изменилось: поклонницы инъекций вдруг осознали, что больше не узнают себя в зеркале, и затосковали по природной красоте. На рынке косметологии тут же появились новые методы, обещающие вернуть молодость, а главное — избежать одутловатого вида и «сохранить себя». Почему в 2025 году все помешались на естественности и почему этот тренд тоже опасен — разбиралась «Лента.ру». Десять лет назад младшая сестра клана Кардашьян Кайли Дженнер запустила тренд, который захватил весь мир и определил новые на тот момент стандарты красоты. Эра Instagram-face (запрещенная в России соцсеть; принадлежит компании Meta, признанной экстремистской организацией и запрещенной в РФ) буквально озолотила косметологов: накачанные филлерами губы предпринимательницы побудили поклонниц отправиться в клиники за аналогичной внешностью, которая, помимо пухлых губ, включила в себя точеные скулы, максимально выгнутые брови, кукольный нос и пышные ресницы. Но 2020-е — это эпоха TikTok, где тренды диктуются не тем, чего хотят люди, а тем, что они высмеивают. Платформа превращает каждую косметологическую ошибку в мем, и истории жертв недобросовестных специалистов разлетаются быстрее, чем рекламные посты об удачных преображениях. «Пациенты запоминают непривлекательные результаты — это выборочная память, которая сильно влияет на их запросы», — объясняет нью-йоркский хирург Рената Хелемски. Она отмечает, что сегодня клиенты как никогда боятся выглядеть неестественно именно из-за осуждения в соцсетях. Фото: Raimonda Kulikauskiene / Getty Images В итоге медийное личности и их фанаты осознали, что потеряли свою природную красоту из-за временной моды. В конце 2024 года 24-летняя тиктокерша Анна Пол заявила, что через восемь лет после первых инъекций выводит филлер из губ в надежде вернуть прежнее лицо. «Я смотрела в зеркало и больше не видела там себя», — призналась девушка в видео, которое собрало миллионы лайков. От наполнителей отказываются не только юные девушки, но и иконы этой моды. Сама Кайли Дженнер убавила объем в щеках и в губах, вернув себе более естественные пропорции, а британский телеведущий Саймон Коуэлл, который тоже злоупотреблял уколами, рассказал, как однажды не узнал себя в отражении зеркала и тут же растворил все филлеры. Об этом же сообщила звезда сериала «Друзья» Кортни Кокс. Актриса призналась, что «погналась за молодостью» и в какой-то момент «перестаралась» с инъекциями. В конце концов, она растворила все наполнители и теперь чувствует себя лучше, несмотря на то, что выглядит старше. Тренд на естественность на самом деле запустили не молодые блогерши, а знаменитости в возрасте около 40 лет. Кристина Агилера вернулась на сцену посвежевшей и стройной, а Линдси Лохан появилась в соцсетях с сияющим лицом и гладкой кожей. И дело вовсе не в пластике. Все это — новые уходовые, инъекционные и биотехнологические процедуры, цель которые заключается не в том, чтобы изменить черты до неузнаваемости, а в том, чтобы улучшить состояние кожи и скорректировать возрастные изменения. Вместо того чтобы просто накачать лицо, пациенты хотят изменить его текстуру. Этот подход спровоцировал развитие регенеративных процедур, которые запускают собственные ресурсы организма для омоложения. Например, речь идет о skin boosters — манипуляциях, улучшающих кожу изнутри без изменения черт лица, о факторах роста и так называемой семенной жидкости лосося. Линдси Лохан Фото: Rodin Eckenroth / Getty Images for Disney Кристина Агилера Фото: Stefanie Keenan / Getty Images for Christina Aguilera Дженнифер Энистон Фото: Chad Salvador / Variety via Getty Images Дениз Ричардс Фото: Robin L Marshall / Getty Images Актриса Дженнифер Энистон призналась, что решилась попробовать последнюю процедуру по совету косметолога — и эффект превзошел все ее ожидания: после микроинъекций смеси из ДНК лосося и гиалуроновой кислоты ее кожа стала более упругой и увлажненной. Узнав об опыте Энистон, на подобный уход решились Дениз Ричардс и Ким Кардашьян — и тоже оценили результат. Косметологи также рассказывают о популярной методике «инъекционные филеры lite». Как правило, в ходе такой процедуры вместо пяти ампул обычного филлера вводят только одну. Укол делается в нужную зону и под контролем УЗИ, чтобы избежать миграции геля в будущем. Набирает популярность и «микроботокс», который не парализует мышцу целиком, а точечно расслабляет ее часть. «Новый подход — это микродозы и мягкая техника, — подтверждает врач Джейн Харт. — Мы используем маленькие объемы, вводим постепенно, наблюдаем по фото в динамике. Филлеры ставим только в нужные анатомические карманы и учитываем их поведение с течением времени. Цель — естественное, постепенное улучшение, а не резкие, драматичные перемены». Мужчины составляют около шести процентов от общего числа клиентов пластических хирургов, однако сегодня на уколы красоты и подтяжку лица начали приходить не только голливудские актеры и политики, но и мужчины самых разных профессий — от IT-специалистов до фитнес-блогеров. Это подтвердила повестка таблоидов: если раньше журналисты обращали внимание на перемены во внешности исключительно у женщин, то теперь в сети обсуждают «посвежевшее лицо» Брэдли Купера и «подозрительно гладкий лоб» Райана Гослинга. Не избежал обсуждений и Бен Аффлек, который «помолодел» во времена отношений с Дженнифер Лопес, а комика Уилла Феррелла фанаты и вовсе прямо спросили, что случилось с его лбом, намекая на ботокс. Поначалу звезды скрывали вмешательства. Однако в 2022 году солист группы Jonas Brothers Джо Джонас стал лицом рекламной кампании ботокса для мужчин. «Я делаю эстетические процедуры в качестве ухода, чтобы стать лучшей версией себя», — гордо заявил он. А дизайнер Марк Джейкобс показал в соцсетях весь процесс своей подтяжки лица и подробно рассказал о каждом этапе восстановления. Даже рэпер Канье Уэст публично заявлял, что перенес липосакцию. Брэдли Купер Фото: Swan Gallet / WWD via Getty Images Райан Гослинг Фото: Mario Anzuoni / Reuters Бен Аффлек Фото: Mario Anzuoni / Reuters Марк Джейкобс Фото: Taylor Hill / Getty Images Уилл Феррелл Фото: Stephanie Augello / Variety via Getty Images Джо Джонас Фото: Jamie McCarthy / Getty Images Согласно апрельскому исследованию Британской ассоциации эстетических пластических хирургов, самая популярная процедура у мужчин — ринопластика. На втором месте — блефаропластика (коррекция нависших век), а на третьем — липосакция. Востребованы также контурная пластика, когда при помощи филлеров усиливают углы нижней челюсти, подбородок, скулы, чтобы лицо выглядело более мужественным. А те, у кого впалая от природы грудная клетка, ставят грудные импланты. Примечательно, что весной 2025 года в топе впервые оказался мужской лифтинг (подтяжка лица и шеи). По словам нью-йоркского врача Рамтина Кассира, на эту процедуру записываются не только возрастные певицы и актеры, но и обычные бизнесмены 50-60 лет. Его коллега, пластический хирург Дэвид Шафер отмечает, что сейчас к нему все чаще обращаются молодые мужчины в возрасте 30-40 лет. Такие пациенты предпочитают заранее освежить лицо, не дожидаясь возрастных изменений. «Мои мужчины-клиенты становятся все моложе. Они открыты к процедурам, потому что вдохновляются трендами из интернета» — предположил врач. При этом результат преследуется тот же — мужчины хотят выглядеть естественно. «Они еще более щепетильны в плане незаметности: просят сделать так, чтобы никто не понял, что была операция», — делится доктор Дженнифер Левин. Тренд на естественность коснулся не только лица, но и фигуры. В 2010-х стандарты тела диктовали Ники Минаж, Карди Би, Ким Кардашьян, Бейонсе — обладательницы крупных бедер и ягодиц. Тогда BBL (бразильская подтяжка ягодиц), при которой жир из талии перекачивают в ягодицы, была одной из самых востребованных в клиниках, но уже в 2024-м звездам надоел и этот образ. «Десять лет нам внушали, что чем больше изгибов, тем лучше. А теперь произошел резкий поворот к утонченности», — признал президент Американского общества пластических хирургов Стивен Уильямс. Специалист отметил, что в результате клиники наводнили пациенты, которые стремительно сбросили вес с помощью препаратов вроде «Оземпика», а теперь желают подтянуть дряблую кожу. Даже Карди Би открыто заявила, что удалила 95 процентов биополимерных гелевых имплантов из своих ягодиц и бедер, которые когда-то поставила у подпольного косметолога. Процедура оказалась сложной: чтобы достать весь застывший материал, потребовалось несколько этапов. Однако после нее почувствовала облегчение и призвала молодых девушек не повторять ее ошибок. В 2023 году «обратным» преображением решилась публично поделиться и модель Блэк Чайна. Она заявила, что хочет подать дочери пример естественной красоты, и поэтому избавилась от филлеров из щек и губ, грудных имплантов и излишков геля в бедрах. Даже Хлои и Ким Кардашьян уменьшили объемы фигуры, удалив часть филлеров. Карди Би Фото: Marcus Ingram / Getty Images Блэк Чайна Фото: Alan Chapman / Dave Benett/Getty Images Кендалл Дженнер Фото: Ernesto Ruscio / Getty Images Саймон Коуэлл Фото: MEGA / GC Images / Getty Images Кортни Кокс Фото: Charley Gallay / Getty Images Новый идеал красоты — спортивная рельефная форма — вызвал бум неинвазивных технологий для тела. Вместо того чтобы отправиться в фитнес-зал, люди прибегают к устройствам, которые обещают накачать мышцы и сжечь жир «без единого приседания». Например, аппараты вроде Ensculpt (электромагнитная стимуляция мышц) за 30 минут имитируют 20 тысяч приседаний, а лазерные и радиочастотные платформы убирают жир точечно и подтягивают кожу на животе после родов. Психологи бьют тревогу: культ «сделанной» внешности всегда негативно влиял на самооценку, и тренд на натуральность, казалось бы, должен был решить эту проблему. Но сегодня естественная красота достигается неестественными способами. Более того, возвращение в моду худобы уже привело к всплеску расстройств пищевого поведения у молодежи. Поколение, которое уже было готово принять разнообразие комплекций, вновь сталкивается с жесткими правилами, навязанными соцсетями. «Эта мода на выступающие тазовые кости чуть не убила меня в 2010-х… Не могу поверить, что она вернулась», — пожаловалась одна из тысяч пользовательниц платформы Reddit, пережившая анорексию.","https://lenta.ru/news/2025/06/06/trendnature/","фото, images, getty, чтобы, себя","https://icdn.lenta.ru/images/2025/05/21/18/20250521181141603/detail_b725300450cf9531a10130f9a04e4e25.jpg","Политика","Lenta.ru","1836223" +"Косатки сопроводили туристов к маяку Анива","2025-06-05 23:08:00.0","Фото: Unsplash Антонина Черташ На Сахалине косатки устроили туристам представление. Видео публикует Telegram-канал Amur Mash. Кортеж из косаток сопроводил туристов к маяку Анива. Киты ныряли, выпускали в воздух фонтаны и показывали акробатические номера. Ранее краснокнижный гренландский кит заплыл в бухту, где в это время начался отлив, уходя от стаи косаток. На место инцидента выехали ученые. Они наблюдали за состоянием млекопитающего, поливали его водой и отпугивали птиц. Позднее выяснилось, что животному помогали не только люди. Один из сородичей пытался вытолкнуть друга, рискуя попасть в ловушку.","https://lenta.ru/news/2025/06/05/kosatki-soprovodili-turistov-k-mayaku-aniva/","анива, маяку, косатки, косаток, туристов","https://icdn.lenta.ru/images/2025/06/05/19/20250605194510695/detail_9292ce9487e86f6e3c2416fd9b39d79d.jpg","Общее","Lenta.ru","1848122" +"Весна в Москве оказалась аномально солнечной","2025-06-05 23:00:00.0","Фото: Пелагия Тихонова / РИА Новости Виктория Клабукова Прошедшая весна в Москве оказалась небывало солнечной. Об этом со ссылкой на статистику обсерватории МГУ в своем Telegram-канале рассказал ведущий специалист центра погоды «Фобос» Михаил Леус. За весенние месяцы солнце в столице светило 603 часа, что на 13 процентов больше климатической нормы. Как правило, весной солнце светит порядка 535 часов. Солнечнее всего выдался май — месяц выполнил 94 процента от нормы, выдав 257 солнечных часов. В апреле показатель составит 206 часов (больше нормы на 21 процент), в марте — 140 часов (на 17 процентов выше среднего). По прогнозам синоптика, июнь подарит москвичам 279 часов солнца. С учетом небольшого дефицита осадков норма будет превышена на 5-10 процентов, отмечает Леус. Как подтверждают наблюдения последних лет, в 40 процентах случаев за июнь накапливалось более 300 часов солнца. Ранее ведущий специалист Центра погоды «Фобос» Евгений Тишковец пообещал столичным жителям уютную и нежаркую погоду в июне. По его данным, дневная температура будет варьироваться между плюс 15 и плюс 20 градусами.","https://lenta.ru/news/2025/06/05/msk-solnce/","часов, нормы, процентов, погоды, больше","https://icdn.lenta.ru/images/2025/06/05/20/20250605204346140/detail_8ae5446aa0333fd753a4f6a03372cadb.jpg","Общее","Lenta.ru","1848149" +"Раскрыта провалившаяся цель Мерца на переговорах с Трампом","2025-06-05 22:53:00.0","Фридрих Мерц Фото: Kevin Lamarque / Reuters Юлия Мискевич «Украинская» миссия канцлера ФРГ Фридриха Мерца в Вашингтоне провалилась. Об этом пишет The New York Times (NYT). Издание пишет, что канцлер Германии прибыл в Вашингтон в надежде убедить американского лидера Дональда Трампа сыграть более активную роль в защите Украины. Однако он получил совершенно другой ответ: Трамп фактически развел руками, заявив, что сейчас США ничего не могут сделать, чтобы положить конец войне между Россией и Украиной, уточняет издание. Ранее немецкий политолог Александр Рар подчеркнул, что в ходе визита в Соединенные Штаты Мерц может попытаться вернуть президента США в «лагерь европейцев».","https://lenta.ru/news/2025/06/05/nazvana-provalivshayasya-tsel-peregovorov-mertsa-i-trampa/","пишет, мерц, издание, мерца, юлия","https://icdn.lenta.ru/images/2025/06/05/23/20250605230126880/detail_5e8d350d9a822952063eb3b18e542175.jpg","Политика","Lenta.ru","1848167" +"В Госдепе раскрыли мотивацию Трампа в украинском конфликте","2025-06-05 22:52:00.0","Фото: Kevin Lamarque / Reuters Евгений Силаев Президент США Дональд Трамп хочет добиться деэскалации конфликта на Украине и последующего установления мира. Мотивацию американского лидера раскрыл заместитель официального представителя Госдепа США Томми Пигот во время брифинга. «С самого начала было ясно, что президент Трамп хочет увидеть деэскалацию. Он хочет видеть прямой диалог. Он хочет видеть мир. Это было движущей мотивацией с самого начала», — подчеркнул Пигот. По его словам, Трамп хочет видеть прямой диалог между Россией и Украиной. Ранее президент России Владимир Путин заявил, что Киев не нуждается в перемирии. «Нынешнему киевскому режиму мир вообще не нужен. Мир для него, скорее всего, означает утрату власти», — высказался политик.","https://lenta.ru/news/2025/06/05/v-gosdepe-raskryli-motivatsiyu-trampa-v-ukrainskom-konflikte/","хочет, трамп, президент, видеть, диалог","https://icdn.lenta.ru/images/2025/06/05/22/20250605225246288/detail_726f138930061529807ed478224ed09d.jpg","Политика","Lenta.ru","1848176" +"Мерц не упомянул вклад СССР в освобождение Германии от нацизма","2025-06-05 22:40:00.0","Фото: Kevin Lamarque / Reuters Евгений Силаев Канцлер ФРГ Фридрих Мерц во время совместной пресс-конференции с президентом США Дональдом Трампом поблагодарил Соединенные Штаты за освобождение Германии от нацизма, не упомянув СССР в своей речи. Трансляция переговоров и беседы политиков с журналистами велась на YouTube-канале агентства Associated Press (AP). Мерц напомнил, что 6 июня отмечается годовщина высадки сил США, Канады и Великобритании в Нормандии. «В долгосрочной перспективе, господин президент, это было освобождением моей страны от нацистской диктатуры, и мы знаем, чем обязаны вам», — сказал Мерц. Ранее Трамп заявил, что Россия была крупным фактором в победе над нацизмом, но войну выиграли США. Слова о вкладе СССР прозвучали после заявлений президента о том, что Первую и Вторую мировую войну выиграли американцы, а не какая-либо другая держава.","https://lenta.ru/news/2025/06/05/merts-ne-upomyanul-vklad-sssr-v-osvobozhdenii-germanii-ot-natsizma/","мерц, ссср, нацизма, освобождение, германии","https://icdn.lenta.ru/images/2025/06/05/22/20250605224123724/detail_eec101d12c1c6e4908a80d721c81a684.jpg","Политика","Lenta.ru","1848174" +"Вероятность резких похолоданий в экстремально жарком 2025 году оценили","2025-06-05 22:35:00.0","Фото: Александр Миридонов / Коммерсантъ Виктория Клабукова 2025 год рискует стать одним из самых жарких в истории метеонаблюдений. Ожидаются ли в будущем резкие волны похолодания, «Комсомольской правде» объяснил научный руководитель Гидрометцентра России Роман Вильфанд. Внезапные похолодания, равно как и чрезмерная жара, являются последствиями одного метеорологического процесса и не исключают друг друга. Как отмечает Вильфанд, в этом заключается нервозность погоды в период глобального потепления. «Нервозность погоды заключается в том, что средние значения складываются из экстремальных событий», — пояснил он. Так, синоптик привел в пример весеннюю погоду в Москве. «Какие были перепады от жары к холоду и обратно! Мы не нашли в истории аналога такой изменчивой погоды», — заявил Вильфанд. Начавшись с по-январски сильных холодов, весна завершилась температурами, которые скорее соответствовали середине лета. Несмотря на такие качели, температура в среднем за сезон была близка к климатической норме. Ранее Вильфанд предположил, что 2025 год войдет в тройку лидеров по самой высокой температуре в мире. Вероятность такого развития событий он оценил в 90 процентов.","https://lenta.ru/news/2025/06/05/kholod-zhara/","вильфанд, погоды, вероятность, нервозность, заключается","https://icdn.lenta.ru/images/2025/06/05/20/20250605205625390/detail_5456df8ca521ed5b7d13bfc31f3e6929.jpg","Общее","Lenta.ru","1848150" +"Маск намекнул на педофилию Трампа","2025-06-05 22:34:00.0"," Фото: Nathan Howard / Reuters Марина Совина Американский предприниматель Илон Маск намекнул на педофилию президента США Дональда Трампа. Об этом он написал в социальной сети X. По его словам, политик фигурирует в деле против американского миллиардера Джеффри Эпштейна. Также Маск добавил, что по этой причине файлы по Эпштейну публикуют не полностью. В январе 2024 года в США опубликовали список фигурантов дела в отношении Эпштейна. В нем оказалось более 150 имен. Позднее материалы дела были рассекречены. Эпштейна осудили за создание криминальной сети, которая позволяла ему сексуально эксплуатировать десятки девушек. Среди них были 14-летние подростки. По некоторым данным, он продолжал заниматься торговлей людьми вплоть до ареста. Финансиста задержали 6 июля 2019-го в Нью-Йорке. В августе того же года он свел счеты с жизнью, не дождавшись судебного процесса.","https://lenta.ru/news/2025/06/05/mask-nameknul-na-pedofiliyu-trampa/","маск, эпштейна, сети, трампа, намекнул","https://icdn.lenta.ru/images/2025/06/05/22/20250605223715890/detail_23b53dfc1238eab9b526eaa30fad62ca.jpg","Политика","Lenta.ru","1848172" +"На Западе развеяли миф о «российской угрозе»","2025-06-05 22:29:00.0","Фото: Кристина Кормилицына / РИА Новости Марина Совина Западные политики и журналисты, пламенно заявляющие о «российской угрозе», должны остановиться. У Москвы нет причин начинать полномасштабный конфликт с НАТО, пишет издание The National Interest (NI). «Россия уже побеждает на Украине. У [президента России Владимира] Путина просто нет причин обострять войну. И западные комментаторы, пронзительно предупреждающие о третьей мировой войне, особенно правые, должны сбавить обороты», — говорится в статье. Издание также отмечает, что российский глава всегда четко осознавал последствия своих действий, даже если американцы и европейцы этого не делали. Ранее постоянный представитель РФ при ООН Василий Небензя заявил, что западные страны должны выбрать между открытой русофобией и конструктивным диалогом с Россией. Повторения сценария Минских соглашений или жестов доброй воли больше не будет, добавил он. Ранее СМИ написали, что Штаты могут выступить против обозначения России как угрозы для НАТО в итоговой декларации будущего саммита альянса, который пройдет в Гааге 24-25 июня.","https://lenta.ru/news/2025/06/05/na-zapade-razveyali-mif-o-rossiyskoy-ugroze/","должны, западные, российской, россии, угрозе","https://icdn.lenta.ru/images/2025/06/05/22/20250605222911382/detail_31d1f9dc82c22e7151e4b6333f980a39.jpg","Политика","Lenta.ru","1848171" +"Минобороны Казахстана прокомментировало данные о неопознанных светящихся объектах в небе","2025-06-05 22:21:00.0","Фото: Maksim Konstantinov / Global Look Press Александра Синицына Неопознанные светящиеся объекты в небе над Астаной могут быть метеоритным потоком либо обломками космического аппарата, входящими в атмосферу. Воздушное пространство Казахстана не было нарушено, угрозы для населения нет, так прокомментировало загадочные вспышки Минобороны (МО) Казахстана в Telegram-канале. «Данное явление схоже со входящими в атмосферу остатками обломков космического аппарата или на метеоритный поток. Как правило, все они сгорают в плотных слоях атмосферы, не долетая до земной поверхности», — уточнили в военном ведомстве. Там добавили, что профильные службы держат ситуацию под контролем. Граждан призвали сохранять спокойствие и ждать официальных разъяснений. Ранее в сети опубликовали видео с яркими вспышками в небе над Казахстаном. Сразу несколько светящихся объектов летели в неизвестном направлении и скрылись за крышами домов.","https://lenta.ru/news/2025/06/05/minoborony-kazahstana-prokommentirovalo/","казахстана, небе, минобороны, атмосферу, космического","https://icdn.lenta.ru/images/2025/06/05/22/20250605222550743/detail_662b2b62ded64fa252accb9e2992f833.jpg","Общее","Lenta.ru","1848170" +"Посол заявил об ожидании России реакции от Британии на удары ВСУ по аэродромам","2025-06-05 22:19:00.0","Фото: Maksim Konstantinov/Global Look Press Марина Совина Россия ждет реакции Лондона на удары Вооруженных сил Украины (ВСУ) по российским аэродромам. Подобные операции невозможны без поддержки Великобритании или Вашингтона, заявил посол РФ в Великобритании Андрей Келин в интервью британскому телеканалу Sky News. «Президент РФ Владимир Путин вчера говорил с президентом Трампом, и они обсудили атаку Киева. И мы надеемся, что также получим реакцию из Лондона. Потому что такого рода атаки, конечно же, предполагают использование высоких технологий», — отметил он. Посол также подчеркнул, что, в то время как Дональд Трамп заявил о непричастности Штатов к атаке, Лондон этого не сделал. В России знают, насколько сильно Лондон вовлечен в украинский конфликт, насколько глубоко британские силы вовлечены в работу с Украиной по наведению атак на Крымский мост, а также по другим аналогичным вопросам, посетовал Келин. При этом Дональд Трамп во время встречи с канцлером Германии Фридрихом Мерцем заявил, что понимает действия российского лидера Владимира Путина на фоне атак на российские аэродромы. До этого глава Белого дома сообщил, что в ходе телефонного разговора с Путиным просил его не отвечать на атаку Киева на аэродромы.","https://lenta.ru/news/2025/06/05/posol-zayavil-ob-ozhidanii-rossii-reaktsii-ot-britanii-na-udary-vsu-po-aerodromam/","заявил, также, посол, лондона, великобритании","https://icdn.lenta.ru/images/2025/06/05/22/20250605221949728/detail_accd9d2b4d61f74bcdd8ec203298fcb9.jpg","Политика","Lenta.ru","1848169" +"Маск прокомментировал слова Трампа о сумасшествии предпринимателя","2025-06-05 22:12:00.0","Илон Маск Фото: Nathan Howard / Reuters Марина Совина Американский предприниматель Илон Маск прокомментировал слова президента США Дональда Трампа о своем сумасшествии. Об этом он написал в социальной сети X.  Ранее американский лидер заявил, что Маск «изнашивался». По словам политика, он попросил миллиардера уйти с поста главы департамента государственной эффективности США (DOGE) и отобрал его мандат на электромобили, «который заставлял всех покупать электрические машины, которые никто больше не хотел». Трамп также добавил, что Маск «просто сошел с ума». «Такая очевидная ложь. Так грустно», — написал миллиардер в ответ на пост политика. Ранее Маск подверг резкой критике «большой, красивый» законопроект Трампа, назвав его «отвратительной мерзостью». Позже предприниматель негативно высказался о проекте госбюджета США и призвал конгрессменов не голосовать за него.","https://lenta.ru/news/2025/06/05/mask-prokommentiroval-slova-trampa-o-sumasshestvii-predprinimatelya/","маск, трампа, написал, американский, предприниматель","https://icdn.lenta.ru/images/2025/06/05/22/20250605223037162/detail_cc673d1c698aae8f484741e5e3670b26.jpg","Политика","Lenta.ru","1848168" +"Трамп заявил о сошедшем с ума Маске","2025-06-05 22:07:00.0","Дональд Трамп Фото: Kevin Lamarque / Reuters Марина Совина Президент США Дональд Трамп заявил, что предприниматель Илон Маск сошел с ума. Об этом он написал в своей социальной сети Truth. По словам главы Белого дома, миллиардер «изнашивался» и политик попросил его покинуть должность главы департамента государственной эффективности США (DOGE). «Я отобрал его мандат на электромобили, который заставлял всех покупать электрические машины, которые никто больше не хотел (он знал об этом месяцами, что я собираюсь это сделать!), и он просто сошел с ума!» — заявил американский лидер. Ранее Маск подверг резкой критике «большой, красивый» законопроект Трампа, назвав его «отвратительной мерзостью». Позже предприниматель негативно высказался о проекте госбюджета США и призвал конгрессменов не голосовать за него.","https://lenta.ru/news/2025/06/05/tramp-zayavil-o-soshedshem-s-uma-maske/","заявил, трамп, главы, предприниматель, этом","https://icdn.lenta.ru/images/2025/06/05/22/20250605222844011/detail_6bcbfafbc8bf4ac514ae5de81cbf77e0.jpg","Политика","Lenta.ru","1848166" +"Акции Tesla обвалились из-за конфликта Трампа с Маском","2025-06-05 21:49:00.0","Фото: Yves Herman / Reuters Александра Синицына 5 июня акции американской компании Tesla теряли более 9 процентов из-за публичного конфликта ее главы, миллиардера Илона Маска и президента США Дональда Трампа. Об обвале котировок свидетельствуют данные электронной биржи Nasdaq, передает ТАСС. Согласно статистике торговой площадки на 19.55 по московскому времени, бумаги теряли 9,08 процента, подешевев до 301,91 доллара на акцию. При этом к 20.35 по московскому времени показатели немного выровнялись, корректировка составила уже 8,87 процента (302,61 доллара на акцию). Ранее предприниматель Илон Маск заявил, что без его помощи Дональд Трамп проиграл бы прошедшие в ноябре выборы. Он также оставил под своей публикацией подпись «какая неблагодарность».","https://lenta.ru/news/2025/06/05/aktsii-tesla-obvalilis-posle-perepalki-trampa-s-maskom/","акции, tesla, доллара, теряли, конфликта","https://icdn.lenta.ru/images/2025/06/05/21/20250605214110230/detail_bb03291e0786291d1a821efec37bbfa4.jpg","Политика","Lenta.ru","1848161" +"В США заявили о ракетном превосходстве России","2025-06-05 21:43:00.0","Фото: Alexey Belkin / News.ru / Global Look Press Евгений Силаев Россия имеет превосходство над Украиной в количестве ракет, «ракетная математика» идет ей на пользу. Об этом заявил обозреватель американского издания Politico Джереми Детмер. По его подсчетам, у Украины осталось восемь установок Patriot, шесть из которых находятся в строю. Он отметил, что на каждую российскую цель требуются минимум две ракеты, а всего в распоряжении Киева осталось меньше 200 перехватчиков. Детмер отметил, что Россия в свою очередь наращивает потенциал. В частности, в 2025 году планируется производство до 3000 ракет, в том числе 750 единиц класса «Искандер». В результате Россия имеет стратегическую инициативу, а также наращивает нагрузку на украинскую систему противовоздушной обороны, говорится в статье. Ранее американское издание The National Interest подчеркнуло, что российские крылатые ракеты морского базирования «Калибр» стали кошмаром для Украины благодаря своей эффективности.","https://lenta.ru/news/2025/06/05/v-ssha-zayavili-o-raketnom-prevoshodstve-rossii/","россия, детмер, украины, ракеты, ракет","https://icdn.lenta.ru/images/2025/06/05/21/20250605215351388/detail_e1063d8a6e4c0f687eed0491e6c726bf.jpg","Общее","Lenta.ru","1848162" +"Зеленский назначил нового командующего беспилотниками ВСУ. Он уже предложил план ударов вглубь России","2025-06-05 20:11:00.0","Фото: Inna Varenytsia / Reuters Юрий Леонов Силы беспилотных систем (СБС) Вооруженных сил Украины (ВСУ) должны в течение 100 дней разработать и внедрить обновленный план ударов вглубь территории России. Об этом заявил новый командующий СБС Роберт Бровди с позывным Мадьяр после своего вступления в должность. 4 июня президент Украины Владимир Зеленский назначил Мадьяра командующим СБС ВСУ. До назначения Бровди руководил 414-м отдельным батальоном ударных беспилотных систем морской пехоты ВСУ в звании майора. Роберт Броуди Фото: Птахи Мадяра 414ОБ / Wikipedia В апреле 2025 года офицер угрожал украинским властям возможным бунтом военных в том случае, если Киев откажется от территорий в пользу России. Он подчеркнул, что, если украинские власти будут разговаривать с Москвой не в интересах вернувшихся с фронта военнослужащих, они «получат по куполу». В период президентства Виктора Януковича Бровди был заместителем председателя Государственной продовольственно-зерновой корпорации Украины, однако покинул должность после скандала с невыполненными поставками зерна в Китай. Позднее он занимался девелоперским бизнесом и поставками зерна, а в феврале 2022 года вступил в ряды территориальной обороны ВСУ и создал подразделение операторов беспилотных летательных аппаратов (БПЛА) «Птахи Мадьяра». В качестве приоритетных мер в первые 100 дней на посту Бровди назвал следующие: По мнению Мадьяра, в течение первых 100 дней СБС ВСУ должны организовать «12 слоев влияния» на фронте на оперативном уровне. В числе направлений указаны радиоэлектронная разведка и перехват БПЛА ВС России, пролетающих через линию фронта для ударов по объектам инфраструктуры. Мадьяр также указал на необходимость реформы организации ударов БПЛА вглубь России, однако отказался раскрывать подробности из соображений секретности. По его словам, приоритеты в данном направлении уже определены. Фото: Viacheslav Ratynskyi / Reuters Особое внимание Бровди уделил использованию опыта проекта «Линия дронов», участником которого являются и «Птахи Мадьяра». 9 февраля министр обороны Украины Рустем Умеров объявил о запуске инициативы по применению БПЛА на фронте с участием пяти лучших подразделений ВСУ. Умеров подчеркнул, что в рамках проекта пехота и БПЛА совмещаются в «единую ударную систему», что должно кардинально изменить тактику на поле боя. Задачей «Линии дронов» является формирование killzone глубиной 10-15 километров для замедления продвижения ВС России. Издание The New York Times предположило, что «Линия дронов» ВСУ станет запасным планом Киева в случае провала переговоров с Россией. По оценкам экспертов, полномасштабное использование БПЛА поможет решить проблему с нехваткой личного состава на фронте. Как рассказали изданию опрошенные аналитики, для пуска одного FPV-дрона может потребоваться до четырех человек, при этом найти людей на эти должности проще, чем в пехоту, где требуется действовать в окопах.","https://lenta.ru/news/2025/06/05/whoismadyar/","россии, бпла, бровди, украины, мадьяра","https://icdn.lenta.ru/images/2025/06/05/18/20250605184148836/detail_3ca0732c39f11ba49004e8034cce12bb.jpg","СВО","Lenta.ru","1848073" +"Журналиста с инвалидностью полтора часа продержали в самолете после посадки","2025-06-04 17:02:00.0","Франк Гарднер Фото: Cover Images / Globallookpress.com Мария Борисова Известного британского журналиста с инвалидностью полтора часа продержали в самолете после посадки в лондонском аэропорту Хитроу. Об этом сообщило издание Daily Mail. 63-летний корреспондент «Би-би-си» Фрэнк Гарднер подвергся нападению боевиков «Аль-Каиды» (запрещенная в России террористическая организация) в 2004 году во время репортажа в Саудовской Аравии. С тех пор он частично парализован и передвигается на инвалидной коляске. 3 июня Гарднер прилетел в Лондон из командировки в Сингапуре рейсом British Airways. После 13-часового перелета мужчину вынудили еще полтора часа провести на борту, пока не приехал лифт, который спустил его на землю. «Честно говоря, это происходит не каждый раз, и персонал наземного обслуживания всегда услужлив и вежлив, но в 2025 году такого вообще не должно происходить, — отметил журналист. — На каждого человека, который, как и я, публично заявляет о случившемся, сколько других молча терпят это?» В октябре 2024 года Гарднер попал в более неприятную ситуацию. Авиакомпания LOT вынудила его ползти до туалета во время рейса. ","https://lenta.ru/news/2025/06/04/zhurnalista-s-invalidnostyu-poltora-chasa-proderzhali-v-samolete-posle-posadki/","гарднер, полтора, часа, после, инвалидностью","https://icdn.lenta.ru/images/2025/06/04/14/20250604141635341/detail_787076da7a4b0cc125e3bc5ab8c67d0d.jpg","Общее","Lenta.ru","1847043" +"Общение с собаками оказалось полезным для здоровья","2025-06-04 16:58:38.0","Фото: Unsplash Екатерина Графская Ранние взаимодействия с собаками могут ослаблять генетическую предрасположенность к экземе у детей. К такому выводу пришли исследователи из Университета Эдинбурга, опубликовавшие результаты крупного анализа в журнале Allergy. Ученые проанализировали данные более 25 тысяч человек из 16 европейских исследований, чтобы выяснить, как 24 генных варианта, связанных с экземой, взаимодействуют с 18 факторами ранней среды — включая грудное вскармливание, наличие старших братьев и сестер, курение, антибиотики, домашних животных и др. Среди них было выявлено 14 значимых взаимодействий. Особое внимание привлек ген, отвечающий за выработку рецептора интерлейкина-7 — молекулы, участвующей в регуляции иммунной системы. Оказалось, что у детей с определенной формой этого гена контакт с собакой в младенчестве снижает воспалительную активность кожи и, соответственно, риск развития экземы. Лабораторные модели подтвердили: воздействие на этот участок ДНК подавляется именно при наличии собаки в доме. Авторы подчеркивают, что это первое исследование, объясняющее предполагаемое защитное действие собак на молекулярном уровне. И хотя для подтверждения результатов потребуются дополнительные работы, исследование открывает новые возможности в профилактике аллергических заболеваний. Ранее ученые предупреждали о вреде собак для окружающей среды. Исследования показали, что даже домашние питомцы могут нарушать экосистемы, пугать диких животных и загрязнять природу.","https://lenta.ru/news/2025/06/04/vzaimodeystvie-s-sobakami-okazalos-poleznym-dlya-zdorovya/","оказалось, исследование, собак, детей, животных","https://icdn.lenta.ru/images/2025/06/04/15/20250604153047535/detail_e53b02dd4aefb8adda708b629e8a61e7.jpg","Экономика","Lenta.ru","1847113" +"Россиянка Андреева вылетела в четвертьфинале «Ролан Гаррос»","2025-06-04 16:57:00.0","Фото: Илья Наймушин / РИА Новости Дарья Коршунова Российская теннисистка Мирра Андреева вылетела в четвертьфинале «Ролан Гаррос». Об этом сообщает корреспондент «Ленты.ру». Андреева не смогла выйти в полуфинал, проиграв француженке Лоис Буассон. Встреча завершилась со счетом 7:6 (8:6), 6:3 в пользу 361-й ракетки мира. Спортсменки провели на корте 2 часа 9 минут. Андреева продолжает борьбу в парном турнире. В полуфинале ей вместе с соотечественницей Дианой Шнайдер предстоит сыграть с итальянками Жасмин Паолини и Сарой Эррани. Ранее соперницы победили российский дуэт на Олимпийских играх-2024 в матче за золото. 18-летняя Андреева — первая ракетка России. В мировом рейтинге она занимает шестое место.","https://lenta.ru/news/2025/06/04/rossiyanka-andreeva-vyletela-v-chetvertfinale-rolan-garros/","андреева, ролан, гаррос, вылетела, четвертьфинале","https://icdn.lenta.ru/images/2025/06/04/16/20250604165801242/detail_a9ecec21e84aeda7068ae38bb421443f.jpg","Экономика","Lenta.ru","1847200" +"В Сбере развеяли миф о «цифровой колонии» западных технологий","2025-06-04 16:56:00.0","Кирилл Меньшов Фото: Павел Лисицын / РИА Новости Алена Шаповалова Старший вице-президент, руководитель блока «Технологии» Сбербанка Кирилл Меньшов принял участие в сессии «Технологическое лидерство. Своевременность и новеллы национальной идеи». Она прошла в рамках конференции «Цифровая индустрия промышленной России» (ЦИПР) в Нижнем Новгороде. «Сбер – большая технологическая компания, которая существует не в вакууме, а в сложной ИТ-экосистеме законченного цикла, которая в России окончательно сформировалась три года назад. До этого в мире существовало только две подобные экосистемы — западная и китайская, где компании охватывают полный жизненный цикл разработки: от железа до системного софта и прикладного ПО. Такой подход обеспечивает полную независимость и суверенность производства. Сейчас аналогичная модель формируется в нашей стране. Появились вендоры, способные создавать законченные технологические цепочки. Это закладывает основу для будущего технологического суверенитета страны. Мы должны очень твердо стоять на своих ногах. Российский ИТ-рынок принципиально отличается от западного», – сказал старший вице-президент, руководитель блока «Технологии» Сбербанка Кирилл Меньшов. По его словам, ключевые меры поддержки должны быть направлены на консолидацию отрасли. Несмотря на относительную молодость, российский рынок уже преодолел статус 'цифровой колонии' западных технологий, отметил он. «Мы также наблюдаем устойчивую тенденцию к укрупнению компаний, появление национальных технологических чемпионов и развитие программ их поддержки. Эти процессы формируют основу для создания полноценного технологического уклада будущего. Особо подчеркну: параллельно с формированием технологического фундамента мы активно развиваем передовые направления в сфере искусственного интеллекта. Россия входит в узкий круг стран, обладающих собственными фундаментальными ИИ-моделями. Кроме того, по числу суверенных ИТ-специалистов мы занимаем третье место в мире. Сочетание этих факторов — прочная основа для технологического прорыва, чтобы Россия стала одним из глобальных ИТ- лидеров», – сказал Меньшов.","https://lenta.ru/news/2025/06/04/v-sbere-razveyali-mif-o-tsifrovoy-kolonii-zapadnyh-tehnologiy/","технологического, меньшов, кирилл, западных, будущего","https://icdn.lenta.ru/images/2025/06/04/17/20250604170108165/detail_e2d2ac7c631dcf66c889da36acff576f.jpg","Экономика","Lenta.ru","1847201" +"МГЕР и «Волонтерская Рота» провели акцию у посольств недружественных стран","2025-06-04 16:55:29.0"," Фото: Единая Россия Татьяна Романова МГЕР и «Волонтерская Рота» провели акцию у посольств недружественных стран, поставляющих оружие и боеприпасы украинским террористам. Мероприятия организовали в День невинных детей — жертв агрессии. Более 1,5 тысячи активистов вышли с портретами детей к посольствам Латвии, Литвы, Польши, Эстонии, Чехии, Франции, Германии, Румынии, Великобритании, Норвегии в Москве и Санкт-Петербурге. «Эти дети попали под обстрелы, бомбежки жилых кварталов, погибали при авиаударах. Преступления украинского террористического режима можно перечислять бесконечно. Дети пострадали и совсем недавно при теракте в Брянской области накануне Дня защиты детей. Мы хотим в очередной раз напомнить спонсорам украинских нацистов о жертвах, к которым ежедневно приводит использование поставляемого ими оружия», — сказал председатель МГЕР Антон Демидов. Спонсирование нацистского режима Украины приводит к страшным последствиям, подчеркнул руководитель Центрального штаба МГЕР Александр Амелин. «Постоянные обстрелы уносят жизни мальчишек и девчонок, которые только начинают жить. Эти преступления не имеют срока давности», — отметил он. С января по конец декабря 2024 года от ударов ВСУ ранения получили 4398 мирных граждан, в том числе, 285 детей. Жертвами атак со стороны Украины за этот же период стали 785 человек, включая 50 детей. С начала 2025 года жертвами ударов ВСУ по российской территории стали 10 детей. Еще 92 ребенка получили ранения.","https://lenta.ru/news/2025/06/04/mger-i-volonterskaya-rota-proveli-aktsiyu-u-posolstv-nedruzhestvennyh-stran/","детей, мгер, недружественных, получили, провели","https://icdn.lenta.ru/images/2025/06/04/17/20250604170237075/detail_292778d717ac57fd05c95b6f29999f11.jpg","Экономика","Lenta.ru","1847194" +"Отец шестерых детей из США со всей семьей переехал в Россию и ушел на СВО","2025-06-04 16:43:00.0","Фото: Belkin Alexey / Globallookpress.com Елена Торубарова Отец шестерых детей из США со всей семьей переехал в Россию и ушел добровольцем в зону проведения специальной военной операции (СВО) на Украине. Его историю публикует NN.RU. Как пишет издание, многодетный отец приехал в Россию из Хьюстона в штате Техас, где работал на местном заводе. Решение переехать он объяснил недовольством обстановки в стране. «Нам надоела массированная пропаганда ЛГБТ (международное общественное движение признано экстремистским и запрещено в России), преступность, падение уровня культуры. Я хочу, чтобы мои дети росли на традиционных ценностях. Я очарован Россией, ее политикой, культурой, невероятной природой, доброжелательностью людей», — рассказал мужчина. Он отметил, что уже начал изучать русский язык, а контракт с Минобороны для отправки на СВО заключил, «чтобы доказать, что достоин быть частью России». На боевую подготовку он отправился из единого пункта отбора в Нижнем Новгороде. «Стреляю хорошо. Но пока мало что знаю о русской армии, но слышал, что там очень крутые ребята», — заключил он. Ранее президент России Владимир Путин рассказал о воюющих на стороне России французах. По его словам, иностранные добровольцы служат в составе подразделения «Нормандия — Неман». ","https://lenta.ru/news/2025/06/04/otets-shesteryh-detey-iz-ssha-so-vsey-semiey-pereehal-v-rossiyu-i-ushel-na-svo/","россии, россию, отец, переехал, детей","https://icdn.lenta.ru/images/2025/06/04/16/20250604163358790/detail_9a0a1392b65f0be9f3b9f7b9ea100942.jpg","Политика","Lenta.ru","1847176" +"Россиянам назвали простой способ уберечься от мошенников","2025-06-04 16:41:00.0","Фото: Дмитрий Ермаков / «Лента.ру» Маргарита Щигарева МВД России порекомендовало жителям страны проверять ссылки, полученные в мессенджерах, на специальном сайте. Простой способ уберечься от мошенников ведомство назвало в Telegram-канале «Вестник киберполиции России». Для проверки ссылки на предмет фишинга или вредоносного программного обеспечения в МВД посоветовали воспользоваться сайтом VirusTotal, который анализирует подозрительные файлы и ссылки. Чтобы проверить адрес, нужно скопировать его и вставить на портале в строку поиска в разделе с URL, указали в ведомстве. Согласно опубликованным скриншотам, VirusTotal затем покажет, безопасна ли проверяемая ссылка с точки зрения разных поставщиков систем безопасности. Положительный ответ будет показан зеленым цветом, а отрицательный — красным. «Если хотя бы один из проверяемых параметров горит красным, проверяемый сайт опасен!» — предупредили в МВД. Ранее в ведомстве описали схему мошенников с трудоустройством. Уточнялось, что злоумышленники предлагают россиянам набирать тексты со сканов, а затем просят внести одноразовый платеж. Перед этим также стало известно, что мошенники подделали сайт МВД России, чтобы красть данные россиян. Фишинговый ресурс имел адрес, похожий адрес настоящего сайта ведомства.","https://lenta.ru/news/2025/06/04/rossiyanam-nazvali-prostoy-sposob-uberechsya-ot-moshennikov/","россии, адрес, мошенников, ссылки, уберечься","https://icdn.lenta.ru/images/2025/06/04/13/20250604132434152/detail_55cffb6658b8c188a79ebf9bf3c10621.jpg","Общее","Lenta.ru","1846984" +"Microsoft собралась метить хакеров","2025-06-04 16:33:43.0","Фото: PR Image Factory / Shutterstock / Fotodom Андрей Ставицкий Корпорация Microsoft заявила о желании собрать базу хакеров, чтобы быстро реагировать на угрозы. Об этом сообщается на сайте компании. IT-гигант запустил инициативу вместе с партнером Crowdstrike. «Одной из основных причин медленного реагирования на взлом является непонимание атрибуции субъекта угрозы», — заметили в фирме. В этой связи корпорация собралась создать базу, в которую будут вносить и помечать известные хакерские группировки по всему миру. Базу от Microsoft начнут использовать органы власти, эксперты по безопасности, предприятия и поставщики средств безопасности. «Мы предоставим специалистам по безопасности возможность быстрее связывать информацию и принимать решения с большей уверенностью», — заметили в компании. Хакерские группировки будут собирать по нескольким категориям — в том числе, предполагаемой географической принадлежности. Так, группы из России решили объединить под названием «Метель» (Blizzard), Китая — «Тайфун» (Typhoon). Группы с определенным финансированием объединили в категорию «Буря» (Tempest), разработчиков коммерческого кибероружия — «Цунами» (Tsunami). Microsoft и Crowdstrike заявили, что уже выявили и добавили в базу 130 группировок. Корпорации призвали другие компании присоединиться к их проекту. В конце мая Microsoft рассказала об опасном вирусе Lumma Stealer, который заразил сотни тысяч компьютеров с Windows по всему миру.","https://lenta.ru/news/2025/06/04/microsoft-hackers/","microsoft, базу, безопасности, компании, собралась","https://icdn.lenta.ru/images/2025/06/04/14/20250604140141692/detail_5113aae8cef8dc971319c130e3db000b.jpg","Общее","Lenta.ru","1847032" +"Некоторым россиянам посоветовали ограничить употребление клубники и черешни","2025-06-04 16:31:55.0","Фото: Natasha Skov / Unsplash Александра Лисица Некоторым людям следует ограничить употребление клубники и черешни, заявил диетолог-эндокринолог Антон Поляков. Об этом он сообщил россиянам в беседе с aif.ru. По его словам, много клубники и черешни нельзя употреблять людям, у которых есть заболевания желудочно-кишечного тракта в стадии обострения. «Не стоит есть, во всяком случае в больших количествах, эти продукты, если существует выраженная аллергическая реакция, особенно пищевая аллергия. То есть клубника и черешня могут обострить в этом случае течение аллергии», — сказал врач. Поляков также предостерег от чрезмерного потребления клубники и черешни женщин, которые кормят грудью. По его словам, кормящим матерям нужно соблюдать определенную диету, потому что клубника и черешня, особенно импортные, могут вызвать аллергическую реакцию. Ранее врач-терапевт Любовь Дроздова посоветовала россиянкам есть красное мясо два-три раза в неделю. По ее словам, такая диета положительно влияет на продолжительность жизни.","https://lenta.ru/news/2025/06/04/nekotorym-rossiyanam-posovetovali-ogranichit-upotreblenie-klubniki/","есть, клубники, черешни, словам, россиянам","https://icdn.lenta.ru/images/2025/06/04/11/20250604115036856/detail_156420cee189809456a7334077661564.jpg","Экономика","Lenta.ru","1846873" +"Генерал одним словом объяснил отсутствие защиты стратегических аэродромов от дронов","2025-06-04 16:15:00.0","Фото: ФСБ РФ / РИА Новости Антон Похиляк Депутат Госдумы, генерал-лейтенант запаса Андрей Гурулев объяснил отсутствие защиты стратегических российских аэродромов от FPV-дронов после атак украинских беспилотников. Свое мнение он выразил в эфире «Царьграда». Гурулев назвал это раздолбайством, пояснив, что по-другому объяснить это не может. Он напомнил, что стратегическая авиация — часть ядерной триады России. «Теперь, внимание, вопрос: кто головой отвечает за ядерную триаду? Правильно, Генеральный штаб ВС России. И вы знаете, что самое страшное? Мне очень хотелось бы, чтобы это не прошло мимо, чтобы за это понесли ответственность», — призвал он. Он назвал отсутствие защиты системной проблемой, посчитав, что в связи с этим есть вопросы к Службе внешней разведки, к Главному управлению Генштаба и к органам контрразведки. Гурулев также указал на минимальное число блокпостов на российских дорогах, призвав к тотальным проверкам транспорта. «Например, до аэродрома в Мурманской области, где действовали беспилотники, дорога одна. Никак не объедешь. Если ее перекрыть и тотально проверять — никто ничего не провезет», — пояснил депутат. Ранее военный эксперт Юрий Кнутов также посчитал, что надо усиливать контроль со стороны ГАИ и увеличивать количество проверок на местах, где присутствует весь необходимый инвентарь для взвешивания машин. По его словам, давно известно, что некоторые дроны вылетают к целям с территории России. ","https://lenta.ru/news/2025/06/04/general-odnim-slovom-ob-yasnil-otsutstvie-zaschity-strategicheskih-aerodromov-ot-dronov/","россии, отсутствие, гурулев, защиты, дронов","https://icdn.lenta.ru/images/2025/06/04/16/20250604160837634/detail_65f2cca05622070205684f519f4de99e.jpg","Спорт","Lenta.ru","1847154" +"Азербайджан принял участие в военных учениях НАТО","2025-06-04 16:14:00.0","Фото: Евгений Биятов / РИА Новости Сергей Болиев Азербайджан принял участие в военных командно-штабных учениях НАТО «Решительная сила-2025». Об этом сообщили в Минобороны республики. «Целью учений является реализация мероприятий в соответствии с процессом принятия военных решений, а также совершенствование навыков командиров и штабов по управлению подразделениями в оперативных условиях», — говорится в пресс-релизе. В учениях принимала участие группа офицеров Вооруженных сил Азербайджанской республики. Военные маневры с участием Азербайджана и НАТО прошли в Болгарии. В них также приняли участие Грузия и некоторые члены НАТО, в том числе Северная Македония, Греция и Румыния. Накануне Минобороны Азербайджана провело совместный с НАТО тренинг в рамках «Программы сотрудничества по индивидуальному партнерству» между республикой и североатлантическим альянсом в 2025 году. Тема тренировок — процесс планирования операций НАТО.","https://lenta.ru/news/2025/06/04/azerbaydzhan-prinyal-uchastie-v-voennyh-ucheniyah-nato/","нато, участие, военных, учениях, принял","https://icdn.lenta.ru/images/2025/06/04/16/20250604160328190/detail_c9ca99f5f5fa20016c92ec1357fc8fbf.jpg","Общее","Lenta.ru","1847149" +"Алаудинов фразой «потянулся бы к пистолету» описал возможную встречу с похищенным блогером","2025-06-04 16:13:00.0","Фото: Сергей Бобылев / РИА Новости Анна Щербакова Командир спецназа «Ахмат», замначальника главного военно-политического управления Минобороны России генерал-лейтенант Апти Алаудинов фразой «потянулся бы за пистолетом» описал свою возможную встречу с похищенным в Москве бизнес-тренером Арегом Щепихиным. Он возмутился, что чеченцам вместо силовиков пришлось задерживать блогера. Свой комментарий относительно ситуации соратник главы Чечни опубликовал в Telegram-канале. Алаудинов в обращении отметил, что некий представитель «армянского происхождения» призывал к свержению власти, ненависти к мусульманам. По мнению чеченского командира, Щепихин вел себя как животное. «Как девочка визжит, когда его просто несут, просто петух», — сказал он. Алаудинов порекомендовал бизнес-тренеру «ехать к себе в ад». Командир «Ахмата» остался недоволен тем, что представителям республики пришлось самим задерживать Щепихина. Кроме того, Алаудинов подчеркнул, что «просто так оскорблять чеченский народ нельзя». До этого член Совета по правам человека при президенте России Ева Меркачева высказалась о задержании блогера в столице. По ее словам, похитившие Щепихина будто показывают, что они выше российских законов. В то же время она подчеркнула, что блогер, записывая оскорбительные ролики, вел себя неадекватно. 4 июня в Чечне прояснили судьбу увезенного на Mercedes со спецсигналами бизнес-тренера. В республике рассказали, что Щепихин подозревается по шести уголовным статьям. Щепихина увезли с Ярославского вокзала в Москве вечером 3 июня. Блогера протащили по всему вокзалу, затолкали в багажник черного Mercedes со спецсигналами и номерами серии АМР и увезли в неизвестном направлении. Позже Щепихин вышел на связь и рассказал подробности случившегося.","https://lenta.ru/news/2025/06/04/komandir-ahmata-otreagiroval-frazoy-potyanulsya-by-k-pistoletu-na-pohischenie/","алаудинов, щепихина, щепихин, просто, бизнес","https://icdn.lenta.ru/images/2025/06/04/15/20250604155828799/detail_c20a494b18dd244a9489b9b0cc8ebbff.jpg","Общее","Lenta.ru","1847146" +"Захарова предупредила о последствиях эскалации напряженности вокруг Ирана","2025-06-04 16:11:00.0","Фото: Natalia Shatokhina / NEWS.ru / Globallookpress.com Виктория Кондратьева Эскалация напряженности вокруг Ирана чревата проблемами не только для Исламской Республики. С таким предупреждением в ходе брифинга выступила официальный представитель МИД России Мария Захарова, передает корреспондент «Ленты.ру». Дипломат подчеркнула, что скатывание ситуации в полномасштабную конфронтацию обернется плачевными последствиями и для Ближнего Востока, и для всего мира. «Тем более с учетом глубокой зависимости мировой экономики от стабильных и непрерывных поставок энергоносителей из стратегически важного региона Персидского залива. Это чревато экологическими последствиями», — пояснила Захарова. Ранее стало известно, что импульс переговоров по заключению новой ядерной сделки между США и Ираном сходит на нет. Высокопоставленный иранский чиновник заявил, что новое предложение по ядерной сделке, представленное Тегерану в последние дни, является «несвязным и разрозненным». США изменили позицию по вопросу обогащения урана в новом предложении. Оно предполагает, что США могли бы инвестировать в гражданскую ядерную энергетическую программу Ирана и присоединиться к консорциуму, который будет контролировать обогащение низкоактивного урана внутри Ирана в течение неопределенного периода времени.","https://lenta.ru/news/2025/06/04/zaharova-predupredila-o-posledstviyah-eskalatsii-napryazhennosti-vokrug-irana/","ирана, захарова, вокруг, напряженности, урана","https://icdn.lenta.ru/images/2025/06/04/16/20250604161359703/detail_afff56e099d0c561485bcf0f4a46d0aa.jpg","Экономика","Lenta.ru","1847161" +"В МИД оценили значение обмена пленными после переговоров в Стамбуле","2025-06-04 16:10:00.0","Фото: Roman Naumov / Ura.ru / Globallookpress.com Глеб Палехов Обмен пленными, завершившийся 25 мая, — это колоссальный результат первого раунда переговоров с Украиной в Стамбуле. Значение гуманитарных мероприятий оценила в ходе еженедельного брифинга представитель МИД России Мария Захарова, передает корреспондент «Ленты.ру». «Вы считаете, это переговоры незначимые? Первый раунд — по тысяче человек вернулись домой. (...) Самый масштабный обмен удерживаемыми — тысяча на тысячу человек», — заявила дипломат. Она добавила, что этот результат особенно ценен, если учитывать низкий уровень договороспособности украинских властей. Во время брифинга Захарова заявила также, что украинским властям не нужны собственные граждане. Так она прокомментировала утверждение президента Украины Владимира Зеленского, что Россияв ходе предстоящего обмена якобы попытается передать Киеву тела не только украинских бойцов.","https://lenta.ru/news/2025/06/04/v-mid-otsenili-znachenie-obmena-plennymi-posle-peregovorov-v-stambule/","пленными, результат, захарова, человек, переговоров","https://icdn.lenta.ru/images/2025/06/04/16/20250604160746942/detail_99e2881674b8fa0d8c1b3940b6996620.jpg","Общее","Lenta.ru","1847155" +"Задержан начальник московского вокзала","2025-06-04 15:58:00.0","Фото: Иван Водопьянов / Коммерсантъ Илона Палей Начальника Казанского вокзала Москвы Игоря Черникова задержали по делу о посредничестве во взятке. Об этом сообщает «МК». По данным источника, задержание произошло несколько дней назад. Дорогомиловский районный суд Москвы взял Черникова под домашний арест. По версии следствия, фигуранта пытался подкупить предприниматель — он заплатил 1,3 миллиона рублей за общее покровительство при ведении бизнеса на территории вокзала. Ранее сообщалось, что в Москве суд отправил под домашний арест главу отдела департамента Минздрава. ","https://lenta.ru/news/2025/06/04/zaderzhan-nachalnik-moskovskogo-vokzala/","вокзала, арест, черникова, москвы, домашний","https://icdn.lenta.ru/images/2025/06/04/15/20250604155628244/detail_a0bfb37152da2a103caa8c143d99913b.jpg","Общее","Lenta.ru","1847136" +"Три рейса ушли на запасные аэродромы из-за непогоды в Москве","2025-06-08 18:41:22.0","","https://www.rbc.ru/rbcfreenews/6845aefe9a7947822df37ac4","запасные, ушли, москве, аэродромы, рейса","https://s0.rbk.ru/v6_top_pics/media/img/1/18/347493976336181.jpeg","Общее","RBC","6845aefe9a7947822df37ac4" +"Пожар в ТЦ в Москве","2025-06-08 18:29:58.0","Видео","https://www.rbc.ru/?video_id=6845ad579a794765a5aaa9c3","москве, пожар, видео","https://s0.rbk.ru/v6_top_pics/media/img/0/49/347493968046490.jpeg","Общее","RBC","6845ad579a794765a5aaa9c3" +"В Башкирии составили обобщенный портрет муниципального служащего","2025-06-08 18:25:36.0","","https://ufa.rbc.ru/ufa/08/06/2025/6845aaed9a794731f5a05117","составили, муниципального, портрет, башкирии, обобщенный","https://s0.rbk.ru/v6_top_pics/media/img/5/05/347493964719055.jpeg","Общее","RBC","6845aaed9a794731f5a05117" +"Глушенков и Батраков не сыграют за сборную России против Белоруссии","2025-06-08 18:23:49.0","Карпин отпустил в расположение клубов Батракова, Глушенкова и Осипенко, вратарь Сафонов уедет в Париж 9 июня. На матч с Белоруссией в Минск отправились 23 игрока","https://www.rbc.ru/sport/08/06/2025/6845a7f19a79472ff3a34fc2","париж, россии, белоруссии, вратарь, матч","https://s0.rbk.ru/v6_top_pics/media/img/1/24/347493956864241.png","Спорт","RBC","6845a7f19a79472ff3a34fc2" +"Во Внуково подтопило терминал из-за ливня","2025-06-08 18:18:36.0","Видео","https://www.rbc.ru/?video_id=6845aa159a7947de8876ea58","внуково, подтопило, терминал, видео, ливня","https://s0.rbk.ru/v6_top_pics/media/img/9/78/347493959875789.jpeg","Общее","RBC","6845aa159a7947de8876ea58" +"Зеленский заявил о передаче обещанных Украине ракет на Ближний Восток","2025-06-08 18:17:08.0","Зеленский посетовал, что администрация Трампа отказалась передать Киеву 20 тыс. ракет, об отправке которых тот условился с США при Байдене. Вместо этого, по словам президента, они были отправлены на Ближний Восток","https://www.rbc.ru/politics/08/06/2025/6845a08a9a794734b3f2c979","ближний, ракет, зеленский, восток, администрация","https://s0.rbk.ru/v6_top_pics/media/img/2/59/347493954690592.jpeg","Политика","RBC","6845a08a9a794734b3f2c979" +"Мощный ливень в Москве. Видео","2025-06-08 18:12:26.0","","https://www.rbc.ru/rbcfreenews/6845a5ca9a79472067c4dffe","москве, ливень, мощный, видео","https://s0.rbk.ru/v6_top_pics/media/img/0/77/347493950081770.jpeg","Общее","RBC","6845a5ca9a79472067c4dffe" +"Мощный ливень в Москве","2025-06-08 18:04:52.0","Видео","https://www.rbc.ru/?video_id=6845a6a59a7947bad8b4bf7e","москве, ливень, мощный, видео","https://s0.rbk.ru/v6_top_pics/media/img/3/92/347493951068923.jpeg","Общее","RBC","6845a6a59a7947bad8b4bf7e" +"Режиссеру Беркович запретили ставить спектакли в колонии","2025-06-08 18:03:51.0","","https://www.rbc.ru/rbcfreenews/6845a22d9a794713721cf8fd","ставить, спектакли, колонии, режиссеру, беркович","https://s0.rbk.ru/v6_top_pics/media/img/5/97/347493944414975.jpeg","Общее","RBC","6845a22d9a794713721cf8fd" +"Мбаппе принес Франции победу над Германией в матче за бронзу Лиги наций","2025-06-08 17:57:55.0","Матч завершился со счетом 2:0 — у Мбаппе гол и результативная передача на Олисе. VAR отменила назначенный пенальти в ворота французов и гол Ундава","https://www.rbc.ru/sport/08/06/2025/68459bc99a794763cc6a1cbd","мбаппе, германией, ворота, матч, наций","https://s0.rbk.ru/v6_top_pics/media/img/6/74/347493926580746.jpeg","Спорт","RBC","68459bc99a794763cc6a1cbd" +"Клишас заявил о начале «денацификации» Днепропетровской области","2025-06-08 17:34:31.0","","https://www.rbc.ru/rbcfreenews/68459a549a794760b5160e72","днепропетровской, клишас, начале, заявил, денацификации","https://s0.rbk.ru/v6_top_pics/media/img/2/33/347493938355332.jpeg","Общее","RBC","68459a549a794760b5160e72" +"Дмитриев назвал диалог Путина и Трампа возможностью для деэскалации","2025-06-08 17:06:39.0","","https://www.rbc.ru/rbcfreenews/6845935f9a7947043aa88752","дмитриев, трампа, деэскалации, путина, диалог","https://s0.rbk.ru/v6_top_pics/media/img/2/30/347493919760302.jpeg","Политика","RBC","6845935f9a7947043aa88752" +"Президент Литвы раскритиковал Мерца за невыполненное обещание о санкциях","2025-06-08 17:05:09.0","Мерц не выполнил обещание о санкциях против России, чем подорвал доверие к ЕС, считает Науседа. По его мнению, в рамках 18-го пакета должны быть введены санкции против энергокомпаний, а от SWIFT нужно отключить все банки России","https://www.rbc.ru/politics/08/06/2025/68458f3c9a7947b445246151","россии, обещание, против, санкциях, науседа","https://s0.rbk.ru/v6_top_pics/media/img/5/30/347493899020305.jpeg","Экономика","RBC","68458f3c9a7947b445246151" +"Bank of America назвал рубль «самой успешной» валютой 2025 года","2025-06-08 17:03:41.0","Рубль вырос более чем на 40% по отношению к доллару в 2025 году и стал «самой успешной» валютой в мире, подсчитал Bank of America. Эксперты объяснили это высокой ключевой ставкой и «затовариванием» некоторых сегментов рынка","https://www.rbc.ru/economics/08/06/2025/684594739a79475cfdb14750","america, bank, успешной, рубль, самой","https://s0.rbk.ru/v6_top_pics/media/img/6/41/347493910283416.jpeg","Экономика","RBC","684594739a79475cfdb14750" +"Главные новости. Выпуск за 17:00, 08.06.2025","2025-06-08 17:00:00.0","","https://tv.rbc.ru/archive/news/684599a82ae596bdf8d904ac","выпуск, главные, новости","https://s0.rbk.ru/v6_top_pics/media/img/1/33/347223478378331.png","Общее","RBC","684599a82ae596bdf8d904ac" +"На Дону возбудили уголовные дела против группы с оборотом более ₽512 млн","2025-06-08 16:48:56.0","","https://rostov.rbc.ru/rostov/freenews/684594849a794707c921e9ec","более, уголовные, группы, дону, против","null","Общее","RBC","684594849a794707c921e9ec" +"Экс-тренер «Зенита» объявил об отставке из сборной Италии","2025-06-08 16:41:34.0","Спаллетти возглавлял сборную Италии с лета 2023 года. Он проиграл с командой в 1/8 финала Евро и неудачно стартовал в отборе ЧМ-2026","https://www.rbc.ru/sport/08/06/2025/68458e169a79470198af24e0","италии, финала, отборе, евро, зенита","https://s0.rbk.ru/v6_top_pics/media/img/8/00/347493900461008.jpeg","Экономика","RBC","68458e169a79470198af24e0" +"Кандидата в президенты Колумбии прооперировали после покушения","2025-06-08 16:36:21.0","","https://www.rbc.ru/rbcfreenews/68458ecf9a7947dc736ef73f","колумбии, покушения, кандидата, прооперировали, президенты","https://s0.rbk.ru/v6_top_pics/media/img/9/05/347493894391059.jpeg","Общее","RBC","68458ecf9a7947dc736ef73f" +"Воскресный тираж лотереи сделал рублевыми миллионерами 18 россиян","2025-06-08 16:18:03.0","","https://www.rbc.ru/rbcfreenews/684589629a7947096da87817","россиян, миллионерами, воскресный, сделал, рублевыми","https://s0.rbk.ru/v6_top_pics/media/img/8/12/347493884956128.jpeg","Общее","RBC","684589629a7947096da87817" +"Буданов раскрыл, когда Киев заберет тела в рамках обмена с Россией","2025-06-08 16:15:53.0","Обмен, по словам Буданова, должен начаться на следующей неделе, о чем уполномоченные были уведомлены еще 3 июня. Россия уже доставила первую партию тел погибших в район обмена и ожидает подтверждения от Киева","https://www.rbc.ru/politics/08/06/2025/6845875e9a79472f23a4c622","обмена, уполномоченные, уведомлены, подтверждения, партию","https://s0.rbk.ru/v6_top_pics/media/img/3/50/347493874348503.jpeg","Общее","RBC","6845875e9a79472f23a4c622" +"Израиль пообещал перехватить «флотилию Греты Тунберг» с грузом для Газы","2025-06-08 16:14:05.0","Судно Madleen с активисткой и гуманитарной помощью на борту в течение 48 часов должно прибыть к сектору Газа, пишет JPost. Глава Минобороны Израиля призвал «флотилию» развернуться и пообещал, что она не достигнет анклава","https://www.rbc.ru/politics/08/06/2025/68457bab9a79472dca7f5e11","пообещал, флотилию, jpost, судно, призвал","https://s0.rbk.ru/v6_top_pics/media/img/6/72/347493849885726.jpeg","Общее","RBC","68457bab9a79472dca7f5e11" +"Главу «Марафона» избрали президентом Федерации тяжелой атлетики России","2025-06-08 16:11:32.0","У Александра Винокурова не было конкурентов. Он сменил на посту Дмитрия Василенко. Тот победил на выборах в ноябре, но федерации вскоре приостановили аккредитацию","https://www.rbc.ru/sport/08/06/2025/684584ac9a79472da127bae2","федерации, тяжелой, россии, василенко, президентом","https://s0.rbk.ru/v6_top_pics/media/img/8/25/347493868997258.png","Общее","RBC","684584ac9a79472da127bae2" +"В Крыму приступил к работе новый апарт-отель Alba del Mare","2025-06-08 16:07:58.0","В Евпатории начал работать новый объект курортной недвижимости петербургского девелопера","https://companies.rbc.ru/news/rvd57m528V/v-kryimu-pristupil-k-rabote-novyij-apart-otel-alba-del-mare/","новый, евпатории, курортной, крыму, работе","https://pics.rbc.ru/v2_companies_s3/resized/960xH/media/company_press_release_image/a5a7e770-d49e-4631-930f-3f0369f188dd.jpg","Отдых","RBC","rvd57m528V" +"Удар молнии по Останкинской башне не повлиял на ее работу","2025-06-08 16:07:48.0","","https://www.rbc.ru/rbcfreenews/684582fa9a7947d7dc40befc","удар, повлиял, башне, молнии, работу","https://s0.rbk.ru/v6_top_pics/media/img/0/53/347493862186530.jpeg","СВО","RBC","684582fa9a7947d7dc40befc" +"ЧЭЗ. Выпуск от 08.06.2025, часть 3","2025-06-08 15:58:25.0","","https://tv.rbc.ru/archive/chez/684593872ae596bdf8d904ab","выпуск, часть","https://s0.rbk.ru/v6_top_pics/media/img/2/77/346903554139772.jpeg","Общее","RBC","684593872ae596bdf8d904ab" +"Из-за атаки ВСУ часть Курской области осталась без света","2025-06-08 15:45:17.0","ВСУ ударили по подстанции 110 кВ «Рыльск», без света остались жители Рыльского, Глушковского и Кореневского районов, сообщил Хинштейн. Он анонсировал возобновление энергоснабжения, «как только позволит оперативная обстановка»","https://www.rbc.ru/politics/08/06/2025/684585cb9a79472ccb947aa2","света, рыльск, остались, обстановка, позволит","https://s0.rbk.ru/v6_top_pics/media/img/0/41/347493871855410.jpeg","Общее","RBC","684585cb9a79472ccb947aa2" +"Медведев предрек «новые реалии» из-за боев в Днепропетровской области","2025-06-08 15:41:58.0","Киев и его союзников предупреждали, что те, кто не хочет признавать «реалии войны» на переговорах, столкнутся с новыми реалиями на земле, написал Медведев. Минобороны заявило, что силы России вышли в Днепропетровскую область","https://www.rbc.ru/politics/08/06/2025/68457fb09a79477d1d6bc309","реалии, медведев, россии, написал, признавать","https://s0.rbk.ru/v6_top_pics/media/img/7/83/347493861588837.jpeg","Общее","RBC","68457fb09a79477d1d6bc309" +"ЧЭЗ. Выпуск от 08.06.2025, часть 2","2025-06-08 15:37:50.0","","https://tv.rbc.ru/archive/chez/68458af72ae596bdf8d904aa","выпуск, часть","https://s0.rbk.ru/v6_top_pics/media/img/2/77/346903554139772.jpeg","Общее","RBC","68458af72ae596bdf8d904aa" +"Воробьев рассказал о последствиях атак дронов в Подмосковье","2025-06-08 15:19:59.0","Над Московским регионом сбили десять беспилотников, из-за падения одного из них пострадала 75-летняя женщина, поврежден жилой дом, сообщил Воробьев. В Истре поврежден фасад жилого дома","https://www.rbc.ru/politics/08/06/2025/68457f529a7947c805c39c5d","воробьев, поврежден, московским, пострадала, одного","https://s0.rbk.ru/v6_top_pics/media/img/3/01/347493857070013.jpeg","СВО","RBC","68457f529a7947c805c39c5d" +"«Умный дом» в загородном коттедже: что продумать до начала строительства","2025-06-08 15:19:52.0","Как построить дом, готовый к технологиям будущего? 6 обязательных элементов для умного дома, которые надо предусмотреть заранее","https://companies.rbc.ru/news/ejoNtzDqoJ/umnyij-dom-v-zagorodnom-kottedzhe-chto-produmat-do-nachala-stroitelstva/","надо, предусмотреть, коттедже, продумать, умного","https://pics.rbc.ru/v2_companies_s3/resized/960xH/media/company_press_release_image/5b3176b9-bd34-4176-8366-29baeb90ed03.jpg","Экономика","RBC","ejoNtzDqoJ" +"РЖД усилят меры безопасности после теракта с поездом в Брянской области","2025-06-08 15:13:40.0","РЖД уже принимают меры для обеспечения безопасности на железных дорогах, в том числе против беспилотников. Однако произошедшее в Брянской области показало, что этого недостаточно, и меры надо усилить, сказал Белозеров","https://www.rbc.ru/society/08/06/2025/684575899a794740c4920f41","меры, брянской, области, безопасности, обеспечения","https://s0.rbk.ru/v6_top_pics/media/img/0/25/347493830898250.jpeg","Общее","RBC","684575899a794740c4920f41" +"Над Черным морем сбили украинскую ракету «Нептун-МД»","2025-06-08 15:10:28.0","","https://www.rbc.ru/rbcfreenews/68457d3d9a7947b23cfae39a","нептун, ракету, морем, украинскую, черным","https://s0.rbk.ru/v6_top_pics/media/img/6/90/347493856256906.jpeg","Общее","RBC","68457d3d9a7947b23cfae39a" +"ЧЭЗ. Выпуск от 08.06.2025, часть 1","2025-06-08 15:04:43.0","","https://tv.rbc.ru/archive/chez/684586d82ae596bdf8d904a9","выпуск, часть","https://s0.rbk.ru/v6_top_pics/media/img/2/77/346903554139772.jpeg","Общее","RBC","684586d82ae596bdf8d904a9" +"Захарова задалась вопросом о решении Украины не принимать тела на обмен","2025-06-08 15:03:22.0","","https://www.rbc.ru/rbcfreenews/6845715c9a7947f1ef2bd376","решении, принимать, тела, украины, задалась","https://s0.rbk.ru/v6_top_pics/media/img/0/80/347493818772800.jpeg","Общее","RBC","6845715c9a7947f1ef2bd376" +"Клопы вдохновили инженеров на создание экологичного фасада дома","2025-06-08 15:00:00.0","Инженеры Штутгартского университета в Германии изучили строение насекомоядного растения и полосатого клопа, чтобы разработать устойчивую систему фасадов. Рассказываем, чем она примечательна","https://trends.rbc.ru/trends/green/68429cfa9a794764ddd3e6ed","систему, клопа, примечательна, фасадов, изучили","https://s0.rbk.ru/v6_top_pics/media/img/7/45/347491962968457.jpeg","Общее","RBC","68429cfa9a794764ddd3e6ed" +"Главные новости. Выпуск за 15:00, 08.06.2025","2025-06-08 15:00:00.0","","https://tv.rbc.ru/archive/news/68457d542ae596bdf8d904a8","выпуск, главные, новости","https://s0.rbk.ru/v6_top_pics/media/img/1/33/347223478378331.png","Общее","RBC","68457d542ae596bdf8d904a8" +"Выбившие россиянок в полуфинале итальянки выиграли «Ролан Гаррос» в парах","2025-06-08 14:53:24.0","Сара Эррани и Джасмин Паолини в финале обыграли уроженку Москвы Анну Данилину и сербку Александру Крунич — 6:4, 2:6, 6:1. Эррани ранее выиграла «Ролан Гаррос» и в миксте","https://www.rbc.ru/sport/08/06/2025/684577519a79473b0e9ef567","ролан, гаррос, эррани, сара, паолини","https://s0.rbk.ru/v6_top_pics/media/img/1/53/347493830406531.jpeg","Общее","RBC","684577519a79473b0e9ef567" +"Минобороны сообщило о доставке первой партии тел для передачи Украине","2025-06-08 14:52:24.0","Видео","https://www.rbc.ru/?video_id=684579989a79472ec49ff75e","сообщило, минобороны, партии, передачи, первой","https://s0.rbk.ru/v6_top_pics/media/img/6/55/347493835753556.jpeg","Общее","RBC","684579989a79472ec49ff75e" +"Степашин заявил, что Россия «прозевала» Украину в 1990-х","2025-06-08 14:51:14.0","Мнение, что Украина «никуда не денется», было «стратегической ошибкой» России в 1990-х годах, и теперь «приходится наверстывать упущенное», заявил экс-премьер","https://www.rbc.ru/politics/08/06/2025/68456be39a794767ed5fca19","заявил, украина, стратегической, россии, упущенное","https://s0.rbk.ru/v6_top_pics/media/img/5/08/347493809054085.jpeg","Общее","RBC","68456be39a794767ed5fca19" +"Пермской IT-компании «Пармалогика» грозит банкротство","2025-06-08 14:50:28.0","","https://perm.rbc.ru/perm/freenews/684566cf9a7947018ec22658","банкротство, пармалогика, грозит, компании, пермской","https://s0.rbk.ru/v6_top_pics/media/img/9/51/347493831595519.png","Экономика","RBC","684566cf9a7947018ec22658" +"«Инвесторы замерли в ожидании». Что будет с биткоином в ближайшую неделю","2025-06-08 14:45:54.0","Эксперты проанализировали ситуацию на рынке и рассказали, как она может измениться на предстоящей неделе","https://www.rbc.ru/crypto/news/684572909a7947ec4027c4b6","проанализировали, предстоящей, может, измениться, рынке","https://s0.rbk.ru/v6_top_pics/media/img/7/17/347493826695177.jpeg","Общее","RBC","684572909a7947ec4027c4b6" +"Армянское церковное попечительство в Краснодаре отремонтируют за ₽37 млн","2025-06-08 14:42:24.0","","https://kuban.rbc.ru/krasnodar/freenews/684576b29a794706b7c5c190","краснодаре, армянское, отремонтируют, попечительство, церковное","null","Общее","RBC","684576b29a794706b7c5c190" +"Соболенко назвала «какой-то шуткой» условия в финале «Ролан Гаррос»","2025-06-08 14:26:27.0","Арина Соболенко в финале «Ролан Гаррос» в трех сетах проиграла Кори Гауфф. Белоруска пожаловалась на коварный ветер и заявила, что американка воспользовалась ее ошибками","https://www.rbc.ru/sport/08/06/2025/68456bd29a794767ed5fca15","соболенко, ролан, гаррос, финале, кори","https://s0.rbk.ru/v6_top_pics/media/img/9/67/347493802010679.png","Общее","RBC","68456bd29a794767ed5fca15" +"Гладков рассказал о последствиях атаки дронов на парковку под Белгородом","2025-06-08 14:24:14.0","Под удар дважды попала парковка магазина в селе Никольское. Пострадали пять человек, загорелась легковая машина","https://www.rbc.ru/politics/08/06/2025/6845717d9a79471754c068b7","загорелась, удар, пять, последствиях, рассказал","https://s0.rbk.ru/v6_top_pics/media/img/6/24/347493817707246.jpeg","СВО","RBC","6845717d9a79471754c068b7" +"Москвичам посоветовали сидеть дома из-за ливня и грозы","2025-06-08 14:15:32.0","","https://www.rbc.ru/rbcfreenews/68456da99a7947e6bd227f96","дома, москвичам, сидеть, посоветовали, грозы","https://s0.rbk.ru/v6_top_pics/media/img/8/09/347493810721098.jpeg","Общее","RBC","68456da99a7947e6bd227f96" +"Неонацистская группа с уехавшим в Россию лидером анонсировала сбор в США","2025-06-08 14:10:55.0","Неонацистская группировка The Base, с которой в прошлом боролось ФБР, решила активизироваться в США после победы Трампа, пишет The Guardian. Ее основатель, экс-подрядчик Пентагона, с 2018 года живет в России","https://www.rbc.ru/politics/08/06/2025/684562239a7947831563e493","неонацистская, россии, лидером, трампа, после","https://s0.rbk.ru/v6_top_pics/media/img/7/06/347493814583067.jpeg","Политика","RBC","684562239a7947831563e493" +"Первая летняя гроза в Москве","2025-06-08 14:10:38.0","Видео","https://www.rbc.ru/?video_id=68456fc99a79478f63f224c6","гроза, москве, первая, летняя, видео","https://s0.rbk.ru/v6_top_pics/media/img/8/12/347493810606128.jpeg","Общее","RBC","68456fc99a79478f63f224c6" +"Грузинский чемпион UFC бросился к Трампу после защиты титула","2025-06-08 14:09:31.0","Двалишвили удушающим приемом победил О'Мэлли, после чего перелез через клетку к Трампу, несколько раз пожал ему руку и сфотографировался. Другие бойцы тоже подходили к президенту США после поединков","https://www.rbc.ru/sport/08/06/2025/6845669f9a794730da512f8c","после, трампу, чемпион, клетку, тоже","https://s0.rbk.ru/v6_top_pics/media/img/2/02/347493798126022.png","Политика","RBC","6845669f9a794730da512f8c" +"Генерал Зорин заявил об отправке к границе поездов с телами военных ВСУ","2025-06-08 14:06:58.0","","https://www.rbc.ru/rbcfreenews/6845661d9a79475ffdb33742","генерал, отправке, телами, военных, заявил","https://s0.rbk.ru/v6_top_pics/media/img/8/20/347493790197208.jpeg","Общее","RBC","6845661d9a79475ffdb33742" +"Главные новости. Выпуск за 14:00, 08.06.2025","2025-06-08 14:00:00.0","","https://tv.rbc.ru/archive/news/68456f572ae596bdf8d904a5","выпуск, главные, новости","https://s0.rbk.ru/v6_top_pics/media/img/1/33/347223478378331.png","Общее","RBC","68456f572ae596bdf8d904a5" +"Минобороны показало кадры водружения российского флага над Зарей в ДНР","2025-06-08 13:56:08.0","Видео","https://www.rbc.ru/?video_id=68456c699a7947dcf56b8834","российского, показало, минобороны, зарей, водружения","https://s0.rbk.ru/v6_top_pics/media/img/0/60/347493801971600.jpeg","Общее","RBC","68456c699a7947dcf56b8834" +"Какие высокооплачиваемые профессии ИИ «удешевит» в первую очередь","2025-06-08 13:54:55.0","Чем выше зарплата, тем выше шанс, что ИИ выполнит вашу работу. Хотя полностью отобрать ее он пока не способен. О том, каких профессий это касается и как нейросети их меняют, рассказывает CEO Университета «Зерокодер» Кирилл Пшинник","https://pro.rbc.ru/demo/683815639a7947ddc0332569","выше, вашу, нейросети, зерокодер, какие","https://s0.rbk.ru/v6_top_pics/media/img/6/69/347489798149696.png","Общее","RBC","683815639a7947ddc0332569" +"Мераб Двалишвили пожал руку Трампу","2025-06-08 13:49:14.0","Видео","https://www.rbc.ru/?video_id=68456abf9a79472e07d96eb1","пожал, руку, трампу, двалишвили, мераб","https://s0.rbk.ru/v6_top_pics/media/img/0/75/347493797747750.jpeg","Политика","RBC","68456abf9a79472e07d96eb1" +"Глава СК раскрыл детали подрывов железнодорожных путей","2025-06-08 13:48:44.0","Исполнители недавних подрывов ж/д путей и моста использовали украинский блок управления Lora, что говорит о причастности спецслужб Украины, заявил Бастрыкин. СК квалифицировал произошедшее как теракты","https://www.rbc.ru/politics/08/06/2025/684560789a794719b0d85ca8","путей, подрывов, недавних, lora, спецслужб","https://s0.rbk.ru/v6_top_pics/media/img/1/40/347493773176401.jpeg","Общее","RBC","684560789a794719b0d85ca8" +"Спорт. Выпуск за 13:48, 08.06.2025","2025-06-08 13:48:30.0","","https://tv.rbc.ru/archive/newssport/68456c6f2ae596bdf8d904a4","спорт, выпуск","https://s0.rbk.ru/v6_top_pics/media/img/2/94/347223477149942.png","Спорт","RBC","68456c6f2ae596bdf8d904a4" +"Автодилеры Краснодара рассказали о ценах на новые авто летом 2025 г.","2025-06-08 13:45:00.0","","https://kuban.rbc.ru/krasnodar/freenews/684564069a7947831563e498","рассказали, ценах, летом, автодилеры, авто","null","Общее","RBC","684564069a7947831563e498" +"Автодилеры Дона рассказали о ценах на новые авто летом 2025 г.","2025-06-08 13:45:00.0","","https://rostov.rbc.ru/rostov/freenews/684565699a7947831563e4a3","рассказали, ценах, летом, дона, автодилеры","null","Общее","RBC","684565699a7947831563e4a3" +"Автодилеры Ставрополя рассказали о ценах на новые авто летом 2025 г.","2025-06-08 13:45:00.0","","https://kavkaz.rbc.ru/kavkaz/freenews/684563049a7947a4617dba24","рассказали, ценах, летом, автодилеры, авто","null","Общее","RBC","684563049a7947a4617dba24" diff --git a/news-aggregator/lib/postgresql-42.7.6.jar b/news-aggregator/lib/postgresql-42.7.6.jar new file mode 100644 index 0000000..8f3bc82 Binary files /dev/null and b/news-aggregator/lib/postgresql-42.7.6.jar differ diff --git a/news-aggregator/pom.xml b/news-aggregator/pom.xml new file mode 100644 index 0000000..69118f7 --- /dev/null +++ b/news-aggregator/pom.xml @@ -0,0 +1,55 @@ + + 4.0.0 + + com.example + news-aggregator + 1.0-SNAPSHOT + + + 21 + 21 + UTF-8 + + + + + org.jsoup + jsoup + 1.15.3 + + + + org.postgresql + postgresql + 42.6.0 + + + junit + junit + RELEASE + compile + + + org.junit.jupiter + junit-jupiter + RELEASE + compile + + + + + + + maven-compiler-plugin + 3.11.0 + + 21 + 21 + + + + + diff --git a/news-aggregator/src/main/java/com/example/news/Main.java b/news-aggregator/src/main/java/com/example/news/Main.java new file mode 100644 index 0000000..9967be3 --- /dev/null +++ b/news-aggregator/src/main/java/com/example/news/Main.java @@ -0,0 +1,150 @@ +package com.example.news; + +import java.util.Scanner; +import java.util.Timer; +import java.util.TimerTask; + +public class Main { + + private static void ShowLegend() { + System.out.println("\n 0. Выход \t\t\t\t\t 1. Сколько отображать новостей \t 2. Сортировать по дате \t\t\t 3. Сортировать по источнику"); + System.out.println(" 5. Собрать новости \t\t 6. Показать последние \t\t\t\t 7. Показать последние (все поля) \t 8. Экспорт в CSV"); + System.out.println("10. Поиск по слову (везде)\t11. Поиск в заголовке\t\t\t\t 12. Поиск в тексте \t\t\t\t13. Поиск по дате (формат YYYY-MM-DD) \t14. Поиск по категории\t\t\t15. Поиск по источнику"); + System.out.println("16. Список источников \t\t17. Добавить RBC и Lenta \t\t\t 18. Добавить свой источник \t\t19. Удалить источник \t\t\t\t\t20. Аналитика-статистика\t\t"); + System.out.print("Выберите опцию: "); + } + + + public static void main(String[] args) { + NewsAggregator aggregator = new NewsAggregator(); + Scanner scanner = new Scanner(System.in); + Object lock = new Object(); + + + Timer timer = new Timer(); // Поток таймера, который выводит сообщение раз в минуту + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run () { + + synchronized (lock) { + System.out.println("\n\nАвто обновление:"); + aggregator.fetchAndStore(); + System.out.println("Обновлённая лента:"); + aggregator.ShowNews(); + ShowLegend(); + } + } + }, 20 * 1000, 60 * 1000); // 60 * 1000 = 1 минута + + // Поток, который слушает ввод пользователя + Thread inputThread = new Thread(() -> { + + while (true) { + ShowLegend(); + String input = scanner.nextLine(); + synchronized (lock) { + switch (input) { + case "0" -> { + timer.cancel(); // Остановить таймер + aggregator.CloseDB(); + System.out.println("До скорых встреч!"); + return; + } + case "1" -> { + System.out.print("Введите количество новостей: "); + try { + aggregator.SetСountRows(Integer.parseInt(scanner.nextLine())); + } catch (NumberFormatException e) { + System.out.println("Это не число!"); + } + } + case "2" -> aggregator.SetSortByDate(); + case "3" -> aggregator.SetSortBySource(); + case "5" -> aggregator.fetchAndStore(); + case "6" -> aggregator.ShowNews(); + case "7" -> aggregator.ShowNewsFull(); + case "8" -> aggregator.exportToCSV(); + case "10" -> { + System.out.print("Ключевое слово: "); + aggregator.FindNews_Title_And_Text(scanner.nextLine()); + } + case "11" -> { + System.out.print("Ключевое слово: "); + aggregator.FindNews_Title(scanner.nextLine()); + } + case "12" -> { + System.out.print("Ключевое слово: "); + aggregator.FindNews_Text(scanner.nextLine()); + } + case "13" -> { + System.out.print("Введите дату (формат YYYY-MM-DD): "); + aggregator.FindNews_Date(scanner.nextLine()); + } + case "14" -> { + System.out.print("Введите категорию (Политика, Экономика, Спорт, Культура, СВО, Отдых или Общее): "); + aggregator.FindNews_Category(scanner.nextLine()); + } + case "15" -> { + System.out.print("Имя Источника (RBC, Lenta.ru...): "); + + aggregator.FindNews_Source(scanner.nextLine()); + + } + case "16" -> aggregator.listSources(); + case "17" -> { + aggregator.insertSource("title", "publish_date_t", "body", "fronturl", "picture", "RBC", "id", "https://www.rbc.ru/search/ajax/?dateFrom=", "items"); + aggregator.insertSource("title", "pubdate", "text", "url", "image_url", "Lenta.ru", "docid", "https://lenta.ru/search/v2/process?modified%2Cfrom=", "matches"); + } + case "18" -> { + System.out.print("Название поля для Title: "); + String title = scanner.nextLine(); + + System.out.print("Название поля для Date: "); + String date = scanner.nextLine(); + + System.out.print("Название поля для Summary: "); + String summary = scanner.nextLine(); + + System.out.print("Название поля для URL: "); + String url = scanner.nextLine(); + + System.out.print("Название поля для Video/Photo URL: "); + String media = scanner.nextLine(); + + System.out.print("Имя источника: "); + String source = scanner.nextLine(); + + System.out.print("Название поля для Source ID: "); + String sourceId = scanner.nextLine(); + + System.out.print("URl источника для Json: "); + String json_url = scanner.nextLine(); + + System.out.print("Название корня для JSON: "); + String root_tag = scanner.nextLine(); + + aggregator.insertSource(title, date, summary, url, media, source, sourceId, json_url, root_tag); + } + case "19" -> { + System.out.print("Введите ID источника для удаления: "); + try { + int id = Integer.parseInt(scanner.nextLine()); + aggregator.deleteSourceById(id); + } catch (NumberFormatException e) { + System.out.println("Некорректный ID"); + } + } + case "20" -> { + aggregator.Analitics(); + } + default -> System.out.println("Неверный выбор"); + } + } + } + + }); + + inputThread.setDaemon(false); // основной поток не завершится, пока этот жив + inputThread.start(); + } +} \ No newline at end of file diff --git a/news-aggregator/src/main/java/com/example/news/NewsAggregator.java b/news-aggregator/src/main/java/com/example/news/NewsAggregator.java new file mode 100644 index 0000000..6493240 --- /dev/null +++ b/news-aggregator/src/main/java/com/example/news/NewsAggregator.java @@ -0,0 +1,103 @@ +package com.example.news; + + +import com.example.news.db.DatabaseManager; +import com.example.news.model.NewsArticle; +import com.example.news.model.Source; +import com.example.news.parser.NewsParser; + +import java.time.LocalDate; +import java.util.List; + + +public class NewsAggregator { + + private final NewsParser rbcParser = new NewsParser(); + private final DatabaseManager db = new DatabaseManager(); + + + public void CloseDB() { + db.close(); + } + + public void SetСountRows(int new_count_rows) { + db.SetСountRows(new_count_rows); + } + + public void SetSortByDate() { + db.SetSortByDate(); + System.out.print("Включена сортировка по дате"); + } + public void SetSortByPopularity() { + db.SetSortByPopularity(); + } + public void SetSortBySource() { + db.SetSortBySource(); + System.out.print("Включена сортировка по источнику"); + } + + public void fetchAndStore() { + List rbcNews = rbcParser.parse( (LocalDate.now().minusDays(4)).toString(), db.getAllSources()); + db.saveArticles(rbcNews); + } + + public void ShowNews() { + db.printRecentNews(); + } + + public void ShowNewsFull() { + db.printRecentNewsFull(); + } + + public void FindNews_Title_And_Text(String keyword) { + db.FindNews_Title_And_Text(keyword); + } + + public void FindNews_Title(String keyword) { + db.FindNews_Title(keyword); + } + + public void FindNews_Text(String keyword) { + db.FindNews_Text(keyword); + } + + public void FindNews_Date(String keyword) { + db.FindNews_Date(keyword); + } + + public void FindNews_Category(String keyword) { + db.FindNews_Category(keyword); + } + + public void FindNews_Source(String keyword) { + db.FindNews_Source(keyword); + } + + public void exportToCSV() { + db.exportToCSV(); + } + + public void listSources() { + db.listSources(); + } + + public void insertSource(String title_field_name, String date_field_name, String summary_field_name, String url_field_name, + String video_or_foto_url_field_name, String source, String sourceId_field_name, String json_url, String root_tag) { + db.insertSource(title_field_name, date_field_name, summary_field_name, url_field_name, video_or_foto_url_field_name, source, sourceId_field_name, json_url, root_tag); + } + + public void deleteSourceById(int id) { + db.deleteSourceById(id); + } + + public void Analitics() { + db.Analitics(); + } + + public List getAllSources() { + return db.getAllSources(); + } + + +} + diff --git a/news-aggregator/src/main/java/com/example/news/db/DatabaseManager.java b/news-aggregator/src/main/java/com/example/news/db/DatabaseManager.java new file mode 100644 index 0000000..c698342 --- /dev/null +++ b/news-aggregator/src/main/java/com/example/news/db/DatabaseManager.java @@ -0,0 +1,343 @@ +package com.example.news.db; + +import com.example.news.model.Source; +import com.example.news.model.NewsArticle; +import com.example.news.util.KeywordExtractor; + +import java.sql.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.io.FileInputStream; +import java.io.PrintWriter; + + +public class DatabaseManager { + private int count_rows = 10; + private String field_for_sort = "date DESC"; + private Connection conn; + + public DatabaseManager() { + try { + Properties props = new Properties(); + props.load(new FileInputStream("src/main/resources/config.properties")); + conn = DriverManager.getConnection(props.getProperty("db.url"), props); + createTable(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void createTable() throws SQLException { + Statement stmt = conn.createStatement(); + stmt.execute("CREATE TABLE IF NOT EXISTS news (" + + " id SERIAL PRIMARY KEY," + + " title TEXT," + + " date TIMESTAMP," + + " summary TEXT," + + " url TEXT," + + " keywords TEXT," + + " video_or_foto_url TEXT," + + " category TEXT," + + " source TEXT," + + " source_id TEXT," + + " CONSTRAINT unique_source_pair UNIQUE (source, source_id))"); + stmt.execute("CREATE TABLE IF NOT EXISTS sources (" + + " id SERIAL PRIMARY KEY," + + " title_field_name TEXT," + + " date_field_name TEXT," + + " summary_field_name TEXT," + + " url_field_name TEXT," + + " video_or_foto_url_field_name TEXT," + + " source TEXT," + + " source_id_field_name TEXT," + + " json_url TEXT, " + + " root_tag TEXT," + + " CONSTRAINT unique_source_pair2 UNIQUE (source, source_id_field_name))"); + } + + + public void insertSource(String title_field_name, String date_field_name, String summary_field_name, String url_field_name, + String video_or_foto_url_field_name, String source, String sourceId_field_name, String json_url, String root_tag) { + + try (PreparedStatement stmt = conn.prepareStatement("INSERT INTO sources (title_field_name, date_field_name, summary_field_name, url_field_name, video_or_foto_url_field_name, source, source_id_field_name, json_url, root_tag)\n" + + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)" + + "ON CONFLICT (source, source_id_field_name) DO NOTHING")) { + stmt.setString(1, title_field_name); + stmt.setString(2, date_field_name); + stmt.setString(3, summary_field_name); + stmt.setString(4, url_field_name); + stmt.setString(5, video_or_foto_url_field_name); + stmt.setString(6, source); + stmt.setString(7, sourceId_field_name); + stmt.setString(8, json_url); + stmt.setString(9, root_tag); + stmt.executeUpdate(); + System.out.println("Источник добавлен."); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public void deleteSourceById(int id) { + try (PreparedStatement stmt = conn.prepareStatement("DELETE FROM sources WHERE id = ?")) { + stmt.setInt(1, id); + int rows = stmt.executeUpdate(); + if (rows > 0) { + System.out.println("Источник удалён."); + } else { + System.out.println("Источник не найден."); + } + } catch (SQLException e) { + e.printStackTrace(); + } + } + + + public void Analitics() { + try { + System.out.println("Общее количество запросов по категориям:"); + System.out.println("Категория\tКоличество"); + ResultSet rs = conn.createStatement().executeQuery("select category, count(*) as cnt from news group by (category)order by 2 desc"); + while (rs.next()) { + System.out.println(String.format("%-8s", rs.getString("category")) + "\t" + rs.getString("cnt")); + } + System.out.println(); + System.out.println("Динамика запросов"); + System.out.println("Категория\tДата\t\tКоличество"); + rs = conn.createStatement().executeQuery("select category, date::date as dt, count(*) as cnt from news group by (category, date::date) order by 1,2 desc"); + while (rs.next()) { + System.out.println(String.format("%-8s", rs.getString("category")) + "\t" + rs.getString("dt")+ "\t" + rs.getString("cnt") ); + } + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public void listSources() { + List sources = getAllSources(); + for (Source source : sources) { + System.out.print(source.toString()); + } + } + + public List getAllSources() { + List sources = new ArrayList<>(); + try (ResultSet rs = conn.createStatement().executeQuery("SELECT * FROM sources ORDER BY id")) { + while (rs.next()) { + Source source = new Source( + rs.getInt("id"), + rs.getString("title_field_name"), + rs.getString("date_field_name"), + rs.getString("summary_field_name"), + rs.getString("url_field_name"), + rs.getString("video_or_foto_url_field_name"), + rs.getString("source"), + rs.getString("source_id_field_name"), + rs.getString("json_url"), + rs.getString("root_tag") + ); + sources.add(source); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return sources; + } + + +public void SetСountRows(int new_count_rows) { + count_rows = new_count_rows; + } + + public void SetSortByDate() { + field_for_sort = "date DESC"; + } + public void SetSortByPopularity() { + field_for_sort = "date DESC"; + } + public void SetSortBySource() { + field_for_sort = "source"; + } + + public void saveArticles(List articles) { + try { + PreparedStatement ps = conn.prepareStatement( + "INSERT INTO news(title, date, summary, url, keywords, video_or_foto_url, category, source, source_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)" + + "ON CONFLICT (source, source_id) DO NOTHING"); + for (NewsArticle a : articles) { + + ps.setString(1, a.getTitle()); + ps.setTimestamp(2, Timestamp.valueOf(a.getDate())); + ps.setString(3, a.getSummary()); + ps.setString(4, a.getUrl()); // TO DO категории + String keywords = String.join(", ", KeywordExtractor.extractKeywords(a.getSummary() + " " + a.getTitle(), 5)); + ps.setString(5, keywords); + ps.setString(6, a.getVideo_or_foto_url()); + ps.setString(7, a.getCategory()); // to do category + ps.setString(8, a.getSource()); + ps.setString(9, a.getSource_id()); + + + ps.addBatch(); + } + + int[] results = ps.executeBatch(); + int inserted = 0; + for (int result : results) { + if (result == 1) { + inserted++; + } + } + + System.out.println("Сохранено " + inserted + " свежих новостей."); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public void printRecentNews() { + try { + ResultSet rs = conn.createStatement().executeQuery("SELECT title, url, keywords, video_or_foto_url, summary, category, source, source_id FROM news ORDER BY " + field_for_sort + ", date DESC LIMIT " + count_rows); + while (rs.next()) { + System.out.println(rs.getString("title") + " — " + rs.getString("url")); + System.out.println("Краткое содержание:" + rs.getString("summary") + "\n"); + } + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public void printRecentNewsFull() { + try { + ResultSet rs = conn.createStatement().executeQuery("SELECT title, url, keywords, video_or_foto_url, summary, category, source, date FROM news ORDER BY " + field_for_sort + ", date DESC LIMIT " + count_rows); + while (rs.next()) { + + System.out.println(rs.getString("title") + " — " + rs.getString("url")); + System.out.println("Краткое содержание: " + rs.getString("summary")); + System.out.println("Дата: " + rs.getString("date") + " Ключевые слова: "+ rs.getString("keywords")); + System.out.println("Кагория: " + rs.getString("category") + " Источник: " + rs.getString("source") + " Фото/видео: " + rs.getString("video_or_foto_url") + "\n"); + } + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public void FindNews_Title_And_Text(String keyword) { + try { + PreparedStatement ps = conn.prepareStatement( + "SELECT title, url FROM news WHERE LOWER(title||summary) LIKE ? ORDER BY " + field_for_sort + ", date DESC LIMIT " + count_rows ); + ps.setString(1, "%" + keyword.toLowerCase() + "%"); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + System.out.println(rs.getString("title") + " — " + rs.getString("url")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public void FindNews_Title(String keyword) { + try { + PreparedStatement ps = conn.prepareStatement( + "SELECT title, url FROM news WHERE LOWER(title) LIKE ? ORDER BY " + field_for_sort + ", date DESC LIMIT " + count_rows ); + ps.setString(1, "%" + keyword.toLowerCase() + "%"); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + System.out.println(rs.getString("title") + " — " + rs.getString("url")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public void FindNews_Text(String keyword) { + try { + PreparedStatement ps = conn.prepareStatement( + "SELECT title, url FROM news WHERE LOWER(summary) LIKE ? ORDER BY " + field_for_sort + ", date DESC LIMIT " + count_rows ); + ps.setString(1, "%" + keyword.toLowerCase() + "%"); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + System.out.println(rs.getString("title") + " — " + rs.getString("url")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public void FindNews_Date(String keyword) { + try { + PreparedStatement ps = conn.prepareStatement( + "SELECT title, url FROM news WHERE date::date = ? ORDER BY " + field_for_sort + ", date DESC LIMIT " + count_rows ); + ps.setDate(1, Date.valueOf(keyword)); + + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + System.out.println(rs.getString("title") + " — " + rs.getString("url")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public void FindNews_Category(String keyword) { + try { + PreparedStatement ps = conn.prepareStatement( + "SELECT title, url FROM news WHERE LOWER(category) LIKE ? ORDER BY " + field_for_sort + ", date DESC LIMIT " + count_rows ); + ps.setString(1, "%" + keyword.toLowerCase() + "%"); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + System.out.println(rs.getString("title") + " — " + rs.getString("url")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public void FindNews_Source(String keyword) { + try { + PreparedStatement ps = conn.prepareStatement( + "SELECT title, url FROM news WHERE LOWER(source) LIKE ? ORDER BY " + field_for_sort + ", date DESC LIMIT " + count_rows ); + ps.setString(1, "%" + keyword.toLowerCase() + "%"); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + System.out.println(rs.getString("title") + " — " + rs.getString("url")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public void exportToCSV() { + try (PrintWriter writer = new PrintWriter("export.csv")) { + writer.println("title, date,summary, url, keywords, video_or_foto_url, category, source, source_id"); + ResultSet rs = conn.createStatement().executeQuery("SELECT * FROM news ORDER BY " + field_for_sort + ", date DESC"); + while (rs.next()) { + String line = String.format("\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"", + rs.getString("title").replace("\"" , "'"), + rs.getTimestamp("date").toString(), + rs.getString("summary").replace("\"", "'"), + rs.getString("url"), + rs.getString("keywords"), + rs.getString("video_or_foto_url"), + rs.getString("category"), + rs.getString("source"), + rs.getString("source_id")); + writer.println(line); + } + System.out.println("Файл export.csv сохранён."); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void close() { + try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } + } + + + public void SetField_for_sort(String t) {field_for_sort = t;} + + + public List getSources() {return getAllSources();} +} diff --git a/news-aggregator/src/main/java/com/example/news/model/NewsArticle.java b/news-aggregator/src/main/java/com/example/news/model/NewsArticle.java new file mode 100644 index 0000000..bfb62a8 --- /dev/null +++ b/news-aggregator/src/main/java/com/example/news/model/NewsArticle.java @@ -0,0 +1,46 @@ +package com.example.news.model; + +import java.time.LocalDateTime; + +public class NewsArticle { + private String title; + private LocalDateTime date; + private String summary; + private String url; + private String video_or_foto_url; + private String category; + private String source; + private String source_id; + + public NewsArticle(String title, LocalDateTime date, String summary, String url, String video_or_foto_url, String category,String source, String source_id) { + this.title = title; + this.date = date; + this.summary = summary; + this.url = url; + this.video_or_foto_url = video_or_foto_url; + this.category = category; + this.source = source; + this.source_id = source_id; + } + + public String getTitle() { return title; } + public LocalDateTime getDate() { return date; } + public String getSummary() { return summary; } + public String getUrl() { return url; } + public String getVideo_or_foto_url() { return video_or_foto_url; }; + public String getCategory() { return category; } + public String getSource() { return source; }; + public String getSource_id() { return source_id; }; + + public void setTitle(String newTitle) { title = newTitle;} + public void setSummary(String newSummary) { summary = newSummary;} + public void setUrl(String newUrl) {url = newUrl;} + + public void setVideo_or_foto_url(String url) {video_or_foto_url = url;} + + public void setCategory(String newCategory) { category = newCategory;} + + public void setSource(String newSource) {source = newSource;} + + public void setSource_id(String newSource_id) {source_id = newSource_id;} +} diff --git a/news-aggregator/src/main/java/com/example/news/model/Source.java b/news-aggregator/src/main/java/com/example/news/model/Source.java new file mode 100644 index 0000000..8b02056 --- /dev/null +++ b/news-aggregator/src/main/java/com/example/news/model/Source.java @@ -0,0 +1,77 @@ +package com.example.news.model; + + +public class Source { + private int id; + private String title_field_name; + private String date_field_name; + private String summary_field_name; + private String url_field_name; + private String videoOrFotoUrl_field_name; + private String source; + private String sourceId_field_name; + private String json_url; + private String root_tag; + + public Source(int id, String title_field_name, String date_field_name, String summary_field_name, String url_field_name, + String videoOrFotoUrl_field_name, String source, String sourceId_field_name, String json_url, String root_tag) { + this.id = id; + this.title_field_name = title_field_name; + this.date_field_name = date_field_name; + this.summary_field_name = summary_field_name; + this.url_field_name = url_field_name; + this.videoOrFotoUrl_field_name = videoOrFotoUrl_field_name; + this.source = source; + this.sourceId_field_name = sourceId_field_name; + this.json_url = json_url; + this.root_tag = root_tag; + + } + + public int getId() { return id; } + public String getTitle_field_name() { return title_field_name; } + public String getDate_field_name() { return date_field_name; } + public String getSummary_field_name() { return summary_field_name; } + public String getUrl_field_name() { return url_field_name; } + public String getVideoOrFotoUrl_field_name() { return videoOrFotoUrl_field_name; } + public String getSource() { return source; } + public String getSourceId_field_name() { return sourceId_field_name; } + public String getJson_url() { return json_url; } + public String getRoot_tag() { return root_tag; } + + + @Override + public String toString() { + return String.format(""" + ID: %d + Поле для Title: %s + Поле для Date: %s + Поле для Summary: %s + Поле для URL: %s + Поле для ссылки на видео/аудио: %s + Имя Источника: %s + Поле для Source ID: %s + Название корня для JSON: %s + URL сайта для JSON: %s + """, + id, title_field_name, date_field_name, summary_field_name, url_field_name, videoOrFotoUrl_field_name, source, sourceId_field_name, root_tag, json_url); + } + + public void setTitle_field_name(String s) {title_field_name = s;} + + public void setDate_field_name(String s) {date_field_name = s;} + + public void setSummary_field_name(String s) {summary_field_name = s;} + + public void setUrl_field_name(String s) {url_field_name = s;} + + public void setVideoOrFotoUrl_field_name(String s) {videoOrFotoUrl_field_name = s;} + + public void setSource(String s) {source = s;} + + public void setSourceId_field_name(String s) {sourceId_field_name = s;} + + public void setJson_url(String s) {json_url = s;} + + public void setRoot_tag(String s) {root_tag = s;} +} \ No newline at end of file diff --git a/news-aggregator/src/main/java/com/example/news/parser/NewsParser.java b/news-aggregator/src/main/java/com/example/news/parser/NewsParser.java new file mode 100644 index 0000000..dce9837 --- /dev/null +++ b/news-aggregator/src/main/java/com/example/news/parser/NewsParser.java @@ -0,0 +1,68 @@ +package com.example.news.parser; + +import com.example.news.model.NewsArticle; +import com.example.news.model.Source; +import com.example.news.util.CategoryClassifier; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.jsoup.Connection; +import org.jsoup.Jsoup; + +import java.util.ArrayList; +import java.util.List; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; + +/** + * Парсер новостей с сайта РБК (https://www.rbc.ru) через AJAX API. + * Использует формат: https://www.rbc.ru/search/ajax/?dateFrom=YYYY-MM-DD + */ +public class NewsParser { + + public List parse(String dateFrom, List sources) { + List articles = new ArrayList<>(); + + for (Source source : sources) { + try { + + String url = source.getJson_url() + dateFrom; + String source_name = source.getSource(); + + + // Получаем JSON-ответ от сервера + String json = Jsoup.connect(url) + .ignoreContentType(true) + .method(Connection.Method.GET) + .execute() + .body(); + + // Парсим JSON + ObjectMapper mapper = new ObjectMapper(); + JsonNode root = mapper.readTree(json); + + for (JsonNode item : root.path(source.getRoot_tag())) { + String title = item.path(source.getTitle_field_name()).asText(); // + LocalDateTime date = Instant.ofEpochSecond(item.path(source.getDate_field_name()).asLong()) + .atZone(ZoneId.of("Europe/Moscow")) + .toLocalDateTime(); + String summary = item.path(source.getSummary_field_name()).asText(); + if ("null".equals(summary.toLowerCase())) { + summary = ""; + } + String link = item.path(source.getUrl_field_name()).asText(); + String video_or_foto_url = item.path(source.getVideoOrFotoUrl_field_name()).asText(); + String category = CategoryClassifier.classify(title + summary); + String source_id = item.path(source.getSourceId_field_name()).asText(); + + articles.add(new NewsArticle(title, date, summary, link, video_or_foto_url, category, source_name, source_id)); + } + + } catch (Exception e) { + System.out.println("Ошибка при получении данных : "+ source.getSource()+ ". " + e.getMessage()); + } + } + return articles; + } +} diff --git a/news-aggregator/src/main/java/com/example/news/tests/CategoryClassifierTest.java b/news-aggregator/src/main/java/com/example/news/tests/CategoryClassifierTest.java new file mode 100644 index 0000000..4657fd9 --- /dev/null +++ b/news-aggregator/src/main/java/com/example/news/tests/CategoryClassifierTest.java @@ -0,0 +1,39 @@ +package com.example.news.tests; + +import com.example.news.util.CategoryClassifier; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class CategoryClassifierTest { + + @Test + void testClassifyPolitics() { + String text = "Путин и выборы в парламенте — главные новости."; + assertEquals("Политика", CategoryClassifier.classify(text)); + } + + @Test + void testClassifyEconomy() { + String text = "Курс доллара, рубля, евро — финансовые рынки колеблются."; + assertEquals("Экономика", CategoryClassifier.classify(text)); + } + + @Test + void testClassifyTechnology() { + String text = "Нейросети и искусственный интеллект развиваются."; + assertEquals("Технологии", CategoryClassifier.classify(text)); + } + + @Test + void testClassifySport() { + String text = "Футболисты играют в Лиге чемпионов. Сборная России победила."; + assertEquals("Спорт", CategoryClassifier.classify(text)); + } + + @Test + void testClassifyOther() { + String text = "Сегодня хороший день для прогулки в парке."; + assertEquals("Другое", CategoryClassifier.classify(text)); + } +} diff --git a/news-aggregator/src/main/java/com/example/news/tests/DatabaseManagerTest.java b/news-aggregator/src/main/java/com/example/news/tests/DatabaseManagerTest.java new file mode 100644 index 0000000..ba18446 --- /dev/null +++ b/news-aggregator/src/main/java/com/example/news/tests/DatabaseManagerTest.java @@ -0,0 +1,23 @@ +package com.example.news.tests; + +import com.example.news.db.DatabaseManager; +import com.example.news.model.Source; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class DatabaseManagerTest { + + @Test + void testSettersAndClose() { + DatabaseManager db = new DatabaseManager(); + db.SetСountRows(5); + db.SetField_for_sort("title ASC"); + db.close(); + + List sources = db.getSources(); + assertNotNull(sources); + } +} diff --git a/news-aggregator/src/main/java/com/example/news/tests/KeywordExtractorTest.java b/news-aggregator/src/main/java/com/example/news/tests/KeywordExtractorTest.java new file mode 100644 index 0000000..0fc86bf --- /dev/null +++ b/news-aggregator/src/main/java/com/example/news/tests/KeywordExtractorTest.java @@ -0,0 +1,45 @@ +package com.example.news.tests; + +import com.example.news.util.KeywordExtractor; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class KeywordExtractorTest { + + @Test + void testExtractKeywordsBasic() { + String text = "Россия и мир, новости сегодня, Россия победила в бою, бой за Москву."; + List keywords = KeywordExtractor.extractKeywords(text, 3); + + assertNotNull(keywords); + assertTrue(keywords.contains("россия")); + assertTrue(keywords.contains("бой") || keywords.contains("бою")); + } + + @Test + void testExtractKeywordsEmptyText() { + List keywords = KeywordExtractor.extractKeywords("", 5); + assertNotNull(keywords); + assertTrue(keywords.isEmpty()); + } + + @Test + void testExtractKeywordsAllStopWords() { + String text = "и в на с по что как а но за из к для это то же не о от до"; + List keywords = KeywordExtractor.extractKeywords(text, 5); + assertNotNull(keywords); + assertTrue(keywords.isEmpty()); + } + + @Test + void testExtractKeywordsTopNLimit() { + String text = "один два три четыре пять один два три один два"; + List keywords = KeywordExtractor.extractKeywords(text, 2); + assertEquals(2, keywords.size()); + assertEquals("один", keywords.get(0)); + assertEquals("два", keywords.get(1)); + } +} diff --git a/news-aggregator/src/main/java/com/example/news/tests/NewsAggregatorTest.java b/news-aggregator/src/main/java/com/example/news/tests/NewsAggregatorTest.java new file mode 100644 index 0000000..e1df22e --- /dev/null +++ b/news-aggregator/src/main/java/com/example/news/tests/NewsAggregatorTest.java @@ -0,0 +1,16 @@ +package com.example.news.tests; + +import com.example.news.NewsAggregator; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class NewsAggregatorTest { + + @Test + void testAggregatorMethods() { + NewsAggregator aggregator = new NewsAggregator(); + aggregator.SetСountRows(5); + aggregator.CloseDB(); + } +} diff --git a/news-aggregator/src/main/java/com/example/news/tests/NewsArticleTest.java b/news-aggregator/src/main/java/com/example/news/tests/NewsArticleTest.java new file mode 100644 index 0000000..2abf42d --- /dev/null +++ b/news-aggregator/src/main/java/com/example/news/tests/NewsArticleTest.java @@ -0,0 +1,45 @@ +package com.example.news.tests; + +import com.example.news.model.NewsArticle; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.*; + +class NewsArticleTest { + + @Test + void testNewsArticleGettersAndSetters() { + LocalDateTime now = LocalDateTime.now(); + NewsArticle article = new NewsArticle( + "Title", now, "Summary", "http://example.com", + "http://example.com/image.jpg", "Politics", "RBC", "123" + ); + + assertEquals("Title", article.getTitle()); + assertEquals(now, article.getDate()); + assertEquals("Summary", article.getSummary()); + assertEquals("http://example.com", article.getUrl()); + assertEquals("http://example.com/image.jpg", article.getVideo_or_foto_url()); + assertEquals("Politics", article.getCategory()); + assertEquals("RBC", article.getSource()); + assertEquals("123", article.getSource_id()); + + article.setTitle("New Title"); + article.setSummary("New Summary"); + article.setUrl("http://newurl.com"); + article.setVideo_or_foto_url("http://newimage.jpg"); + article.setCategory("Business"); + article.setSource("Mail"); + article.setSource_id("456"); + + assertEquals("New Title", article.getTitle()); + assertEquals("New Summary", article.getSummary()); + assertEquals("http://newurl.com", article.getUrl()); + assertEquals("http://newimage.jpg", article.getVideo_or_foto_url()); + assertEquals("Business", article.getCategory()); + assertEquals("Mail", article.getSource()); + assertEquals("456", article.getSource_id()); + } +} diff --git a/news-aggregator/src/main/java/com/example/news/tests/NewsParserTest.java b/news-aggregator/src/main/java/com/example/news/tests/NewsParserTest.java new file mode 100644 index 0000000..87a13ad --- /dev/null +++ b/news-aggregator/src/main/java/com/example/news/tests/NewsParserTest.java @@ -0,0 +1,23 @@ +package com.example.news.tests; + +import com.example.news.model.NewsArticle; +import com.example.news.parser.NewsParser; +import com.example.news.NewsAggregator; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class NewsParserTest { + + @Test + void testParseReturnsList() { + NewsParser parser = new NewsParser(); + NewsAggregator newsAggregator1 = new NewsAggregator(); + List articles = parser.parse("2025-06-08", newsAggregator1.getAllSources()); + + assertNotNull(articles); + assertTrue(articles instanceof List); + } +} diff --git a/news-aggregator/src/main/java/com/example/news/tests/SourceTest.java b/news-aggregator/src/main/java/com/example/news/tests/SourceTest.java new file mode 100644 index 0000000..c119f64 --- /dev/null +++ b/news-aggregator/src/main/java/com/example/news/tests/SourceTest.java @@ -0,0 +1,49 @@ +package com.example.news.tests; + +import com.example.news.model.Source; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class SourceTest { + + @Test + void testSourceGettersAndSetters() { + Source source = new Source( + 1, + "title", "date", "summary", "url", + "media", "rbc", "source_id", "http://json.url", "root" + ); + + assertEquals(1, source.getId()); + assertEquals("title", source.getTitle_field_name()); + assertEquals("date", source.getDate_field_name()); + assertEquals("summary", source.getSummary_field_name()); + assertEquals("url", source.getUrl_field_name()); + assertEquals("media", source.getVideoOrFotoUrl_field_name()); + assertEquals("rbc", source.getSource()); + assertEquals("source_id", source.getSourceId_field_name()); + assertEquals("http://json.url", source.getJson_url()); + assertEquals("root", source.getRoot_tag()); + + source.setTitle_field_name("t"); + source.setDate_field_name("d"); + source.setSummary_field_name("s"); + source.setUrl_field_name("u"); + source.setVideoOrFotoUrl_field_name("m"); + source.setSource("mail"); + source.setSourceId_field_name("sid"); + source.setJson_url("http://updated.url"); + source.setRoot_tag("updated"); + + assertEquals("t", source.getTitle_field_name()); + assertEquals("d", source.getDate_field_name()); + assertEquals("s", source.getSummary_field_name()); + assertEquals("u", source.getUrl_field_name()); + assertEquals("m", source.getVideoOrFotoUrl_field_name()); + assertEquals("mail", source.getSource()); + assertEquals("sid", source.getSourceId_field_name()); + assertEquals("http://updated.url", source.getJson_url()); + assertEquals("updated", source.getRoot_tag()); + } +} diff --git a/news-aggregator/src/main/java/com/example/news/util/CategoryClassifier.java b/news-aggregator/src/main/java/com/example/news/util/CategoryClassifier.java new file mode 100644 index 0000000..2b21cfa --- /dev/null +++ b/news-aggregator/src/main/java/com/example/news/util/CategoryClassifier.java @@ -0,0 +1,41 @@ +package com.example.news.util; + +public class CategoryClassifier { + + public static String classify(String text) { + // Приведение к нижнему регистру и удаление знаков препинания + text = text.toLowerCase().replaceAll("[^а-яa-z0-9\\s]", ""); + + if (text.contains("политик") || text.contains("выборы") || text.contains("парламент") || + text.contains("путин") || text.contains("трамп") || text.contains("собянин") || + text.contains("калифорни")) { + return "Политика"; + + } else if (text.contains("рубль") || text.contains("банк") || text.contains("экономик") || + text.contains("доллар") || text.contains("евро") || text.contains("инвестпроект") || + text.contains("apple") || text.contains("цены") || text.contains("жильё") || + text.contains("дивиденд") || text.contains("рейтинг") || text.contains("акци") || + text.contains("строительств") || text.contains("стоимост")) { + return "Экономика"; + + } else if (text.contains("футбол") || text.contains("спорт") || text.contains("матч") || + text.contains("баскетбол") || text.contains("теннис") || text.contains("волейбол")) { + return "Спорт"; + + } else if (text.contains("кино") || text.contains("фильм") || text.contains("актер") || + text.contains("театр") || text.contains("мюзикл") || text.contains("временной")) { + return "Культура"; + + } else if (text.contains("миде") || text.contains("сбит") || text.contains("дрон") || + text.contains("потуши") || text.contains("танк") || text.contains("экипаж")) { + return "СВО"; + + } else if (text.contains("поездка") || text.contains("петербург") || text.contains("водоём") || + text.contains("курорт")) { + return "Отдых"; + + } else { + return "Общее"; + } + } +} diff --git a/news-aggregator/src/main/java/com/example/news/util/KeywordExtractor.java b/news-aggregator/src/main/java/com/example/news/util/KeywordExtractor.java new file mode 100644 index 0000000..8b5e9a3 --- /dev/null +++ b/news-aggregator/src/main/java/com/example/news/util/KeywordExtractor.java @@ -0,0 +1,32 @@ +package com.example.news.util; + +import java.util.*; +import java.util.regex.Pattern; + + + +public class KeywordExtractor { + private static final Set STOP_WORDS = Set.of( + "и", "в", "на", "с", "по", "что", "как", "а", "но", "за", "из", "к", "для", "это", "то", "же", "не", "о", "от", "до" + ); + + public static List extractKeywords(String text, int topN) { + Map freq = new HashMap<>(); + Pattern wordPattern = Pattern.compile("[а-яА-Яa-zA-ZёЁ]+"); + + var matcher = wordPattern.matcher(text.toLowerCase()); + while (matcher.find()) { + String word = matcher.group(); + if (!STOP_WORDS.contains(word) && word.length() > 3) { + freq.put(word, freq.getOrDefault(word, 0) + 1); + } + } + + return freq.entrySet() + .stream() + .sorted(Map.Entry.comparingByValue().reversed()) + .limit(topN) + .map(Map.Entry::getKey) + .toList(); + } +} diff --git a/news-aggregator/src/main/resources/config.properties b/news-aggregator/src/main/resources/config.properties new file mode 100644 index 0000000..ede8a36 --- /dev/null +++ b/news-aggregator/src/main/resources/config.properties @@ -0,0 +1,3 @@ +db.url=jdbc:postgresql://localhost:5432/tododb +user=todo_user +password=todo_pass diff --git a/news-aggregator/target/classes/com/example/news/Main$1.class b/news-aggregator/target/classes/com/example/news/Main$1.class new file mode 100644 index 0000000..59cf278 Binary files /dev/null and b/news-aggregator/target/classes/com/example/news/Main$1.class differ diff --git a/news-aggregator/target/classes/com/example/news/Main.class b/news-aggregator/target/classes/com/example/news/Main.class new file mode 100644 index 0000000..2c99e0e Binary files /dev/null and b/news-aggregator/target/classes/com/example/news/Main.class differ diff --git a/news-aggregator/target/classes/com/example/news/NewsAggregator.class b/news-aggregator/target/classes/com/example/news/NewsAggregator.class new file mode 100644 index 0000000..a2fa75d Binary files /dev/null and b/news-aggregator/target/classes/com/example/news/NewsAggregator.class differ diff --git a/news-aggregator/target/classes/com/example/news/db/DatabaseManager.class b/news-aggregator/target/classes/com/example/news/db/DatabaseManager.class new file mode 100644 index 0000000..bbec490 Binary files /dev/null and b/news-aggregator/target/classes/com/example/news/db/DatabaseManager.class differ diff --git a/news-aggregator/target/classes/com/example/news/model/NewsArticle.class b/news-aggregator/target/classes/com/example/news/model/NewsArticle.class new file mode 100644 index 0000000..a3dcc2f Binary files /dev/null and b/news-aggregator/target/classes/com/example/news/model/NewsArticle.class differ diff --git a/news-aggregator/target/classes/com/example/news/model/Source.class b/news-aggregator/target/classes/com/example/news/model/Source.class new file mode 100644 index 0000000..6c6d8b6 Binary files /dev/null and b/news-aggregator/target/classes/com/example/news/model/Source.class differ diff --git a/news-aggregator/target/classes/com/example/news/parser/NewsParser.class b/news-aggregator/target/classes/com/example/news/parser/NewsParser.class new file mode 100644 index 0000000..b204e60 Binary files /dev/null and b/news-aggregator/target/classes/com/example/news/parser/NewsParser.class differ diff --git a/news-aggregator/target/classes/com/example/news/tests/CategoryClassifierTest.class b/news-aggregator/target/classes/com/example/news/tests/CategoryClassifierTest.class new file mode 100644 index 0000000..c3927ca Binary files /dev/null and b/news-aggregator/target/classes/com/example/news/tests/CategoryClassifierTest.class differ diff --git a/news-aggregator/target/classes/com/example/news/tests/DatabaseManagerTest.class b/news-aggregator/target/classes/com/example/news/tests/DatabaseManagerTest.class new file mode 100644 index 0000000..2082719 Binary files /dev/null and b/news-aggregator/target/classes/com/example/news/tests/DatabaseManagerTest.class differ diff --git a/news-aggregator/target/classes/com/example/news/tests/KeywordExtractorTest.class b/news-aggregator/target/classes/com/example/news/tests/KeywordExtractorTest.class new file mode 100644 index 0000000..13a2a04 Binary files /dev/null and b/news-aggregator/target/classes/com/example/news/tests/KeywordExtractorTest.class differ diff --git a/news-aggregator/target/classes/com/example/news/tests/NewsAggregatorTest.class b/news-aggregator/target/classes/com/example/news/tests/NewsAggregatorTest.class new file mode 100644 index 0000000..e5a6de4 Binary files /dev/null and b/news-aggregator/target/classes/com/example/news/tests/NewsAggregatorTest.class differ diff --git a/news-aggregator/target/classes/com/example/news/tests/NewsArticleTest.class b/news-aggregator/target/classes/com/example/news/tests/NewsArticleTest.class new file mode 100644 index 0000000..ec0397d Binary files /dev/null and b/news-aggregator/target/classes/com/example/news/tests/NewsArticleTest.class differ diff --git a/news-aggregator/target/classes/com/example/news/tests/NewsParserTest.class b/news-aggregator/target/classes/com/example/news/tests/NewsParserTest.class new file mode 100644 index 0000000..41166a1 Binary files /dev/null and b/news-aggregator/target/classes/com/example/news/tests/NewsParserTest.class differ diff --git a/news-aggregator/target/classes/com/example/news/tests/SourceTest.class b/news-aggregator/target/classes/com/example/news/tests/SourceTest.class new file mode 100644 index 0000000..76d11a0 Binary files /dev/null and b/news-aggregator/target/classes/com/example/news/tests/SourceTest.class differ diff --git a/news-aggregator/target/classes/com/example/news/util/CategoryClassifier.class b/news-aggregator/target/classes/com/example/news/util/CategoryClassifier.class new file mode 100644 index 0000000..39d9560 Binary files /dev/null and b/news-aggregator/target/classes/com/example/news/util/CategoryClassifier.class differ diff --git a/news-aggregator/target/classes/com/example/news/util/KeywordExtractor.class b/news-aggregator/target/classes/com/example/news/util/KeywordExtractor.class new file mode 100644 index 0000000..428a8f8 Binary files /dev/null and b/news-aggregator/target/classes/com/example/news/util/KeywordExtractor.class differ diff --git a/news-aggregator/target/classes/config.properties b/news-aggregator/target/classes/config.properties new file mode 100644 index 0000000..ede8a36 --- /dev/null +++ b/news-aggregator/target/classes/config.properties @@ -0,0 +1,3 @@ +db.url=jdbc:postgresql://localhost:5432/tododb +user=todo_user +password=todo_pass diff --git a/test_github b/test_github new file mode 100644 index 0000000..72a1099 --- /dev/null +++ b/test_github @@ -0,0 +1,2 @@ +0000 +11111