将未知类型的元素输入到矢量中

时间:2012-06-10 13:19:58

标签: c++ types vector

我正在开发一个程序,它接受用户的元素并对它们进行排序。对于这个程序,我必须使用向量,因为在用户输入之前元素列表的大小是未知的。我们的指示是:<​​/ p>

  

用C ++编写程序来实现元素列表的排序。   元素可以是任何类型,但所有类型都是相同的类型   整数或所有浮点数或所有字符或所有字符串(字符串应为   像在字典中一样排序)。您可以实现任何排序算法   你的选择。

     
      
  1. 询问用户将有多少元素
  2.   
  3. 要求用户输入元素
  4.   
  5. 要求用户选择排序顺序:升序或降序或两者都
  6.   
  7. 打印输入和输出列表
  8.   
  9. 用户不会提供有关元素类型的任何信息
  10.   

我对矢量不太熟悉(老师基本上在课堂上略读主题)而且我的书并没有给我很多关于这个主题的信息。我遇到的问题是,在用户开始输入之前,我不知道元素列表的类型。到目前为止,我已经尝试过:

  • 创建一个void类型向量(现在显然不允许我研究它,oops)
  • 通过将第一个元素发送到函数并让函数根据第一个元素的类型确定要创建哪个矢量类型来重载一个名为insertInVector的函数(当我想到它时,这似乎是我最好的选择,除了我需要在函数终止后访问向量,所以最终也是不行了)
  • 在程序中
  • #include <typeinfo>,找到第一个元素的类型,然后 使用vector<typeid(firstElement).name()>创建一个向量,老实说,我不是 确定为什么不起作用,但事实并非如此。

就像我说过,我对矢量的经验非常有限,因为这是我第一次使用它们。我也是一个相当新的程序员,所以我在这方面所做的很多研究都已经过去了。任何可以提供的帮助都将非常感激!

4 个答案:

答案 0 :(得分:4)

C ++是一种静态类型的语言。这意味着在编译期间应确定所有类型:运行程序时不能引入新类型。

  
      
  • 创建一个void类型向量(现在显然不允许我研究它,oops)
  •   

void实际上是一个非常奇怪的类型,大多数是占位符,当你期望一个类型(如函数返回类型)并且没有提供时。 void*用作指向未知类型的指针(主要在C中),但这是一个非常黑客,因为关于原始的信息被丢弃(就语言而言)所以这会导致问题实际发生具有如此价值的东西。

  
      
  • 通过将第一个元素发送到函数并让函数根据第一个元素的类型确定要创建的矢量类型来重载一个名为insertInVector的函数

  •   程序中的
  • #include <typeinfo>,找到第一个元素的类型,然后使用vector<typeid(firstElement).name()>创建一个向量,说实话,我不确定为什么这个不起作用,但它没有“T

  •   

不幸的是,两者都不可能:因为你不能在没有类型的情况下声明变量,那么firstElement的类型是什么?


您所描述的问题一般很难。基本上,这意味着您必须接受一串字符,然后编写一组规则来确定如何解释这些字符。这通常通过使用语法来编码这些规则来完成;但语法可能很复杂,可能是一项简单的任务。

让我举一个小例子:

class Input {
public:
    enum Type {
        Int,
        Double,
        String
    };

    static Input Parse(std::string const& s);

    Input(): _type(Int), _int(0), _double(0.0) {} // need to define a default...

    Type type() const { return _type; }

    int asInt() const {
        assert(_type == Int && "not an int");
        return _int;
    }

    double asDouble() const {
        assert(_type == Double && "not a double");
        return _double;
    }

    std::string const& asString() const {
        assert(_type == String && "not a string");
        return _string; 
    }

private:
    Type _type;
    int _int;
    double _double;
    std::string _string;
};

显然,真正的挑战是正确Parse输入。

这个想法是使用一组规则,例如:

  • int仅由数字组成,可选地以-
  • 为前缀
  • double仅由数字组成,最多只有一个.,并且可选地以-为前缀
  • string可以是任何东西,因此是我们的全能

然后我们可以编写Parse方法的识别部分:

static bool isInt(std::string const& s) {
    if (s.empty()) { return false; }

    // The first character may be among digits and '-'
    char const first = s.at(0);
    if (not isdigit(first) and first != '-') { return false; }

    // Subsequent characters may only be digits
    for (char c: s.substr(1)) {
        if (not isdigit(c)) { return false; }
    }

    // Looks like it is an int :)
    return true;
} // isInt

// Note: any int could be interpreted as a double too
static bool maybeDouble(std::string const& s) {
    if (s.empty()) { return false; }

    // The first character may be among digits, '.' and '-'
    char const first = s.at(0);
    if (not isdigit(first) and first != '.' and first != '-') { return false; }

    // There may only be one dot
    bool hasSeenDot = s.at(0) == '.';

    // Subsequent characters may only be digits and a dot now
    for (char c: s.substr(1)) {
        if (not isdigit(c) and c != '.') { return false; }

        if (c == '.') {
            if (hasSeenDot) { return false; } // no second dot allowed
            hasSeenDot = true;
        }
    }

    // Looks like it could be a double
    return true;
} // maybeDouble

static Input::Type guessType(std::string const& s) {
    if (isInt(s)) { return Input::Int; }

    // Test double after we ensured it was not an int
    if (maybeDouble(s)) { return Input::Double; }

    return Input::String;
} // guessType

随着猜测逻辑的结合,最后解析出来了:

Input Input::Parse(std::string const& s) {
    Input result;

    result._type = guessType(s);

    switch(result._type) {
    case Input::Int: {
        std::istringstream stream(s);
        s >> result._int;
        return result;
    }
    case Input::Double: {
        std::istringstream stream(s);
        s >> result._double;
        return result;
    }
    case Input::String:
        result._string = s;
        return result;
    }

    // Unreachable (normally)
    abort();
} // Input::Parse

呼!

那么?差不多了。现在我们需要确定如何比较两个输入。如果它们都具有相同的类型很容易,如果不是,则需要确定任意逻辑。你可以很容易地在输入Double中转换输入Int,但对于字符串来说,它有点怪异。

// define < for comparing two instance of "Input",
// assuming they both have the same type
bool operator<(Input const& left, Input const& right) {
    assert(left.type() == right.type() && "Different Types!");

    switch(left.type()) {
    case Input::Int: return left.asInt() < right.asInt();
    case Input::Double: return left.asDouble() < right.asDouble();
    case Input::String: return left.asString() < right.asString();
    }
} // operator<

最后,该计划:

int main(int argc, char* argv[]) {
    // parse command line
    std::vector<Input> inputs;

    // by convention argv[0] is the program name, it does not count!
    for (int i = 1; i != argc; ++i) {
        inputs.push_back(Input::Parse(argv[i]));

        // Detect that the type is the same as the first input
        if (inputs.size() >= 2) {
            if (inputs.back().type() != inputs.front().type()) {
                std::cerr << "Please only use one type among Int, Double and String\n";
                return 1; // non-0 is an error
            }
        }
    }

    // sort
    std::sort(inputs.begin(), inputs.end());

    // echo back to the user
    for (Input const& i: inputs) {
        switch(i.type()) {
        case Input::Int: std::cout << i.asInt() << "\n"; break;
        case Input::Double: std::cout << i.asDouble() << "\n"; break;
        case Input::String: std::cout << i.asString() << "\n"; break;
        }
    }

    // End of the program
    return 0;
}

当然,因为我不知道你想要处理的类型..我已经决定了一个任意的集合;)但是这应该给你一个基于自己的骨架。

答案 1 :(得分:3)

根据评论中所述的问题的实际要求,我建议您将所有输入存储在std::vector<std::string>中,并使用std::sort对矢量进行排序。因此,您可以根据解释向量中的字符串来表示排序逻辑,而不是担心不同的类型。所以

  1. 根据字符串代表的内容(更晚些时候)实现字符串的排序函数
  2. 将输入作为字符串存储在向量中。
  3. 确定字符串代表的类型
  4. 根据此类型选择排序功能
  5. 使用std::sort和相应的排序函数对矢量进行排序。
  6. 关于排序功能,std::sort接受二元仿函数或函数,它将两个元素的“小于”比较应用,因此你的仿函数或函数看起来应该像

    bool foo(const std::string& rhs, const std::string& lhs) {
      // implement the logic
    }
    

    编辑:查看最近的评论,似乎主要目的是练习可能是为不同类型实现排序算法。在这种情况下,我建议遵循C ++标准库采用的方法,即按照术语实现排序或两种类型之间的 less-than 比较,从而将排序逻辑与类型分离待分类。所以你需要一个模板排序函数,在迭代器类型和比较函数/函数上进行模板化。

答案 2 :(得分:1)

如果您知道用户可以输入的类型,您可以使用模板和继承:

class Generic {
public:
  virtual void process_input() = 0; // Handles the next input from user
  virtual void process_output() = 0; // Processes the data inserted
};

template <typename T>
class HandleInput : public Generic {
private:
    std::vector<T> storage;
public:
    HandleInput(T first)
    {
      storage.push_back(first);
    }

    void process_input()
    {
      // do whatever you want
    }

    void process_output()
    {
      // do whatever you want
    }
};

int main(int argc, char **argv)
{
  // Get first input
  Input i = input();
  Generic *g;

  // Instantiate the "right" generic with a switch
  switch (i.type) {
    case T:
      g = new HandleInput<T>(i.value);
  }

  // Use Generic from here onwards
}

这只是一个想法(Input不能像那样实现,你需要用从用户那里得到东西并确定其类型的逻辑来改变那部分),但它有一个好处将类型掩盖到泛型类中,因此您可以围绕Generic提供的接口对代码进行分解。

另一个想法(可能更简单)是使用std::vector<void*>enum来告诉您矢量中存储的数据类型是什么。当您需要在将来的某个地方处理该数据时,您可以打开枚举以适当地将向量元素转换为正确的类型并将它们分派给相应的代码。

编辑:另一个想法是定义一个模板化函数,它接受输入并使用标准比较器对数组进行排序:

#include <iostream>

#include <vector>
#include <algorithm>
#include <boost/lexical_cast.hpp>

template <typename T>
void print_v(std::vector<T> &v)
{
    typename std::vector<T>::iterator it;
    for (it = v.begin(); it != v.end(); it++)
        std::cout << *it << " ";
    std::cout << std::endl;
}

template <typename T>
void sort_and_print(T first, size_t n, bool asc)
{
    std::vector<T> v;
    v.push_back(first);
    for (size_t i = 0; i < n; i++) {
        std::string s;
        std::cin >> s;
        T e = boost::lexical_cast<T>(s);
        v.push_back(e);
    }

    print_v(v);
    if (asc)
        std::sort(v.begin(), v.end(), std::greater<T>());
    else
        std::sort(v.begin(), v.end());
    print_v(v);
}

int main(int argc, char **argv)
{
    std::string s = "test";
    sort_and_print(s, 2, true);
    unsigned int j = 3;
    sort_and_print(j, 2, true);
    return 0;
}

确定第一个输入类型的逻辑取决于你(也许你可以打开另一个问题);)

答案 3 :(得分:1)

这个问题有两个方面:解析&amp;排序

  • 您可以使用正则表达式来检查用户输入的数据类型。
  • 您可以使用cin来解析数据。

首先:意识到您不能必然知道您的类型 用户输入,直到您收到所有这些 〜例如:考虑一个用户名列表:

728278243
390349346
495045594
elizabeth

因此,最好不要假设最好地了解传入的数据(会导致令人沮丧的用户体验),而是更愿意将所有内容都视为字符串。将所有原始输入存储为字符串,以便输出与输入格式相同的格式。 你可以使用say,枚举类型来切换排序比较器 考虑使用mutliset/multimap。在这里你会 构建有序集。所以没有必要排序。 注意:对于N个未排序列表元素上的单个排序,构造N个元素的有序集合的复杂性大致相当于〜&gt; NlogN

对于您的任务而言,它几乎不重要,但实际上取决于列表的使用方式,一种或另一种方法在性能方面更为合适。

如果你已经使用了std::vector这样的内容,那么std::multimap就不会太可怕了。松散地,它是一个关联的键值对数组。这里的 multi 意味着它可以使用相同的键存储多个元素(在这里,你想要)。


在本例中,我使用 boost 正则表达式库来确定一些 funky 输入数据类型。
(例如:sudo apt-get install libboost-regex1.46-dev

这个正则表达式可能看起来很神秘,但在i / web上有很多关于几乎所有可想到的模式的例子。 [注意:C ++ 11正则表达式几乎是boost regex的替代品。即:boost regex应该与新兴的C ++ 11标准正向兼容]


blah.cpp:

#include <iostream>
#include <sstream>
#include <string>
#include <list>
#include <map>
#include <set>
#include <boost/regex.hpp>    
//NB: GNU gcc added *experimental support for regular expressions in TR1 v 4.3.0.
//    compile with:  -std=c++0x

using namespace std;
using namespace boost;

//some example input data-types (perhaps notably missing a date!) 
const regex re_char("[^0-9]", regex_constants::extended); //non numeric chars
const regex re_digit("[[:digit:]]+", regex_constants::extended); //a string of only digits in range [0..9] ~ie: Z+
const regex re_xdigit("0[xX][[:xdigit:]]+", regex_constants::extended); //support hex iff starts with '0x' or '0X'
const regex re_float("[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?", regex_constants::extended); //all kinds of numbers


int main(int argc, char** argv)
{    
    int i, countc=0;
    double d;
    string str;
    int element_count;    

    do
    {
        cout << "how many elements will there be? "; 
        if (cin >> element_count) break;
        cin.clear();
        cin >> str;
        cout << "\033[A\033[2K" << flush;
    }
    while(13);
    cin.ignore(128,'\n'); 

    multimap<double, string> list_num; 
    multimap<double, string> list_fp; 
    //NB: below, by way of example, construction using the 'greater<int>' comparison class achieves _descending_ order 
    multimap<int, string, greater<int> > list_int; 
    list<string> list_str; 

    for (int next=0; next < element_count; next++)
    {
        cout << "\033[A\033[2K" << flush;
        cout << "enter next element in list ["<< next+1 << "/" << element_count << "] : "; 
        getline (cin,str);

        if (regex_match(str, re_xdigit))
        {
            //see all about manipulators here:
            //http://www.cplusplus.com/reference/iostream/istream/operator%3E%3E/
            stringstream(str) >> hex >> i;            
            list_int.insert(pair<int, string>(i, str)); 
            list_num.insert(pair<double, string>(i, str)); 
        }
        else if (regex_match(str, re_digit))
        {
            stringstream(str) >> i;            
            list_int.insert(pair<int, string>(i, str));            
            list_num.insert(pair<double, string>(i, str)); 
        }
        else if (regex_match(str, re_float))
        {
            stringstream(str) >> d;    
            list_fp.insert(pair<double, string>(d, str));        
            list_num.insert(pair<double, string>(d, str)); 
        } 

        if (regex_match(str, re_char)) countc++;      
        list_str.push_back(str);
    }    

    cout << "\033[A\033[2K" << flush;

    cout << "input: unsorted list:" << endl;
    for (list<string>::iterator it=list_str.begin(); it!=list_str.end(); it++) 
        cout << *it << endl;

    if (list_int.size() == element_count)
    {
        cout << endl << "output: sorted list of Z+ types:" << endl;
        for (multimap<int, string>::iterator it=list_int.begin() ; it != list_int.end(); it++ )
            cout << (*it).second << endl;
    }
    else if (list_fp.size() == element_count)
    {
        cout << endl << "output: sorted list of fp types:" << endl;
        for (multimap<double, string>::iterator it=list_fp.begin() ; it != list_fp.end(); it++ )
            cout << (*it).second << endl;
    }
    else if (list_num.size() == element_count)
    {
        cout << endl << "output: sorted list of numeric types:" << endl;
        for (multimap<double, string>::iterator it=list_num.begin() ; it != list_num.end(); it++ )
            cout << (*it).second << endl;
    }
    else //output as sorted strings ~but in _descending_ order, using reverse iterator, by way of example
    {
        list_str.sort(); //but best to use list_str.sort(greater<string>()); with forward iterators
        cout << endl << "output: sorted list of " <<  (countc == element_count ? "non numeric char" : "string") << " types:" << endl;
        for (list<string>::reverse_iterator it=list_str.rbegin(); it!=list_str.rend(); ++it) 
            cout << *it << endl;        
    }   

    return 0;
}

示例编译和&amp;在Ubuntu上运行。命令行的东西:

$
$ lsb_release -d
Description:    Ubuntu 11.10

$ g++ --version
g++ (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1 

$ g++ --pedantic -oblah blah.cpp -lboost_regex
$ ./blah
input: unsorted list:
4.77
2.0e+2
-.3
11
0x10

output: sorted list of numeric types:
-.3
4.77
11
0x10
2.0e+2
$


NB:这是示例代码:

  • 这里可以做很多优化。你清楚 不要需要我正在使用的stl个容器。
  • 我没有严格处理排序的方向(但是可以通过几种方式来实现)。
  • 封装特定于类型的功能也可能不错 在C ++对象中;有一个基类&amp;然后为您希望的每个类型派生类 支持〜但这个家庭作业对吗? - 那么可能不值得过头了 ;)