Должны ли мы избегать повторяющегося кода в C ++, чтобы быть «Pythonic», и как? - PullRequest
1 голос
/ 15 апреля 2011

Я на стадии личинки с Python и до яичной стадии в C ++, но я стараюсь делать все возможное, особенно с принципом "Не повторяйся".

У меня многоканальное сырьеоткрываемый формат файла с основным заголовком ascii с полями, представленными в виде строк и целых чисел (всегда закодированных в виде символов, дополненных пробелами).Вторая часть - это N заголовков, причем N является полем основного заголовка, и каждый из этих заголовков имеет гораздо больше текстовых и числовых полей (закодированных как ascii), ссылающихся на длину и размер фактических 16-битных многоканальных потоков.которые составляют остальную часть файла.

Пока у меня есть этот рабочий код на C ++:

#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <map>

using namespace std;

struct Header {
    string version;
    string patinfo;
    string recinfo;
    string start_date;
    string start_time;
    int header_bytes;
    string reserved;
    int nrecs;
    double rec_duration;
    int nchannels;
};

struct Channel {
    string label;
    string transducertype;
    string phys_dim;
    int pmin;
    int pmax;
    int dmin;
    int dmax;
    string prefiltering;
    int n_samples;
    string reserved;
};


int main()
{
    ifstream edf("/home/helton/Dropbox/01MIOTEC/06APNÉIA/Samples/Osas2002plusQRS.rec", ios::binary);

    // prepare to read file header
    Header header;
    char buffer[80];

    // reads header fields into the struct 'header'
    edf.read(buffer, 8);
    header.version = string(buffer, 8);

    edf.read(buffer, 80);
    header.patinfo = string(buffer, 80);

    edf.read(buffer, 80);
    header.recinfo = string(buffer, 80);

    edf.read(buffer, 8);
    header.start_date = string(buffer, 8);

    edf.read(buffer, 8);
    header.start_time = string(buffer, 8);

    edf.read(buffer, 8);
    stringstream(buffer) >> header.header_bytes;

    edf.read(buffer, 44);
    header.reserved = string(buffer, 44);

    edf.read(buffer, 8);
    stringstream(buffer) >> header.nrecs;

    edf.read(buffer,8);
    stringstream(buffer) >> header.rec_duration;

    edf.read(buffer,4);
    stringstream(buffer) >> header.nchannels;

    /*
    cout << "'" << header.version << "'" << endl;
    cout << "'" << header.patinfo << "'" << endl;
    cout << "'" << header.recinfo << "'" << endl;
    cout << "'" << header.start_date << "'" << endl;
    cout << "'" << header.start_time << "'" << endl;
    cout << "'" << header.header_bytes << "'" << endl;
    cout << "'" << header.reserved << "'" << endl;
    cout << "'" << header.nrecs << "'" << endl;
    cout << "'" << header.rec_duration << "'" << endl;
    cout << "'" << header.nchannels << "'" << endl;
    */

    // prepare to read channel headers
    int ns = header.nchannels; // ns tells how much channels I have
    char title[16]; // 16 is the specified length of the "label" field of each channel

    for (int n = 0; n < ns; n++)
    {
        edf >> title;
        cout << title << endl; // and this successfully echoes the label of each channel
    }


    return 0;
};

Некоторые замечания, которые я уже должен сделать:

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

Мой (надеюсь, простой) вопрос:

"Должен ли я беспокоиться о сокращении углов, чтобы сделать этот вид кода более« пифоническим »(более абстрактным, менее повторяющимся), или это не так?y все работает в C ++? "

Многие евангелисты Python (как я сам, потому что мне это нравится) подчеркивают его простоту в использовании и все такое.Итак, я некоторое время удивляюсь, делаю ли я глупые вещи или делаю только правильные, но не настолько "автоматические" из-за самой природы C ++.

Спасибо за чтение

Хелтон

Ответы [ 5 ]

5 голосов
/ 15 апреля 2011

Я бы сказал, что нет такой вещи, как код Python для C ++. Принцип DRY применяется в обоих языках, но большая часть того, что считается «Pythonic», является просто самым коротким и сладким способом выражения логики в Python с использованием конструкций, специфичных для Python. Идиоматический C ++ совсем другой.

lambda, например, иногда не считается очень Pythonic и зарезервирован для случаев, когда не существует никакого другого решения, но просто добавляется в стандарт C ++. C ++ не имеет ключевых аргументов, которые очень Pythonic. Программисты C ++ не любят создавать map, когда в этом нет необходимости, в то время как программист на Python может выбросить dict при большом количестве проблем, когда они просто делают намерение более ясным, чем эффективная альтернатива.

Если вы хотите сохранить набор текста, используйте функцию, которую я разместил ранее , затем:

header.version = read_field(edf, 8);
header.patinfo = read_field(edf, 80);

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

2 голосов
/ 15 апреля 2011

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

  1. Чтение поля из потока
  2. Убедитесь, что чтение успешно завершено
  3. Разбор данных (при необходимости)
  4. Убедитесь, что анализ выполнен успешно (при необходимости)
  5. Скопировать данные в целевое местоположение

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

template <typename TStream, typename TResult>
void ReadFixedWidthFieldFromStream(TStream& str, TResult& result, unsigned sz) 
{
    std::vector<char> data(sz);

    if (!str.read(&data[0], sz))
        throw std::runtime_error("Failed to read from stream");

    std::stringstream ss(&data[0]);
    if (!(ss >> result))
        throw std::runtime_error("Failed to parse data from stream");
}

// Overload for std::string:
template <typename TStream>
void ReadFixedWidthFieldFromStream(TStream& str, std::string& result, unsigned sz) 
{
    std::vector<char> data(sz);

    if (!str.read(&data[0], sz))
        throw std::runtime_error("Failed to read from stream");

    result = std::string(&data[0], sz);
}

Теперь ваш код может быть более кратким:

ReadFixedWidthFieldFromStream(edf, header.version, 8);
ReadFixedWidthFieldFromStream(edf, header.patinfo, 80);
ReadFixedWidthFieldFromStream(edf, header.recinfo, 80);
// etc.
1 голос
/ 15 апреля 2011

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

0 голосов
/ 15 апреля 2011

Zen of Python явно не упоминает DRY.

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
0 голосов
/ 15 апреля 2011

Для чтения из файла непосредственно в строках см. этот вопрос Остальное неправильно . но лично я думаю, что есть лучший / более чистый способ сделать это.

Если вы знаете, что размер структуры не использует строку, используйте примитивные типы C (и убедитесь, что структура упакована).Смотрите эти ссылки: http://msdn.microsoft.com/en-us/library/2e70t5y1(v=vs.80).aspx & http://gcc.gnu.org/onlinedocs/gcc-3.2.3/gcc/Type-Attributes.html

Я бы сделал это, например, таким образом (не уверен насчет размера каждой строки, но вы поняли идею):

struct Header {
    char version[8];
    char patinfo[80];
    char recinfo[80];
    char start_date[8];
    char start_time[8];
    int header_bytes;
    char reserved[44];
    int nrecs;
    double rec_duration;
    int nchannels;
};

Когда у вас есть упакованная структура, вы можете прочитать ее прямо из файла:

struct Header h;
edf.read(&h,sizeof(struct Header));

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

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