Palíndromo modelado

Sep 07 2020

Estou tentando implementar uma verificação de palíndromo para cada elemento em um vetor e retornar um vetor apenas com os palíndromos. Isso deve funcionar para diferentes tipos de strings .eg int vetor de ints. Fiz uma solução de modelo, mas sinto que isso não tira total proveito dos modelos.

//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;
}

Respostas

7 Quuxplusone Sep 07 2020 at 05:10

Primeiro: isso parece uma aplicação de "The OO Antipattern" . Eu não vejo porque você precisa class Palindrome; e se você deve mantê-lo, certamente não deve armazenar o conjunto de dados inteiro - apenas processe-o uma vez no construtor e mantenha os palíndromos!

Da mesma forma, Palindrome<T>::Print()parece que deve ser generalizado para "imprimir esta coisa , seja o que for"; essa operação não tem nada a ver com palíndromos e pode ser dividida em sua própria função de utilidade.

Então, ficamos com isso:

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;
    }
};

Então, poderíamos reescrever seus casos de teste como:

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";
}

A propósito, é muito bom que você tenha escrito casos de teste! Muito poucas pessoas o fazem. Seus casos de teste são úteis porque mostram como você pretende que a classe seja usada - e me permitem mostrar como pretendo que minha reescrita seja usada!

Eu aviso que você não testar todos os casos de canto, tais como 1, 0, -1, "", {42}, ou {}. Isso não é tão bom.


Seu IsPalindromeiterável para qualquer coisa será exatamente o mesmo. Portanto, prefira escrever algo como

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)
    );
}

Aqui, estou usando o tipo de retorno SFINAE para dizer que esse modelo deve ser considerado para instanciação apenas quando a expressão std::begin(seq), std::rbegin(seq), trueestiver bem formada. Em C ++ 20, você poderia transmitir melhor a intenção com algo assim:

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)
    );
}

Em qualquer caso, você ainda terá que escrever sua outra sobrecarga

bool is_palindromic(int x)

à mão.

Uma versão deste código com um pouco menos de ondulação manual e uma sintaxe mais misteriosa está em https://godbolt.org/z/aqPfGx - pode ser interessante dar uma olhada, mesmo que parte da sintaxe arcana seja intimidante (e, honestamente, desnecessária - se eu fosse imprimir um vetor de vetores em C ++, "Eu não começaria por aqui").

2 bipll Sep 07 2020 at 02:00

I. A construção está dividida em várias fases. Ou, em outras palavras, um objeto recém-construído não está em um estado finalizado e pronto para uso e precisa de mais uma chamada de inicialização explícita. Este é um antipadrão muito forte, que pode obviamente levar a erros.
Uma solução mais decente seria filtrar o conjunto de dados dentro do construtor, e não manter dois vetores ao mesmo tempo, dobrando assim o espaço necessário (especialmente quando um dos vetores é necessário apenas como um argumento para criar o outro, e é impossível e inacessível de outra forma). (Uma desvantagem deste projeto é, obviamente, que a construção seria muito rígida, mas isso está bem, desde que negligenciemos a possibilidade de que o filtro de palíndromo construído nunca possa ser realmente usado.)

II. O código para Printé absolutamente idêntico entre ints e strings, e para vetores, difere apenas na impressão de uma parte do elemento.

III. O próprio filtro de palíndromo talvez pudesse ser mais útil, se fosse implementado simplesmente como uma função operando em intervalos, semelhantes aos definidos em <algorithm>; ou pelo menos se implementou interface idiomática iterator/ begin/ end. std::remove_ifcom IsPalindromeo predicado seria um bom começo.

4. E o próprio construtor (como está escrito) pode ser modelado, aceitando um pacote de argumentos arbitrário e encaminhando-o ao datasetctor.