Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/impls/cpp/Makefile
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
CXX = g++
#CXX = clang++
CXXFLAGS = -std=c++2a -O2 -Wall
CXXFLAGS = -std=c++23 -O2 -Wall

# For MinGW (Windows), specify `-lstdc++exp` to use std::print.
# ref: https://www.reddit.com/r/cpp_questions/comments/1bsh7nd/undefined_reference_to_std_open_terminal/
CXX_TARGET := $(shell $(CXX) -dumpmachine)

ifneq (,$(findstring w64-mingw32,$(CXX_TARGET)))
LDFLAGS += -lstdc++exp
endif

all: polish

polish: polish.o
$(CXX) polish.o -o polish
$(CXX) polish.o -o polish $(LDFLAGS)

polish.o: polish.cpp
$(CXX) $(CXXFLAGS) -c polish.cpp
Expand Down
126 changes: 53 additions & 73 deletions src/impls/cpp/polish.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
// SPDX-FileCopyrightText: 2022 smdn <smdn@smdn.jp>
// SPDX-License-Identifier: MIT
#include <algorithm>
#include <concepts>
#include <charconv>
#include <format>
#include <functional>
#include <iostream>
#include <limits>
#include <memory>
#include <ostream>
#include <sstream>
#include <print>
#include <string>
#include <string_view>

Expand All @@ -28,31 +27,28 @@ class Node {

// 二分木を巡回し、ノードの行きがけ・通りがけ・帰りがけに指定された関数をコールバックするメソッド
void traverse(
std::function<void(Node&)> on_visit, // ノードの行きがけにコールバックする関数
std::function<void(Node&)> on_transit, // ノードの通りがけにコールバックする関数
std::function<void(Node&)> on_leave // ノードの帰りがけにコールバックする関数
std::invocable<Node&> auto on_visit, // ノードの行きがけにコールバックする関数
std::invocable<Node&> auto on_transit, // ノードの通りがけにコールバックする関数
std::invocable<Node&> auto on_leave // ノードの帰りがけにコールバックする関数
);

// 後行順序訪問(帰りがけ順)で二分木を巡回して
// すべてのノードの演算子または項をstreamに出力するメソッド
void write_postorder(std::ostream& stream);
void write_postorder(std::FILE* stream);

// 中間順序訪問(通りがけ順)で二分木を巡回して
// すべてのノードの演算子または項をstreamに出力するメソッド
void write_inorder(std::ostream& stream);
void write_inorder(std::FILE* stream);

// 先行順序訪問(行きがけ順)で二分木を巡回して
// すべてのノードの演算子または項をstreamに出力するメソッド
void write_preorder(std::ostream& stream);
void write_preorder(std::FILE* stream);

// 後行順序訪問(帰りがけ順)で二分木を巡回して、二分木全体の値を計算するメソッド
// すべてのノードの値が計算できた場合はtrue、そうでない場合(記号を含む場合など)はfalseを返す
// 計算結果はresult_valueに代入する
bool calculate_expression_tree(double& result_value);

// 演算結果の数値を文字列化するためのメソッド
static std::string format_number(const double& number) noexcept;

private:
// 式expression内の括弧の対応を検証するメソッド
// 開き括弧と閉じ括弧が同数でない場合はエラーとする
Expand Down Expand Up @@ -256,85 +252,82 @@ std::string::size_type Node::get_operator_position(const std::string_view& expre
}

void Node::traverse(
std::function<void(Node&)> on_visit,
std::function<void(Node&)> on_transit,
std::function<void(Node&)> on_leave
std::invocable<Node&> auto on_visit,
std::invocable<Node&> auto on_transit,
std::invocable<Node&> auto on_leave
)
{
// このノードの行きがけに行う動作をコールバックする
if (on_visit)
on_visit(*this);
on_visit(*this);

// 左に子ノードをもつ場合は、再帰的に巡回する
if (left)
left->traverse(on_visit, on_transit, on_leave);

// このノードの通りがけに行う動作をコールバックする
if (on_transit)
on_transit(*this);
on_transit(*this);

// 右に子ノードをもつ場合は、再帰的に巡回する
if (right)
right->traverse(on_visit, on_transit, on_leave);

// このノードの帰りがけに行う動作をコールバックする
if (on_leave)
on_leave(*this);
on_leave(*this);
}

void Node::write_postorder(std::ostream& stream)
void Node::write_postorder(std::FILE* const stream)
{
// 巡回を開始する
traverse(
nullptr, // ノードへの行きがけには何もしない
nullptr, // ノードの通りがけには何もしない
[](const Node&) {}, // ノードへの行きがけには何もしない
[](const Node&) {}, // ノードの通りがけには何もしない
// ノードからの帰りがけに、ノードの演算子または項を出力する
// (読みやすさのために項の後に空白を補って出力する)
[&stream](Node& node) { stream << node.expression << ' '; }
[&stream](const Node& node) { std::print(stream, "{} ", node.expression); }
);
}

void Node::write_inorder(std::ostream& stream)
void Node::write_inorder(std::FILE* const stream)
{
// 巡回を開始する
traverse(
// ノードへの行きがけに、必要なら開き括弧を補う
[&stream](Node& node) {
[&stream](const Node& node) {
// 左右に項を持つ場合、読みやすさのために項の前(行きがけ)に開き括弧を補う
if (node.left && node.right)
stream << '(';
std::print(stream, "(");
},
// ノードの通りがけに、ノードの演算子または項を出力する
[&stream](Node& node) {
[&stream](const Node& node) {
// 左に子ノードを持つ場合は、読みやすさのために空白を補う
if (node.left)
stream << ' ';
std::print(stream, " ");

// 左の子ノードから右の子ノードへ巡回する際に、ノードの演算子または項を出力する
stream << node.expression;
std::print(stream, "{}", node.expression);

// 右に子ノードを持つ場合は、読みやすさのために空白を補う
if (node.right)
stream << ' ';
std::print(stream, " ");
},
// ノードからの帰りがけに、必要なら閉じ括弧を補う
[&stream](Node& node) {
[&stream](const Node& node) {
// 左右に項を持つ場合、読みやすさのために項の後(帰りがけ)に閉じ括弧を補う
if (node.left && node.right)
stream << ')';
std::print(stream, ")");
}
);
}

void Node::write_preorder(std::ostream& stream)
void Node::write_preorder(std::FILE* const stream)
{
// 巡回を開始する
traverse(
// ノードへの行きがけに、ノードの演算子または項を出力する
// (読みやすさのために項の後に空白を補って出力する)
[&stream](Node& node) { stream << node.expression << ' '; },
nullptr, // ノードの通りがけ時には何もしない
nullptr // ノードからの帰りがけ時には何もしない
[&stream](const Node& node) { std::print(stream, "{} ", node.expression); },
[](const Node&) {}, // ノードの通りがけ時には何もしない
[](const Node&) {} // ノードからの帰りがけ時には何もしない
);
}

Expand All @@ -344,8 +337,8 @@ bool Node::calculate_expression_tree(double& result_value)
// ノードからの帰りがけに、ノードが表す部分式から、その値を計算する
// 帰りがけに計算することによって、末端の部分木から順次計算し、再帰的に木全体の値を計算する
traverse(
nullptr, // ノードへの行きがけには何もしない
nullptr, // ノードの通りがけには何もしない
[](const Node&) {}, // ノードへの行きがけには何もしない
[](const Node&) {}, // ノードの通りがけには何もしない
Node::calculate_node // ノードからの帰りがけに、ノードの値を計算する
);

Expand Down Expand Up @@ -378,10 +371,10 @@ void Node::calculate_node(Node& node)
// 現在のノードの演算子に応じて左右の子ノードの値を演算し、
// 演算した結果を文字列に変換して再度expressionに代入することで現在のノードの値とする
switch (node.expression.front()) {
case '+': node.expression = format_number(left_operand + right_operand); break;
case '-': node.expression = format_number(left_operand - right_operand); break;
case '*': node.expression = format_number(left_operand * right_operand); break;
case '/': node.expression = format_number(left_operand / right_operand); break;
case '+': node.expression = std::format("{:.17g}", left_operand + right_operand); break;
case '-': node.expression = std::format("{:.17g}", left_operand - right_operand); break;
case '*': node.expression = std::format("{:.17g}", left_operand * right_operand); break;
case '/': node.expression = std::format("{:.17g}", left_operand / right_operand); break;
// 上記以外の演算子の場合は計算できないものとして扱い、処理を終える
default: return;
}
Expand All @@ -406,26 +399,13 @@ bool Node::parse_number(const std::string_view& expression, double& number) noex
return ptr == std::to_address(std::end(expression));
}

std::string Node::format_number(const double& number) noexcept
{
std::ostringstream stream;

// %.17g
stream.precision(17); // %.17
stream
<< std::defaultfloat // %g
<< number;

return stream.str();
}

// main関数。 結果によって次の値を返す。
// 0: 正常終了 (二分木への分割、および式全体の値の計算に成功した場合)
// 1: 入力のエラーによる終了 (二分木への分割に失敗した場合)
// 2: 計算のエラーによる終了 (式全体の値の計算に失敗した場合)
int main()
{
std::cout << "input expression: ";
std::print("input expression: ");

// 標準入力から二分木に分割したい式を入力する
std::string expression;
Expand All @@ -450,44 +430,44 @@ int main()
// 二分木の根(root)ノードを作成し、式全体を格納する
root = std::make_unique<Node>(expression);

std::cout << "expression: " << expression << std::endl;
std::println("expression: {}", expression);

// 根ノードに格納した式を二分木へと分割する
root->parse_expression();
}
catch (const MalformedExpressionException& err) {
std::cerr << err.what() << std::endl;
std::println(stderr, "{}", err.what());
return 1;
}

// 分割した二分木を帰りがけ順で巡回して表示する(前置記法/逆ポーランド記法で表示される)
std::cout << "reverse polish notation: ";
root->write_postorder(std::cout);
std::cout << std::endl;
std::print("reverse polish notation: ");
root->write_postorder(stdout);
std::println();

// 分割した二分木を通りがけ順で巡回して表示する(中置記法で表示される)
std::cout << "infix notation: ";
root->write_inorder(std::cout);
std::cout << std::endl;
std::print("infix notation: ");
root->write_inorder(stdout);
std::println();

// 分割した二分木を行きがけ順で巡回して表示する(後置記法/ポーランド記法で表示される)
std::cout << "polish notation: ";
root->write_preorder(std::cout);
std::cout << std::endl;
std::print("polish notation: ");
root->write_preorder(stdout);
std::println();

// 分割した二分木から式全体の値を計算する
double result_value;

if (root->calculate_expression_tree(result_value)) {
// 計算できた場合はその値を表示する
std::cout << "calculated result: " << Node::format_number(result_value) << std::endl;
std::println("calculated result: {:.17g}", result_value);
return 0;
}
else {
// (式の一部あるいは全部が)計算できなかった場合は、計算結果の式を中置記法で表示する
std::cout << "calculated expression: ";
root->write_inorder(std::cout);
std::cout << std::endl;
std::print("calculated expression: ");
root->write_inorder(stdout);
std::println();
return 2;
}
}
4 changes: 2 additions & 2 deletions src/impls/cpp/polish.vcxproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!--
<!--
SPDX-FileCopyrightText: 2022 smdn <smdn@smdn.jp>
SPDX-License-Identifier: MIT
-->
Expand All @@ -19,7 +19,7 @@ SPDX-License-Identifier: MIT
<ClCompile>
<!-- ref: https://docs.microsoft.com/en-us/cpp/build/reference/std-specify-language-standard-version -->
<!-- cspell:ignore stdcpp -->
<LanguageStandard>stdcpp20</LanguageStandard>
<LanguageStandard>stdcpp23</LanguageStandard>
<!-- ref: https://docs.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8 -->
<AdditionalOptions>/utf-8</AdditionalOptions>
<WarningLevel>Level3</WarningLevel>
Expand Down
Loading