Вопрос создания виджетов

Всем доброго дня!

Меня зовут Алексей, я основатель и разработчик системы автоматизации работы управляющих компаний «Оператор 18». 

Я написал ее с использованием языка Dart и фреймворка Flutter, что позволило мне использовать единую кодовую базу сразу для веб приложения и мобильных платформ iOS и Android. 

Как то я уже писал статью об Операторе18. Но тогда это была первая версия проекта, я собирал мнения, пробовал оценить рынок, выбрать вариант монетизации и т. д.

Сейчас я занимаюсь тем что называется — переписыванием проекта с нуля. Хочу, учитывая ошибки прошлого, переписать код, применить архитектуру, структурировать проект лучше. В общем вложиться в развитие на будущее.

В этой и последующих статьях, я хочу поделиться своим опытом о том, с какими сложностями сталкиваюсь при переписывании с нуля и как решаю их. Хорошо если кто нибудь найдёт в моих статьях что-то полезное для себя.


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

Для второй версии я решил заказать дизайн.

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

Но, как известно, дизайн — это создание виджетов, нестандартных, кастомных. И на этом моменте я оказался перед выбором: писать свои виджеты или искать что-то работающее с нужным мне функционалом в пабе.

Изначально я был уверен что буду сам писать виджеты, прямо с нуля. Я считал что не стоит держать в проекте много сторонних зависимостей! Можешь если сам написать что-то — пиши! 

Вот, например, окно входа в систему в новом дизайне выглядит так:

И я, решив прислушаться к себе, не стал искать готовых решений, а написал свой виджет:

class LoginDropdown extends StatefulWidget {   final Function(String) onUserRoleChanged;    const LoginDropdown({     required this.onUserRoleChanged,   });    @override   State<LoginDropdown> createState() => _LoginDropdownState(); }  class _LoginDropdownState extends State<LoginDropdown> {   bool isShowMenu = false;   String currentRole = UserRole.mcOperator;   Color roleDropdownButtonColor = AppColors.gray_3;    @override   Widget build(     BuildContext context,   ) =>       MouseRegion(         onEnter: (_) =>             setState(() => roleDropdownButtonColor = AppColors.gray_1),         onExit: (_) =>             setState(() => roleDropdownButtonColor = AppColors.gray_3),         child: GestureDetector(           onTap: () {             setState(() {               // ignore: avoid_bool_literals_in_conditional_expressions               isShowMenu = isShowMenu ? false : true;             });           },           child: Stack(             children: [               Container(                 height: 56,                 width: 418,                 decoration: BoxDecoration(                   color: roleDropdownButtonColor,                   borderRadius: const BorderRadius.all(                     Radius.circular(12),                   ),                 ),                 child: Padding(                   padding: const EdgeInsets.only(                     left: 20,                     right: 20,                   ),                   child: Row(                     mainAxisAlignment: MainAxisAlignment.spaceBetween,                     children: [                       Text(                         currentRole,                         style: AppFonsts.dropDown_1,                       ),                       Image.asset(                         'icons/dropdown_arrow.png',                         height: 20,                         width: 20,                       ),                     ],                   ),                 ),               ),               if (isShowMenu)                 Padding(                   // 56 - is height of first container,                   // 8 - is constraint between containers                   padding: const EdgeInsets.only(top: 56 + 8),                   child: Container(                     height: 166,                     width: 418,                     decoration: const BoxDecoration(                       boxShadow: [                         // BoxShadow setup found here:                         // https://devsheet.com/code-snippet/add-box-shadow-to-container-in-flutter/                         BoxShadow(                           color: AppColors.gray_6,                           blurRadius: 90,                           offset: Offset(0, 20),                         )                       ],                       color: AppColors.white,                       borderRadius: BorderRadius.all(                         Radius.circular(12),                       ),                     ),                     child: Padding(                       padding: const EdgeInsets.symmetric(vertical: 14),                       child: Column(                         mainAxisAlignment: MainAxisAlignment.spaceBetween,                         crossAxisAlignment: CrossAxisAlignment.start,                         children: [                           for (var role in UserRole.list)                             LoginDropdownItem(                               onTap: () {                                 setState(() {                                   isShowMenu = false;                                   currentRole = role;                                   widget.onUserRoleChanged(role);                                 });                               },                               userRole: role,                             ),                         ],                       ),                     ),                   ),                 ),             ],           ),         ),       ); }

Для меня «свой виджет» — это не просто кнопка, например, которой поменяли цвет и скруглили углы. «Свой виджет» — это виджет при создании которого нужно описывать его поведение и реакцию на действия пользователя. 

Получилось хорошо, возможно не хватает какой нибудь анимации, но в целом — я остался доволен!

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

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

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

// Inside a Row widget DropdownButtonHideUnderline(    child: DropdownButton2(       icon: const SizedBox(),       dropdownWidth: 418.w,       dropdownMaxHeight: 233.h,       dropdownDecoration: BoxDecoration(          borderRadius: BorderRadius.circular(16.r),       ),       hint: Row(          children: [             Text(                AppString.more,                style: AppFonts.menuUnselected,             ),             SizedBox(                width: 5.w,             ),             Image.asset(                'icons/dropdown_arrow.png',                height: 20.h,                width: 20.w,             ),          ],       ),       items: items.map(          (item) => DropdownMenuItem<String>(             value: item,             child: Text(                item,                style: AppFonts.dropDownBlack,             ),          ),       ).toList(),       value: selectedValue,       onChanged: widget.onLogSelected,       itemHeight: 35.h,       itemPadding: EdgeInsets.only(          left: 28.w,       ),     ), ), // Inside a Row widget

Спасибо за прочтение! Буду благодарен за критику/советы/иные комментарии.


ссылка на оригинал статьи https://habr.com/ru/post/683202/

Linux за 2$/100 рублей: Какой UMPC можно получить, покопавшись на барахолках?

Всем привет! Вот и подошла следующая часть из моего цикла статей очень дешевых девайсов, которым я всегда стараюсь найти применение. И на этот раз, я хочу вам показать еще одну классную сторону онлайн-барахолок(дабы не было рекламой — названия не упоминаю, но вы и сами догадались). Китайцы многое делают, чтобы сделать рынок UMPC как можно более дешевым и доступным — Lctech выпустили свою высокоинтегрированную плату Pi Zero на бутербродном AllWinner F1C100S — в который уже встроено 32/64мб ОЗУ, и цена которого — около 900руб на AliExpress. Я же предлагаю собрать UMPC за 100-500 рублей, и без единого кликбейта, на гораздо более мощном железе и с встроенным Wi-Fi, иногда 3G, полным USB стеком и встроенной NAND памятью 4-8гб. Интересно? Добро пожаловать под кат!

Описание

На рынке Китая существует много производителей SoC — и большинство из них начинали выпускать свои решения 10-12 лет назад. Это и VIA с их WonderMedia(помните такие мини-лэптопы на WinCE/Android за 2000руб?), и Rockchip, с их очень недорогими чипсетами для планшетов и электронных книг, и Amlogic, ныне популярный в сфере ТВ приставок, раньше на нем было очень много планшетов и игровых приставок(обзоры на которые есть у меня в профиле) — спасибо чипу 8726-MX.

ARM нетбук
ARM нетбук

Была еще GeneralPlus — но куда-то пропала после своего чипсета GP33003. И была среди этих компаний молодая фирма AllWinner — которая представила свои чипы A10 и A13, которые быстро стали очень популярными на рынке среди дешевых планшетов. Помните китайские планшеты за 2-3 тысячи рублей, с аккумуляторами от нокий сзади? Или подделки под айпад?

Вероятнее всего, они работали на тех же аллвиннерах/рокчипах. Потом уже пошли планшеты на медиатеках, но они представляют гораздо меньшую ценность в рамках этой статьи. Почему? К ним нельзя просто так подцепить дисплей(они MIPI и не так совместимы), и у них не всегда есть HDMI. Ниже же фото моего пациента: Exploay Informer 708 3G.

Покупка

В начале статьи я написал что бюджет нашего UMPC — 100-500руб. Заходим на авито или юлу, выбираем наш город и пишем в поиске «планшет на запчасти». Смотрим визуально старые модели(обычно 7 дюйм) и с HDMI выходом(это важно, и таких планшетов было очень много), гуглим хар-ки на каком процессоре они работают. Нам нужен BoxChip Axx/AllWinnerAxx. Кто-то скажет «так подожди, они же на запчасти», и я отвечу: неисправности у обычных людей, связанные с такими планшетами типовые — сломалось гнездо зарядки/умер АКБ/слетела прошивка(нам она вообще не будет нужна, аппарат в приоритете загружается с SD карты без всяких заморочек), разбит дисплей(это неважно, т.к есть HDMI). Это все фиксится очень легко, например запитать планшет можно напрямую от 5в зарядки, просто кинув плюс и минус(или можно поступить по уму — припаять аккумулятор от нокии — будет служить как ИБП своеобразный. Но обычно АКБ в таких планшетах не совсем уж дохлые, и их хватает на работу от сети напрямую и от АКБ 30-40мин). Купили? И что мы получили за эти деньги?

  1. Wi-Fi модуль, обычно распаянный как USB.

  2. Иногда 3G модуль. Проприетарный, доков на них нет, но в линуксе они видятся из коробки и без каких либо проблем. Может пригодится для сигнализаций, видеонаблюдения и.т.п вещей.

  3. Дисплей. TTL дисплеи очень дешевые если знать где искать) Никакого MIPI тогда дешевые чипсеты не поддерживали, разве что амлогик через чип-прослойку. Ссылки кидать не буду — кому интересно, напишут ЛС.

  4. 4-8гб встроенной NAND памяти, куда тоже при желании можно поставить систему. А можно сделать дуалбут с встроенным андроидом.

  5. Полноценный usb стек. Иногда на плате есть выводы на распайку USB разъема(как у меня), в остальных случаях можно выпаять Wi-Fi модуль и впаять платы-хабы с али по 100руб и получить несколько разъемов. Можно использовать otg кабели. Единственный нюанс — питание 3.3в, это разрешено стандартом, однако usb hdd работать например не будут.

  6. Встроенный аудио-кодек с микрофоном, и динамиком, а так же поддержку CSI камер(пусть и не самого лучшего качества).

Впечатляет? Отлично, добро пожаловать ниже.

Настройка окружения

Главное преимущество чипсетов AllWinner и AMLogic перед всякими медиатеками — встроенная возможность загрузки с SD карты, а у AllWinner до dtb был свой конфиг ядра, позволявший настроить почти всю периферию без перекомпиляции ядра, в виде обычного текстового файла. Назывался он config.fex, а ядро было как-бы универсальным для всех чипсетов. И поэтому, почти для всех чипсетов этого вендора(даже таких старых как A10) есть драйвера в мейнлайн ядре.

Вот яндекс диск с кучей уже настроенных и установленных образов:

https://disk.yandex.ru/d/FtZBeN4NrhuwB

Я изначально качал лубунту — но его не рекомендую, там ABI armel, которое очень давно не поддерживается. А еще нет драйверов на мали(GPU) и cedar(декодер) видео.

Качаем Win32DiskImager(или пишем напрямую через dd) и пишем образ флешку:

Флешка должна быть 4гб и больше. Не рекомендую брать очень китайские убитые дешевые флешки — могут подохнуть в процессе записи(или уже даже в системе) и уйти в ридонли.

Вставляем microHDMI -> HDMI в девайс, вставляем флешку, нажимаем кнопку включения и… ничего. А все потому, что некоторые дистрибутивы включают HDMI выход уже когда загружен lightdm(можно в config.fex включить моментальный вывод на HDMI и отключить подсветку/вывод на битый дисплей).

Ничего не появилось? Тогда попробуйте поставить lubuntu из линка выше — не все ядра совместимы со всем железом. У меня 100% работал lubuntu и для проверки начать можно с него.

Ввод

Не факт, что родной тач вашего девайса будет работать(вернее — это очень маловероятно), поэтому нам нужна мышь с клавиатурой. Как её подключить? Берем otg кабель(можно сделать и самому при желании или купить в днсе за 100руб) или распаиваем свой хаб на месте wi-fi модуля

Берем хаб, подключаем мышь и клавиатуру… и вуаля — всё работает. Возможно какие-то старые мышки или клавиатуры требуют 5в, но все современные прекрасно работают от 3.3. Входим в систему:

Стандартный репозиторий ubuntu ports давно не содержит в себе пакетов под ubuntu precise. Однако old-releases все еще держит пакеты для официально дропнутой в 2013 году(!) ABI armel. В стандартной поставке есть Firefox(очень тормозит, упор и в процессор, и в память — процессор греется градусов до 60, потом начинает троттлить). Я собирал квейк интереса ради свежим компилятором, но он не запустился, зато openarena из репозиториев работала ~2FPS. Увы, в большинстве дистров отсутствуют драйвера на Mali, и 3D или хотя-бы плавный интерфейс мы не получим — нужно собирать дрова и драйвер фреймбуффера руками. Я не стал этим заниматься — это очень геморно делать на свежих дистрах, а на самом планшете что-то компилировать — самоубийство. Даже хардварные кодеки не завести без компиляции собственно этих самых кодеков 😉 Но на некоторых дистрибутивах — всё уже сделано за нас, и есть драйвера.

Прожимаем стандартную конфигурацию Ctrl + Shift + T, и попадаем в терминал. Тут уже список доступных нам возможностей зависит от выбранного нами дистра(и наличия драйвера cedar/mali):

Что есть изначально в любом дистрибутиве, и что можно делать без драйверов:

  1. Сеть, как 3g, так и wifi.

  2. Компилировать любой софт с GCC.

  3. Сёрфить интернет через FireFox (без дров тормоз тот еще, особенно на девайсах с 512мб ОЗУ)

  4. Развернуть веб сервер, файлохранилище(очень бюджетное), короче всё, что связано с сетевой инфраструктурой — благо порты есть.

  5. Слушать музыку

Что можно делать с драйверами:

  1. Смотреть видео вплоть до 1080p, через VLC. Из кодеков точно есть h264.

  2. Ютуб(с большой натяжкой для A10, более свежие тянут его легко)

  3. Играть в порты игр: SuperTux, Quake 3 и.т.п — короче вся игровая библиотека с малины доступна. Сюда же и ретропай — консоль легко тянет эмуляцию вплоть до ps1/gamecube.

А GPIO? Без GPIO это не UMPC.

На платах зачастую распаян UART. Так почему бы не купить ардуинку и не сделать простенький IO Expander, и не получить возможность в user-mode дергать любые пины? Pro Mini стоит рублей 300.

Android

На внутренней памяти обычно стоит Android(иногда убитый — в таком случае помогает перепрошивка с той же SD карты или с ПК.). Обычно это версия 4.0.3(как в моем случае), но может быть и выше. В чём может быть её плюс? Она умеет делать фактически всё, что можно делать под обычным линуксом(сеть развернуть, компилировать программы, сёрфить сеть с достаточным комфортом) но при этом, сразу содержит в себе все нужные блобы, и позволяет без костылей сделать из девайса медиастанцию(с поддержкой FHD видео)/игровую станцию с эмуляторами. Android отлично управляется как с мышки, так и с клавиатуры, и даже геймпада. Кроме того — мы получаем коллекцию Android игр, пусть и не самых свежих. Получается эдакий tv бокс, только не за 2-3-4 тысячи рублей, а за 5 пачек(а иногда и половинку пачки) сигарет 🙂

Итоги

Если вам всегда было интересно попробовать что такое миниатюрные ПК, но на малинку раскошеливаться 3-4к руб. не хотелось — то вот вполне себе вариант) Такой UMPC годится для многих целей, и практически ничего не стоит, а список применений не ограничивается мультимедиа: можно ведь сделать например простенькую систему видеонаблюдения, с GSM сигнализацией. А ваше мнение?

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Полезная статья?
83.75% Да. Всё хотел себе мини-пк, но никак руки не доходили — ведь стоят они не так уж и дешево 67
16.25% Нет, лучше бы про мобилки писал дальше 13
Проголосовали 80 пользователей. Воздержались 20 пользователей.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Есть ли применение данного девайса в современном мире?
55.06% Их много. Отличный девайс, а благодаря упавшей цене — просто топ за свои деньги! 49
44.94% Их нет. Я бы лучше малинку с алика купил. 40
Проголосовали 89 пользователей. Воздержались 14 пользователей.

ссылка на оригинал статьи https://habr.com/ru/post/685718/

Оптимизационные задачи в ритейле

Привет, Habr! На связи отдел аналитики данных X5 Tech.

Сегодня мы поговорим об очень интересном разделе прикладной математики — оптимизации.

Цели данной статьи:

  • рассказать про задачи в ритейле, которые могут решаться методами оптимизации,

  • продемонстрировать, как модельная задача ценообразования решается пакетами Pyomo и SciPy,

  • сравнить производительность солверов* Pyomo и SciPy на примере поставленной задачи.

Прим. Солвер (от англ. Solver) — программа, скомпилированная под выбранную платформу, для решения математической задачи.

Так как данная тема достаточно обширна, то помимо данной статьи (статья 1) мы планируем написать ещё две:

  • Статья 2: Обзор open-source солверов на примере задачи ритейла.

  • Статья 3: Решение модельной задачи ценообразования оптимизаторами в различных постановках.

Примеры задач

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

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

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

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

Ценообразование. Поиск оптимальной конфигурации цен с учетом ценового позиционирования, допустимых ценовых диапазонов для каждого товара и набора бизнес-ограничений. Цены должны максимизировать суммарный доход, а прибыль быть не ниже заданного уровня.

Оптимальное распределение маркетингового бюджета. Реализовать максимально эффективно выделенный на маркетинговые активности бюджет. Есть несколько каналов для рекламных акций, цель — инвестировать бюджет так, чтобы суммарный доход со всех коммуникаций был максимален. Необходимо учесть ограничения на предельную нагрузку на канал, допустимое количество коммуникаций для каждого клиента и пр.

Планирование ассортимента. Подобрать полочный ассортимент товаров так, чтобы максимизировать оборот с учетом полочного пространства и других характеристик магазина.

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

Логистика. Задача — найти оптимальный график доставки продуктов с учётом вместимости грузовиков, затрат на логистику и тд.

Общая постановка задачи и её разновидности

Прежде всего рассмотрим постановку задачи в общем виде:

x— вектор размерности nx  \in X — допустимое множество значений этих переменных.

f(x)→\min(\max), f(\cdot)— целевая функция;

g_{i}(x) \leqslant 0,\ i=1..m— ограничения вида неравенств;

h_i(x) = 0,\ j=1..k— ограничения вида равенств;

Исходя из практики можно разложить данную постановку на несколько классов в зависимости от вида целевой функции, ограничений и X:

  • Безусловная оптимизация g_i(x), h_j(x)— отсутствуют, X = \mathbb{R}^n

  • LP (linear programming) — линейное программирование. f(x), g_i(x), h_j(x)— линейные функции, X = \mathbb{R}_+^n

  • MILP (mixed integer linear programming) — смешанное целочисленное линейное программирование, это задача LP в которой только часть переменных являются целочисленными;

  • NLP (nonlinear programming) — нелинейное программирование, возникает когда хотя бы одна из функций f(x),\ g_i(x),\ h_j(x)нелинейна;

  • MINLP (mixed integer nonlinear programming) — смешанное целочисленное нелинейное программирование, возникает как и в MILP, когда часть переменных принимает целочисленные значения;

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

Подробнее о том, как получаются различные виды задачи ценообразования исходя из бизнес-формулировки, мы будем говорить в статье 3.

Оптимизация модельной задачи ценообразования

Рассмотрев основные классы оптимизационных задач, перейдём к построению модели ценообразования.

Предположим, что для товара iизвестно значение эластичности E_i, а спрос задаётся следующей зависимостью:

\begin{equation} Q_{i}(P_{i}) = Q_{0, i} \exp\bigg(E_{i} \cdot \bigg(\frac{P_{i}}{P_{0, i}} - 1\bigg)\bigg). \end{equation}

Введём обозначения:

  • n— количество товаров,

  • C_i— себестоимость i-го товара,

  • Q_{i}, Q_{0, i}— спрос по новой P_iи текущей P_{0, i}ценам, соответственно,

  • x_{i}=P_{i}\mathbin{/}P_{0, i}.

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

Задача — найти такой набор новых цен, чтобы:

  • максимизировать оборот со всех товаров,

  • общая прибыль осталась на прежнем уровне,

  • цены лежали в заданных границах (индекс l и u — для нижней и верхней, соответственно).

Тогда оптимизационную задачу можно записать следующей системой:

\begin{cases} \sum_{i=1}^{n} P_i(x_i) \cdot Q_i(x_i) \to \max_{x},\\ \\ \sum_{i=1}^{n} (P_i(x_i) - C_i) \cdot Q_i(x_i) \geqslant \sum_{i=1}^{n} (P_{0, i} - C_i) \cdot Q_ {0, i},\\ \\ x_i \in [x_i^l, x_i^u], \ i=1..n\\ \end{cases}

Данная модель принадлежит к классу NLP, как нетрудно заметить из данных выше определений.

Реализация модели в Pyomo и SciPy

Для демонстрации решения необходимы данные.

Реальные данные мы использовать не можем из-за NDA, поэтому будем генерировать их из случайных распределений (функция generate_data), исходя из наших представлений о возможных значениях величин в фуд-ритейле.

 Пример данных для NLP постановки: 

SciPy и Pyomo имеют разные интерфейсы, чтобы как-то унифицировать работу с ними, будем наследоваться от базового класса, код класса.

Методы, которые необходимо реализовать для каждого солвера:

  • init_objective — задание целевой функции,

  • init_constraints — добавление ограничений,

  • solve — поиск оптимального решения.

Опишем далее отличия в реализации этих методов в SciPy и Pyomo.

Другие примеры из официальной документации можно найти здесь, для Pyomo и здесь, для SciPy.

Задание целевой функции

В SciPy оптимизатор работает в режиме минимизации, поэтому суммарный оборот берём со знаком «-«.

В Pyomo функцию пересчёта суммарного оборота необходимо передать в переменную expr объекта pyo.Objective.

# SciPy def objective(x):     return -sum(self.P * x * self.Q * self._el(self.E, x))  # Pyomo objective = sum(self.P[i] * self.model.x[i] * self.Q[i] * self._el(i) for i in range(self.N)) self.model.obj = pyo.Objective(expr=objective, sense=pyo.maximize)

Задание ограничений

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

Для SciPy ограничения передаются через NonlinearConstraint или LinearConstraint().

Для Pyomo ограничения передаются через pyo.Constraint().

# SciPy A = np.eye(self.N, self.N, dtype=float) bounds = LinearConstraint(A, self.x_lower, self.x_upper) self.constraints.append(bounds)  def con_mrg(x):     m = sum((self.P * x - self.C) * self.Q * self._el(self.E, x))     return m constr = NonlinearConstraint(con_mrg, self.m_min, np.inf) self.constraints.append(constr)  # Pyomo self.model.x = pyo.Var(range(self.N), domain=pyo.Reals, bounds=bounds_fun, initialize=init_fun) con_mrg_expr = sum((self.P[i] * self.model.x[i] - self.C[i]) * self.Q[i] * self._el(i)                    for i in range(self.N)) >= self.m_min self.model.con_mrg = pyo.Constraint(rule=con_mrg_expr)

Поиск решения

В SciPy задача оптимизации запускается через метод minimize, а в Pyomo через проинициализированный объект SolverFactory методом solve.

# SciPy result = minimize(self.obj, self.x_init,  method='cobyla', constraints=self.constraints)  # Pyomo solver = pyo.SolverFactory(solver, tee=False) result = solver.solve(self.model)

Расчёт и результаты

Так как оптимизаторы устанавливаются отдельно от python, то для получения аналогичных результатов можно воспользоваться Dockerfile, настроив контейнер, как описано в README проекта.

Для сравнения расчётов достаточно выполнить команду (осторожно, в режиме compare расчёт на макбуке 2019 года занимает > 3 часов, для ускорения можно уменьшить GRID_SIZE):

python runner.py -m compare --GRID_SIZE 255

Зависимость длительности поиска оптимального решения от количества переменных представлена на графике ниже.

Из графика можно сделать вывод, что Pyomo с солвером ipopt значительно превосходит SciPy с cobyla при N > 20.

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

Заключение

В данной статье мы:

  • Привели типичные примеры постановок задач оптимизации в области ритейла.

    • Рассчитываем, что читателю станет легче распознавать подобные задачи в своей области.

  • Показали, как можно решать задачу ценообразования с помощью Pyomo (код для статьи).

    • Будем рады, если новичкам в моделировании данный материал упростит ознакомительный этап.

  • Сравнили производительность солверов Pyomo.ipopt и Scipy.cobyla.

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

В следующих статьях более детально обсудим другие open-source солверы для python, их различия в реализации и производительности.

Над статьей работали Антон Денисов, Михаил Будылин.


ссылка на оригинал статьи https://habr.com/ru/company/X5Tech/blog/685590/

Найти вероятность выпадения k (сумма выпавших значений) при бросании n кубиков (часть 2 из 2)

Введение

В своей предыдущей статье я описал способ нахождения делимого вероятности выпадения какой-то суммы чисел на кубиках при помощи многократной свёртки последовательности [1 1 1 1 1 1] на саму себя. Иными словами, многократное умножение в столбик (без переноса переполнившихся разрядов) последовательности/числа 111111 на саму/само себя. Почему, правда, не пишут, что умножение в столбик является прямой аналогией свёртки последовательностей — для меня загадка (может я что-то упускаю из вида — если я не прав, пожалуйста, напишите). Однако, дальше в статье я буду применять два словосочетания «свёртка последовательностей» и «умножение в столбик» совместно, т.к. первое — корректное описание операции, а второе отвечает за наглядность и простоту восприятия.

Напомню:

Так же в конце предыдущей статьи я «страшился» найти вероятности для 1000 кубиков. Вот именно этим и предлагаю заняться.

Прелюдия

Хотелось бы подчеркнуть, что и на картинке вверху, и собственно в предыдущей статье упор делался на «умножение в столбик» / свёртку последовательностей [1 1 1 1 1 1], и не затрагивалась возможность «умножить» на что-либо ещё. Вот эту оплошность хотелось бы упразднить.

Отвлекусь немного на факт, что любое натуральное число может быть представлено как сумма натуральных степеней числа 2 (Производящие функции — туда и обратно ответ на вопрос: какие грузы можно взвесить с помощью гирь в 20, 21, 22,…, 2n грамм и сколькими способам?). Приведу визуализацию:

Данное знание нам будет полезно для операций со степенями. Например, нахождение какого-то числа a63 будем представлять как: a63 = a1 + 2 + 4 + … + 32 = a1 * a2 * a4 * … * a32. То есть зная только I элемент и умея умножать/свёртывать будем пытаться найти 63-й элемент (степень 63) используя как можно меньше операций умножения/свёртки.

Для начала хотелось бы обкатать операцию свёртки последовательностей / «умножение в столбик» на уже знакомом треугольнике Паскаля. А именно попробовать найти 9-й элемент треугольника Паскаля зная только I элемент (именно [1 1]) не при помощи многократной свёртки последовательностей / «умножение в столбик» [1 1] на саму себя (дискретная свёртка или полиномиальное умножение), а представив что каждая последовательность чисел в треугольнике Паскаля соответствует степени последовательности [1 1]. Звучит наверно запутанно, так что приведу картинку из прошлой статьи, для визуализации:

Попробуем найти 9-й элемент треугольника Паскаля.

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

Python. Пример. II, IV, VIII, XI элемент треугольника Паскаля
# -*- coding: utf-8 -*-  import numpy  convolve_out = numpy.convolve([1, 1], [1, 1]) # [1 2 1] print(convolve_out)  convolve_out = numpy.convolve(convolve_out, convolve_out) # [1 4 6 4 1] print(convolve_out)  convolve_out = numpy.convolve(convolve_out, convolve_out) # [ 1 8 28 56 70 56 28 8 1] print(convolve_out)  convolve_out = numpy.convolve(convolve_out, [1, 1]) # [ 1 9 36 84 126 126 84 36 9 1] print(convolve_out) 

Визуализация алгоритма, I попытка

По аналогии с треугольником Паскаля хочется провернуть аналогичную операцию с кубиками, и найти для примера вероятность выпадения суммы костей 19 для 5 кубиков. Т.е. возьмём первоначальную последовательность [1 1 1 1 1 1] и дойдём до 5-ого кубика (степень 5) по следующей цепочке операций свёртки последовательностей / «умножение в столбик»:

a1 * a1 = a2

a2 * a2 = a4

a1 * a4 = a5

Приложу скрипт для нахождения “Сколько раз встречается значение” в “Этап I. Генерация 2-х списков/массивов: Значения (сумма выпавших костей) И Сколько раз встречается значение” при помощи свёртки последовательностей / “умножения в столбик”.

Python. Пример. Свёртка последовательностей [1 1 1 1 1 1]
# -*- coding: utf-8 -*-  import numpy  convolve_out = numpy.convolve([1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1]) # [1 2 3 4 5 6 5 4 3 2 1] print(convolve_out)  convolve_out = numpy.convolve(convolve_out, convolve_out) # [ 1 4 10 20 35 56 80 104 125 140 146 140 125 104 80 56 35 20 10 4 1] print(convolve_out)  convolve_out = numpy.convolve(convolve_out, [1, 1, 1, 1, 1, 1]) # [ 1 5 15 35 70 126 205 305 420 540 651 735 780 780 735 651 540 420 305 205 126 70 35 15 5 1] print(convolve_out) 

Скрипты, I попытка

В целом, как мне кажется, задумка достаточно расписана. Остаётся выложить получившейся скрипты, написанные по описанным лекалам. Простор для оптимизаций оставляю читателям.

Python
# -*- coding: utf-8 -*-  def main():     c_int_side_dice: int = 6  # сколько граней у кубика     c_int_dice_number: int = 1000  # кол-во кубиков     c_int_number_to_find: int = 2000  # число, вероятность выпадения которого хотим найти     probability = dice_probability(c_int_dice_number, c_int_number_to_find, c_int_side_dice)     print(probability)   # собственно поиск вероятности определённого значения def dice_probability(int_dice_number: int, int_number_to_find: int, c_int_side_dice: int) -> float:     if int_number_to_find >= int_dice_number and int_number_to_find <= c_int_side_dice * int_dice_number:         list_values: list[int] = [i for i in range(int_dice_number, c_int_side_dice * int_dice_number + 1)]         list_interm_probability = interm_probabilities(c_int_side_dice, int_dice_number)          for i in range(len(list_values)):             if list_values[i] == int_number_to_find:                 int_out: int = list_interm_probability[i]                 break         return int_out / (c_int_side_dice ** int_dice_number)     else:         # задаваемое число выходит за рамки реально возможного диапазона значений         return 0.0   # возвращает список/массив: сколько раз встречается значение def interm_probabilities(int_side_dice: int, int_pow: int) -> list[int]:     """     На примере int_side_dice = 6, int_pow = 5     {       1: [1, 1, 1, 1, 1, 1],       2: [1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1],       4: [1, 4, 10, 20, 35, 56, 80, 104, 125, 140, 146, 140, 125, 104, 80, 56, 35, 20, 10, 4, 1]       5: [1, 5, 15, 35, 70, 126, 205, 305, 420, 540, 651, 735, 780, 780, 735, 651, 540, 420, 305, 205, 126, 70, 35, 15, 5, 1]     }     """     dict_interm_probability: dict[int, list[int]] = {1: [1] * int_side_dice}     if int_pow == 0:         print("Не поддерживается")         quit()     elif int_pow != 1:         list_to_do = map_todo(int_pow)          for elem in list_to_do:             dict_interm_probability[elem[2]] = multiply_cins_orig(dict_interm_probability[elem[0]], dict_interm_probability[elem[1]])     return dict_interm_probability[int_pow]   # Как добраться до интересующего значения, используя x2/+nx для степеней def map_todo(int_wanted: int) -> list[tuple[int, int, int]]:     """     На примере int_wanted = 5     Степени "числа":     1     1 * 2 = 2 -> tuple(1, 1, 2)     2 * 2 = 4 -> tuple(2, 2, 4)     4 + 1 = 5 -> tuple(4, 1, 5)     """      int_current_id: int = 1     int_sum: int = 1     b_ascending: bool = True     list_solution: list[tuple[int, int, int]] = []      while True:         if int_sum == int_wanted:             break         elif b_ascending and 2 * int_current_id <= int_wanted:             list_solution.append(  # mult_1, mult_2, result                 (int_current_id, int_current_id, 2 * int_current_id)             )             int_current_id = 2 * int_current_id             int_sum = int_current_id         elif b_ascending and 2 * int_current_id > int_wanted:             b_ascending = False             int_sum = int_current_id             int_current_id = int(int_current_id / 2)  # чтобы возвращал именно integer         elif not b_ascending and int_sum + int_current_id <= int_wanted:             list_solution.append(  # mult_1, mult_2, result                 (int_sum, int_current_id, int_sum + int_current_id)             )             int_sum = int_sum + int_current_id             int_current_id = int(int_current_id / 2)  # чтобы возвращал именно integer         elif not b_ascending and int_sum + int_current_id > int_wanted:             int_current_id = int(int_current_id / 2)  # чтобы возвращал именно integer     return list_solution   # "умножение" в столбик двух массивов/списков def multiply_cins_orig(list_in_1: list[int], list_in_2: list[int]) -> list[int]:     int_len_2: int = len(list_in_2)     list_dummy: list[list[int]] = []     for i in range(int_len_2):         list_dummy.append([0] * i)  # [], [0], [0, 0], [0, 0, 0] ...      list_for_sum: list[list[int]] = []     i: int = -1     for elem_2 in list_in_2:         i += 1         list_interm: list[int] = [elem_1 * elem_2 for elem_1 in list_in_1]         list_for_sum.append(list_dummy[i] + list_interm + list_dummy[int_len_2 - i - 1])      """     [list_in_1 X elem_2[0], 0, 0, 0, 0, 0]     [0, list_in_1 X elem_2[1], 0, 0, 0, 0]     [0, 0, list_in_1 X elem_2[2], 0, 0, 0]     [0, 0, 0, list_in_1 X elem_2[3], 0, 0]     [0, 0, 0, 0, list_in_1 X elem_2[4], 0]     [0, 0, 0, 0, 0, list_in_1 X elem_2[5]]     """      list_out: list[int] = []     for i in range(len(list_for_sum[0])):         sum_out: int = 0         for j in range(int_len_2):             sum_out += list_for_sum[j][i]         list_out.append(sum_out)     """     [1, 3, 6, 10, 15, 21, 25, 27, 27, 25, 21, 15, 10, 6, 3, 1]     """     return list_out   main() 

JavaScript
function main(){     const c_int_side_dice = 6;  // сколько граней у кубика     const c_int_dice_number = 100; // кол-во кубиков     const c_int_number_to_find = 300; // число, вероятность выпадения которого хотим найти     let probability = dice_probability(c_int_dice_number, c_int_number_to_find, c_int_side_dice);     console.log(probability); }   // собственно поиск вероятности определённого значения function dice_probability(int_dice_number, int_number_to_find, c_int_side_dice){     if (int_number_to_find >= int_dice_number && int_number_to_find <= c_int_side_dice * int_dice_number){         let list_values = new Array();         let i = 0;         for (let j = int_dice_number; j <= c_int_side_dice * int_dice_number; j++){             list_values[i] = j;             i++;         }         let list_interm_probability = interm_probabilities(c_int_side_dice, int_dice_number);         let int_out;         for (let i = 0; i <= list_values.length; i++){             if (list_values[i] == int_number_to_find){                 int_out = list_interm_probability[i];                 break;             }         }         return int_out / Math.pow(c_int_side_dice, int_dice_number);     } else {         // задаваемое число выходит за рамки реально возможного диапазона значений         return 0.0;     } }  // возвращает список/массив: сколько раз встречается значение function interm_probabilities(int_side_dice, int_pow){     // На примере int_side_dice = 6, int_pow = 5     // {     //   1: [1, 1, 1, 1, 1, 1],     //   2: [1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1],     //   4: [1, 4, 10, 20, 35, 56, 80, 104, 125, 140, 146, 140, 125, 104, 80, 56, 35, 20, 10, 4, 1]     //   5: [1, 5, 15, 35, 70, 126, 205, 305, 420, 540, 651, 735, 780, 780, 735, 651, 540, 420, 305, 205, 126, 70, 35, 15, 5, 1]     // }     let dict_interm_probability = {1: Array(int_side_dice).fill(1)};     if (int_pow == 0){         console.log("Не поддерживается");         return;     } else if (int_pow != 1){         let list_to_do = map_todo(int_pow);          for (let i = 0; i < list_to_do.length; i++){             dict_interm_probability[list_to_do[i][2]] = multiply_cins_orig(dict_interm_probability[list_to_do[i][0]], dict_interm_probability[list_to_do[i][1]]);         }     }     return dict_interm_probability[int_pow]; }   // Как добраться до интересующего значения, используя x2/+nx для степеней function map_todo(int_wanted){     // На примере int_wanted = 5     // Степени "числа":     // 1     // 1 * 2 = 2 -> Array(1, 1, 2)     // 2 * 2 = 4 -> Array(2, 2, 4)     // 4 + 1 = 5 -> Array(4, 1, 5)      let int_current_id = 1;     let int_sum = 1;     let b_ascending = true;     let list_solution = new Array();     let i = 0;     while (true){         if (int_sum == int_wanted){             break;         } else if (b_ascending && 2 * int_current_id <= int_wanted){             list_solution[i] = [int_current_id, int_current_id, 2 * int_current_id];  // mult_1, mult_2, result             i++;             int_current_id = 2 * int_current_id;             int_sum = int_current_id;         } else if (b_ascending && 2 * int_current_id > int_wanted){             b_ascending = false;             int_sum = int_current_id;             int_current_id = Math.ceil(int_current_id / 2);  // чтобы возвращал именно integer         } else if (!b_ascending && int_sum + int_current_id <= int_wanted){             list_solution[i] = [int_sum, int_current_id, int_sum + int_current_id];  // mult_1, mult_2, result             i++;             int_sum = int_sum + int_current_id;             int_current_id = Math.ceil(int_current_id / 2);  // чтобы возвращал именно integer         } else if (!b_ascending && int_sum + int_current_id > int_wanted){             int_current_id = Math.ceil(int_current_id / 2);  // чтобы возвращал именно integer         }     }     return list_solution; }  // "умножение" в столбик двух массивов/списков function multiply_cins_orig(list_in_1, list_in_2){     let int_len_1 = list_in_1.length;     let int_len_2 = list_in_2.length;          let list_dummy = new Array();     for (let j = 0; j < int_len_2; j++){         list_dummy[j] = Array(j).fill(0);  // [], [0], [0, 0], [0, 0, 0] ...     }      let list_for_sum = new Array();     for (let j = 0; j < int_len_2; j++){         let list_interm = new Array();         for (let i = 0; i < int_len_1; i++){             list_interm[i] = list_in_1[i] * list_in_2[j]         }         list_for_sum[j] = list_dummy[j].concat(list_interm, list_dummy[int_len_2 - j - 1]);     }          // [list_in_1 X elem_2[0], 0, 0, 0, 0, 0]     // [0, list_in_1 X elem_2[1], 0, 0, 0, 0]     // [0, 0, list_in_1 X elem_2[2], 0, 0, 0]     // [0, 0, 0, list_in_1 X elem_2[3], 0, 0]     // [0, 0, 0, 0, list_in_1 X elem_2[4], 0]     // [0, 0, 0, 0, 0, list_in_1 X elem_2[5]]           let list_out = new Array();     for (let i = 0; i < list_for_sum[0].length; i++){         let sum_out = 0;         for (let j = 0; j < int_len_2; j++){             sum_out += list_for_sum[j][i];         }         list_out[i] = sum_out;     }      // [1, 3, 6, 10, 15, 21, 25, 27, 27, 25, 21, 15, 10, 6, 3, 1]     return list_out; }  main();

VBS
Option Explicit   Sub main()     Const c_int_side_dice = 6  'сколько граней у кубика     Const c_int_dice_number = 100  'кол-во кубиков     Const c_int_number_to_find = 200  'число, вероятность выпадения которого хотим найти     Dim probability     probability = dice_probability(c_int_dice_number, c_int_number_to_find, c_int_side_dice)     MsgBox probability End Sub   ' собственно поиск вероятности определённого значения Function dice_probability(int_dice_number, int_number_to_find, c_int_side_dice)     If int_number_to_find >= int_dice_number And int_number_to_find <= c_int_side_dice * int_dice_number Then         ReDim list_values(int_dice_number * (c_int_side_dice - 1))         Dim i, j         i = 0         For j = int_dice_number To c_int_side_dice * int_dice_number             list_values(i) = j             i = i + 1         Next Dim list_interm_probability() interm_probabilities c_int_side_dice, int_dice_number, list_interm_probability For i = 0 To int_dice_number * (c_int_side_dice - 1) If list_values(i) = int_number_to_find Then Exit For End If Next dice_probability = list_interm_probability(i) / (c_int_side_dice ^ int_dice_number)     Else         'задаваемое число выходит за рамки реально возможного диапазона значений         dice_probability = 0.0     End If End Function   'возвращает список/массив: сколько раз встречается значение Sub interm_probabilities(int_side_dice, int_pow, list_out)     'На примере int_side_dice = 6, int_pow = 5     '{     '  1: [1, 1, 1, 1, 1, 1],     '  2: [1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1],     '  4: [1, 4, 10, 20, 35, 56, 80, 104, 125, 140, 146, 140, 125, 104, 80, 56, 35, 20, 10, 4, 1]     '  5: [1, 5, 15, 35, 70, 126, 205, 305, 420, 540, 651, 735, 780, 780, 735, 651, 540, 420, 305, 205, 126, 70, 35, 15, 5, 1]     '}     Dim j     Dim list_interm_probability()     ReDim list_interm_probability(int_side_dice - 1)     For j = 0 To int_side_dice - 1         list_interm_probability(j) = 1     Next     Dim dict_interm_probability     Set dict_interm_probability = CreateObject("Scripting.Dictionary")     dict_interm_probability.Add 1, list_interm_probability      If int_pow = 0 Then         MsgBox "Не поддерживается"         Quit     ElseIf int_pow <> 1 Then         Dim list_to_do()         map_todo list_to_do, int_pow         For j = 0 To UBound(list_to_do, 2)             'MsgBox list_to_do(0, j) & vbTab & list_to_do(1, j) & vbTab & list_to_do(2, j)             multiply_cins_orig _                 dict_interm_probability.Item(list_to_do(0, j)), _                 dict_interm_probability.Item(list_to_do(1, j)), _                 list_out             dict_interm_probability.Add list_to_do(2, j), list_out             ' ArrOut_1 list_out         Next     End If End Sub   'Как добраться до интересующего значения, используя x2/+nx для степеней Sub map_todo(list_solution, int_wanted)     'На примере int_wanted = 5     'Степени "числа":     '1     '1 * 2 = 2 -> Array(1, 1, 2)     '2 * 2 = 4 -> Array(2, 2, 4)     '4 + 1 = 5 -> Array(4, 1, 5)      Dim int_current_id     Dim int_sum     Dim b_ascending     Dim i          int_current_id = 1     int_sum = 1     b_ascending = True     i = -1      Do         If b_ascending And 2 * int_current_id <= int_wanted Then             i = i + 1             ReDim Preserve list_solution(2, i)             list_solution(0, i) = int_current_id             list_solution(1, i) = int_current_id             list_solution(2, i) = 2 * int_current_id             int_current_id = 2 * int_current_id             int_sum = int_current_id         ElseIf b_ascending And 2 * int_current_id > int_wanted Then             b_ascending = False             int_sum = int_current_id             int_current_id = CInt(int_current_id / 2)  'чтобы возвращал именно integer         ElseIf Not b_ascending And int_sum + int_current_id <= int_wanted Then             i = i + 1             ReDim Preserve list_solution(2, i)             list_solution(0, i) = int_sum             list_solution(1, i) = int_current_id             list_solution(2, i) = int_sum + int_current_id             int_sum = int_sum + int_current_id             int_current_id = CInt(int_current_id / 2)  'чтобы возвращал именно integer         ElseIf Not b_ascending And int_sum + int_current_id > int_wanted Then             int_current_id = CInt(int_current_id / 2)  'чтобы возвращал именно integer         End If     Loop Until (int_sum = int_wanted) End Sub   ' "умножение" в столбик двух массивов/списков Sub multiply_cins_orig(list_in_1, list_in_2, list_in)     Dim int_len_1     Dim int_len_2     int_len_1 = Ubound(list_in_1, 1)     int_len_2 = Ubound(list_in_2, 1)      Dim list_for_sum()     ReDim list_for_sum(int_len_2, int_len_1 + int_len_2)     Dim i, j, k, n     For i = 0 To int_len_2         j = 0         For n = 0 To int_len_2             If i = n Then                 For k = 0 To int_len_1                     list_for_sum(i, j) = list_in_1(k) * list_in_2(n)                     j = j + 1                 Next             Else                 list_for_sum(i, j) = 0                 j = j + 1             End If         Next     Next     '[list_in_1 X elem_2[0], 0, 0, 0, 0, 0]     '[0, list_in_1 X elem_2[1], 0, 0, 0, 0]     '[0, 0, list_in_1 X elem_2[2], 0, 0, 0]     '[0, 0, 0, list_in_1 X elem_2[3], 0, 0]     '[0, 0, 0, 0, list_in_1 X elem_2[4], 0]     '[0, 0, 0, 0, 0, list_in_1 X elem_2[5]]      'ArrOut_2 list_for_sum     Erase list_in     ReDim list_in(int_len_1 + int_len_2)     Dim sum_out     For j = 0 To int_len_1 + int_len_2         sum_out = 0         For i = 0 To int_len_2             sum_out = sum_out + list_for_sum(i, j)         Next         list_in(j) = sum_out     Next     ' [1, 3, 6, 10, 15, 21, 25, 27, 27, 25, 21, 15, 10, 6, 3, 1]     'ArrOut_1 list_in End Sub   '================================================== '<Additional_MsgBox_For_Arrays> Sub ArrOut_1(arr_in)     Dim str_out     Dim i     For i = 0 To UBound(arr_in)         If i = 0 Then             str_out = arr_in(i)         Else             str_out = str_out & " " & arr_in(i)         End If     Next     MsgBox str_out End Sub  Sub ArrOut_2(arr_in)     Dim str_out     Dim i, j     For i = 0 To UBound(arr_in, 1)         For j = 0 To UBound(arr_in, 2)             If i = 0 And j = 0 Then                 str_out = arr_in(i, j)             ElseIf j = 0 Then                 str_out = str_out & vbNewLine & arr_in(i, j)             Else                 str_out = str_out & " " & arr_in(i, j)             End If         Next     Next     MsgBox str_out End Sub '</Additional_MsgBox_For_Arrays> '==================================================  main

Визуализация алгоритма, II попытка

Если быть достаточно честным, то из 3-х выложенных скриптов именно до 1000-го кубика может добраться только Python (без использования библиотеки numpy), а JavaScript и VBS выпадают в ошибку переполнение переменной. Предлагаю сделать небольшую хитрость: считать сразу вероятность выпадения внутри операции свёртки последовательностей / «умножения в столбик», вместо только делимого. Т.е. на вход сразу подавать последовательность [1/6 1/6 1/6 1/6 1/6 1/6] вместо [1 1 1 1 1 1] и, следовательно, на выходе всех операций свёртки последовательностей / «умножения в столбик» мы получим последовательность / массив / список вероятностей.

Python. Пример. Свёртка последовательностей [1/6 1/6 1/6 1/6 1/6 1/6]

Понимаю, что дроби проверять — дело не благодарное, следовательно добавляю степень 6 ** n * … — делитель вероятности. Это сделано чисто для упрощения проверки.

# -*- coding: utf-8 -*-  import numpy  convolve_out = numpy.convolve([1 / 6] * 6, [1 / 6] * 6) # [1 2 3 4 5 6 5 4 3 2 1] print(6 ** 2 * convolve_out)  convolve_out = numpy.convolve(convolve_out, convolve_out) # [ 1 4 10 20 35 56 80 104 125 140 146 140 125 104 80 56 35 20 10 4 1] print(6 ** 4 * convolve_out )  convolve_out = numpy.convolve(convolve_out, [1 / 6] * 6) # [ 1 5 15 35 70 126 205 305 420 540 651 735 780 780 735 651 540 420 305 205 126 70 35 15 5 1] print(6 ** 5 * convolve_out) 

Скрипты, II попытка

До 1000-го кубика добираются все. Простор для оптимизаций оставляю читателям.

Python
# -*- coding: utf-8 -*- # import numpy  # <можно_использовать_numpy>  def main():     c_int_side_dice: int = 6  # сколько граней у кубика     c_int_dice_number: int = 1000  # кол-во кубиков     c_int_number_to_find: int = 2000  # число, вероятность выпадения которого хотим найти     probability = dice_probability(c_int_dice_number, c_int_number_to_find, c_int_side_dice)     print(probability)   # собственно поиск вероятности определённого значения def dice_probability(int_dice_number: int, int_number_to_find: int, c_int_side_dice: int) -> float:     if int_number_to_find >= int_dice_number and int_number_to_find <= c_int_side_dice * int_dice_number:         list_values: list[int] = [i for i in range(int_dice_number, c_int_side_dice * int_dice_number + 1)]         list_probability = get_probabilities(c_int_side_dice, int_dice_number)          for i in range(len(list_values)):             if list_values[i] == int_number_to_find:                 float_out: float = list_probability[i]                 break         return float_out     else:         # задаваемое число выходит за рамки реально возможного диапазона значений         return 0.0   # возвращает список/массив: вероятности выадения def get_probabilities(int_side_dice: int, int_pow: int) -> list[float]:     """     На примере int_side_dice = 6, int_pow = 5     {       1: [1 / 6, 1 / 6, 1 / 6, 1 / 6, 1 / 6, 1 / 6],       2: [1 / 36, 2 / 36, 3 / 36, 4 / 36, 5 / 36, 6 / 36, 5 / 36, 4 / 36, 3 / 36, 2 / 36, 1 / 36],       4: [1 / 1296, 4 / 1296, 10 / 1296, 20 / 1296, 35 / 1296, 56 / 1296, 80 / 1296, 104 / 1296, 125 / 1296, 140 / 1296, 146 / 1296, 140 / 1296, 125 / 1296, 104 / 1296, 80 / 1296, 56 / 1296, 35 / 1296, 20 / 1296, 10 / 1296, 4 / 1296, 1 / 1296]       5: [1 / 7776, 5 / 7776, 15 / 7776, 35 / 7776, 70 / 7776, 126 / 7776, 205 / 7776, 305 / 7776, 420 / 7776, 540 / 7776, 651 / 7776, 735 / 7776, 780 / 7776, 780 / 7776, 735 / 7776, 651 / 7776, 540 / 7776, 420 / 7776, 305 / 7776, 205 / 7776, 126 / 7776, 70 / 7776, 35 / 7776, 15 / 7776, 5 / 7776, 1 / 7776]     }     """     dict_interm_probability = {1: [1 / int_side_dice] * int_side_dice}     if int_pow == 0:         print("Не поддерживается")         quit()     elif int_pow != 1:         list_to_do = map_todo(int_pow)          for elem in list_to_do:             dict_interm_probability[elem[2]] = multiply_cins_orig(dict_interm_probability[elem[0]], dict_interm_probability[elem[1]])             # dict_interm_probability[elem[2]] = numpy.convolve(dict_interm_probability[elem[0]], dict_interm_probability[elem[1]])  # <можно_использовать_numpy> и не использовать multiply_cins_orig()     return dict_interm_probability[int_pow]   # Как добраться до интересующего значения, используя x2/+nx для степеней def map_todo(int_wanted: int) -> list[tuple[int, int, int]]:     """     На примере int_wanted = 5     Степени "числа":     1     1 * 2 = 2 -> tuple(1, 1, 2)     2 * 2 = 4 -> tuple(2, 2, 4)     4 + 1 = 5 -> tuple(4, 1, 5)     """      int_current_id: int = 1     int_sum: int = 1     b_ascending: bool = True     list_solution: list[tuple[int, int, int]] = []      while True:         if int_sum == int_wanted:             break         elif b_ascending and 2 * int_current_id <= int_wanted:             list_solution.append(  # mult_1, mult_2, result                 (int_current_id, int_current_id, 2 * int_current_id)             )             int_current_id = 2 * int_current_id             int_sum = int_current_id         elif b_ascending and 2 * int_current_id > int_wanted:             b_ascending = False             int_sum = int_current_id             int_current_id = int(int_current_id / 2)  # чтобы возвращал именно integer         elif not b_ascending and int_sum + int_current_id <= int_wanted:             list_solution.append(  # mult_1, mult_2, result                 (int_sum, int_current_id, int_sum + int_current_id)             )             int_sum = int_sum + int_current_id             int_current_id = int(int_current_id / 2)  # чтобы возвращал именно integer         elif not b_ascending and int_sum + int_current_id > int_wanted:             int_current_id = int(int_current_id / 2)  # чтобы возвращал именно integer     return list_solution   # "умножение" в столбик двух массивов/списков def multiply_cins_orig(list_in_1: list[int], list_in_2: list[int]) -> list[int]:     int_len_2: int = len(list_in_2)     list_dummy: list[list[int]] = []     for i in range(int_len_2):         list_dummy.append([0] * i)  # [], [0], [0, 0], [0, 0, 0] ...      list_for_sum: list[list[int]] = []     i: int = -1     for elem_2 in list_in_2:         i += 1         list_interm: list[int] = [elem_1 * elem_2 for elem_1 in list_in_1]         list_for_sum.append(list_dummy[i] + list_interm + list_dummy[int_len_2 - i - 1])      """     [list_in_1 X list_in_2[0], 0, 0, 0, 0, 0]     [0, list_in_1 X list_in_2[1], 0, 0, 0, 0]     [0, 0, list_in_1 X list_in_2[2], 0, 0, 0]     [0, 0, 0, list_in_1 X list_in_2[3], 0, 0]     [0, 0, 0, 0, list_in_1 X list_in_2[4], 0]     [0, 0, 0, 0, 0, list_in_1 X list_in_2[5]]     """      list_out: list[int] = []     for i in range(len(list_for_sum[0])):         sum_out: int = 0         for j in range(int_len_2):             sum_out += list_for_sum[j][i]         list_out.append(sum_out)     """     [1 / 216, 3 / 216, 6 / 216, 10 / 216, 15 / 216, 21 / 216, 25 / 216, 27 / 216, 27 / 216, 25 / 216, 21 / 216, 15 / 216, 10 / 216, 6 / 216, 3 / 216, 1 / 216]     """     return list_out   main() 

JavaScript
function main(){     const c_int_side_dice = 6;  // сколько граней у кубика     const c_int_dice_number = 1000; // кол-во кубиков     const c_int_number_to_find = 2000; // число, вероятность выпадения которого хотим найти     let probability = dice_probability(c_int_dice_number, c_int_number_to_find, c_int_side_dice);     console.log(probability); }   // собственно поиск вероятности определённого значения function dice_probability(int_dice_number, int_number_to_find, c_int_side_dice){     if (int_number_to_find >= int_dice_number && int_number_to_find <= c_int_side_dice * int_dice_number){         let list_values = new Array();         let i = 0;         for (let j = int_dice_number; j <= c_int_side_dice * int_dice_number; j++){             list_values[i] = j;             i++;         }         let list_probability = get_probabilities(c_int_side_dice, int_dice_number);         let float_out;         for (let i = 0; i <= list_values.length; i++){             if (list_values[i] == int_number_to_find){                 float_out = list_probability[i];                 break;             }         }         return float_out;     } else {         // задаваемое число выходит за рамки реально возможного диапазона значений         return 0.0;     } }  // возвращает список/массив: вероятности выадения function get_probabilities(int_side_dice, int_pow){     // На примере int_side_dice = 6, int_pow = 5     // {     //   1: [1 / 6, 1 / 6, 1 / 6, 1 / 6, 1 / 6, 1 / 6],     //   2: [1 / 36, 2 / 36, 3 / 36, 4 / 36, 5 / 36, 6 / 36, 5 / 36, 4 / 36, 3 / 36, 2 / 36, 1 / 36],     //   4: [1 / 1296, 4 / 1296, 10 / 1296, 20 / 1296, 35 / 1296, 56 / 1296, 80 / 1296, 104 / 1296, 125 / 1296, 140 / 1296, 146 / 1296, 140 / 1296, 125 / 1296, 104 / 1296, 80 / 1296, 56 / 1296, 35 / 1296, 20 / 1296, 10 / 1296, 4 / 1296, 1 / 1296]     //   5: [1 / 7776, 5 / 7776, 15 / 7776, 35 / 7776, 70 / 7776, 126 / 7776, 205 / 7776, 305 / 7776, 420 / 7776, 540 / 7776, 651 / 7776, 735 / 7776, 780 / 7776, 780 / 7776, 735 / 7776, 651 / 7776, 540 / 7776, 420 / 7776, 305 / 7776, 205 / 7776, 126 / 7776, 70 / 7776, 35 / 7776, 15 / 7776, 5 / 7776, 1 / 7776]     // }      let dict_interm_probability = {1: Array(int_side_dice).fill(1 / int_side_dice)};     if (int_pow == 0){         console.log("Не поддерживается");         return;     } else if (int_pow != 1){         let list_to_do = map_todo(int_pow);          for (let i = 0; i < list_to_do.length; i++){             dict_interm_probability[list_to_do[i][2]] = multiply_cins_orig(dict_interm_probability[list_to_do[i][0]], dict_interm_probability[list_to_do[i][1]]);         }     }     return dict_interm_probability[int_pow]; }   // Как добраться до интересующего значения, используя x2/+nx для степеней function map_todo(int_wanted){     // На примере int_wanted = 5     // Степени "числа":     // 1     // 1 * 2 = 2 -> Array(1, 1, 2)     // 2 * 2 = 4 -> Array(2, 2, 4)     // 4 + 1 = 5 -> Array(4, 1, 5)      let int_current_id = 1;     let int_sum = 1;     let b_ascending = true;     let list_solution = new Array();     let i = 0;     while (true){         if (int_sum == int_wanted){             break;         } else if (b_ascending && 2 * int_current_id <= int_wanted){             list_solution[i] = [int_current_id, int_current_id, 2 * int_current_id];  // mult_1, mult_2, result             i++;             int_current_id = 2 * int_current_id;             int_sum = int_current_id;         } else if (b_ascending && 2 * int_current_id > int_wanted){             b_ascending = false;             int_sum = int_current_id;             int_current_id = Math.ceil(int_current_id / 2);  // чтобы возвращал именно integer         } else if (!b_ascending && int_sum + int_current_id <= int_wanted){             list_solution[i] = [int_sum, int_current_id, int_sum + int_current_id];  // mult_1, mult_2, result             i++;             int_sum = int_sum + int_current_id;             int_current_id = Math.ceil(int_current_id / 2);  // чтобы возвращал именно integer         } else if (!b_ascending && int_sum + int_current_id > int_wanted){             int_current_id = Math.ceil(int_current_id / 2);  // чтобы возвращал именно integer         }     }     return list_solution; }  // "умножение" в столбик двух массивов/списков function multiply_cins_orig(list_in_1, list_in_2){     let int_len_1 = list_in_1.length;     let int_len_2 = list_in_2.length;          let list_dummy = new Array();     for (let j = 0; j < int_len_2; j++){         list_dummy[j] = Array(j).fill(0);  // [], [0], [0, 0], [0, 0, 0] ...     }      let list_for_sum = new Array();     for (let j = 0; j < int_len_2; j++){         let list_interm = new Array();         for (let i = 0; i < int_len_1; i++){             list_interm[i] = list_in_1[i] * list_in_2[j]         }         list_for_sum[j] = list_dummy[j].concat(list_interm, list_dummy[int_len_2 - j - 1]);     }          // [list_in_1 X list_in_2[0], 0, 0, 0, 0, 0]     // [0, list_in_1 X list_in_2[1], 0, 0, 0, 0]     // [0, 0, list_in_1 X list_in_2[2], 0, 0, 0]     // [0, 0, 0, list_in_1 X list_in_2[3], 0, 0]     // [0, 0, 0, 0, list_in_1 X list_in_2[4], 0]     // [0, 0, 0, 0, 0, list_in_1 X list_in_2[5]]           let list_out = new Array();     for (let i = 0; i < list_for_sum[0].length; i++){         let sum_out = 0;         for (let j = 0; j < int_len_2; j++){             sum_out += list_for_sum[j][i];         }         list_out[i] = sum_out;     }      // [1 / 216, 3 / 216, 6 / 216, 10 / 216, 15 / 216, 21 / 216, 25 / 216, 27 / 216, 27 / 216, 25 / 216, 21 / 216, 15 / 216, 10 / 216, 6 / 216, 3 / 216, 1 / 216]     return list_out; }  main();

VBS
Option Explicit   Sub main()     Const c_int_side_dice = 6  'сколько граней у кубика     Const c_int_dice_number = 1000  'кол-во кубиков     Const c_int_number_to_find = 2000  'число, вероятность выпадения которого хотим найти     Dim probability     probability = dice_probability(c_int_dice_number, c_int_number_to_find, c_int_side_dice)     MsgBox probability End Sub   ' собственно поиск вероятности определённого значения Function dice_probability(int_dice_number, int_number_to_find, c_int_side_dice)     If int_number_to_find >= int_dice_number And int_number_to_find <= c_int_side_dice * int_dice_number Then         ReDim list_values(int_dice_number * (c_int_side_dice - 1))         Dim i, j         i = 0         For j = int_dice_number To c_int_side_dice * int_dice_number             list_values(i) = j             i = i + 1         Next         Dim list_probability()         get_probabilities c_int_side_dice, int_dice_number, list_probability         For i = 0 To int_dice_number * (c_int_side_dice - 1)             If list_values(i) = int_number_to_find Then                 Exit For             End If         Next         dice_probability = list_probability(i)     Else         'задаваемое число выходит за рамки реально возможного диапазона значений         dice_probability = 0.0     End If End Function   'возвращает список/массив: вероятности выадения Sub get_probabilities(int_side_dice, int_pow, list_out)     'На примере int_side_dice = 6, int_pow = 5     '{     '  1: [1 / 6, 1 / 6, 1 / 6, 1 / 6, 1 / 6, 1 / 6],     '  2: [1 / 36, 2 / 36, 3 / 36, 4 / 36, 5 / 36, 6 / 36, 5 / 36, 4 / 36, 3 / 36, 2 / 36, 1 / 36],     '  4: [1 / 1296, 4 / 1296, 10 / 1296, 20 / 1296, 35 / 1296, 56 / 1296, 80 / 1296, 104 / 1296, 125 / 1296, 140 / 1296, 146 / 1296, 140 / 1296, 125 / 1296, 104 / 1296, 80 / 1296, 56 / 1296, 35 / 1296, 20 / 1296, 10 / 1296, 4 / 1296, 1 / 1296]     '  5: [1 / 7776, 5 / 7776, 15 / 7776, 35 / 7776, 70 / 7776, 126 / 7776, 205 / 7776, 305 / 7776, 420 / 7776, 540 / 7776, 651 / 7776, 735 / 7776, 780 / 7776, 780 / 7776, 735 / 7776, 651 / 7776, 540 / 7776, 420 / 7776, 305 / 7776, 205 / 7776, 126 / 7776, 70 / 7776, 35 / 7776, 15 / 7776, 5 / 7776, 1 / 7776]     '}     Dim j     Dim list_probability()     ReDim list_probability(int_side_dice - 1)     For j = 0 To int_side_dice - 1         list_probability(j) = 1 / int_side_dice     Next     Dim dict_interm_probability     Set dict_interm_probability = CreateObject("Scripting.Dictionary")     dict_interm_probability.Add 1, list_probability      If int_pow = 0 Then         MsgBox "Не поддерживается"         Quit     ElseIf int_pow <> 1 Then         Dim list_to_do()         map_todo list_to_do, int_pow         For j = 0 To UBound(list_to_do, 2)             'MsgBox list_to_do(0, j) & vbTab & list_to_do(1, j) & vbTab & list_to_do(2, j)             multiply_cins_orig _                 dict_interm_probability.Item(list_to_do(0, j)), _                 dict_interm_probability.Item(list_to_do(1, j)), _                 list_out             dict_interm_probability.Add list_to_do(2, j), list_out             ' ArrOut_1 list_out         Next     End If End Sub   'Как добраться до интересующего значения, используя x2/+nx для степеней Sub map_todo(list_solution, int_wanted)     'На примере int_wanted = 5     'Степени "числа":     '1     '1 * 2 = 2 -> Array(1, 1, 2)     '2 * 2 = 4 -> Array(2, 2, 4)     '4 + 1 = 5 -> Array(4, 1, 5)      Dim int_current_id     Dim int_sum     Dim b_ascending     Dim i          int_current_id = 1     int_sum = 1     b_ascending = True     i = -1      Do         If b_ascending And 2 * int_current_id <= int_wanted Then             i = i + 1             ReDim Preserve list_solution(2, i)             list_solution(0, i) = int_current_id             list_solution(1, i) = int_current_id             list_solution(2, i) = 2 * int_current_id             int_current_id = 2 * int_current_id             int_sum = int_current_id         ElseIf b_ascending And 2 * int_current_id > int_wanted Then             b_ascending = False             int_sum = int_current_id             int_current_id = CInt(int_current_id / 2)  'чтобы возвращал именно integer         ElseIf Not b_ascending And int_sum + int_current_id <= int_wanted Then             i = i + 1             ReDim Preserve list_solution(2, i)             list_solution(0, i) = int_sum             list_solution(1, i) = int_current_id             list_solution(2, i) = int_sum + int_current_id             int_sum = int_sum + int_current_id             int_current_id = CInt(int_current_id / 2)  'чтобы возвращал именно integer         ElseIf Not b_ascending And int_sum + int_current_id > int_wanted Then             int_current_id = CInt(int_current_id / 2)  'чтобы возвращал именно integer         End If     Loop Until (int_sum = int_wanted) End Sub   ' "умножение" в столбик двух массивов/списков Sub multiply_cins_orig(list_in_1, list_in_2, list_in)     Dim int_len_1     Dim int_len_2     int_len_1 = Ubound(list_in_1, 1)     int_len_2 = Ubound(list_in_2, 1)      Dim list_for_sum()     ReDim list_for_sum(int_len_2, int_len_1 + int_len_2)     Dim i, j, k, n     For i = 0 To int_len_2         j = 0         For n = 0 To int_len_2             If i = n Then                 For k = 0 To int_len_1                     list_for_sum(i, j) = list_in_1(k) * list_in_2(n)                     j = j + 1                 Next             Else                 list_for_sum(i, j) = 0                 j = j + 1             End If         Next     Next     '[list_in_1 X list_in_2[0], 0, 0, 0, 0, 0]     '[0, list_in_1 X list_in_2[1], 0, 0, 0, 0]     '[0, 0, list_in_1 X list_in_2[2], 0, 0, 0]     '[0, 0, 0, list_in_1 X list_in_2[3], 0, 0]     '[0, 0, 0, 0, list_in_1 X list_in_2[4], 0]     '[0, 0, 0, 0, 0, list_in_1 X list_in_2[5]]      'ArrOut_2 list_for_sum     Erase list_in     ReDim list_in(int_len_1 + int_len_2)     Dim sum_out     For j = 0 To int_len_1 + int_len_2         sum_out = 0         For i = 0 To int_len_2             sum_out = sum_out + list_for_sum(i, j)         Next         list_in(j) = sum_out     Next     ' [1 / 216, 3 / 216, 6 / 216, 10 / 216, 15 / 216, 21 / 216, 25 / 216, 27 / 216, 27 / 216, 25 / 216, 21 / 216, 15 / 216, 10 / 216, 6 / 216, 3 / 216, 1 / 216]     'ArrOut_1 list_in End Sub   '================================================== '<Additional_MsgBox_For_Arrays> Sub ArrOut_1(arr_in)     Dim str_out     Dim i     For i = 0 To UBound(arr_in)         If i = 0 Then             str_out = arr_in(i)         Else             str_out = str_out & " " & arr_in(i)         End If     Next     MsgBox str_out End Sub  Sub ArrOut_2(arr_in)     Dim str_out     Dim i, j     For i = 0 To UBound(arr_in, 1)         For j = 0 To UBound(arr_in, 2)             If i = 0 And j = 0 Then                 str_out = arr_in(i, j)             ElseIf j = 0 Then                 str_out = str_out & vbNewLine & arr_in(i, j)             Else                 str_out = str_out & " " & arr_in(i, j)             End If         Next     Next     MsgBox str_out End Sub '</Additional_MsgBox_For_Arrays> '==================================================  main

Пару слов о проверке

Проверку и перепроверку чьих бы то ни было слов всегда приветствую.

Однако, отмечу, что на текущий момент кроме как эмпирического (т.е. обычного сравнения результата работы описанного в текущей статье алгоритма с работой алгоритма в предыдущей статье или обычного расчёта “в лоб”) метода проверки я не располагаю каким-либо иным доказательством своей правоты, которое бы сочетало как доступность восприятия так и наглядность.

Следовательно:

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

Если не доверяете предыдущей статье, тогда могу предложить следующее: генератор SQL запросов, считающих в лоб кубики, суммы их выпадений и их вероятности.

Python для проверок в лоб
# -*- coding: utf-8 -*-  import sqlite3 import re  def main() -> None:     c_int_side_dice: int = 6  # сколько граней у кубика     c_int_dice_number: int = 6  # кол-во кубиков      str_query = select_values_and_interm_probabilities(c_int_side_dice, c_int_dice_number)     if True:         # Просмотр SQL кода         print(str_query)     else:         # Прогон SQL запроса         conn = sqlite3.connect(":memory:")         cursor = conn.cursor()          cursor.execute(str_query)         return_select(cursor)          cursor.close()         conn.close()   def select_values_and_interm_probabilities(int_side_dice: int, int_dice_number: int) -> str:     str_sub_query_1: str = """     -- заводим значения сторон кубика     WITH RECURSIVE step_01_insert (dice) AS (         SELECT 1 AS dice         UNION ALL         SELECT dice + 1 AS dice          FROM step_01_insert         WHERE dice < {}  -- сколько граней у кубика     )     """.format(int_side_dice)     list_sub_query_1: list[str] = []     list_sub_query_2: list[str] = []     list_sub_query_3: list[str] = []     for i in range(int_dice_number):         list_sub_query_1.append("T{}.dice AS dice_{}".format(i, i))         list_sub_query_2.append("T{}.dice".format(i))         list_sub_query_3.append("step_01_insert AS T{}".format(i))      str_sub_query_2: str = "\n".join([         "-- генерируем все возможные ситуации для {}-х кубиков".format(int_dice_number),         ", step_02_spawn AS (",             "SELECT",             "\n, ".join(list_sub_query_1),             ", " + " + ".join(list_sub_query_2) + " AS dice_sum  -- Значения (сумма выпавших костей)",             "FROM",             "\n, ".join(list_sub_query_3),         ")"     ])     del list_sub_query_1, list_sub_query_2, list_sub_query_3      str_sub_query_3: str = """     -- считаем в лоб, сколько раз встречается значение     , step_03_dividend (dice_sum, dividend) AS (         SELECT dice_sum  -- Значения (сумма выпавших костей)         , COUNT(1) AS dividend  -- Сколько раз встречается значение         FROM step_02_spawn         GROUP BY dice_sum     )     , step_04_divisor(divisor) AS (         SELECT SUM(dividend) AS divisor         FROM step_03_dividend     )     SELECT T1.dice_sum  -- Значения (сумма выпавших костей)     , T1.dividend  -- Сколько раз встречается значение     , CAST(T1.dividend AS REAL) / CAST(T2.divisor AS REAL) AS probability  -- Вероятность     FROM step_03_dividend AS T1     , step_04_divisor AS T2     ORDER BY T1.dice_sum;     """     return lazy_prety_print(str_sub_query_1 + str_sub_query_2 + str_sub_query_3)   def lazy_prety_print(str_in: str) -> str:     list_line: list[str] = []     str_offset: str = ""     for str_line_1 in str_in.split("\n"):         str_line_2 = re.sub(r"^\s+", "", str_line_1)         if len(str_line_2) > 0 and str_line_2[0] == ")":             str_offset = str_offset[:-4]         list_line.append(str_offset + str_line_2)         if len(str_line_2) > 0 and str_line_2[-1] == "(":             str_offset = str_offset + "    "     return "\n".join(list_line)   def return_select(cursor: sqlite3.Cursor) -> None:     column_list = []      for column in cursor.description:         column_list.append(column[0])      print("\t".join(column_list))      rows = cursor.fetchall()      for row in rows:         column_list = []         for row_column in row:             column_list.append(str(row_column))         print("\t".join(column_list))   if __name__ == "__main__":     main() 

Результаты работы данного скрипта приведены ниже:

SQL для 3-х кубиков
-- заводим значения сторон кубика WITH RECURSIVE step_01_insert (dice) AS (     SELECT 1 AS dice     UNION ALL     SELECT dice + 1 AS dice      FROM step_01_insert     WHERE dice < 6  -- сколько граней у кубика ) -- генерируем все возможные ситуации для 3-х кубиков , step_02_spawn AS (     SELECT     T0.dice AS dice_0     , T1.dice AS dice_1     , T2.dice AS dice_2     , T0.dice + T1.dice + T2.dice AS dice_sum  -- Значения (сумма выпавших костей)     FROM     step_01_insert AS T0     , step_01_insert AS T1     , step_01_insert AS T2 ) -- считаем в лоб, сколько раз встречается значение , step_03_dividend (dice_sum, dividend) AS (     SELECT dice_sum  -- Значения (сумма выпавших костей)     , COUNT(1) AS dividend  -- Сколько раз встречается значение     FROM step_02_spawn     GROUP BY dice_sum ) , step_04_divisor(divisor) AS (     SELECT SUM(dividend) AS divisor     FROM step_03_dividend ) SELECT T1.dice_sum  -- Значения (сумма выпавших костей) , T1.dividend  -- Сколько раз встречается значение , CAST(T1.dividend AS REAL) / CAST(T2.divisor AS REAL) AS probability  -- Вероятность FROM step_03_dividend AS T1 , step_04_divisor AS T2 ORDER BY T1.dice_sum;

dice_sum

dividend

probability

3

1

0.004629629629629629

4

3

0.013888888888888888

5

6

0.027777777777777776

6

10

0.046296296296296294

7

15

0.06944444444444445

8

21

0.09722222222222222

9

25

0.11574074074074074

10

27

0.125

11

27

0.125

12

25

0.11574074074074074

13

21

0.09722222222222222

14

15

0.06944444444444445

15

10

0.046296296296296294

16

6

0.027777777777777776

17

3

0.013888888888888888

18

1

0.004629629629629629

SQL для 4-х кубиков
-- заводим значения сторон кубика WITH RECURSIVE step_01_insert (dice) AS (     SELECT 1 AS dice     UNION ALL     SELECT dice + 1 AS dice      FROM step_01_insert     WHERE dice < 6  -- сколько граней у кубика ) -- генерируем все возможные ситуации для 4-х кубиков , step_02_spawn AS (     SELECT     T0.dice AS dice_0     , T1.dice AS dice_1     , T2.dice AS dice_2     , T3.dice AS dice_3     , T0.dice + T1.dice + T2.dice + T3.dice AS dice_sum  -- Значения (сумма выпавших костей)     FROM     step_01_insert AS T0     , step_01_insert AS T1     , step_01_insert AS T2     , step_01_insert AS T3 ) -- считаем в лоб, сколько раз встречается значение , step_03_dividend (dice_sum, dividend) AS (     SELECT dice_sum  -- Значения (сумма выпавших костей)     , COUNT(1) AS dividend  -- Сколько раз встречается значение     FROM step_02_spawn     GROUP BY dice_sum ) , step_04_divisor(divisor) AS (     SELECT SUM(dividend) AS divisor     FROM step_03_dividend ) SELECT T1.dice_sum  -- Значения (сумма выпавших костей) , T1.dividend  -- Сколько раз встречается значение , CAST(T1.dividend AS REAL) / CAST(T2.divisor AS REAL) AS probability  -- Вероятность FROM step_03_dividend AS T1 , step_04_divisor AS T2 ORDER BY T1.dice_sum;

dice_sum

dividend

probability

4

1

0.0007716049382716049

5

4

0.0030864197530864196

6

10

0.007716049382716049

7

20

0.015432098765432098

8

35

0.02700617283950617

9

56

0.043209876543209874

10

80

0.06172839506172839

11

104

0.08024691358024691

12

125

0.09645061728395062

13

140

0.10802469135802469

14

146

0.11265432098765432

15

140

0.10802469135802469

16

125

0.09645061728395062

17

104

0.08024691358024691

18

80

0.06172839506172839

19

56

0.043209876543209874

20

35

0.02700617283950617

21

20

0.015432098765432098

22

10

0.007716049382716049

23

4

0.0030864197530864196

24

1

0.0007716049382716049

SQL для 5-х кубиков
-- заводим значения сторон кубика WITH RECURSIVE step_01_insert (dice) AS (     SELECT 1 AS dice     UNION ALL     SELECT dice + 1 AS dice      FROM step_01_insert     WHERE dice < 6  -- сколько граней у кубика ) -- генерируем все возможные ситуации для 5-х кубиков , step_02_spawn AS (     SELECT     T0.dice AS dice_0     , T1.dice AS dice_1     , T2.dice AS dice_2     , T3.dice AS dice_3     , T4.dice AS dice_4     , T0.dice + T1.dice + T2.dice + T3.dice + T4.dice AS dice_sum  -- Значения (сумма выпавших костей)     FROM     step_01_insert AS T0     , step_01_insert AS T1     , step_01_insert AS T2     , step_01_insert AS T3     , step_01_insert AS T4 ) -- считаем в лоб, сколько раз встречается значение , step_03_dividend (dice_sum, dividend) AS (     SELECT dice_sum  -- Значения (сумма выпавших костей)     , COUNT(1) AS dividend  -- Сколько раз встречается значение     FROM step_02_spawn     GROUP BY dice_sum ) , step_04_divisor(divisor) AS (     SELECT SUM(dividend) AS divisor     FROM step_03_dividend ) SELECT T1.dice_sum  -- Значения (сумма выпавших костей) , T1.dividend  -- Сколько раз встречается значение , CAST(T1.dividend AS REAL) / CAST(T2.divisor AS REAL) AS probability  -- Вероятность FROM step_03_dividend AS T1 , step_04_divisor AS T2 ORDER BY T1.dice_sum;

dice_sum

dividend

probability

5

1

0.0001286008230452675

6

5

0.0006430041152263374

7

15

0.0019290123456790122

8

35

0.0045010288065843625

9

70

0.009002057613168725

10

126

0.016203703703703703

11

205

0.026363168724279837

12

305

0.03922325102880658

13

420

0.05401234567901234

14

540

0.06944444444444445

15

651

0.08371913580246913

16

735

0.09452160493827161

17

780

0.10030864197530864

18

780

0.10030864197530864

19

735

0.09452160493827161

20

651

0.08371913580246913

21

540

0.06944444444444445

22

420

0.05401234567901234

23

305

0.03922325102880658

24

205

0.026363168724279837

25

126

0.016203703703703703

26

70

0.009002057613168725

27

35

0.0045010288065843625

28

15

0.0019290123456790122

29

5

0.0006430041152263374

30

1

0.0001286008230452675

SQL для 6-х кубиков
-- заводим значения сторон кубика WITH RECURSIVE step_01_insert (dice) AS (     SELECT 1 AS dice     UNION ALL     SELECT dice + 1 AS dice      FROM step_01_insert     WHERE dice < 6  -- сколько граней у кубика ) -- генерируем все возможные ситуации для 6-х кубиков , step_02_spawn AS (     SELECT     T0.dice AS dice_0     , T1.dice AS dice_1     , T2.dice AS dice_2     , T3.dice AS dice_3     , T4.dice AS dice_4     , T5.dice AS dice_5     , T0.dice + T1.dice + T2.dice + T3.dice + T4.dice + T5.dice AS dice_sum  -- Значения (сумма выпавших костей)     FROM     step_01_insert AS T0     , step_01_insert AS T1     , step_01_insert AS T2     , step_01_insert AS T3     , step_01_insert AS T4     , step_01_insert AS T5 ) -- считаем в лоб, сколько раз встречается значение , step_03_dividend (dice_sum, dividend) AS (     SELECT dice_sum  -- Значения (сумма выпавших костей)     , COUNT(1) AS dividend  -- Сколько раз встречается значение     FROM step_02_spawn     GROUP BY dice_sum ) , step_04_divisor(divisor) AS (     SELECT SUM(dividend) AS divisor     FROM step_03_dividend ) SELECT T1.dice_sum  -- Значения (сумма выпавших костей) , T1.dividend  -- Сколько раз встречается значение , CAST(T1.dividend AS REAL) / CAST(T2.divisor AS REAL) AS probability  -- Вероятность FROM step_03_dividend AS T1 , step_04_divisor AS T2 ORDER BY T1.dice_sum;

dice_sum

dividend

probability

6

1

2.143347050754458e-05

7

6

0.0001286008230452675

8

21

0.0004501028806584362

9

56

0.0012002743484224967

10

126

0.002700617283950617

11

252

0.005401234567901234

12

456

0.00977366255144033

13

756

0.016203703703703703

14

1161

0.02488425925925926

15

1666

0.03570816186556927

16

2247

0.048161008230452676

17

2856

0.061213991769547324

18

3431

0.07353823731138547

19

3906

0.08371913580246913

20

4221

0.09047067901234568

21

4332

0.09284979423868313

22

4221

0.09047067901234568

23

3906

0.08371913580246913

24

3431

0.07353823731138547

25

2856

0.061213991769547324

26

2247

0.048161008230452676

27

1666

0.03570816186556927

28

1161

0.02488425925925926

29

756

0.016203703703703703

30

456

0.00977366255144033

31

252

0.005401234567901234

32

126

0.002700617283950617

33

56

0.0012002743484224967

34

21

0.0004501028806584362

35

6

0.0001286008230452675

36

1

2.143347050754458e-05

Выводы

В данной статье мы познакомились:
— с операцией свёртка последовательностей
— как при помощи свёртки последовательностей считать вероятности выпадения кубиков
— как посчитать вероятности выпадения найти вероятность выпадения числа k, а именно суммы всех значений, выпавших для 1000 кубиков.


ссылка на оригинал статьи https://habr.com/ru/post/685552/

Почему стоит чуть конкретнее изучить собственную ЦА, даже если она кажется очевидной

Привет!

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

С подобным случаем мы столкнулись на примере одного из клиентов сервиса Билайн Аналитика — у него есть собственная производственная площадка по изготовлению свежевыжатого сока алоэ и дистрибьюторская сеть в Москве и области. К нам клиент пришёл с запросом просто увеличить количество заказов за счет рекламы. Про ЦА и её особенности на том этапе особо никто не думал, она казалась очевидной.

Но мы решили, что we need to go deeper

Читать далее

Что было проделано командой билайн

Прежде всего, мы:

  • сравнили аудиторию клиента с его конкурентами. 

  • сравнили аудиторию клиента с его партнерами (перепродавцами).

Блок «Соцдем» из наших отчетов браво отрапортовал, что у конкурентов в 4 раза больше мужчин. Зацепка интересная — клиент был уверен, что его продукция интересна в основном женщинам, и не подозревал, что мужчины также активно интересуются соком алоэ. При этом сайт был заточен именно под женскую аудиторию (дизайн и подача).

Решили в качестве эксперимента сделать на сайте подборку рецептов для спортсменов, настроить соответствующее SEO и индексацию поисковой выдачи. А также подумать и над визуальной составляющей.

Следующий отчет показал, что у конкурентов весьма значимый процент молодой аудитории. Сделали вывод, что у конкурентов есть более дешевые продукты в похожей категории. Сам продукт не столько для ЗОЖ лайфстайла, а для косметических процедур и оздоровления/очищения организма. С увеличением числа продаж клиент сможет снизить стоимость единицы товара и начать привлекать более молодую аудиторию. 

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

Кроме этого, мы посоветовали клиенту в пилотном режиме сделать тестовую партию нового продукта, заточенного под молодую аудиторию, а также завести ТГ-канал с соответствующим контентом.

Продается — и ладно

Как-то так клиент на первых порах подходил к продажам, из-за чего и не сильно знал собственную ЦА. Наши отчёты показали, что прямо сейчас у клиента есть женская аудитория (в выборке всех клиентов и в выборке постоянных клиентов), которая находится в возрасте 35+, имеет авто и высокий доход.

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

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

Итого

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


ссылка на оригинал статьи https://habr.com/ru/company/beeline/blog/685738/