Индикатор выполнения в C++


Я сделал простой прогресс-бар с процентом счетчик для консольных приложений в C++. Ниже мой код:

#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <cmath>

void show_progress_bar(int time, const std::string &message, char symbol)
{
    std::string progress_bar;
    const double progress_level = 1.42;

    std::cout << message << "\n\n";

    for (double percentage = 0; percentage <= 100; percentage += progress_level)
    {
        progress_bar.insert(0, 1, symbol);
        std::cout << "\r [" << std::ceil(percentage) << '%' << "] " << progress_bar;
        std::this_thread::sleep_for(std::chrono::milliseconds(time));       
    }
    std::cout << "\n\n";
}

Некоторые объяснения по поводу моего кода. Я использовал три параметра для того, чтобы сделать заголовок "progress_bar.ч", поэтому, когда мне нужно использовать в моих программах можно изменить время отображения прогресс-бара, сообщение, используемое, например "Loading...", "Generating report..."и т. д., а также символ. Иногда я использовал простой '*'в другое время я использовал символ с ASCII кодом 254 (Черный квадрат). Так что, в принципе моя идея была иметь общий индикатор прогресса для того, чтобы использоваться всякий раз, когда мне нужно, и по-разному.

С другой стороны, переменная progress_level имеет значение, указанное выше для того, чтобы предотвратить прогресс бар прыгает на следующую строку (которая была уродливая вещь). Что сделано для изменения переменной percentage к double тип. Чтобы иметь возможность печатать целые значения, я использовал ceil функция.

Мои вопросы:

  1. Насколько хорошо это вообще мой прогресс-бар? Что можно изменить для того, чтобы выглядеть чище код?

  2. Есть проще (лучше) способ использования строк для того, чтобы сделать прогресс-бар, как и выше (т. е. показывает процентный счетчик)? (Я не знаю других способов сделать прогресс-бар, но я больше заинтересован в них, используя строки).



5568
11
задан 1 февраля 2018 в 05:02 Источник Поделиться
Комментарии
1 ответ

Я написал простой main() функция, чтобы проверить это:

int main()
{
show_progress_bar(100, "progress", '#');
}


Я сразу заметил, что я видел только пару обновлений. Это происходит потому, что выход не сбрасывается после каждого обновления. Если вы видите тот же эффект во многом зависит от устройства вывода (я использовал сборник-режим для Emacs, который может иметь больше буферизации, чем традиционные терминалы или эмуляторы терминалов). Это легко исправить для всех пользователей, с помощью std::flush поток манипулятора:

    std::cout << "\r [" << std::ceil(percentage) << '%' << "] "
<< progress_bar << std::flush;

Я исправлена небольшая опечатка там - у вас ceil вместо std::ceil. Адриан Маккарти советует сгонять прогресс это раздражает пользователей, так что может быть лучше округлить или просто приведение к целочисленному:

    std::cout << "\r [" << static_cast<int>(percentage) << '%' << "] "
<< progress_bar << std::flush;


Это, вероятно, хорошая идея, чтобы использовать манипулятор, чтобы установить ширину поля для percentageтак что ] не прыгать положении, когда мы достигнем 10%.

Также message перезаписывается сразу, и я не получаю, чтобы увидеть его, возможно, испускают перевода строки?


Не понятно, почему progress_level имеет значение это - похоже, это попытка соответствовать определенной длине линии. Я бы предпочел увидеть этот расчет:

static const auto line_length = 70;
static const auto progress_level = 100.0 / line_length;

Очень сложный вариант будет пытаться найти доступную ширину (возможно, с использованием библиотеки termcap или проклятия).


Добавление символов прогресса в строку, как правило, более эффективны, чем добавляя:

    progress_bar += symbol;

Наверное, лучше создать полную длину строки и напечатать подстроки его каждый раз, когда вы хотите использовать write() функция предварительного программирования на C++17 (который вводит string_view чтобы дать подстроки без копирования).


Я бы предпочел, чтобы быть в состоянии передавать выходной поток в качестве параметра; в большинстве случаев, стандартный поток журнал подходит больше, чем стандартный выходной поток.


Улучшен код

Я внес изменения, чтобы разместить сообщение на одной линии, если он подходит (я передаю по значению, и повторно использовать, как прогресс-бар; иногда это может сохранить копию).

#include <chrono>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <string>
#include <thread>

void show_progress_bar(std::ostream& os, int time,
std::string message, char symbol = '*')
{
static const auto bar_length = 70;
// not including the percentage figure and spaces

if (message.length() >= bar_length) {
os << message << '\n';
message.clear();
} else {
message += " ";
}

const auto progress_level = 100.0 / (bar_length - message.length());

std::cout << message;

for (double percentage = 0; percentage <= 100; percentage += progress_level) {
message += symbol;
os << "\r [" << std::setw(3) << static_cast<int>(percentage) << "%] "
<< message << std::flush;
std::this_thread::sleep_for(std::chrono::milliseconds(time));
}
os << "\n\n";
}

int main()
{
show_progress_bar(std::clog, 100, "progress", '#');
}


Альтернативный подход

Обычно, когда вам нужен прогресс-бар, работу, которую вы делаете не так удобно, как обычный сон. Для выполнения этих задач, это удобно, чтобы иметь объект, вы можете либо пройти (по ссылке) с этой задачей, или просто обновить между подзадачами. Я сбил одного, чтобы показать, как это выглядит:

#include <cmath>
#include <iomanip>
#include <ostream>
#include <string>

class progress_bar
{
static const auto overhead = sizeof " [100%]";

std::ostream& os;
const std::size_t bar_width;
std::string message;
const std::string full_bar;

public:
progress_bar(std::ostream& os, std::size_t line_width,
std::string message_, const char symbol = '.')
: os{os},
bar_width{line_width - overhead},
message{std::move(message_)},
full_bar{std::string(bar_width, symbol) + std::string(bar_width, ' ')}
{
if (message.size()+1 >= bar_width || message.find('\n') != message.npos) {
os << message << '\n';
message.clear();
} else {
message += ' ';
}
write(0.0);
}

// not copyable
progress_bar(const progress_bar&) = delete;
progress_bar& operator=(const progress_bar&) = delete;

~progress_bar()
{
write(1.0);
os << '\n';
}

void write(double fraction);
};

void progress_bar::write(double fraction)
{
// clamp fraction to valid range [0,1]
if (fraction < 0)
fraction = 0;
else if (fraction > 1)
fraction = 1;

auto width = bar_width - message.size();
auto offset = bar_width - static_cast<unsigned>(width * fraction);

os << '\r' << message;
os.write(full_bar.data() + offset, width);
os << " [" << std::setw(3) << static_cast<int>(100*fraction) << "%] " << std::flush;
}

// Test program

#include <chrono>
#include <iostream>
#include <thread>
int main()
{
progress_bar progress{std::clog, 70u, "Working"};

for (auto i = 0.0; i <= 100; i += 3.65) {
progress.write(i/100.0);
// simulate some work
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}

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

15
ответ дан 1 февраля 2018 в 05:02 Источник Поделиться