Обновить
-14
Сергей@kovserg

Инженер

7
Подписчики
Отправить сообщение

То есть план собрать побольше денег и положить их в резерв.
А то как-то слишком медленно он растёт последние годы. Так?

Вот и всё, мы получили работающую DOS-программу, написанную на Rust.

Замечательно, а теперь под ZX-spectrum

В цифровом помощнике родителю видно...

Что главная цель платформы выкачивание денег из родителей, а обучение это так для прикрытия. Бизнес, ничего личного. Да и эффективность такого обучения крайне сомнительна. Более того постоянный насильный загон всех школьников туда, сразу наводит на плохие мысли. Ведь если загоняют, то значит самостоятельно туда идти не хотят. Значит есть причины.

Еще и страты хотят внедрить, что бы уж нормальная такая сегригация была. Для низших каст электонное обЫчении на цифровых платформах с ИИ, для высших обучение с учителем. Вобщем идей у рулевых много, но они какие-то все не здоровые. При всей этой движухе, денег на повышение зарплат учетилям почему-то нет. Может это неспроста.

По словам ведомства, это сделает образовательный процесс удобнее и повысит эффективность обучения

Это как бы намекает на некомпетентность ведомста. Может пора уже их как-то разгонять? Без ускорения оно работает не так как задумано.

Задумываться можно, но ситуация горорит что одних дум не достаточно. Лучше запустить 10000 своих спутников, и пусть они будут поддерживать равновесие. Чем выражать озабоченность и потом огрести в одни ворота.

Эти спутники смогут контролировать множество роёв дронов, по всей планете. А мы просто спокойно смотрим и радуемся. Свои почему не запускаем?

ps: глушить их практически нечем

«Блокнот» давно утратил простоту, превратившись в перегруженное приложение ..., а после удаления WordPad в 2024 году пользователи окончательно лишились...

Видимо даже разработчики в микрософт не готовы пользоваться "перегруженными приложениями", поэтому в тихоря добавляют не перегруженные.
Консольный текстовый редактор нужен при подключении по ssh, но тогда уж лучше vim

ps: если установть dosbox то 16битные edit и qedit работают на чем угодно

pps: ждём norton microsoft comander

Иногда в С++ не хватает каких-то фич, которые есть в других языках. Мне, например, не хватает preperties из C#

А нехватает для чего? Что бы можно было по имени обращаться с свойствам класса? Что бы можно было отслеживать измениния? Или просто привыкли и скучаете? Если не нравиться синтаксис, так кто мешает сначала все значения сложить в переменные выполнить выражение и положить результат обратно?

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

пример
prop.h
#include <stdarg.h>

namespace PropNames {
	enum Op { Get,Set,GetConstRef,Swap,GetName,GetType,FindByName };
	// Get can convert values
	// Set can convert values
	// GetConstRef only for reading, can't convert
	// Swap - can't convert exchange only same types
	enum { NoName=-1 };
	enum Types { NoType=-1,TypeInt,TypeDouble };
	template<class T>int type(T&);
	template<> int type(int&) { return TypeInt; }
	template<> int type(double&) { return TypeDouble; }
	const char* type_name(int type);
	int prop_def_op(int &v,const char* name,int op,
		int type,void* data,int size,int index);
	int prop_def_op(double &v,const char* name,int op,
		int type,void* data,int size,int index);
}

struct Prop_s {
	typedef int (*op_fn)
		(void* ctx,int name,int op,int type,void* data,int size,int index);
	void *ctx; op_fn op;	
};

struct Prop : Prop_s {
	Prop(op_fn op,void *ctx) { this->ctx=ctx; this->op=op; }
	template<class T>Prop& get(int name,T& value,int index=0) {
		int rc=op(ctx,name,PropNames::Get,PropNames::type(value),
			(void*)&value,sizeof(value),index);
		if (rc) throw_op("get",name,index);
		return *this;
	}
	template<class T>Prop& set(int name,T value,int index=0) {
		int rc=op(ctx,name,PropNames::Set,PropNames::type(value),
			(void*)&value,sizeof(value),index);
		if (rc) throw_op("set",name,index);
		return *this;
	}
	template<class T>Prop& swap(int name,T& value,int index=0) {
		int rc=op(ctx,name,PropNames::Swap,PropNames::type(value),
			(void*)&value,sizeof(value),index);
		if (rc) throw_oprt("swap",name,index,PropNames::type(value));
		return *this;
	}
	template<class T>const T& ref(int name,int index=0) {
		T *value=0;
		int rc=op(ctx,name,PropNames::GetConstRef,PropNames::type(*value),
			(void*)&value,sizeof(value),index);
		if (!value) throw_oprt("ref",name,index,PropNames::type(*(T*)0));
		if (rc) throw_op("ref",name,index);
		return *value;
	}
	const char* name(int name) {
		const char* value="?";
		op(ctx,name,PropNames::GetName,PropNames::NoType,
			(void*)&value,sizeof(value),0);
		return value;
	}
	int type(int name) {
		return op(ctx,name,PropNames::GetType,PropNames::NoType,0,0,0);
	}
	const char* type_name(int name) {
		return PropNames::type_name(type(name));
	}
	int find(const char* name) {
		return op(ctx,PropNames::NoName,PropNames::FindByName,
			PropNames::NoType,(void*)name,-1,0);
	}
	template<class T>Prop& get(const char *name,T& value,int index=0) {
		int iname=find(name);
		if (iname==PropNames::NoName) throw_error("get(?'%s')",name);
		int rc=op(ctx,iname,PropNames::Get,PropNames::type(value),
			(void*)&value,sizeof(value),index);
		if (rc) throw_op("get",name,index);
		return *this;
	}
	template<class T>Prop& set(const char *name,T value,int index=0) {
		int iname=find(name);
		if (iname==PropNames::NoName) throw_error("set(?'%s')",name);
		int rc=op(ctx,iname,PropNames::Set,PropNames::type(value),
			(void*)&value,sizeof(value),index);
		if (rc) throw_op("set",name,index);
		return *this;
	}
	template<class T>Prop& swap(const char *name,T& value,int index=0) {
		int iname=find(name);
		if (iname==PropNames::NoName) throw_error("swap(?'%s')",name);
		int rc=op(ctx,iname,PropNames::Swap,PropNames::type(value),
			(void*)&value,sizeof(value),index);
		if (rc) throw_oprt("swap",name,index,PropNames::type(value));
		return *this;
	}
	template<class T>const T& ref(const char *name,int index=0) {
		int iname=find(name);
		if (iname==PropNames::NoName) throw_error("ref(?'%s')",name);
		T *value=0;
		int rc=op(ctx,iname,PropNames::GetConstRef,PropNames::type(*value),
			(void*)&value,sizeof(value),index);
		if (!value) throw_oprt("ref",name,index,PropNames::type(*(T*)0));
		if (rc) throw_op("ref",name,index);
		return *value;
	}
	void throw_error(const char* msg,...);
	void vthrow_error(const char* msg,va_list v);
	void throw_op(const char* op_name,int name,int index);
	void throw_op(const char* op_name,const char* name,int index);
	void throw_oprt(const char* op_name,int name,int index,int rtype);
	void throw_oprt(const char* op_name,const char *name,int index,int rtype);
};
prop.cpp
#include "prop.h"
#include <stdio.h>
#include <string.h>

void Prop::vthrow_error(const char* msg,va_list v) {	
	printf("ERROR: "); vprintf(msg,v); printf("\n");
	throw this;
}
void Prop::throw_error(const char* msg,...) {
	va_list v; va_start(v,msg);
	vthrow_error(msg,v);
	va_end(v);
}
const char* PropNames::type_name(int type) {
	switch(type) {
		case NoType: return "NoType";
		case TypeInt: return "int";
		case TypeDouble: return "double";
	}
	return "?";
}
void Prop::throw_op(const char* op_name,int name,int index) {
	if (index)
		throw_error("%s(%d=%s[%d])",op_name,name,this->name(name),index);
	throw_error("%s(%d=%s)",op_name,name,this->name(name));
}
void Prop::throw_op(const char* op_name,const char* name,int index) {
	if (index) throw_error("%s('%s'[%d])",op_name,name,index);
	throw_error("%s('%s')",op_name,name);
}
void Prop::throw_oprt(const char* op_name,int name,int index,int rtype) {
	int vtype=type(name);
	if (rtype!=vtype) {
		const char* rtype_name=PropNames::type_name(rtype);
		const char* vtype_name=PropNames::type_name(vtype);
		throw_error("%s(%d:'%s') %s<->%s ?",
			op_name,name,this->name(name),vtype_name,rtype_name);
	}
	if (index)
		throw_error("%s(%d=%s[%d])",op_name,name,this->name(name),index);		
	throw_error("%s(%d=%s)",op_name,name,this->name(name));		
}
void Prop::throw_oprt(const char* op_name,const char *name,int index,int rtype) {
	int iname=find(name);
	int vtype=type(iname);
	if (rtype!=vtype) {
		const char* rtype_name=PropNames::type_name(rtype);
		const char* vtype_name=PropNames::type_name(vtype);
		throw_error("%s('%s') %s<->%s ?",
			op_name,name,vtype_name,rtype_name);
	}
	if (index)
		throw_error("%s('%s'[%d])",op_name,name,index);
	throw_error("%s('%s')",op_name,name);
}

int PropNames::prop_def_op(int &v,const char* name,int op,
	int type,void* data,int size,int index)
{
	int ptype=PropNames::type(v);
	if (op==GetType) return ptype;
	if (op==GetName) {
		if (size<(int)sizeof(name)) return -1;
		memcpy(data,&name,sizeof(name)); return 0;
	}
	if (index!=0) return -1;
	if (op==Set && type==TypeDouble) { // convert double->int
		double vd; if (size<(int)sizeof(vd)) return -1;
		memcpy(&vd,data,sizeof(vd));
		int vi=vd; if (vi!=vd) return -1; // unable to convert
		return prop_def_op(v,name,op,TypeInt,&vi,sizeof(vi),index);
	}
	if (type!=ptype) return -1;
	int sz=(int)sizeof(v);
	if (op==GetConstRef) sz=(int)sizeof(&v);
	if (size<sz) return -1;
	switch(op) {
		case Get: { memcpy(data,&v,sizeof(v)); } break;
		case Set: { memcpy(&v,data,sizeof(v)); } break;
		case GetConstRef: { void *pv=&v; memcpy(data,&pv,sizeof(pv)); } break;
		case Swap: {
			//memswp(data,&v,sizeof(v));
			char t[sizeof(v)];
			memcpy(&t,&v,sizeof(v));
			memcpy(&v,data,sizeof(v));
			memcpy(data,&t,sizeof(v));
		} break;
		default: return -1;
	}
	return 0;
}

int PropNames::prop_def_op(double &v,const char* name,int op,int type,void* data,int size,int index) {
	int ptype=PropNames::type(v);
	if (op==GetType) return ptype;
	if (op==GetName) {
		if (size<(int)sizeof(name)) return -1;
		memcpy(data,&name,sizeof(name)); return 0;
	}
	if (index!=0) return -1;
	if (op==Set && type==TypeInt) { // convert int->double
		int vi; if (size<(int)sizeof(vi)) return -1;
		memcpy(&vi,data,sizeof(vi)); double vd=vi;
		return prop_def_op(v,name,op,TypeDouble,&vd,sizeof(vd),index);
	}	
	if (type!=ptype) return -1;
	int sz=(int)sizeof(v);
	if (op==GetConstRef) sz=(int)sizeof(&v);
	if (size<sz) return -1;
	switch(op) {
		case Get: { memcpy(data,&v,sizeof(v)); } break;
		case Set: { memcpy(&v,data,sizeof(v)); } break;
		case GetConstRef: { void *pv=&v; memcpy(data,&pv,sizeof(pv)); } break;
		case Swap: {
			//memswp(data,&v,sizeof(v));
			char t[sizeof(v)];
			memcpy(&t,&v,sizeof(v));
			memcpy(&v,data,sizeof(v));
			memcpy(data,&t,sizeof(v));
		} break;
		default: return -1;
	}
	return 0;
}
a.h
struct A {
	int x; double y;
	enum { X,Y };
	
	Prop prop() { return Prop(prop_op_ref,this); }
	static A* my(void *ctx) { return (A*)ctx; }
	static int prop_op_ref(void* ctx,int name,int op,
		int type,void* data,int size,int index)
	{ return my(ctx)->prop_op(name,op,type,data,size,index); }
	int prop_op(int name,int op,int type,void* data,int size,int index);
};
a.cpp
#include "prop.h"
#include <string.h>

int A::prop_op(int name,int op,int type,void* data,int size,int index) {
	using namespace PropNames;
	if (op==FindByName) {
		const char* req=(const char*)data;
		if (strcmp(req,"X")==0) return X;
		if (strcmp(req,"Y")==0) return Y;
		return -1;
	}
	switch(name) {
	case X: return prop_def_op(x,"X",op,type,data,size,index);
	case Y: return prop_def_op(y,"Y",op,type,data,size,index);
	}
	return -1;
}
example.cpp
#include "a.h"
#include <stdio.h>

int main(int argc,char** argv) {
	A a; 
	try {
		Prop pa=a.prop();
		pa.set("X",1e3).set("Y",2);
		const int& rx=pa.ref<int>(A::X);
		const double& ry=pa.ref<double>(A::Y);
	
		int x; double y;
		pa.get(A::X,x).get(A::Y,y);
		printf("X=%d a.x=%d &x=%d\n",x,a.x,rx);
		printf("Y=%.2f a.y=%.2f &y=%.2f\n",y,a.y,ry);
		x=x+y; pa.swap("X",x);
		printf("x=%d a.x=%d\n",x,a.x);
	} catch(Prop*) {
		printf("ups\n");
	}
	return 0;
}

А когда нужно вычислять выражения сначала получить все поля по константным ссылкаи посчитать и положить обратно или можно забирать тяжелые данные с помощью операции обмена данных.

struct A {
	int x; double y;
	enum { X,Y }; // имена полей
	
	Prop prop(); // получение доступа к полям
	...
};

int main(int argc,char** argv) {
	A a; 
	try {
		Prop pa=a.prop();
		pa.set("X",1e3).set("Y",2);
		const int& rx=pa.ref<int>(A::X);
		const double& ry=pa.ref<double>(A::Y);
	
		int x; double y;
		pa.get(A::X,x).get(A::Y,y);
		printf("X=%d a.x=%d &x=%d\n",x,a.x,rx);
		printf("Y=%.2f a.y=%.2f &y=%.2f\n",y,a.y,ry);
		x=x+y; pa.swap("X",x);
		printf("x=%d a.x=%d\n",x,a.x);
	} catch(Prop*) {
		printf("problem\n");
	}
	return 0;
}

TFlops-ы измеряются в float32, а у этой коробки float4 а для них можно все операции в таблицы сложить, более того оно умеет рязряженные матрицы, то есть может частично выкидывать данные при свёртках, что защитывается как если бы он всё матрицу влоб обрабатывал (поэтому поводу особо не переживают, просто сравнивают итоговый inference и смотрят потерю точности). Поэтому это совершенно разные flops-ы.

Когда мышь наконец-то научиться перемешаться по столу самостоятельно. И откликаться на голос пользователя :)

Он уже давно не мусор и всё более свежая винда вызывает только недоумение и вопросы куда опять всё попрятали и что из новых безопасных безопасностей и заботах о пользователях надо отключить чтобы опять всё заработало.

Еще вариант накатить 7ку в виртуалку под linux mint-ом.
Раньше п.о. писали под железо, а теперь телега в переди лошади. Linux уже сечас больше железа поддерживает чем винда, на то что в винде нету дров в линуксе работает из коробки.

ps: Расказы про безопасную безопасность при выходе очередной винды повергают в ужос: как же так миллионы людей до этого момента пользовались столь сырой и не безопасной осью. И так каждый раз по кругу. Только теперь шифровальнщих идёт из коробки и внешнее управление через онлай учетки. Что бы можно было блокитровать сразу континентами.

pps: Правда теперь п.о. пишет ии и косяки, которые придётся устранять, будут всё более затейливые и не устранимые.

У нас молодой и дружный коллектив,
потому что мы убиваем всех, кто ссорится и стареет

Как-то ниочем. Где мясо-то? Ни что такое гиперкомплексные числа, ни как с помощью из записать поворот, ни простейших преобразований, ни примеров. И группы ли тут тоже не нужны.
Кватернион это гиперкомплексное число q={w,x,y,z}=w+i∙x+j∙y+k∙z действительная часть w и 3 мнимых вектора i,j,k. Обладающие следующими свойствами i∙i=j∙j=k∙k=-1, i∙j=k, j∙i=-k, i∙k=-j, k∙i=j, j∙k=i, k∙j=-i
Далее как для обычно комплексного числа вводятся сложения, умножения и
Операция комплексного сопряжения: conj(q)={w,-x,-y,-z}
Норма q: norm(q)=q∙conj(q)=w∙w+x∙x+y∙y+z∙z
Алгебра кватенионов похожа на обычные числа, но не коммутативна т.е. надо учитывать что q1∙q2 ≠ q2∙q1

Далее можно показть что если |q|=1 то его можно записать в виде q={ cos(a/2), n∙sin(a/2) } где n единичный вектор. ( q={w,x,y,z}={ cos(a/2), nx∙sin(a/2), ny∙sin(a/2), nz∙sin(a/2) }
Тогда выражение r' = q∙r∙conj(q)
или в развёрнутом виде:
rx'=rx∙(w∙w+x∙x-y∙y-z∙z)+2∙(ry∙(x∙y+w∙z)+rz∙(x∙z-w∙y))
ry'=ry∙(w∙w-x∙x+y∙y-z∙z)+2∙(rz∙(y∙z+w∙x)+rx∙(x∙y-w∙z))
rz'=rz∙(w∙w-x∙x-y∙y+z∙z)+2∙(rx∙(x∙z+w∙y)+ry∙(y∙z-w∙x))

r'=(rx',ry',rz') будет вектором который получается из вектора r=(rx,ry,rz) путём поворота на угол a вокруг единичного вектора n=(nx,ny,nz). То есть кватернион может быть использован для описание ориентации твёрдого тела (камеры например) и его можно однозначно преобразовать в матрицу вращения и обратно. (При этом q и -q описывают одно и тоже преобразование).

Можно определить
Rx(a)={ cos(a/2), sin(a/2),0,0 }
Ry(a)={ cos(a/2), 0,sin(a/2),0 }
Rz(a)={ cos(a/2), 0,0,sin(a/2) }

При этом умножение кватернионов позволяет накапливать изменение ориентации простым умножением кватернионов. q12=q1∙q2 например Rx(a)*Rx(a)=Rx(2a)

И с помощью них явно определить координаты Эйлера или другие использемые координаты
Euler(α,β,γ)=Rz(γ)∙Rx(β)∙Rz(α)
HPR(head,pitch,roll)=Ry(head)∙Rx(pitch)∙Rz(-roll)
т.к. есть разночтения (особенно если учесть что бывают левые и правые системы отсчета).

Интересно а какая у вас вероятность того что Heads < Tails ? Почему у вас постоянно Tails доминируют?

Зато можно будет ставить такие диагнозы: в системе микрофлюидного охлаждения оторвался тромб в результате чего процессор умер.

А вот как деятельность Минцифры выглядит для учителей: https://www.youtube.com/watch?v=gDPKtkDCQ64

Ведомство выделяет среди преимуществ новой онлайн-библиотеки:
контент, соответствующий федеральной рабочей программе Минпросвещения;

Ага только 5-ый класс.

все материалы — бесплатные, и их субсидирует государство

Бесплатные, но не общедоступные.

Посмотреть материалы урока -> тема медиана треугольника -> материал недоступен из-за нарушения авторских прав.

ps: а зачем "лимит на продолжительность заказываемого контента" и "давать согласие на бесплатные материалы"? Еще можно в конце писать вы ознакомились с матереилом на сумму 150тыс руб (пока бесплатно).

Да, облачные+ии - знатный пузырь. Интересно будет понаблюдать за развитием событий.

// Задержка в мс
async me_delay(struct async *pt, uint32_t ticks)
{
    async_begin(pt);
    static uint32_t timer_delay; // <<<--- если забыть static будут прикольно
    timer_delay = timer + ticks;
    while(!LoopCmp(timer, timer_delay)) {
...

У себя использовал похожую схему. Я использовал - "постепенно исполняемые функции" (afn - async fn). Вообще любая такая функция просто набор 3х указателей: 1-состояние асинхронной функции (ctx), 2-обработчик сигналов (setup), 3-обработчик итерации (loop)

afn.h
typedef struct afn_t {
  void *ctx;
  int (*setup)(void *ctx,int sev);
  int (*loop)(void *ctx);
} afn_t;

enum SetupEvents { sevInit, sevDone };

Если функция loop вернула 0 то она закончила, если 1-еще рабоает, остальные коды прерывания можно использовать для запросов. Функция setup тоже 0=ok. Фунция гарантируется что если вызвали setup(sevInit) то обязательно будет вызван setup(sevDone). И то что функция loop быполняет итерацию за конечное время. И самое главное предача кванта управления выполняется явно.

А вот для реализации итераций в обработчике loop была такая же схема как и тут на switch-е. Но не на LINE а на COUNTER. И все переменные которые использует асинхронная функция она явно описывает в своём состоянии. Примерно так:

example.c
/* example.c */
#include <stdio.h>
#include "afn.h"
#include "loop-fn.h"

typedef struct fn1_ctx {
	loop_t loop;
	int i;
} fn1_ctx;

int fn1_setup(fn1_ctx *my,int sev) {
	if (sev==sevInit) LOOP_RESET(my->loop);
	return 0;
}
int fn1_loop(fn1_ctx *my) {
	LOOP_BEGIN(my->loop)
	printf(" fn1.begin");
	LOOP_POINT
	printf(" fn1.step1");
	LOOP_POINT
	for(my->i=0;my->i<3;my->i++) {
		printf(" fn1.i=%d",my->i);
		LOOP_POINT
	}
	printf(" fn1.end");
	LOOP_END
}

int main(int argc,char** argv) {
	fn1_ctx fn1[1]; int it,rc;
	fn1_setup(fn1,sevInit);
	for(it=0;it<10;it++) {
		printf("it=%2d fn1=%2d",it,fn1->loop);
		rc=fn1_loop(fn1);
		if (!rc) printf(" [fn1 is done]");
		printf("\n");
		if (!rc) break;
	}
	fn1_setup(fn1,sevDone);
	return 0;
}

output:

it= 0 fn1= 0 fn1.begin
it= 1 fn1= 1 fn1.step1
it= 2 fn1= 2 fn1.i=0
it= 3 fn1= 3 fn1.i=1
it= 4 fn1= 3 fn1.i=2
it= 5 fn1= 3 fn1.end [fn1 is done]
loop-fn.h
/* loop-fn.h - sequential execution function */
#ifndef __LOOP_FN_H__
#define __LOOP_FN_H__

typedef int loop_t;

#define LOOP_RESET(loop) { loop=0; }
#if defined(__COUNTER__) && __COUNTER__!=__COUNTER__
#define LOOP_BEGIN(loop) { enum { __loop_base=__COUNTER__ }; \
    loop_t *__loop=&(loop); __loop_switch: int __loop_rv=1; \
    switch(*__loop) { default: *__loop=0; case 0: {
#define LOOP_POINT { enum { __loop_case=__COUNTER__-__loop_base }; \
    *__loop=__loop_case; goto __loop_leave; case __loop_case:{} }
#else
#define LOOP_BEGIN(loop) {loop_t*__loop=&(loop);__loop_switch:int __loop_rv=1;\
    switch(*__loop){ default: case 0: *__loop=__LINE__; case __LINE__:{
#define LOOP_POINT { *__loop=__LINE__; goto __loop_leave; case __LINE__:{} }
#endif
#define LOOP_END { __loop_end: *__loop=-1; case -1: return 0; \
	{ goto __loop_end; goto __loop_switch; } } \
	}} __loop_leave: return __loop_rv; }
#define LOOP_SET_RV(rv) { __loop_rv=(rv); } /* rv must be non zero */
#define LOOP_INT(n) { __loop_rv=(n); LOOP_POINT } /* interrupt n */
#define LOOP_POINT_(name) { *__loop=name; goto __loop_leave; case name:{} }
#define LOOP_INT_(n,name) { __loop_rv=(n); LOOP_POINT_(name) }

#endif /* __LOOP_FN_H__ */

Такие функции могут итерироваться супервизорорами. Они очень удобно отслеживают и реагируют на аварийные ситуации и итерируют подопечную функцию. А для выполнения множеста таких функций используются планировщики. Они довольно разнообразны и это целая отдельная тема.

Информация

В рейтинге
5 869-й
Откуда
Калужская обл., Россия
Дата рождения
Зарегистрирован
Активность

Специализация

Инженер встраиваемых систем, Архитектор программного обеспечения
Старший
От 750 000 ₽
Linux
C++
PHP
Java
Docker
SQL
Hibernate
Java Spring Framework
Высоконагруженные системы