Using enum with string variables - c++

I'm trying to do something like this,
enum Data_Property
{
NAME,
DATETIME,
TYPE,
};
struct Item_properties
{
char* Name;
char* DateTime;
int Type;
};
int main() {
std::string data = "/Name Item_Name /DATETIME [TimeStamp] /TYPE [Integer value]";
std::list <std::string> known_properties;
known_properties.push_back("/Name");
known_properties.push_back("/DATETIME");
known_properties.push_back("/TYPE");
Item_properties item_p = extract_properties(data); //I use a function to get properties
//in a structure.
//here I want to use a switch-case statement to get the property by enum value
}
I need to know is there any way i can make it any simple? or how would i combine property keys like (/NAME /DATETIME /TYPE) with an enum and avoid using a std::list i.e known_properties?

As far as I know, there is nothing in C++ enums that is like C# enum attributes. I could be wrong though.
Given that, I'd suggest using a map to map the data property to item property. You should only have to build this map once. You can also associate the string representations to the item and data properties. This should make it easier to get the item properties objects built. I don't think a list would be an ideal data structure for this task.

First, let me say that there is always another alternative, one of my philosophies.
Let's look at the Big Picture. You are trying read an Item from string. The Item is a container of datums. In Object Oriented terms, the Item would like each datum to load its own data from a string. The Item doesn't need to know how to load a datum, as it can request this from each datum.
Your datums are actually more complex and intelligent than the C++ simple POD types. (Simple POD types don't have field names.) So, you need to create classes to represent these (or to encapsulate the extra complexity). Trust me, this will be better in the long run.
Here is a simple example of Refactoring the Name member:
struct Name_Field
{
std::string value; // Change your habits to prefer std:string to char *
void load_from(const std::string& text,
const std::string::size_type& starting_position = 0)
{
value.clear();
std::string::size_type position = 0;
position = text.find("/Name", starting_position);
if (position != std::string::npos) // the field name was found.
{
// Skip the next space
++position;
// Check for end of text
if (position < text.length())
{
std::string::size_t end_position;
end_position = text.find_first_not_of(" ", position);
if (end_position != std::string::npos)
{
value = text.substr(position, end_position - position);
}
}
}
};
You can now add a load_from to the Item class:
struct Item
{
Name_Field Name;
char * DateTime;
char * Type;
void load_from(const std::string& text)
{
std::string::size_t position = 0;
// Notice, the Name member is responsible for load its data,
// relieving Item class from knowing how to do it.
Name.load_from(text, position);
}
};
To load the item:
Item new_item;
new_item.load_from(data);
As you refactor, be aware of common methods. For example, you may want to put common methods between Name_Field and DateTime_Field into a base (parent) class, Field_Base. This will simplify your code and design as well as support re-usability.

Have you looked at Boost::program_options? Here's some example code:
#include "boost\program_options\parsers.hpp"
#include "boost\program_options\variables_map.hpp"
#include <iostream>
using std::cout;
using std::endl;
namespace po = boost::program_options;
int _tmain(int argc, _TCHAR* argv[])
{
po::options_description desc("Allowed options");
string taskName;
desc.add_options()
("help", "produce help message")
("TaskName", po::value<string>(&taskName), "The project's prefix")
;
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
if (vm.count("help")) {
cout << desc << endl;
return 1;
}
if (!vm.count("TaskName"))
{
cout << "Use \"DOTaskTests.exe --help\" for command line options" << endl;
return 1;
}
}

Related

How to put data into a vector from a structure in another class that is in a binary file

class Work{
public:
struct dane {
int id;
char typeOfWork[5];
char Title[50];
char Author_lastName[40];
char Author_name[50];
int year;
char Promotor_lastName[30];
char Promotor_name[40];
char keyWords[50];
char summary[1000];
};
dane dane1;
};
In another class, I enter all values to this variables and save it to a binary file.
I want to save all this input into a vector in another class called liblary:
class liblary : Work{
vector<Work> vec; // here is problem i want to keep all this inputs in this vector
int how_many_works;
void get_data(){
//
// Up here i have inputs for all this variables but I didn't copy it
//
ofstream plik;
plik.open("dane.dat",ios::out | ios::binary | ios::app);
if(plik.is_open()){
plik.write((const char *) &dane1, sizeof(dane));
vec.push_back(dane); // Something like that to store all works in this vector but this ofc don;t work
plik.close();
}
};
Ofc, in method get_data(), I didn't copy here all inputs of data, I did it just to show that that is in the liblary class and includes a binary file. This is a liblary system for a student thesis. I want this in vector because later I must add modify and searching methods in this OOP program.
Main question: How I can put all inputs what are getting saved to binary file, to vector.
Okay, your code is a little odd, but it's not too bad. I'm going to ignore the oddness because you might actually have a reason for it.
Your vector is std::vector<Work>, but you're doing a push_back of a dane. You either need to change to a vector<Work::dane> or do vec.push_back(*this) instead.
I would make one other change. I don't know why you have a nested structure, but that's fine. I think it's dangerous to have dane dane the way you do, and I think it's doubly-dangerous you're leaving the contents willy-nilly.
I'd do this:
std::vector<dane> vec;
if(plik.is_open()){
dane myDane; // This is new, and then I use it instead of dane1
// populate myDane as needed...
plik.write((const char *) &myDane, sizeof(myDane));
vec.push_back(myDane); // Something like that to store all works in this vector but this ofc don;t work
plik.close();
}
That is, I'd use a local copy of your dane so you're not mucking with the one inside your object -- unless there's a good reason you want it to be mucked with while loading your vector.
Since you append the data I assume you want to load the data stored in a previous run. In this case open the file for both reading and writing, and read the elements from the file first and simply store any additional data written in the vector:
Simplified example only setting the summary of a single element, appending it and printing all summaries:
struct dane {
int id;
char typeOfWork[5];
char Title[50];
char Author_lastName[40];
char Author_name[50];
int year;
char Promotor_lastName[30];
char Promotor_name[40];
char keyWords[50];
char summary[1000];
};
class Library
{
std::vector<dane> m_contents;
std::fstream m_file;
public:
Library(std::filesystem::path const& fileName)
{
// open file with
// - input pos at the end (we want to know the number of elements) -> ate
// - output pos at the end (we want to append data) -> app
// - for reading and writing -> (in | out)
// - no special treatment of line breaks -> binary
m_file.open(fileName, std::ios::out | std::ios::in | std::ios::binary | std::ios::app | std::ios::ate);
if (!m_file.is_open())
{
throw std::runtime_error("could not open file " + fileName.string());
}
auto existingDataSize = m_file.tellg();
if ((existingDataSize % sizeof(dane)) != 0)
{
throw std::runtime_error("corrupted data in file " + fileName.string());
}
m_contents.resize(existingDataSize / sizeof(dane));
m_file.seekg(0, std::ios::beg);
m_file.read(reinterpret_cast<char*>(m_contents.data()), existingDataSize);
}
void WriteElement(dane const& element)
{
m_file.write(reinterpret_cast<char const*>(&element), sizeof(element));
m_contents.emplace_back(element);
}
void Print() const
{
for (auto& d : m_contents)
{
std::cout << d.summary << '\n';
}
}
};
int main()
{
Library lib("lib.dat");
dane d;
std::cin >> d.summary;
lib.WriteElement(d);
lib.Print();
}

How to store data in boost::program_options::variable_map?

I am currently trying to rework some code that was handed down to me. The original point of the code is to read a configuration file, and to set up the different options in the file in a boost::program_options::variable_map, which is then read throughout other parts of the code which is already working fine.
Here is the code I am trying to replace:
// Some helpful definitions
boost::program_options::variables_map vm;
std::string filecfg = "File_name";
std::ifstream ifs(filecfg.c_str());
// Setting options (This is command line example, but config file is the same)
boost::program_options::options_description df("Command Line");
df.add_options()
("help", "display help message")
("file-cfg,f", boost::program_options::value<std::string>(), "config file")
("version", "display version and revision number");
boost::program_options::parsed_options parsedc = boost::program_options::parse_config_file(ifs, df, true);
boost::program_options::store(parsedc, vm);
boost::program_options::notify(vm);
std::vector <std::string> unrc = boost::program_options::collect_unrecognized(parsedc.options, boost::program_options::include_positional)
My thinking it to simply replace the boost::program_options::parsed_options parsedc and create this object by myself. The problem I run into is simply that there is no documentation on how to do this. I think it is mostly because it is not designed to be used this way.
In any case, I am just looking to fill up the vm object with the options described in dc, and with values that I can hold in a separate data structure (like a vector).
Is it possible to simply add values to vm? Or do I have to go through a function such as boost::program_options::store()?
Any help would be greatly appreciated! Let me know if something is unclear, or if there is something you'd like me to try!
Thanks!
Yeah you can.
Be aware that you will have to decide how to "mock"/"fake" the other semantics of it though. (E.g. you might want to masquerade the options as having been defaulted)
Conceptually, variable_map would be a map<string, variable_value>. variable_value:
Class holding value of option. Contains details about how the value is
set and allows to conveniently obtain the value.
Note also that because variable_value uses boost::any for storage you will have to be exact about the types you will store. (So, don't store "oops" if you need a std::string("ah okay")).
Here's a simple demo:
Live On Coliru
#include <boost/program_options.hpp>
#include <iostream>
#include <iomanip>
namespace po = boost::program_options;
using namespace std::string_literals;
int main(/*int argc, char** argv*/) {
// Some helpful definitions
po::variables_map vm;
vm.emplace("file-cfg", po::variable_value("string"s, true));
vm.emplace("log-level", po::variable_value(3, false));
vm.emplace("option3", po::variable_value{});
notify(vm);
std::vector unrc = { "unrecognized"s, "options"s };
for (auto& [name, value] : vm) {
std::cout
<< "Name: " << name
<< std::boolalpha
<< "\tdefaulted:" << value.defaulted()
<< "\tempty:" << value.empty();
if (typeid(std::string) == value.value().type())
std::cout << " - string " << std::quoted(value.as<std::string>()) << "\n";
else if (typeid(int) == value.value().type())
std::cout << " - int " << value.as<int>() << "\n";
else if (!value.empty())
std::cout << " - unknown type\n";
}
}
Prints
Name: file-cfg defaulted:true empty:false - string "string"
Name: log-level defaulted:false empty:false - int 3
Name: option3 defaulted:false empty:true
I warn you not to use vm.emplace(…, po::variable_value(…,…)).
It's quite deceptive: it will work to some extent but fail spectacularly elsewhere.
When you use po::store, it internally also produces a po::variable_value and copies the semantic of your option to a private field of the po::variable_value. There's no way to set this semantic yourself. (see: https://github.com/boostorg/program_options/blob/develop/src/variables_map.cpp#L83).
Without the semantic you can't at least:
read the option from multiple sources
vm.notify() will not write the value to variables associated with the option
Here's an (arguably ugly) way that should avoid these issues:
po::variables_map vm;
po::options_description opts;
opts.add_options()("optName", …);
…
po::parsed_options parsedOpts(&opts);
pOpts.options.push_back(po::option("optName", {"optValue"}));
po::store(parsedOpts, vm);
First, one has to create a parsedOpts object that stores the description of options. Then, add an option name and list of values as strings, regardless of the option type. Finally, within po::store, the name and value(s) are parsed and stored in vm.
A complete working example:
#include <boost/program_options.hpp>
#include <iostream>
namespace po = boost::program_options;
int main(int ac, char **av) {
int i = 0;
po::variables_map vm;
po::options_description opts;
opts.add_options()("id", po::value<int>(&i)->default_value(1));
int userInput;
std::cin >> userInput;
if (userInput == 2) {
vm.emplace("id", po::variable_value(userInput, false));
}
if (userInput == 3) {
po::parsed_options pOpts(&opts);
pOpts.options.push_back(po::option("id", {std::to_string(userInput)}));
po::store(pOpts, vm);
}
po::store(po::parse_command_line(ac, av, opts), vm);
//po::store(po::parse_config_file("options.ini", opts, true), vm);
po::notify(vm);
std::cout << "id (notified): " << i << std::endl;
std::cout << "id (from vm): " << vm["id"].as<int>() << std::endl;
return 0;
}
Typing 2 runs vm.emplace and yields:
id (notified): 0
id (from vm): 2
which is bad, for po::value<int>(&i) was ignored
Typing 2 and adding --id=4 command line argument yields:
terminate called after throwing an instance of 'boost::wrapexcept<boost::program_options::multiple_occurrences>'
which is bad, for you cannot use multiple sources
Typing 3 runs the po::store on hand-crafted po::parsed_options and yields:
id (notified): 3
id (from vm): 3
Typing 3 and adding --id=4 command line argument yields:
id (notified): 3
id (from vm): 3
Which is correct & expected, for the subsequent stores shall not replace values that were stored earlier (see https://www.boost.org/doc/libs/1_80_0/doc/html/program_options/tutorial.html#id-1.3.30.4.5)

c++ graph input problem, finding the ways of an input?

we had a algorithm coding event in our school today, and they asked a question and no one could answer. I am trying to find an answer by using only standard library. (I am trying to solve this without .h files because in contests they want us to solve it like that.) So basically question is as follows:
*Write a (C / C++) program that will get a graph model as an argument.
*You must get this values from console while we are starting your application.
*Your program must write down all the possible word combination by using the graph model.
Ex Input on Console to call your app: “yourapp.exe 5ABCD1BCD1CDE”
After your application name, second word gives you information about the graph.
Notation: [STEPS][FROM1-TO1-TO2-...TOn]1[FROM2-TO1-TO2-...TOn]1 .....
[STEPS] First integer value ( 5 in our example) is the maximum word length to measure.
[FROM TO ... TO] blocks show connections in the graph. Each node is symbolized with one Upper
Letter. First on is connections start position others are destinations. Each connection(link) is one
way. So: “ABCD” means we have connection from A to B , A to C and A to D
The first node in the text is the start point for word creation.
This input means you have a graph like: https://imgur.com/BioHGqA
Desired Output:
A
AB
AC
AD
ABD
ABC
ABCD
ABCE
ACD
ACE
--------------------------------------------END OF THE QUESTION-----------------------------------------------------
I personally tried to find the index numbers in input, connection starts and etc. but i couldn't figured out how to solve this properly. Please help :=)
#include <iostream>
#include <string>
using namespace std;
int inputLength,maxLength,digitLength;
string word,digitIndex,starters;
int main(int argc, char *argv[])
{
//Saving the graph input as a variable named word
word = argv[1];
//Finding the max word and input lengths
maxLength=word[0] -'0';
inputLength=word.length();
cout<<"Your graph input "<<word<<endl;
cout<<"Maximum word length : "<<maxLength<<endl;
//Finding the digitIndexes in input.
for(int i=0;i<inputLength;i++){
if(isdigit(word[i])){
digitIndex+=to_string(i);
}
}
digitLength=digitIndex.length();
cout<<"digit indexes : "<<digitIndex<<endl;
cout<<"digitindex[1] : "<<digitIndex[1]-'0'<<endl;
cout<<"your word : "<<word<<endl;
//Finding the connection starts
for(int i=0; i<inputLength;i++){
if(isdigit(word[i])==true){
starters+=word[i+1];
}
}
cout<<"starters : "<<starters<<endl;
}
Interesting problem. But easy to implement using std::algorithms and recursive calls. Also, the data structure selection may help to design such an application.
Unfortunately, the description of the input format is not fully clear. I understand the “steps” part, but there is no description for the rest of the digits. I assume that they are simply delimiters and have no further meaning.
We will split the big task into some subtasks. And, we will use a class Graph, where we store all needed data and functions.
So, obviously the first task is, to split the input string. For that we extract the first characters consisting of digits and convert them to the integer value “steps”. The rest of the string will be tokenized by using a C++ standard functionality: The std::sregex_token_iterator.
This thing is an iterator. For iterating over a string, hence “sregex”. The begin()/end() part defines, on what range of input we shall operate, then there is a std::regex for what should be matched / or what should not be matched in the input string. The type of matching strategy is given with last parameter.
1 --> give me the stuff that I defined in the regex and
-1 --> give me that what is NOT matched based on the regex
We can use this iterator for storing the tokens in a std::vector. The std::vector has a range constructor, which takes 2 iterators as parameter, and copies the data between the first iterator and 2nd iterator to the std::vector. The statement:
std::vector<std::string> split(std::sregex_token_iterator(init.begin(), init.end(), re, 1), {});
defines a variable “split” as a std::vector and uses the so called range-constructor of the std::vector.
You can see that I do not use the std::sregex_token_iterator’s "end()"-iterator for the std::vector explicitly.This iterator will be constructed from the empty brace-enclosed initializer list with the correct type, because it will be deduced to be the same as the type of the first argument due to the std::vector constructor requiring that.
Then we transform the resulting “from-to”-strings to our target storage. This is a std::map consisting of the “from” character as a key, and a std::vector of char’s (the targets) as the value. With that we always have an association from one start point (Vertex) to all end points and with that, implicitly the “Edges”. This data structure will span a virtual tree, which we can later traverse to find the required result.
We put all this in an “init” function, can call it from the class constructor and also from the overwritten extractor operator. I added the extractor as an additional functionality to make life easier. So you do not need to use the main-functions argc and argv, but can directly read from std::cin via:
Graph graph;
std::cin >> graph;
Now we have all data in our map and can start to build the solution. We will store all resulting “ways” in a std::vector of std::string. For building the “ways” we “track” the way through the “graph”. So, every time, when we see a new vertex, we add it to the “track” and if we reach the end of a route or if the route is longer than “steps”, we store the new track in “ways”.
So, the OP requested a special output format. To create this, we must use “Breadth First”- or “Level Order”-Traversal. Meaning, before we descent (with a simple recursive algorithm) we need to go horizontally, resulting in 2 for loops. But no problem. Very simple.
For a simple output functionality, I have overwritten the inserter operator.
And, I do validate the input (I allow also lower letters).
Please see the full working example below.
#include <iostream>
#include <vector>
#include <utility>
#include <regex>
#include <map>
#include <iterator>
#include <string>
#include <algorithm>
//std::string test{"5ABCD1BCD1CDE"};
std::regex re1(R"(([a-zA-Z]+))");
std::regex re2(R"(([0-9]+[a-zA-Z]+)+)");
using Map = std::map<char, std::vector<char>>;
class Graph {
public:
// Constructor
Graph() : steps(), fromTo(), root(), ways(), track() {}
Graph(const std::string input) : steps(), fromTo(), root(), ways(), track() { init(input); }
// Build the result
void build() { int level{ 0 }; rBuild(root, level); }
// inserter
friend std::ostream& operator << (std::ostream& os, const Graph& g) {
std::copy(g.ways.begin(), g.ways.end(), std::ostream_iterator<std::string>(os, " "));
return os << "\n";
}
// extractor
friend std::istream& operator >> (std::istream& is, Graph& g) {
if (std::string input{}; std::getline(is, input)) g.init(input);
return is;
}
private:
// Values derived from input
int steps{};
Map fromTo{};
char root{};
// The result
std::vector<std::string> ways{};
std::string track{};
// Recursive function to build all ways
void rBuild(const char vertex, int& level);
// Initialize source values
void init(const std::string& input);
};
void Graph::init(const std::string& input) {
fromTo.clear(), ways.clear(); track.clear(); steps = 0; root = '\0';
if (std::regex_match(input, re2)) {
// Get steps
size_t pos{}; steps = std::stoi(input, &pos); std::string init = input.substr(pos);
// Split string into substrings
std::vector<std::string> split(std::sregex_token_iterator(init.begin(), init.end(), re1, 1), {});
// Get root
root = split[0][0]; track += root; ways.push_back(track);
// Convert substrings to map entries
std::transform(split.begin(), split.end(), std::inserter(fromTo, fromTo.end()), [](std::string & s) {
return std::make_pair(s[0], std::vector<char>(std::next(s.begin()), s.end())); });
}
else
std::cerr << "\n***** Error: Wrong input format\n";
}
// Recursive function to build all ways through the graph
void Graph::rBuild(const char vertex, int& level) {
// Allow only a certain depth, while descencing down
if (level < steps-1) {
// Search the start point for this entry
if (Map::iterator node{ fromTo.find(vertex) }; node != fromTo.end()) {
// Go through all edges to just the next vertex. This is not a breadth first traversal
// So, first we will go horizontally
for (const char to : node->second) {
// We want to track the way that we were going so far
track.push_back(to);
// Saving this track as a new way
ways.push_back(track);
// Restoring the origninal track befor this way, so that we can generate the next way
track.pop_back();
}
// and now we will descent
for (const char to : node->second) {
// One level further down
++level;
// track will be one vertex longer
track.push_back(to);
// Recursive call, descent
rBuild(to, level);
// And backwards
track.pop_back();
--level;
}
}
}
}
int main(int argc, char* argv[]) {
if (argc == 2) {
std::string test = argv[1];
// Define and initialize the graph
Graph graph(test);
// Build the required strings
graph.build();
// Show result
std::cout << graph;
}
else {
std::cout << "\nEnter init string: \n";
if (Graph graph; std::cin >> graph) {
// Build the required strings
graph.build();
// Show result
std::cout << "\nResult:\n" << graph << "\n";
}
}
return 0;
}
What a pity that nobody will read that . . .

How to (elegantly) read a file whose columns have different types and store the columns appropriately?

I am learning about templates and wanted to solve the following tasks along the way: I would like to read a csv file whose columns have different types (string, int, etc.), store each column in a vector, and then access the vectors. Can somebody kindly point out how I can store the columns well?
For the time being, one example of a csv file the program might encounter looks like this:
first_column,second_column
int,string
1, line1
2, line2
The csv files will always have the column name in the first row, and the data types in the second row, followed by the actual data. However, the potential number of columns is unrestricted, as is its' ordering or its' types. Hence, another example might be
first_column,second_column,third_colum
string, double, string
foo, -19.8, mario
bar, 20.1, anna
Based on the second row, the program knows the datatype of the columns (it also knows the total number of columns from the first row) and it can allocate the appropriate memory.
I imagine the header file of the class solving the task looks as:
#include <fstream>
#include <string>
#include <vector>
class ColumnarCSV {
public:
ColumnarCSV(std::string filename) {read_data(filename);}
std::vector<std::string> get_names() { return column_names; }
std::vector<std::string> get_types() { return column_types; }
// pseudocode
template <typename T>
std::vector<T> get_column(std::string column_name) {
return column;
} //
private:
void read_data(std::string filename);
std::vector<std::string> column_names;
std::vector<std::string> column_types;
// storage for the columns;
};
The class ColumnarCSV is constructed with a string stating the location of the CSV file. Two public functions provide the column names and columns types, encoded in a vector<string>. The function get_column requires a column name and returns its' data. Please note that I do not know how to write this function. The return type can be different if necessary. Does somebody have an idea how to store the columns appropriately and the populate them at run-time depending on the column type?
What I tried so far:
Inheritance: I tried to work with a base class BaseColumn which contains a column name and datatype. The derived class template <typename T>ActualColumn: public BaseColumn contains the actual data. I wanted to access the data through a virtual function but learned that I cannot define virtual template functions.
Std:Variant: I was thinking of working with Std::variant and specify all possible types of the columns. However, I thought that there must be a way without resorting to c++17 innovations.
Create empty vector<vector<T>> for all contingencies: A brute-force idea would be to equip ColumnarCSV with member of vector<vector<T>> for all data types I can think of and populate them at run time. While this completed its' job, the code was very convoluted.
Is there a better way to solve define the class ColumnarCSV?
I think you are overcomplicating the problem. You do not really need templates and definitely you dont need inheritance or any form of type erasure when you always have a int and a string. If one row corresponds to one "entry" in the file, all you need is a
struct entry {
int id;
std::string x;
};
and an input operator
std::istream& operator>>(std::istream& in, entry& e) {
in >> e.id;
in >> e.x;
return in;
}
Now reading the entries is straightforward. To read a single line you do
std::ifstream file("file.name");
entry x;
file >> x;
I think you can store the data line by line as full std::string.
Knowing the types of the data, you will be able to easily convert the std::string into the real type (std::string, int, double, ...).For example, if you have a std::string that is a double in reality, you can use std::stod to convert it.
I have made an example to be more clear. Consider the following struct to handle the data:
typedef std::vector<std::string> StringVec;
struct FileData
{
StringVec col_names;
StringVec type_names;
StringVec data_lines;
bool loadData(const std::string & file_path);
bool getColumn(const std::string & col_name, StringVec & result);
};
The typedef is only here to simplify the code and make it more readable.
The method loadData() will read the file and store its content in the structure.col_names being the list of columns names, type_names being the list of types and data_lines being the list of the read lines.
The method getColumn() writes in the result argument the content of the desired column given in the col_name argument.
Those two methods return of boolean which indicates if the operation was successfully performed (true) or if an error occured (false).
loadData() may return false if the given file could not be opened or if it is corrupted.getColumn() may return false if the given column name does not exist.
A possible implementation of these methods could be:
#include <fstream>
// ========== ========== ========== ========== ==========
StringVec split(const std::string & s, char c)
{
StringVec splitted;
std::string word;
for(char ch : s)
{
if((ch == c) && (!word.empty()))
{
splitted.push_back(word);
word.clear();
}
else
word += ch;
}
if(!word.empty())
splitted.push_back(word);
return splitted;
}
void removeExtraSpaces(std::string & word)
{
while(!word.empty() && (word[0] == ' '))
word.erase(word.begin());
while(!word.empty() && (word[word.size()-1] == ' '))
word.erase(word.end()-1);
}
// ========== ========== ========== ========== ==========
bool FileData::loadData(const std::string & file_path)
{
bool success(false);
std::ifstream in_s(file_path);
if(in_s)
{
bool names_read(false);
bool types_read(false);
std::string line;
while(getline(in_s, line))
{
if(!names_read) // first line
{
col_names = split(line, ',');
if(col_names.empty())
return false; // FILE CORRUPTED
for(std::string & word : col_names)
removeExtraSpaces(word);
names_read = true;
}
else if(!types_read) // second line
{
type_names = split(line, ',');
if(type_names.size() != col_names.size())
{
col_names.clear();
type_names.clear();
return false; // FILE CORRUPTED
}
for(std::string & word : type_names)
removeExtraSpaces(word);
types_read = true;
}
else // other lines
{
if(split(line, ',').size() != col_names.size())
{
col_names.clear();
type_names.clear();
data_lines.clear();
return false; // FILE CORRUPTED
}
data_lines.push_back(line);
}
}
in_s.close();
success = true;
}
return success;
}
bool FileData::getColumn(const std::string & col_name, StringVec & result)
{
bool success(false);
bool contains(false);
size_t index(0);
while(!contains && (index < col_names.size()))
{
if(col_names[index] == col_name)
contains = true;
else
++index;
}
if(contains)
{
for(const std::string & line : data_lines)
{
std::string field(split(line, ',').at(index));
removeExtraSpaces(field);
result.push_back(field);
}
success = true;
}
return success;
}
// ========== ========== ========== ========== ==========
The functions split() and removeExtraSpaces() are defined to simplify the code (and make this example more readable).
From the user's side, this can be used as follows:
DataFile df;
bool loadSuccessful = df.loadData("data.txt"); // if true, df contains now the content of the file.
StringVec col;
bool columnFound = df.getColumn("col_name", col); // if true, col contains now the content of the desired column.
As you can see, very easy to use :)I know that at this point you have a vector of std::string but as the structure contains the names of the real type of each columns, you can convert what you got into the real type.Perhaps you can add a templated convert() method in the structure to make this imperceptible for the user.
I have made the tests with the following data files:
data.txt:
first_col, second_col
string, double
line1, 1.1
line2, -2.5
line3, 10.03
_other_data.txt:_
first_col, second_col, third_col
int, string, char
0, line1, a
5, line2, b
And it worked successfully for both.
I don't know if handling the data as std::string is elegant enough for you but I hope it can help you.

vtk value at x,y,z point

I have a vtk file which maps temperature in 3 dimensions. I would like to determine the temperature at a given x, y, z point. I will use the following code in order to load the vtk file (Reading .vtk file):
int main(int argc, char *argv[])
{
// simply set filename here (oh static joy)
std::string inputFilename = "setYourPathToVtkFileHere";
// Get all data from the file
vtkSmartPointer<vtkGenericDataObjectReader> reader =
vtkSmartPointer<vtkGenericDataObjectReader>::New();
reader->SetFileName(inputFilename.c_str());
reader->Update();
// All of the standard data types can be checked and obtained like this:
if (reader->IsFilePolyData())
{
std::cout << "output is a polydata" << std::endl;
vtkPolyData* output = reader->GetPolyDataOutput();
std::cout << "output has " << output->GetNumberOfPoints() << " points." << std::endl;
}
return EXIT_SUCCESS;
}
However, when searching through the extensive list of methods in the vtk library I cant find the appropriate function to extract a value at a specific location. Any suggestions?
The proper way to retrieve a scalar value at a given position depends on two questions:
How is your data laid out and
From which position do you want to retrieve an attribute
Concerning the data layout there are two main layouts:
Structured: The data resides in a uniform grid
Unstructured: The point samples are arbitrary
Concerning the position you can have two situations:
Query at a sample position: You ask for a point that is directly a sample in your dataset
Query at an arbitrary position: You ask for a point that is somewhere in your domain but does not neccessarily coincide with a sample of your data.
Independent of the data layout, to retrieve the data at a sample position (i.e. a sample of your original data set) you can use the vtkPointLocator class. Use the class as follows (untested):
// Build locator object
vtkSmartPointer<vtkPointLocator> locator = vtkPointLocator::New();
locator->SetDataSet(polyData);
locator->BuildLocator();
// Define query position
double pt[3] = {0.1, 0.2, 0.3};
// Get the ID of the point that is closest to the query position
vtkIdType id = locator->FindClosestPoint(pt);
// Retrieve the first attribute value from this point
double value = polyData->GetPointData()->GetScalars()->GetTuple(id, 0);
This will give you the point value of the closest sample of your data.
Note that this will not give you the explicit position of the point in the data set as this is implicitly encoded in the variable id. To retrieve the actual position of the closest point, you can write:
double *dataPt = polyData->GetPoint(id);
If you want to retrieve data at an arbitrary position of your domain you will need some way of interpolation.
Here, the data layout is important.
For structured data you can first convert your data to a vtkImage and then perform queries on it. If you want to retrieve an interpolated attribute using linear or cubic schemes you can add a vtkImageInterpolator in your filter chain and then retrieve a point using the GetScalarComponentAsDouble method.
For unstructured data you should first decide on an interpolation scheme. vtk has various filters to reconstruct continuous data from data samples. Options include Delaunay triangulation/tetrahedrization (vtkDelaunay2D, vtkDelaunay3D) , as well as the Shepard method (vtkShepardMethod). Either method will give you a new data set with possibilities to query arbitrary points. If you want to retrieve the scalar attributes for a (batch of) points without actually reconstructing a complete data set you can also look at the vtkProbeFilter.
You need to first extract the polyData from the reader. Then, store the points via vtkPolyData::getPoints into a vtksmartPointer<vtkPoints>.
To finish, create a std::vector of a custom struct and store them while iterating over your vtkPoints.
Here's some code to illustrate :
#include <vtkDataArray.h>
#include <vtkDataSet.h>
#include <vtkGenericDataObjectReader.h>
#include <vtkPointLocator.h>
#include <vtkPointData.h>
#include <vtkPolyData.h>
#include <vtkSmartPointer.h>
#include <vtkStructuredGrid.h>
#include <string>
struct Pnt {
double x_, y_, z_;
Pnt(double x, double y, double z) : x_(x), y_(y), z_(z) {}
};
int main ( int argc, char *argv[] )
{
// Ensure a filename was specified
if(argc != 2)
{
std::cerr << "Usage: " << argv[0] << " InputFilename" << endl;
return EXIT_FAILURE;
}
// simply set filename here (oh static joy)
std::string inputFilename = "setYourPathToVtkFileHere";
// Get all data from the file
vtkSmartPointer<vtkGenericDataObjectReader> reader = vtkSmartPointer<vtkGenericDataObjectReader>::New();
reader->SetFileName(inputFilename.c_str());
reader->Update();
vtkSmartPointer<vtkPolyData> polydata = reader->GetPolyDataOutput();
vtkSmartPointer<vtkPoints> vtk_points = polydata->GetPoints();
std::vector<Pnt> my_points;
for (int i = 0; i < vtk_points->GetNumberOfPoints(); i++){
const auto pnt = vtk_points->GetPoint(i);
my_points.emplace_back(pnt[0], pnt[1], pnt[2]);
}
return EXIT_SUCCESS;
}
And here is the version with the vtkPointLocator mentionned in QnD's answer :
int main(int argc, char *argv[])
{
// Ensure a filename was specified
if (argc != 2)
{
std::cerr << "Usage: " << argv[0] << " InputFilename" << endl;
return EXIT_FAILURE;
}
// simply set filename here (oh static joy)
std::string inputFilename = "setYourPathToVtkFileHere";
// Get all data from the file
vtkSmartPointer<vtkGenericDataObjectReader> reader = vtkSmartPointer<vtkGenericDataObjectReader>::New();
reader->SetFileName(inputFilename.c_str());
reader->Update();
vtkSmartPointer<vtkPolyData> polydata = reader->GetPolyDataOutput();
//Building locator
vtkSmartPointer<vtkPointLocator> locator = vtkPointLocator::New();
locator->SetDataSet(polydata);
locator->BuildLocator();
//Finding point
const double pt[3] = { 0.1, 0.2, 0.3 };
vtkIdType id = locator->FindClosestPoint(pt);
double pnt_found[3];
polydata->GetPointData()->GetScalars()->GetTuple(id, pnt_found);
return EXIT_SUCCESS;
}
And the CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
PROJECT(GenericDataObjectReader)
find_package(VTK REQUIRED)
include(${VTK_USE_FILE})
add_executable(GenericDataObjectReader MACOSX_BUNDLE GenericDataObjectReader)
if(VTK_LIBRARIES)
target_link_libraries(GenericDataObjectReader ${VTK_LIBRARIES})
else()
target_link_libraries(GenericDataObjectReader vtkHybrid vtkWidgets)
endif()
Please let me know if I need to do any specific action to correctly credit the other answer. I'm new and don't know all the specifics yet.