简体   繁体   English

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

[英]Inputting elements of unknown type into a vector

I'm working on a program that takes elements from a user and sorts them. 我正在开发一个程序,它接受用户的元素并对它们进行排序。 For this program, I have to use a vector as the size of the element list is unknown prior to user input. 对于这个程序,我必须使用向量,因为在用户输入之前元素列表的大小是未知的。 Our instructions were: 我们的指示是:

Write a program in C++ to implement sorting of a list of elements. 用C ++编写程序来实现元素列表的排序。 Elements can be of any type but will all be of same type, like all integers or all floats or all chars or all strings (strings shall be sorted like in a dictionary). 元素可以是任何类型,但都可以是相同类型,如所有整数或所有浮点数或所有字符或所有字符串(字符串应按字典排序)。 You can implement any sorting algorithm of your choice. 您可以实现您选择的任何排序算法。

  1. Ask the user how many elements will be there 询问用户将有多少元素
  2. Ask the user to input elements 要求用户输入元素
  3. Ask the user to choose the sorting order: ascending or descending or both 要求用户选择排序顺序:升序或降序或两者
  4. Print both input and output lists 打印输入和输出列表
  5. User will not provide any information regarding the type of elements 用户不会提供有关元素类型的任何信息

I'm not very familiar with vectors (teacher basically skimmed topic in class) and my book isn't giving me a whole lot of information on the subject. 我对矢量不太熟悉(老师基本上在课堂上略读主题)而且我的书并没有给我很多关于这个主题的信息。 The problem I'm running into is that I don't know the type of the element list until the user begins input. 我遇到的问题是,在用户开始输入之前,我不知道元素列表的类型。 So far, I have tried: 到目前为止,我尝试过:

  • creating a void type vector (obviously not allowed now that I've researched it, oops) 创建一个void类型向量(现在我已经研究过它了,显然不允许)
  • overloading a function called insertInVector by sending the first element to the function and letting the function determine which vector type to create based on the type of the first element (which seemed like my best option when I thought of it, except I need access to the vector after the function terminates, so that ended up being a no go, too) 通过将第一个元素发送到函数并让函数根据第一个元素的类型确定要创建哪个矢量类型来重载一个名为insertInVector的函数(当我想到它时,这似乎是我最好的选择,除了我需要访问函数终止后的向量,所以最终也是不行的)
  • #include <typeinfo> in program, finding the type of the first element, and then creating a vector using vector<typeid(firstElement).name()> and honestly I'm not sure why that didn't work, but it didn't. #include <typeinfo>在程序中,找到第一个元素的类型,然后使用vector<typeid(firstElement).name()>创建一个向量,老实说,我不确定为什么这不起作用,但它没有“T。

Like I said I have EXTREMELY limited experience with vectors as this is my first time using them. 就像我说的那样,我对矢量的经验非常有限,因为这是我第一次使用它们。 I'm also a fairly new programmer so a lot of the research I've done on this has gone over my head. 我也是一个相当新的程序员,所以我在这方面所做的很多研究都已经过去了。 Any help that could be given in this would be GREATLY appreciated! 任何可以提供的帮助都将非常感激!

C++ is a language statically typed. C ++是一种静态类型的语言。 It means that all the types should be determined during compilation: you cannot introduce new types when running the program. 这意味着在编译期间应确定所有类型:在运行程序时不能引入新类型。

  • creating a void type vector (obviously not allowed now that I've researched it, oops) 创建一个void类型向量(现在我已经研究过它了,显然不允许)

void is actually quite a strange type, mostly a placeholder for when you would expect a type (like a function return type) and have none to provide. void实际上是一个非常奇怪的类型,大多数是占位符,当你期望一个类型(如函数返回类型)并且没有提供时。 void* is used as a pointer to an unknown type (mostly in C) but this is quite a hack, because the information about the original is discarded (as far as the language is concerned) so this causes issues to actually do things with the value so obtained. void*用作指向未知类型的指针(主要在C中),但这是一个非常黑客,因为关于原始的信息被丢弃(就语言而言)所以这会导致问题实际上与如此获得的价值。

  • overloading a function called insertInVector by sending the first element to the function and letting the function determine which vector type to create based on the type of the first element 通过将第一个元素发送到函数并让函数根据第一个元素的类型确定要创建的向量类型来重载一个名为insertInVector的函数

  • #include <typeinfo> in program, finding the type of the first element, and then creating a vector using vector<typeid(firstElement).name()> and honestly I'm not sure why that didn't work, but it didn't. #include <typeinfo>在程序中,找到第一个元素的类型,然后使用vector<typeid(firstElement).name()>创建一个向量,老实说,我不确定为什么这不起作用,但它没有“T。

Unfortunately neither is possible: since you cannot declare a variable without a type, what would be the type of firstElement to begin with ? 遗憾的是两者都不可能:因为你不能在没有类型的情况下声明变量, firstElement的类型是什么?


The problem you are describing is difficult in general. 您所描述的问题一般很难。 Basically it means that you will have to accept a string of characters, and then code a set of rules to determine how to interpret those characters. 基本上,这意味着您必须接受一串字符,然后编写一组规则来确定如何解释这些字符。 This is done generically by using a grammar to encode those rules; 这通常通过使用语法来编码那些规则来完成; but grammars might way complicated for what is probably a simple task. 但语法可能很复杂,可能是一项简单的任务。

Let me put together a small example: 让我把一个小例子放在一起:

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

Obviously, the real challenge is to correctly Parse the input. 显然,真正的挑战是正确Parse输入。

The idea is to use a set of rules, for example: 这个想法是使用一组规则,例如:

  • an int is composed exclusively of digits, optionally prefixed by - int只由数字组成,可选择带前缀-
  • a double is composed exclusively of digits, with at most one . double仅由数字组成,最多只有一个. and optionally prefixed by - 并可选择以-为前缀
  • a string can be anything, therefore is our catch-all 一个string可以是任何东西,因此是我们的全部

Then we can write the recognition part of the Parse method: 然后我们可以编写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

And with the guessing logic together, finally the parse comes: 随着猜测逻辑一起,最后解析来了:

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

Phew! 唷!

So ? 那么? Almost there. 差不多了。 Now we need to determine how to compare two inputs. 现在我们需要确定如何比较两个输入。 It's easy if they all have the same type, if not you will need to determine an arbitrary logic. 如果它们都具有相同的类型很容易,如果不是,则需要确定任意逻辑。 You can transform an input Int in an input Double easily enough, but for string it's a bit weirder. 你可以很容易地在输入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<

And finally, the program: 最后,该计划:

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

Of course as I don't know the types you wish to deal with.. I've decided an arbitrary set ;) However this should give you a skeleton to base yourself on. 当然,因为我不知道你想要处理的类型..我已经决定了一个任意的集合;)但是这应该给你一个基于自己的骨架。

Looking at the actual requirements of the problem as stated in the comments, I suggest you store all the inputs in an std::vector<std::string> and sort the vector using std::sort . 查看注释中所述问题的实际要求,我建议您将所有输入存储在std::vector<std::string>并使用std :: sort对向量进行排序 So, instead of worrying about different types, you can specify the sorting logic depending on what you interpret the strings in your vector to represent. 因此,您可以根据解释向量中的字符串来表示排序逻辑,而不是担心不同的类型。 So 所以

  1. Implement sorting functions for strings depending on what the strings represent (more later) 根据字符串表示的内容实现字符串的排序函数(稍后)
  2. store inputs as strings in a vector. 将输入存储为向量中的字符串。
  3. Determine which type the strings represent 确定字符串表示的类型
  4. select the sorting function based on this type 根据此类型选择排序功能
  5. Sort the vector using std::sort and the appropriate sort function. 使用std::sort和相应的排序函数对向量进行排序。

Concerning the sorting function, std::sort accepts a binary functor or function that applies a "less-than" comparison to two elelemts, so your functors or functions should look something like 关于排序函数, std::sort接受一个二元仿函数或函数,它将两个elelemts的“小于”比较应用,所以你的函子或函数应该看起来像

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

Edit : Looking at more recent comments, it seems that the main purpose if the exercise might have been to implement sorting algorithms for different types. 编辑 :查看更近期的评论,似乎主要目的是练习可能是为不同类型实现排序算法。 In that case, I would suggest following the approach taken by the C++ standard library, that is, to implement sorting in terms or a less-than comparison between two types, thereby decoupling the sorting logic from the types to be sorted. 在这种情况下,我建议由C ++标准库所采取的做法如下,即,为了实现在术语分类或两种类型之间的小于比较,从而去耦从类型分类逻辑进行排序。 So you would want a template sorting function, templated on iterator type and comparison function/functor. 所以你需要一个模板排序函数,模板化迭代器类型和比较函数/仿函数。

If you know what are the types the user may input, you can use templates and inheritance: 如果您知道用户可以输入的类型,您可以使用模板和继承:

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
}

That's just an idea ( Input cannot be implemented like that, you need to change that part with the logic that gets something from the user and determines its type), but it has the benefit of masking the type into a generic class, so you can factor your code around the interface provided by Generic . 这只是一个想法( Input不能像那样实现,你需要用从用户那里得到东西的逻辑来改变那个部分并确定它的类型),但是它具有将类型掩盖到泛型类中的好处,所以你可以使您的代码围绕Generic提供的接口。

Another idea (easier, probably) is using a std::vector<void*> and an enum that tells you what the type of the data stored in the vector is. 另一个想法(可能更容易)是使用std::vector<void*>和一个enum ,它告诉你存储在向量中的数据类型是什么。 When you need to process that data somewhere in the future, you can switch on the enum to appropriately cast the vector elements to the correct type and dispatch them to the appropriate code. 当您需要在将来某处处理该数据时,可以打开枚举以适当地将向量元素转换为正确的类型并将它们分派给相应的代码。

EDIT : another idea is to define a templatized function that takes the input and sorts the array using standard comparators: 编辑 :另一个想法是定义一个模板化函数,它接受输入并使用标准比较器对数组进行排序:

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

The logic to determine the type of the first input is up to you (maybe you can open another question) ;) 确定第一个输入类型的逻辑取决于您(也许您可以打开另一个问题);)

There are two aspects to this question: parsing & sorting. 这个问题有两个方面:解析和排序。

  • You can use regular expressions to check the user-input data-types. 您可以使用正则表达式来检查用户输入的数据类型。
  • You can use cin to parse the data. 您可以使用cin来解析数据。

First: realise that you cannot necessarily know the type of your users input until you have received all of it ~eg: consider a list of user names : 第一:意识到在收到所有用户输入之前你不一定知道用户输入的类型 〜例如:考虑一个用户名列表:

728278243
390349346
495045594
elizabeth

Hence, best not to assume to know best about the incoming data ( can lead to a frustrating user-experience ) but instead, prefer to treat everything as potentially a string. 因此,最好不要假设最好地了解传入的数据( 可能导致令人沮丧的用户体验 ),而是倾向于将所有内容视为潜在的字符串。 Store all raw input as strings so you can output in same format as input. 将所有原始输入存储为字符串,以便输出与输入格式相同的格式。 you can use say, an enumerated type to switch inside a sort comparator or consider using a mutliset/multimap . 你可以使用say,枚举类型来切换排序比较器考虑使用mutliset/multimap here you will be building an ordered set. 在这里,您将构建一个有序集。 so there is no need to sort. 所以没有必要排序。 NB: the complexity for constructing an ordered set of N elements or , for a single sort on N unsorted list elements, is roughly equivalent ~> NlogN 注意:构造N个元素的有序集合的复杂性, 或者对于N个未排序的列表元素的单个排序,大致相当于〜> NlogN

For your task-in-hand, it hardly matters but in reality depending upon upon how the list is used, one or other approach will be far more appropriate in performance terms. 对于您的任务而言,它几乎不重要,但实际上取决于列表的使用方式,一种或另一种方法在性能方面更为合适。

If you have already used the likes of std::vector then std::multimap shouldn't be too scary. 如果您已经使用了std::vector那么std::multimap应该不会太可怕。 Loosely, it is an associated array of key-value pairs. 松散地,它是一个关联的键值对数组。 the multi here meaning it can store multiple elements with the same key (which here, you want). 这里的multi意味着它可以存储具有相同键的多个元素(在这里,您想要)。


In this example i am using the boost regex library in order to determine some funky input data-types. 在这个例子中,我使用boost regex库来确定一些时髦的输入数据类型。
(eg: sudo apt-get install libboost-regex1.46-dev ) (例如: sudo apt-get install libboost-regex1.46-dev

This regex might seem arcane but there are many examples on the i/web for practically every conceivable pattern. 这个正则表达式可能看起来很神秘,但在i / web上有很多关于几乎所有可想到的模式的例子。 [ NB: C++11 regex is pretty-much a drop-in replacement for boost regex. [ 注意:C ++ 11正则表达式几乎是boost regex的替代品。 ie: boost regex should be forward-compatible with the emerging C++11 standard ] 即:boost regex应该与新兴的C ++ 11标准向前兼容 ]


blah.cpp: 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;
}

Example was compiled & run on Ubuntu. 示例在Ubuntu上编译和运行。 Commandline stuff: 命令行的东西:

$
$ 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: This is example code: 注意:这是示例代码:

  • There are many optimisations that can be made here. 可以在这里进行许多优化。 You clearly don't need so many stl containers as i am using. 很显然你并不需要那么多stl是我使用的容器。
  • I do not strictly deal with the direction of the sort (but show a couple of ways it may be achieved). 我没有严格处理排序的方向(但显示可以实现的几种方式)。
  • It might also be nice to encapsulate the type-specific functionality in C++ objects; 将类型特定的功能封装在C ++对象中也可能会很好; have a base class & then derived classes for each type you wish to support ~but this homework right? 有一个基类,然后为你想要支持的每种类型派生类〜但这个功课对吗? -so probably not worth going over-board then ;) - 那么可能不值得去船上;)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM