Панель прогресса в 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.h», поэтому всякий раз, когда мне нужно использовать в своих программах, я могу изменить время отображения индикатора выполнения, используемого сообщения, например "Loading...", "Generating report..." и т. Д., А также символ. Иногда я использовал простой '*', в других случаях я использовал символ с ASCII-кодом 254 (это черный квадрат). Итак, в основном моя идея состояла в том, чтобы иметь общий индикатор прогресса, чтобы использовать его, когда мне нужно, и по-разному.

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

Мои вопросы:

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

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

11 голосов | спросил Xam 1 FebruaryEurope/MoscowbThu, 01 Feb 2018 20:02:22 +0300000000pmThu, 01 Feb 2018 20:02:22 +030018 2018, 20:02:22

1 ответ


14

Я написал простую функцию 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 или curses).


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

    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));
    }
}

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

ответил Toby Speight 1 FebruaryEurope/MoscowbThu, 01 Feb 2018 20:22:53 +0300000000pmThu, 01 Feb 2018 20:22:53 +030018 2018, 20:22:53

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132