Vorlagen-Palindrom

Sep 07 2020

Ich versuche, eine Palindromprüfung für jedes Element in einem Vektor zu implementieren und einen Vektor nur mit den Palindromen zurückzugeben. Dies sollte für verschiedene .eg-Zeichenfolgen vom Typ int von Ints funktionieren. Ich habe eine Vorlagenlösung erstellt, aber ich bin der Meinung, dass dies die Vorlagen nicht voll ausnutzt.

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

Antworten

7 Quuxplusone Sep 07 2020 at 05:10

Erstens: Dies scheint eine Anwendung von "The OO Antipattern" zu sein . Ich verstehe nicht, warum du überhaupt brauchst class Palindrome; und wenn Sie es behalten müssen, sollten Sie auf keinen Fall den gesamten Datensatz speichern - verarbeiten Sie ihn einfach einmal im Konstruktor und behalten Sie die Palindrome!

Ebenso Palindrome<T>::Print()scheint es verallgemeinert zu sein, "dieses Ding zu drucken, was auch immer es ist"; Diese Operation hat nichts mit Palindromen zu tun und kann in eine eigene Utility-Funktion aufgeteilt werden.

Also bleiben wir bei folgendem:

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

Dann könnten wir Ihre Testfälle wie folgt umschreiben:

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

Übrigens ist es sehr schön, dass Sie Testfälle geschrieben haben! Sehr wenige Leute tun es. Ihre Testfälle sind nützlich, weil sie zeigen, wie Sie beabsichtigen, die Klasse zu verwenden - und es mir ermöglichen , zu zeigen, wie ich beabsichtige, mein Umschreiben zu verwenden!

Ich feststellen , dass Sie Eckfällen nicht testen, wie 1, 0, -1, "", {42}, oder {}. Das ist nicht so toll.


Ihr IsPalindromefür alles iterierbares wird genau das gleiche sein. Also lieber so etwas schreiben

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

Dort verwende ich den Rückgabetyp SFINAE, um zu sagen, dass diese Vorlage nur dann für die Instanziierung berücksichtigt werden sollte, wenn der Ausdruck std::begin(seq), std::rbegin(seq), truewohlgeformt ist. In C ++ 20 könnten Sie die Absicht mit so etwas besser vermitteln:

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 beiden Fällen müssten Sie noch Ihre andere Überladung schreiben

bool is_palindromic(int x)

von Hand.

Eine Version dieses Codes mit etwas weniger Handbewegung und mehr arkaner Syntax ist bei https://godbolt.org/z/aqPfGx - könnte interessant sein, einen Blick darauf zu werfen, auch wenn ein Teil der arkanen Syntax einschüchternd ist (und ehrlich gesagt unnötig - wenn ich einen Vektor von Vektoren in C ++ ausdrucken würde, "würde ich nicht von hier aus beginnen").

2 bipll Sep 07 2020 at 02:00

I. Der Bau ist in mehrere Phasen unterteilt. Mit anderen Worten, ein frisch erstelltes Objekt befindet sich nicht in einem endgültigen, gebrauchsfertigen Zustand und benötigt einen weiteren expliziten Initialisierungsaufruf. Dies ist ein sehr starkes Antimuster, das offensichtlich zu Fehlern führen kann.
Eine vernünftigere Lösung wäre, den Datensatz direkt im Konstruktor zu filtern und nicht zwei Vektoren gleichzeitig beizubehalten, wodurch der benötigte Speicherplatz verdoppelt wird (insbesondere, wenn einer der Vektoren nur als Argument zum Erstellen des anderen benötigt wird und nicht aktiviert werden kann und sonst unzugänglich). (Ein Nachteil dieses Designs ist offensichtlich, dass die Konstruktion sehr streng wäre, aber dies ist in Ordnung, solange wir die Möglichkeit vernachlässigen, dass der konstruierte Palindromfilter niemals tatsächlich verwendet werden könnte.)

II. Der Code für Printist zwischen Ints und Strings absolut identisch und unterscheidet sich für Vektoren nur im Druck eines Elementteils.

III. Der Palindromfilter selbst könnte vielleicht nützlicher sein, wenn er einfach als eine Funktion implementiert würde, die mit Bereichen arbeitet, ähnlich den in definierten <algorithm>; oder zumindest , wenn es umgesetzt idiomatischen iterator/ begin/ endSchnittstelle. std::remove_ifmit IsPalindromeals Prädikat wäre ein guter Anfang.

IV. Und der Konstruktor selbst (wie geschrieben) könnte als Vorlage verwendet werden, indem ein beliebiges Argumentationspaket akzeptiert und an datasetctor weitergeleitet wird.