Palindromo modellato

Sep 07 2020

Sto cercando di implementare un controllo palindromo per ogni elemento in un vettore e restituire un vettore con solo i palindromi. Questo dovrebbe funzionare per stringhe di tipo diverso .eg int vettore di int. Ho fatto una soluzione basata su modelli ma sento che questo non sfrutta appieno i modelli.

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

Risposte

7 Quuxplusone Sep 07 2020 at 05:10

Primo: questa sembra un'applicazione di "The OO Antipattern" . Non vedo perché ne hai bisogno class Palindrome; e se devi tenerlo, di certo non dovresti memorizzare l'intero set di dati - elaboralo una volta nel costruttore e mantieni i palindromi!

Allo stesso modo, Palindrome<T>::Print()sembra che dovrebbe essere generalizzato "stampare questa cosa , qualunque essa sia"; quell'operazione non ha nulla a che fare con i palindromi e può essere suddivisa in una sua funzione di utilità.

Quindi ci rimane questo:

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

Quindi potremmo riscrivere i tuoi casi di test come:

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 proposito, è molto bello che tu abbia scritto casi di test! Pochissime persone lo fanno. I tuoi casi di test sono utili perché mostrano come intendi utilizzare la classe e mi permettono di mostrare come intendo che venga utilizzata la mia riscrittura!

Faccio notare che non si prova alcun casi angolo, come ad esempio 1, 0, -1, "", {42}, o {}. Non è così eccezionale.


Il tuo IsPalindromeper qualsiasi cosa iterabile sarà esattamente lo stesso. Quindi preferisci scrivere qualcosa di simile

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

Sto usando il tipo di ritorno SFINAE per dire che questo modello dovrebbe essere considerato per l'istanza solo quando l'espressione std::begin(seq), std::rbegin(seq), trueè ben formata. In C ++ 20 potresti trasmettere meglio l'intento con qualcosa del genere:

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

In entrambi i casi, dovresti comunque scrivere il tuo altro sovraccarico

bool is_palindromic(int x)

a mano.

Una versione di questo codice con un po 'meno ondeggiamento della mano e una sintassi più arcana è disponibile all'indirizzo https://godbolt.org/z/aqPfGx - potrebbe essere interessante dare un'occhiata, anche se parte della sintassi arcana è intimidatoria (e, onestamente, non necessaria - se dovessi stampare un vettore di vettori in C ++, "non inizierei da qui").

2 bipll Sep 07 2020 at 02:00

I. La costruzione è suddivisa in più fasi. O, in altre parole, un oggetto appena costruito non è in uno stato finalizzato, pronto per l'uso e necessita di una chiamata di inizializzazione più esplicita. Questo è un antipattern molto forte, che ovviamente potrebbe portare ad errori.
Una soluzione più decente sarebbe quella di filtrare il set di dati direttamente all'interno del costruttore e non mantenere due vettori contemporaneamente, raddoppiando così lo spazio necessario (specialmente quando uno dei vettori è necessario solo come argomento per creare l'altro, ed è non utilizzabile e altrimenti inaccessibile). (Uno svantaggio di questo progetto è ovviamente che la costruzione sarebbe molto rigida, ma va bene fintanto che trascuriamo la possibilità che il filtro palindromo costruito non possa mai essere effettivamente utilizzato.)

II. Il codice per Printè assolutamente identico tra int e stringhe, e per i vettori, differisce solo nella stampa di una parte di un elemento.

III. Lo stesso filtro palindromo potrebbe forse essere più utile, se fosse implementato semplicemente come una funzione operante su intervalli, simili a quelli definiti in <algorithm>; o almeno se implementasse l' interfaccia idiomatica iterator/ begin/ end. std::remove_ifcon IsPalindromecome predicato sarebbe un buon inizio.

IV. E il costruttore stesso (come è scritto) potrebbe essere modellato, accettando un pacchetto di argomenti arbitrari e inoltrandolo a datasetctor.