Как не дублировать код для печати дерева

У меня есть функция, которая печатает узлы дерева слева направо.

void PrintTree()
{
...
Print(curentNode);
...
}

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

void PrintTreeByCondition(string a)
{
...
if (IsPrefix(a,curentNode->stringVar))
      Print(curentNode);
...
}

Но тогда у меня есть две функции с одинаковым кодом, с разницей в одну строчку. Как мне избежать дублирования кода здесь?

UPD: Код траверса:

void BinTree::TraverseTree()
{
    std::stack<TreeNode*> s;
    s.push(root);
    TreeNode* curentNode = s.top();
    while (curentNode != nullptr|| s.empty() == false)
    {
        while (curentNode != nullptr)
        {
            s.push(curentNode);
            curentNode = curentNode->GetLeft();
        }

        curentNode = s.top();
        s.pop();

        // do stuff

        curentNode = curentNode->GetRight();
    }
}

Как не дублировать код для печати дерева. Как насчет кода, который обходит дерево в целом и вызывает функцию, предоставленную пользователем, при посещении узла? Это было бы намного лучше, общий подход. Что делать, если вы хотите сделать что-то с посещенным узлом, кроме как распечатать его? Давайте посмотрим код обхода.

PaulMcKenzie 24.12.2020 18:59

@PaulMcKenzie Я думал о чем-то подобном и решил использовать указатель на функцию, что-то вроде void TraverseTree(void (*f)(const CTreeNode*)) . Но после этого я не мог понять, как реализовать с ним второе поведение.

IvanIvan 24.12.2020 19:17

Хорошо, вы на правильном пути. Опубликуйте код обхода дерева (не функцию Print, а только код обхода).

PaulMcKenzie 24.12.2020 19:18

@PaulMcKenzie добавил. Глядя на ответ, предоставленный Дитмаром Кюлем, я понимаю, что мне нужно добавить что-то вроде итераторов в мое дерево. Я могу сделать это, только добавив указатель на родителя узла в классе узла, верно?

IvanIvan 24.12.2020 19:32

UPD: понял, мои итераторы могут просто хранить стек, представляющий путь к корню, как я это делаю в своем обходе

IvanIvan 24.12.2020 19:37

Вы можете сделать что-то вроде этого: template <typename fn> void traverse( node *p, fn func) { if ( p ) {traverse(p->left, fn); fn(p); traverse(p->right, fn); }}, где fn — указатель на функцию, объект функции, лямбду и т. д. Это будет обход по порядку.

PaulMcKenzie 24.12.2020 19:41

Но это не позволит моей лямбда-функции иметь другие параметры, верно? Как и в моем PrintTreeByCondition, у меня есть string a. Как мне реализовать что-то подобное с помощью лямбда-функции? Единственный способ, который я могу придумать, - это какая-то глобальная/статическая переменная, которая будет представлять string a, что, на мой взгляд, не лучшее решение.

IvanIvan 24.12.2020 19:46

Используйте объект, который operator() перегружен.

PaulMcKenzie 24.12.2020 19:47

@IvanIvan: Предоставленный вами объект функции будет принимать аргументы, которые функция traverse() будет передавать ему для каждого узла. Например, если узлы вашего дерева содержат объекты T в качестве своего значения, функция, вероятно, примет T const& в качестве параметра. В зависимости от того, является ли функция traverse() шаблоном функции, она не будет принимать функцию посетителя либо в качестве параметра шаблона с [n подразумеваемой] концепцией возможности вызова с помощью T const&. Если это не шаблон, он, вероятно, примет std::function<void(T const&)> в качестве параметра.

Dietmar Kühl 24.12.2020 20:04
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
9
79
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Способы С++ - создать итератор для обхода и использовать его для печати, а также для фильтрации или других целей. Обход дерева, вероятно, может быть выполнен в двунаправленном режиме, что позволит многим алгоритмам стать доступными и для дерева. На самом деле упорядоченные ассоциативные контейнеры (например, std::map, std::set) обычно реализуются как [сбалансированные] бинарные деревья, и их итераторы выполняют обход по порядку.

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

Учитывая вашу функцию, другим подходом было бы заставить функцию TraverseTree принимать функцию для вызова после обнаружения узла:

template <typename fn>
void BinTree::TraverseTree(fn func)
{
    std::stack<TreeNode*> s;
    s.push(root);
    TreeNode* curentNode = s.top();
    while (curentNode != nullptr|| s.empty() == false)
    {
        while (curentNode != nullptr)
        {
            s.push(curentNode);
            curentNode = curentNode->GetLeft();
        }
        curentNode = s.top();
        s.pop();
        func(currentNode);  // call custom function
        curentNode = curentNode->GetRight();
    }
}

Тогда Print будет просто:

void Print(Node *n)
{
   std::cout << n->data << "\n"; // assuming there is a "data" member
}

И последний звонок будет:

BinTree b;
//...
b.TraverseTree(&Print);

Если у вас есть другая информация для печати, передайте объект функции (класс с перегруженным operator()), а внутри объекта находится все необходимое для выполнения намеченной работы (обычно представленной в виде членов класса):

Например:

struct MyPrint
{
    std::string some_value;

    MyPrint(std::string s) : some_value(s) {}

    void operator() (Node *n) 
    { 
       std::cout << some_value << " -- " << n->data << "\n"; 
    }
};

Затем назовите это так:

BinTree b;
//...
MyPrint m("Testing testing");
b.TraverseTree(m);

Другие вопросы по теме