ЛР-7 / ЯП №7
.pdfМинистерство науки и высшего образования Российской Федерации Федеральное государственное автономное образовательное учреждение высшего образования
ТОМСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ СИСТЕМ УПРАВЛЕНИЯ И РАДИОЭЛЕКТРОНИКИ (ТУСУР) Кафедра безопасности информационных систем (БИС)
ФУНКЦИОНАЛЬНЫЙ СТИЛЬ ПРОГРАММИРОВАНИЯ. ПАРАЛЛЕЛЬНОЕ ПРОГРАММИРОВАНИЕ
Отчет по лабораторной работе №7
по дисциплине «Языки программирования»
Студент гр.
_______
_______
Принял:
Доцент каф. КИБЭВС,
д.т.н., доцент
_______ Романов А. С.
_______
Томск 2026
|
Оглавление |
Введение................................................................................................................... |
3 |
Ход работы............................................................................................................... |
4 |
Заключение .............................................................................................................. |
5 |
Приложение А ......................................................................................................... |
6 |
2
Введение
Цель работы – реализовать систему, где:
1.Данные хранятся в неизменяемых структурах.
2.Вся обработка идет через цепочку чистых функций и лямбд с помощьюя
Map/Filter/Reduce.
3. Ошибки и пустые данные обрабатываются через кастомную монаду
Maybe / Either.
4.Настройки и фильтры задаются через каррирование.
5.Сложные вложенные структуры обходятся с помощью рекурсии.
6.Вычисления распараллеливаются через чистый Map/Reduce. Нарезка данных, параллельный маппинг воркерами на уровне процессов ОС и финальная свертка результатов должны выполняться без риска взаимных блокировок благодаря полной чистоте функций. Код написан на ЯП Python для
19 варианта.
3
Ход работы
В ходе работы была реализована система с помощью кода (приложение А), по заданной структуре, проведен демонстрационный вывод (рисунок 1.1).
Рисунок 1.1 – Демонстрационный вывод
4
Заключение
В ходе работы была реализована система, удовлетворяющая всем пунктам структуры для ИЗ по вариантом 19, на ЯП Python.
5
Приложение А
(обязательное)
Код реализации системы
from dataclasses import dataclass, replace
from typing import Callable, List, Tuple, Optional, Any, NamedTuple from functools import reduce
from multiprocessing import Pool, cpu_count import random
# 1. ИММУТАБЕЛЬНЫЕ СТРУКТУРЫ ДАННЫХ
@dataclass(frozen=True)
class GeoCoord: #неизменяемые географические координаты фонаря lat: float
lon: float
@dataclass(frozen=True)
class SunSchedule: #неизменяемый график восхода/захода солнца sunrise_hour: float
sunset_hour: float
@dataclass(frozen=True)
class LampConfig: #неизменяемая конфигурация фонаря lamp_id: str
location: GeoCoord max_lumens: int power_watts: float sun_schedule: SunSchedule
@dataclass(frozen=True)
class SensorReading: #неизменяемое показание датчика lamp_id: str
timestamp: float lux: float is_raining: bool
@dataclass(frozen=True)
class LampState: #неизменяемое состояние фонаря (результат вычислений) lamp_id: str
brightness_percent: float power_consumption: float is_emergency: bool
# 2. МОНАДА MAYBE (обработка ошибок)
class Maybe: #монада Maybe для безопасной работы с отсутствующими данными def __init__(self, value: Any):
self._value = value
def bind(self, func: Callable[[Any], 'Maybe']) -> 'Maybe': #монадическое связывание
6
(flatMap)
if self._value is None: return Maybe(None)
return func(self._value)
def map(self, func: Callable[[Any], Any]) -> 'Maybe': #функтор map if self._value is None:
return Maybe(None) try:
return Maybe(func(self._value)) except Exception:
return Maybe(None)
def get_or_else(self, default: Any) -> Any:
return self._value if self._value is not None else default
def is_nothing(self) -> bool: return self._value is None
def is_just(self) -> bool:
return self._value is not None
def __repr__(self):
return f"Just({self._value})" if self.is_just() else "Nothing"
# 3. ЧИСТЫЕ ФУНКЦИИ (глобальные для сериализации в multiprocessing) def calculate_brightness(lux: float, is_raining: bool, sunset_hour: float,
timestamp: float, max_lumens: int) -> float: #чистая функция: вычисление процента яркости светодиодной лампы в зависимости от уровня естественного света.
if lux < 0: lux = 0
base_brightness = max(0, min(100, 100 - (lux / 10))) rain_multiplier = 1.3 if is_raining else 1.0 hours_after_sunset = max(0, timestamp - sunset_hour) night_boost = 1.0 + (hours_after_sunset * 0.1)
final_brightness = base_brightness * rain_multiplier * night_boost return min(100, final_brightness)
def calculate_power_consumption(brightness_percent: float, max_power: float) -> float:
#чистая функция: расчёт потребляемой мощности return (brightness_percent / 100) * max_power
#4. ЛЯМБДА-ФУНКЦИИ (фильтры) CRITICAL_DARK = 5.0
is_emergency_dark = lambda reading: reading.lux < CRITICAL_DARK is_night_time = lambda reading: reading.timestamp < 7 or reading.timestamp > 19 is_critical_post = lambda reading: reading.lux < CRITICAL_DARK
#5. ФУНКЦИЯ ВЫСШЕГО ПОРЯДКА (ФВП)
def manage_grid(lux_filter: Callable[[SensorReading], bool], dimming_strategy: Callable[[List[LampState]], List[LampState]], lamps: Tuple[LampConfig, ...],
7
readings: Tuple[SensorReading, ...]) -> List[LampState]: #ФВП: контроллер энергосбережения принимает функцию-фильтр и функцию-стратегию диммирования.
filtered_readings = list(filter(lux_filter, readings)) lamp_states = []
for reading in filtered_readings: lamp_config = next(
(l for l in lamps if l.lamp_id == reading.lamp_id), None
)
if lamp_config:
brightness = calculate_brightness( reading.lux, reading.is_raining, lamp_config.sun_schedule.sunset_hour,
reading.timestamp, lamp_config.max_lumens
)
power = calculate_power_consumption(brightness, lamp_config.power_watts) state = LampState(
lamp_id=reading.lamp_id, brightness_percent=brightness, power_consumption=power, is_emergency=reading.lux < CRITICAL_DARK
)
lamp_states.append(state)
return dimming_strategy(lamp_states)
# 6. КАРРИРОВАНИЕ (Currying)
def apply_holiday_profile(profile_name: str): #каррированная функция: фиксация праздничного режима
def with_dimming_curve(dimming_curve: str): def with_lamp_id(lamp_id: str) -> dict:
return {
"profile": profile_name, "curve": dimming_curve, "lamp": lamp_id,
"brightness_override": 80 if profile_name == "New_Year" else 100
}
return with_lamp_id return with_dimming_curve
# 7. РЕКУРСИЯ (обход вложенных структур) @dataclass(frozen=True)
class PowerLineNode: #узел электропередачи (для каскадного отключения) node_id: str
children: Tuple['PowerLineNode', ...] = () is_active: bool = True
def cascade_disconnect(node: PowerLineNode,
fault_node_id: str) -> PowerLineNode: #рекурсивная функция: каскадное отключение линий электропередачи при обнаружении короткого замыкания.
if node.node_id == fault_node_id:
return replace(node, is_active=False, children=tuple( replace(child, is_active=False) for child in node.children
))
8
new_children = tuple( cascade_disconnect(child, fault_node_id) for child in node.children
)
any_child_disabled = any(not c.is_active for c in new_children)
return replace(node, children=new_children, is_active=not any_child_disabled)
# 8. REDUCE (свёртка)
def reduce_grid_consumption(states: List[LampState]) -> dict: # Reduce: cжатие данных о потреблении всей сети фонарей в итоговую метрику.
if not states:
return {"total_kwh": 0, "avg_brightness": 0, "emergency_count": 0} total_power = reduce(lambda acc, s: acc + s.power_consumption, states, 0)
avg_brightness = reduce(lambda acc, s: acc + s.brightness_percent, states, 0) / len(states) emergency_count = reduce(lambda acc, s: acc + (1 if s.is_emergency else 0), states, 0) total_kwh = total_power / 1000
return {
"total_kwh": total_kwh, "avg_brightness": avg_brightness, "emergency_count": emergency_count,
"savings_percent": max(0, 100 - avg_brightness)
}
# 9. ПАРАЛЛЕЛЬНЫЙ MAP/REDUCE
class WorkerInput(NamedTuple): #иммутабельный вход для воркера readings: List[SensorReading]
lamps: List[LampConfig]
def process_chunk_worker(worker_input: WorkerInput) -> List[LampState]: #чистая глобальная функция-маппер для воркера.
results = []
lamps_dict = {l.lamp_id: l for l in worker_input.lamps} for reading in worker_input.readings:
lamp = lamps_dict.get(reading.lamp_id) if lamp:
brightness = calculate_brightness( reading.lux, reading.is_raining, lamp.sun_schedule.sunset_hour, reading.timestamp, lamp.max_lumens
)
power = calculate_power_consumption(brightness, lamp.power_watts) results.append(LampState(
lamp_id=reading.lamp_id, brightness_percent=brightness, power_consumption=power, is_emergency=reading.lux < CRITICAL_DARK
))
return results
def parallel_process_lamps(lamps: Tuple[LampConfig, ...], readings: Tuple[SensorReading, ...],
num_workers: Optional[int] = None) -> List[LampState]: #параллельная
9
обработка через чистый Map/Reduce if num_workers is None:
num_workers = min(cpu_count(), 4)
readings_list = list(readings) lamps_list = list(lamps)
chunk_size = max(1, len(readings_list) // num_workers) chunks = [
WorkerInput(readings_list[i:i + chunk_size], lamps_list) for i in range(0, len(readings_list), chunk_size)
]
with Pool(num_workers) as pool:
mapped_results = pool.map(process_chunk_worker, chunks)
all_states = reduce(lambda acc, chunk: acc + chunk, mapped_results, []) return all_states
# ГЕНЕРАЦИЯ ТЕСТОВЫХ ДАННЫХ
def generate_test_data(num_lamps: int = 100) -> Tuple[Tuple[LampConfig, ...], Tuple[SensorReading, ...]]: #чистая функция генерации тестовых данных
random.seed(42) lamps = tuple(
LampConfig( f"lamp_{i:03d}",
GeoCoord(56.0 + i * 0.001, 84.0 + i * 0.001), 5000 + (i % 3) * 1000,
50.0 + (i % 3) * 10,
SunSchedule(6.0 + (i % 2) * 0.5, 19.0 + (i % 2) * 0.5)
)
for i in range(num_lamps)
)
readings = tuple( SensorReading(
f"lamp_{i:03d}",
22.0 if i % 3 == 0 else 14.0, random.uniform(0.5, 200.0), random.choice([True, False])
)
for i in range(num_lamps)
)
return lamps, readings
# ДЕМОНСТРАЦИЯ РАБОТЫ СИСТЕМЫ def run_demo():
#базовые данные lamps = (
LampConfig("lamp_001", GeoCoord(56.49, 84.98), 5000, 50.0, SunSchedule(6.5, 19.5)), LampConfig("lamp_002", GeoCoord(56.50, 84.99), 6000, 60.0, SunSchedule(6.5, 19.5)), LampConfig("lamp_003", GeoCoord(56.51, 85.00), 4500, 45.0, SunSchedule(6.5, 19.5)), LampConfig("lamp_004", GeoCoord(56.52, 85.01), 7000, 70.0, SunSchedule(6.5, 19.5)),
10
