将带有yield(string)的python函数翻译为C++

问题描述 投票:0回答:5

我正在将 python 函数转换为 C++ 函数;这个函数使用了一个我不知道如何翻译的

yield(string)
语句。

整个故事在这里...我有一个特定的函数,可以读取输入 ASCII 文件

filename
,其中包含数据行,并将文件的内容放入
std::vector
中。当读取这个输入文件时(在另一个函数方面,如下所示),我需要跳转一堆行(5或6,这取决于输入文件的名称),为此,我定义了一个函数,
file_struct_generator
,它将数据线标记为“数据”,将我不需要的数据线标记为“未使用”。我需要在 C++ 中类似于这个函数的东西,特别是,我需要类似于
yield(string)
的东西(注意,
string
!),但当然是在 C++ 中。在这里,我向您展示了我需要从 python“翻译”为 C++ 的代码行。如何用 C++ 重写
yield("unused")
yield("data")
?或者,如果
yield
在 C++ 中不可用,我可以使用不同的东西在 C++ 中编写类似的函数作为生成器吗?谢谢你帮助我!

def file_struct_generator(filename):
    if "more" in filename:
        bad_line_number = 5
    else:
        bad_line_number = 6

    yield("data")
    for i in range(bad_line_number):
        yield("unused")
    while True:
        yield("data")
    

编辑: 我不使用 C++20,而是使用 C++11。我尝试了@Arthur Tacca 代码,它适合我的目的,谢谢大家。

python c++ function yield translate
5个回答
3
投票

这是一个状态机,可重现该生成器的功能。

#include <stdexcept>
#include <string>
#include <iostream>

class FileStructIterator {
public:
    explicit FileStructIterator(const std::string& filename): 
        m_filename(filename), m_state(STATE_START) 
    {}

    std::string getNext() {
        switch (m_state) {
        case STATE_START:
            m_state = STATE_LOOP1;
            if (m_filename.find("more") != std::string::npos) {
                m_badLineNumber = 5;
            } else {
                m_badLineNumber = 6;
            }
            m_i = 0;
            return "data";

        case STATE_LOOP1:
            ++m_i;
            if (m_i >= m_badLineNumber) {
                m_state = STATE_LOOP2;
            }            
            return "unused";

        case STATE_LOOP2:
            return "data";

        default:
            throw std::logic_error("bad state");
        }
    }

private:
    std::string m_filename;

    enum State { STATE_START, STATE_LOOP1, STATE_LOOP2 };
    State m_state;

    size_t m_badLineNumber;
    size_t m_i;
};

这是一个使用它的示例(在本例中,我将输出限制为前 10 个结果,因此它不会永远循环)。

int main() {
    auto it = FileStructIterator("nomore.txt");
    for (int i = 0; i < 10; ++i) {
        std::string nextValue = it.getNext();
        std::cout << nextValue << "\n";
    }
}

3
投票

将带有yield(string)的python函数翻译为C++

在 C++20 之前,我认为没有简单、准确的翻译。

另一种方法是编写一个接受函子的函数模板:

template<class Yield>
void file_struct_generator(const std::string& filename, const Yield& yield)
{
    // ...
    yield("unused")
    // ...

通过这种方式,调用者可以提供一个函子,以他们想要使用的方式处理输出。例如,他们可以提供打印值的一个,或将值存储在容器中的另一个。

至关重要的是,这种替代方案并不懒惰,并且不能像 python 代码中那样存在无限循环。这是否是一个有用的翻译取决于生成器在 Python 中的使用方式。这对于消耗整个序列的情况很有用。


我认为这将是 C++20 的直接翻译:

generator<std::string>
file_struct_generator(const std::string& filename)
{
    bad_line_number = filename.find("more") != std::string::npos
        ? 5
        : 6;

    co_yield "data";
    for (int i : std::views::iota(0, bad_line_number))
        co_yield "unused";
    for(;;)
        co_yield "data";
}

由于缺乏符合要求的编译器而未进行测试。该标准也没有提供

generator
类型。


1
投票

从 C++20 开始,您可以使用协同例程,您可以使用一些 thread_local 变量(静态)实现与 python 代码片段类似的功能:

std::string file_struct_generator(std::string filename) 
{
  thread_local int bad_line_number = filename.find("more") != std::string::npos ? 5 : 6;

  thread_local int i = 0;

  if (i++ && i <= bad_line_number) 
    return "unused";

  return "data";
}

请注意,正如 ArthurTacca 在评论中指出的那样,实际上无法使用不同的文件名调用此函数,或者至少,如果您这样做,它不会启动新的循环。


1
投票

在 C++20 中,这将使用

co_yield
。在此之前,您可以编写一个迭代器。

class file_struct_iterator : public std::iterator<std::input_iterator_tag, std::string, std::ptrdiff_t, const std::string *, std::string> {
    enum class State {
        initial,
        bad_lines,
        remainder
    }

    State state = State::initial;
    size_t bad_line_number = 0;

public:

    file_struct_iterator(std::string filename) 
        : bad_line_number((filename.find("more") != filename.end()) ? 5 : 6)
    {}

    std::string operator*() {
        switch (state) {
            case State::initial:
            case State::remainder: return "data";
            case State::bad_lines: return "unused";
        }
    }

    file_struct_iterator& operator++() {
        switch (state) {
            case State::initial: state = State::bad_lines; break;
            case State::bad_lines: if (--bad_line_number == 0) { state = remainder; } break;
            case State::remainder: break;
        }
        return *this;
    }

    file_struct_iterator operator++(int) {
        file_struct_iterator retval = *this;
        ++(*this);
        return retval;
    }

    bool operator==(file_struct_iterator other) const {
        return (state == other.state) && (bad_line_number == other.bad_line_number);
    }
    
    bool operator!=(file_struct_iterator other) const {
        return !(*this == other);
    }
};

0
投票

我喜欢 Arthur Tacca 的解决方案,但只有两条评论:

其一,我不会声明“getNext()”方法,而是定义 () 运算符:

std::string operator()() {
        switch (m_state) {
        case STATE_START:
...

所以用法如下:

int main() {
    auto generator = FileStructIterator("nomore.txt");
    for (int i = 0; i < 10; ++i) {
        std::string nextValue = generator();
        std::cout << nextValue << "\n";
    }
}

还有两个:如果只需要一个函数而不是 C++ 对象,我们总是可以将其封装在 lambda 中:

int main() {
    auto generator = [](){ static FileStructIterator localGenerator("nomore.txt"); return localGenerator();};

    for (int i = 0; i < 10; ++i) {
        std::string nextValue = generator();
        std::cout << nextValue << "\n";
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.