Palindrome modelé
J'essaie d'implémenter un contrôle de palindrome pour chaque élément d'un vecteur et de renvoyer un vecteur avec uniquement les palindromes. Cela devrait fonctionner pour différents types de chaînes .eg int vector of ints.J'ai fait une solution basée sur un modèle mais j'ai l'impression que cela ne tire pas pleinement parti des modèles.
//main.cpp
// Checking palindrome with integers
Palindrome <int>pal = {125125, 4947, 74347, 11};
pal.FindPalindromeDataset();
pal.Print();
// Checking palindrome with strings
Palindrome <std::string>pal1 = {"yay", "world", "level", "hello"};
pal1.FindPalindromeDataset();
pal1.Print();
// Checking palindrome with vector of ints
Palindrome<std::vector<int> > pal3 = {{6, 2, 2, 6},
{1, 2, 2},
{1, 4, 6, 3, 5, 3, 6, 4, 1},
{5, 2, 2, 6, 9, 1, 2}};
//palindrome.hpp
#ifndef PALINDROME_HHP
#define PALINDROME_HHP
#include <vector>
#include <iostream>
template <class T>
class Palindrome {
public:
//! Construct from a std::initializer list
Palindrome(std::initializer_list<T> _dataset) : dataset(_dataset)
{}
//! Print the palindromeDataset
void Print() const;
/* Traverse to check if each element of the vector is a palindrome
* and push them in the new array
*/
void FindPalindromeDataset()
{
for (auto i : dataset)
{
if (IsPalindrome(i))
{
palindromeDataset.push_back(i);
}
}
}
private:
//! Is the element of the vector palindrome
bool IsPalindrome(const T& s) const;
//! Initial dataset
std::vector<T> dataset;
//! Dataset after palindrome check
std::vector<T> palindromeDataset;
};
#endif
#include "palindrome.hpp"
#include "iostream"
#include "string"
template <>
void Palindrome<int>::Print() const
{
std::cout << "{";
for (auto iter = palindromeDataset.begin(); iter != palindromeDataset.end();)
{
std::cout << *iter;
if (++iter != palindromeDataset.end())
{
std::cout << ", ";
}
}
std::cout << "}"<<std::endl;
}
template <>
void Palindrome<std::string>::Print() const
{
std::cout << "{";
for (auto iter = palindromeDataset.begin(); iter != palindromeDataset.end();)
{
std::cout << *iter;
if (++iter != palindromeDataset.end())
{
std::cout << ", ";
}
}
std::cout << "}"<<std::endl;
}
template <>
void Palindrome<std::vector<int>>::Print() const
{
std::cout << "{";
for (auto iter1 = palindromeDataset.begin(); iter1 != palindromeDataset.end();)
{
std::cout << "{";
for (auto iter2 = iter1->begin(); iter2 != iter1->end();)
{
std::cout << *iter2;
if (++iter2 != iter1->end())
{
std::cout << ", ";
}
}
std::cout << "}";
if (++iter1 != palindromeDataset.end())
{
std::cout << ", ";
}
}
std::cout << "}"<<std::endl;
}
template <>
bool Palindrome<int>::IsPalindrome(const int& s) const
{
int x = s;
long int rev = 0;
if (x<0)
{
return false;
}
while (x!=0)
{
rev= rev*10+(x%10);
x=x/10;
}
return s == rev;
}
template <>
bool Palindrome<std::string>::IsPalindrome(const std::string& s) const
{
const size_t len = s.size();
if (!len)
{
return true;
}
size_t l = 0;
size_t r = len - 1;
while (l < r)
{
if (s[l] != s[r])
{
return false;
}
++l;
--r;
}
return true;
}
template <>
bool Palindrome<std::vector<int>>::IsPalindrome(const std::vector<int>& s) const
{
const size_t len = s.size();
if (!len)
{
return true;
}
size_t l = 0;
size_t r = len - 1;
while (l < r)
{
if(s[l] != s[r])
{
return false;
}
++l;
--r;
}
return true;
}
Réponses
Premièrement: cela ressemble à une application de "The OO Antipattern" . Je ne vois pas du tout pourquoi vous en avez besoin class Palindrome; et si vous devez le conserver, vous ne devriez certainement pas stocker l'intégralité du jeu de données - il suffit de le traiter une fois dans le constructeur et de conserver les palindromes!
De même, il Palindrome<T>::Print()semble qu'il faille généraliser pour «imprimer cette chose , quelle qu'elle soit»; cette opération n'a rien à voir avec les palindromes et peut être scindée en sa propre fonction d'utilité.
Il nous reste donc ceci:
template<class T>
std::vector<T> keep_only_palindromes(std::vector<T> dataset) {
std::erase_if(dataset, [](auto&& elt) {
return !is_palindromic(elt);
});
return dataset;
}
template<class T>
class PrintableVector {
const std::vector<T> *v_;
public:
explicit PrintableVector(const std::vector<T>& v) : v_(&v) {}
friend std::ostream& operator<<(std::ostream& os, const PrintableVector& me) {
os << "{ ";
for (auto&& elt : *me.v_) os << elt << ", ";
os << "}";
return os;
}
};
Ensuite, nous pourrions réécrire vos cas de test comme:
int main() {
auto pal = keep_only_palindromes(
std::vector<int>{125125, 4947, 74347, 11}
);
std::cout << PrintableVector(pal) << "\n";
auto pal1 = keep_only_palindromes(
std::vector<std::string>{"yay", "world", "level", "hello"}
);
std::cout << PrintableVector(pal1) << "\n";
std::vector<std::vector<int>> pal2_data = {
{6, 2, 2, 6},
{1, 2, 2},
{1, 4, 6, 3, 5, 3, 6, 4, 1},
{5, 2, 2, 6, 9, 1, 2}
};
auto pal2 = keep_only_palindromes(pal2_data);
std::cout << PrintableVector(pal2) << "\n";
}
Au fait, c'est très bien que vous ayez écrit des cas de test! Très peu de gens le font. Vos cas de test sont utiles car ils montrent comment vous comptez utiliser la classe - et me permettent de montrer comment je compte utiliser ma réécriture!
Je constate que vous ne testez pas de cas de coin, tels que 1, 0, -1, "", {42}ou {}. Ce n'est pas si génial.
Votre IsPalindromepour tout ce qui est itérable sera exactement le même. Alors préférez écrire quelque chose comme
template<class T>
auto is_palindromic(const T& seq)
-> decltype(std::begin(seq), std::rbegin(seq), true)
{
return std::equal(
std::begin(seq), std::end(seq),
std::rbegin(seq), std::rend(seq)
);
}
Là, j'utilise le type de retour SFINAE pour dire que ce modèle ne doit être pris en compte pour l'instanciation que lorsque l'expression std::begin(seq), std::rbegin(seq), trueest bien formée. En C ++ 20, vous pourriez mieux transmettre l'intention avec quelque chose comme ceci:
template<class T>
concept sequence = requires (const T& seq) {
seq.begin(); seq.rbegin();
};
template<class T> requires sequence<T> // !!
bool is_palindromic(const T& seq) {
return std::equal(
std::begin(seq), std::end(seq),
std::rbegin(seq), std::rend(seq)
);
}
Dans les deux cas, vous devrez toujours écrire votre autre surcharge
bool is_palindromic(int x)
par la main.
Une version de ce code avec un peu moins de handwaving et une syntaxe plus arcane est à https://godbolt.org/z/aqPfGx - pourrait être intéressant de jeter un coup d'œil, même si une partie de la syntaxe des arcanes est intimidante (et, honnêtement, inutile - si j'allais imprimer un vecteur de vecteurs en C ++, "je ne commencerais pas à partir d'ici").
I. La construction est divisée en plusieurs phases. Ou, en d'autres termes, un objet fraîchement construit n'est pas dans un état finalisé, prêt à l'emploi et a besoin d'un appel d'initialisation plus explicite. C'est un anti-modèle très fort, qui pourrait évidemment conduire à des erreurs.
Une solution plus décente serait de filtrer l'ensemble de données directement à l'intérieur du constructeur, et de ne pas conserver deux vecteurs à la fois, doublant ainsi l'espace nécessaire (en particulier lorsque l'un des vecteurs n'est nécessaire que comme argument pour créer l'autre, et qu'il est impossible et inaccessible autrement). (Un inconvénient de cette conception est évidemment que la construction serait très stricte, mais cela ne pose aucun problème tant que nous négligeons la possibilité que le filtre palindrome construit ne puisse jamais être utilisé.)
II. Le code pour Printest absolument identique entre les entiers et les chaînes, et pour les vecteurs, ne diffère que dans l'impression d'une partie élément.
III. Le filtre palindrome lui-même pourrait peut-être être plus utile s'il était implémenté simplement comme une fonction opérant sur des plages, similaires à celles définies dans <algorithm>; ou du moins s'il implémentait l' interface idiomatique iterator/ begin/ end. std::remove_ifavec IsPalindromecomme prédicat serait un bon début.
IV. Et le constructeur lui-même (tel qu'il est écrit) pourrait être modélisé, en acceptant un pack d'arguments arbitraires et en le transmettant à datasetctor.