Parsing doubles and words in a string - c++

I was working on the following exercise from The C++ Programming Language:
Read a sequence of possibly whitespace-separated (name,value) pairs,
where the name is a single whitespace-separated word and the value is
an integer or a floating-point value. Compute and print the sum and
mean for each name and the sum and mean for all names.
For example, given:
hello world5.678popcorn 8.123 rock 123 hello world 8.761 popcorn 98 rock 1.9rock2.3
The output of my implementation is:
rock: Sum (127.2), Mean (42.4)
hello world: Sum (14.439), Mean (7.2195)
popcorn: Sum (106.123), Mean (53.0615)
My implementation:
#include <iostream>
#include <string>
#include <unordered_map>
std::unordered_map<std::string, double> pairs;
std::unordered_map<std::string, int> occurences;
void save(const std::string& name, const std::string& value);
void trim(std::string& s, const std::string& chars = " ");
int main() {
std::string line;
getline(std::cin, line);
std::string name;
std::string value;
bool name_saved;
for(char c : line) {
if(!name_saved && isdigit(c)) { // reached end of name
name_saved = true;
trim(name);
value += c;
} else if(!name_saved) { // add char to name
name += c;
} else if(name_saved) {
if(isdigit(c) || (c == '.' && (value.find_first_of(".") == std::string::npos))) { // add char to value
value += c;
} else { // reached end of value
trim(value);
save(name, value);
name = "";
value = "";
name_saved = false;
if(isalpha(c)) {
name += c;
}
}
}
}
if(value != "") {
save(name, value);
}
std::cout << "\n";
for(auto pair : pairs) {
std::cout << pair.first << ": " << "Sum (" << pair.second << "), Mean (" << (pair.second / occurences[pair.first]) << ")\n";
}
return 0;
}
void save(const std::string& name, const std::string& value) {
pairs[name] += std::stod(value);
occurences[name]++;
}
void trim(std::string& s, const std::string& chars) {
s.erase(0, s.find_first_not_of(chars));
s.erase(s.find_last_not_of(chars) + 1);
}
I was wondering what would a more efficient approach to this exercise be? I feel that my code is quite messy and I would like to get some input on what I could use to clean it up and make it more compact.

There are many many different soultions. It depends a little bit on your personal programming style and what you have learned already or not.
The above example cries for "regular expressions". You can read here in the Cpp- Reference about them. And especially the function std::regex_search will be your friend.
First to the regurlar expression. You are looking for a "text" with embedded spaces ("single whitespace-separated word "), followed by a int or float. This can be expressed easily with a "regex":
([a-zA-Z]+ ?[a-zA-Z]+) ?(\d+\.?\d*)
So, first we have 1 or more alpha-characters, then an optional space and then again 1 or more alpha-characters. This makes up the name.
For the value, we have 1 or more digits, maybe followed by a "." and maybe more digits.
You get a better understanding, if you paste the regex and the test string in some online regex-tester like this.
It will give you some more detailed explanation. Espcially for the brackets "()", which form groups.
An the groups can be extracted after a std::regex_search has found a match. Meaning: If std::regex_search will find a match for the given regex, it will return true and the resulting groups can be found in std::smatch, see here for a description.
And with all this, we can define a simple for loop to get all names and values from the test string.
for (std::string s{ test }; std::regex_search(s, sm, re); s = sm.suffix())
First we will initialize the "loop-run" variable, in this case a std::string and initialize it with the given test-string. Then we will search for a match in the test-string. If there was a match, then we will find the result in sm[1] and sm[2]. After we did all operations inside the loop body, we set the "loop-run" variable to the not yet matched rest of the test string. The suffix.
To goup, calculate and aggregate the values, we use a std::map. The key is the "name" and the value is a std::pair consisting of the count of names and the sum of the associated values. So, in the loop:
for (std::string s{ test }; std::regex_search(s, sm, re); s = sm.suffix()) {
// Count the occurences of a text
aggregator[sm[1]].first++;
// Sum up the values for a text
aggregator[sm[1]].second += std::stod(sm[2]);
}
we use aggregator[sm[1]] to create the name in or retrieve the name from map. In any case, we have then a reference to the current name entry, and we can increment the count and build the running sum.
This is a very simple 3 line approach, which does already nearly all the expected work.
The rest is simple calculation of expected sum and mean values and showing everything on the screen.
Please see the full code below:
#include <iostream>
#include <string>
#include <regex>
#include <vector>
#include <iterator>
#include <map>
// The regex for words with embedded space and floats/ints
const std::regex re{ R"(([a-zA-Z]+ ?[a-zA-Z]+) ?(\d+\.?\d*))" };
int main() {
// Definition Section --------------------------------------------------------------------------------
// The input test string
std::string test{"hello world5.678popcorn 8.123 rock 123 hello world 8.761 popcorn 98 rock 1.9rock2.3"};
// Here we will store the result. The text and the associated "count" and "sum"
std::map<std::string, std::pair<unsigned int, double>> aggregator{};
std::smatch sm;
// Find, store and calculate data -------------------------------------------------------------------
// Iterate though the string and get the text and the float value
for (std::string s{ test }; std::regex_search(s, sm, re); s = sm.suffix()) {
// Count the occurences of a text
aggregator[sm[1]].first++;
// Sum up the values for a text
aggregator[sm[1]].second += std::stod(sm[2]);
}
// Output data ----------------------------------------------------------------------------------------
// Since the task is to calculate also the overall results, we will do
unsigned int countOverall{};
double sumOverall{};
// Iterate over the "text" data and output sum and mean value per text and aggregate the overall values
for (const auto& [text, agg] : aggregator) {
// Output sum and mean per text
std::cout << "\n" << text << ": Sum (" << agg.second << "), Mean (" << agg.second / agg.first << ")";
// Aggregate overall values
countOverall += agg.first;
sumOverall += agg.second;
}
// Show overall result to the user.
std::cout << "\n\nSum overall: (" << sumOverall << "), Mean overall: (" << sumOverall / countOverall << ")\n\n";
return 0;
}
If this solution is "better" or not? Please decide yourself . . .

Related

C++ checking textbox value in application

I'm working on an app with a map and do not want a person to be able to enter an invalid point on the map (think Latitude and longitude).
Say you have a panel that looks something like this. Note not codewise how it looks, just how it would look like if you looked at the application:
[My great application]
Textbox1 [XX:XX:XX]
Textbox2 [XX:XX:XX]
Textbox3 [XX:XX:XX]
Pushbutton [APPLY]
My main problem
I have no way of knowing if what someone enters is right in the text boxes. My TextBox is expecting 3 sets integers [XX:XX:XX]. Nothing below 00, and nothing above 99 (strictly 2 digits). For this example I will only show on int to keep it short:
int myInt = NULL; //Don't want garbage data here
bool isValid = false;
//Gets input and sets textbox to myInt
std::string myString = std::to_string(myInt); //Cast int to a string
If(myString.length() - 1 == 0) //NULL intToString.length() == 1?
{
std::cout << "Incorrect, please try again" << std::endl;
}
else
{
isValid = true;
}
if(isValid)
{
//applyChanges(); <-- MY INTS WOULD GO INTO THIS
}
then I quickly realized that 0-9 are totally valid. (00, 01, ..., 09). So this wouldn't work.
My question
How am I able to test if what a user inputs is correct? It's not quite making sense to me. I'm not sure what else I can do besides test the length.
All in all I want to find a way to get a condition so that when all is well THEN isValid = true;
Notes
I can directly access all the data in the textboxes
I have the ability to check for gaining or loss of focus
Here's a solution I came up with. Let me know if I misunderstood your problem.
The algorithm is simple:
Splits the input into 3 parts;
Make sure each is 2 digits long;
Convert to an integer and make sure its within range;
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
// Simple string split algorithm
std::vector<std::string> string_split(
std::string const& str,
char delimiter)
{
std::istringstream ss{str};
std::vector<std::string> result;
std::string line;
while(std::getline(ss, line, delimiter)) {
result.emplace_back(std::move(line));
}
return result;
}
// Validates the input
bool isValid(std::string const& input) {
// Split string on each ':'
// 00:00:00
// ^ ^
auto substr = string_split(input, ':');
// If there are not 3 parts, its malformed
if(substr.size() != 3)
return false;
// For reach result...
for(auto const& str : substr) {
// If it is not 2 digits long, error.
if(str.size() != 2)
return false;
try {
// Try converting it to an integer
// This throws on failure
auto value = std::stoi(str);
// make sure its within range
return value >= 0 && value <= 99;
} catch(...) {
// Failed to convert
return false;
}
}
return true;
}
int main()
{
// Some tests
std::vector<std::string> inputs{
"00:00:00",
"99:99:99",
"1:00:00",
"AA:00:00",
"0B:0000",
"0B:00c00",
};
// Run tests and print result
for(auto& input : inputs) {
if(isValid(input)) {
std::cout << input << " - Valid!"<< std::endl;
} else {
std::cout << input << " - Invalid!"<< std::endl;
}
}
}
Results:
$g++ -o main *.cpp
$main
00:00:00 - Valid!
99:99:99 - Valid!
1:00:00 - Invalid!
AA:00:00 - Invalid!
0B:0000 - Invalid!
0B:00c00 - Invalid!
Hope this helps,
Cheers.
I agree with markhc but you might also like using a regular expression way of doing things. The advantage is that the same code can be used to validate other type of input also and of course is much shorter.
Here is the code (parts stolen from markhc!):
#include <regex>
#include <string>
#include <iostream>
using namespace std;
int main()
{
string regx = R"([0-9]{2}:[0-9]{2}:[0-9]{2})";
smatch matches;
// some test data
std::vector<std::string> inputs{
"00:00:00",
"99:99:99",
"1:00:00",
"AA:00:00",
"0B:0000",
"0B:00c00",
};
// Run tests and print result
for (auto& input : inputs) {
if (regex_search(input, matches, regex(regx)))
{
std::cout << input << " - Valid!" << std::endl;
}
else {
std::cout << input << " - Invalid!" << std::endl;
}
}
}
This also produces as output:
00:00:00 - Valid!
99:99:99 - Valid!
1:00:00 - Invalid!
AA:00:00 - Invalid!
0B:0000 - Invalid!
0B:00c00 - Invalid!
The regular expression [0-9]{2}:[0-9]{2}:[0-9]{2} is explained below.
[0-9] means digit 0-9
{2} means 2 times
: means the separator you need
To show how flexible the regular expressions are:
..If you wanted a different format like 000:0000 for example you just have to change the regex to
[0-9]{3}:[0-9]{4} and the code remains the same.

How do I look for a word in a string of randomly generated letters i C++?

I'm trying to make a program that contiues to randomly generate a string of letters, but stop when it has generated a word the user have entered.
I have made it generate the letters but I don't know how to make it recognize a word from it.
for (int i = 1; i < 1000; i++) {
int n = rand() % 26;
char c = (char)(n + 65);
cout << c;
}
return 0;
I'm gonna change the for loop to a while loop when I know how to make it find the users input.
I'm very new to programming so the solution is most likely obvious.
As one of the comments suggests, you need to create a string from your chars. After that, I would suggest looking at:
http://www.cplusplus.com/reference/string/string/find/
its a search function for strings used on other strings... which is exactly what you're looking for.
One of the comments also suggest using == to compare the string you created from chars and the user input string, but there isn't much use in doing it this way when the string::find function does the exact same thing but more efficently
A Modern C++ solution.
The interesting part in the main function which is below.
#include <random>
#include <iostream>
#include <algorithm>
#include <list>
#include <sstream>
void NotFoundMessage(std::list<char>& randomSequence);
void FoundMessage(long long iterationCount);
// Seed with a real random value, if available
std::random_device r;
std::default_random_engine e1(r());
// A random character between 'A' and 'Z'
std::uniform_int_distribution<int> uniform_dist('A', 'Z');
char nextRandomCharacter()
{
return static_cast<char>(uniform_dist(e1));
}
int main()
{
std::string input;
std::cin >> input;
// <--- NEEDS CHECKS IF INPUT IS CORRECT!!!!
std::list< char > randomSequence;
// Fill randomSequence with initial data
for ( const auto& c : input )
{
randomSequence.push_back( nextRandomCharacter() );
}
long long iterationCount = 1;
while ( !std::equal( input.begin(), input.end(),
randomSequence.begin() ) )
{
NotFoundMessage( randomSequence );
// remove character from front and add random char at end.
randomSequence.pop_front();
randomSequence.push_back( nextRandomCharacter() );
iterationCount++;
}
FoundMessage(iterationCount);
}
void NotFoundMessage(std::list<char>& randomSequence)
{
std::cout << "Not found in: ";
for ( const auto& c : randomSequence )
std::cout << c << ' ';
std::cout << '\n';
}
void FoundMessage(long long iterationCount)
{
std::cout << "Found after "
<< iterationCount
<< " iterations."
<< std::endl;
}

Get every regex match one by one with their positions

I need to get all regex matches and their positions.
For example, I have this regex:
std::regex r("(a)|(b)|(c)");
And this input text:
std::string text("abcab");
Now I want to loop the matches there in every loop I can access all occurrences from one match. So in first loop I could get "a" at position 0 and "a" at position 3. In second loop it'd be "b" at 1 and "b" at 4. And in third loop it'd be "c" at position 2. How can I do this?
Currently I have every regex part separately (regex for (a), (b) and (c)) and go through them one by one. But there are quite many of them so I'm looking for better/faster solution.
You can declare string vectors to store the captured values in, and then check which alternative branch matched, and add it to the corresponding vector.
Here is a C++ demo:
#include <string>
#include <iostream>
#include <regex>
using namespace std;
int main() {
std::regex r("(a)|(b)|(c)");
std::string s = "abcab";
std::vector<std::string> astrings; // Declare the vectors to
std::vector<std::string> bstrings; // populate with the contents
std::vector<std::string> cstrings; // of capturing groups
for(std::sregex_iterator i = std::sregex_iterator(s.begin(), s.end(), r);
i != std::sregex_iterator();
++i)
{
std::smatch m = *i;
if (m[1].matched) { // Check if Group 1 matched and
astrings.push_back(m[1].str()); // Put a value into a string vector
}
else if (m[2].matched) { // Check if Group 2 matched and
bstrings.push_back(m[2].str()); // Put a value into b string vector
}
else if (m[3].matched) { // Check if Group 3 matched and
cstrings.push_back(m[3].str()); // Put a value into c string vector
}
}
// Printing vectors - DEMO
for (auto i: astrings)
std::cout << i << ' ';
std::cout << "\n";
for (auto i: bstrings)
std::cout << i << ' ';
std::cout << "\n";
for (auto i: cstrings)
std::cout << i << ' ';
return 0;
}
You may also consider using std::regex_constants::optimize flag when declaring the regexp (see Galik's comment).

How to compare two arrays and return non matching values in C++

I would like to parse through two vectors of strings and find the strings that match each other and the ones that do not.
Example of what I want get:
input vector 1 would look like: [string1, string2, string3]
input vector 2 would look like: [string2, string3, string4]
Ideal output:
string1: No Match
string2: Match
string3: Match
string4: No Match
At the moment I use this code:
vector<string> function(vector<string> sequences, vector<string> second_sequences){
for(vector<string>::size_type i = 0; i != sequences.size(); i++) {
for(vector<string>::size_type j = 0; j != second_sequences.size(); j++){
if (sequences[i] == second_sequences[j]){
cout << "Match: " << sequences[i];
}else{
cout << "No Match: " << sequences[i];
cout << "No Match: " << second_sequences[j];
}
}
}
}
It works great for the ones that match, but iterates over everything so many times,
and the ones that do not match get printed a large number of times.
How can I improve this?
The best code is the code that you did not have to write.
If you take a (STL) map container it will take care for you of sorting and memorizing the different strings you encounter.
So let the container works for us.
I propose a small code quickly written. You need for this syntax to enable at least the C++ 2011 option of your compiler ( -std=c++11 on gcc for example ). The syntax that should be used before C++11 is much more verbose (but should be known from a scholar point of view ).
You have only a single loop.
This is only a hint for you ( my code does not take into account that in the second vector string4 could be present more than once, but I let you arrange it to your exact needs)
#include <iostream>
#include <vector>
#include <string>
#include <map>
using namespace std;
vector<string> v1 { "string1","string2","string3"};
vector<string> v2 { "string2","string3","string4"};
//ordered map will take care of "alphabetical" ordering
//The key are the strings
//the value is a counter ( or could be any object of your own
//containing more information )
map<string,int> my_map;
int main()
{
cout << "Hello world!" << endl;
//The first vector feeds the map before comparison with
//The second vector
for ( const auto & cstr_ref:v1)
my_map[cstr_ref] = 0;
//We will look into the second vector ( it could also be the third,
//the fourth... )
for ( const auto & cstr_ref:v2)
{
auto iterpair = my_map.equal_range(cstr_ref);
if ( my_map.end() != iterpair.first )
{
//if the element already exist we increment the counter
iterpair.first->second += 1;
}
else
{
//otherwise we put the string inside the map
my_map[cstr_ref] = 0;
}
}
for ( const auto & map_iter: my_map)
{
if ( 0 < map_iter.second )
{
cout << "Match :";
}
else
{
cout << "No Match :" ;
}
cout << map_iter.first << endl;
}
return 0;
}
Output:
No Match :string1
Match :string2
Match :string3
No Match :string4
std::sort(std::begin(v1), std::end(v1));
std::sort(std::begin(v2), std::end(v2));
std::vector<std::string> common_elements;
std::set_intersection(std::begin(v1), std::end(v1)
, std::begin(v2), std::end(v2)
, std::back_inserter(common_elements));
for(auto const& s : common_elements)
{
std::cout<<s<<std::endl;
}

C++ code for taking substrings

I have a string as "1.0.0" and I want to extract the "1", "0", and "0". If the last zero is not present, the string must store 0 by default:
verstr.substr(0,verstr.find(".");
The above statement can find the first digit that is "1", however, I am not able to think of a solution for extracting the remainder of the string.
After this i convert it to a long as:
va = atol(verstr.substr(0,verstr.find(".")).c_str());
so i want the "1" in va , 0 in "vb" and so on
Thanks.
C++11 solution:
#include <iostream>
#include <string>
#include <regex>
using namespace std;
int main(int, char **) {
string version("1.2.3");
match_results<string::const_iterator> m;
regex re("([0-9]+)\\.([0-9]+)(\\.([0-9]+))?");
if (regex_match(version, m, re)) {
int major = stoi(m[1].str()),
minor = stoi(m[2].str()),
rev = stoi(m[4].str().length() == 0 ? 0 : m[4].str());
cout << "major: " << major << endl;
cout << "minor: " << minor << endl;
cout << "rev: " << rev << endl;
} else {
cout << "no match\n";
}
}
The regular expression used is ([0-9]+)\.([0-9]+)(\.([0-9]+))? and breaks down as follows:
[0-9]+ matches one or more digits
\. matches a literal dot.
? following the last expression indicates that it is optional
Expressions wrapped in ( and ) are capture groups. There are five capture groups in this expression:
0 - always matches the entire string - we don't use this.
1 - matches the major version number.
2 - matches the minor version number.
3 - matches a dot followed by the revision number - we don't use this but it is necessary because we use the parentheses followed by a ? to make this whole group optional.
4 - matches the revision number.
Not sure if I understand what you need, if you want to retrieve the digits as strings, with a minimum of x digits, you can do something like this.
vector<string> GetVersion(const string &strInput, int iMinSize)
{
vector<string> vRetValue;
std::stringstream ss(strInput);
string strItem;
while(std::getline(ss, strItem, '.'))
vRetValue.push_back(strItem);
while(vRetValue.size() < iMinSize)
vRetValue.push_back("0");
return vRetValue;
}
int _tmain(int argc, _TCHAR* argv[])
{
vector<string> vRetValue = GetVersion("1.0", 3);
return 0;
}
A possibility would to use std::sscanf(). It is simple to use and provides a level of error checking with relatively few lines of code:
#include <iostream>
#include <string>
#include <cstdio>
int main()
{
std::string input[] = { "1.0.7", "1.0.", "1.0", "1.", "1" };
for (size_t i = 0; i < sizeof(input)/sizeof(input[0]); i++)
{
std::cout << input[i] << ": ";
// Init to zero.
int parts[3] = { 0 };
// sscanf() returns number of assignments made.
if (std::sscanf(input[i].c_str(),
"%d.%d.%d",
&parts[0],
&parts[1],
&parts[2]) >= 2)
{
// OK, the string contained at least two digits.
std::cout << parts[0]
<< ","
<< parts[1]
<< ","
<< parts[2]
<< "\n";
}
else
{
std::cout << "bad format\n";
}
}
return 0;
}
Output:
1.0.7: 1,0,7
1.0.: 1,0,0
1.0: 1,0,0
1.: bad format
1: bad format
See online demo: http://ideone.com/0Ox9b .
find and substr are two really nice family of function overloads that are pretty well suited to many simple parsing problems, especially when your syntax checking only needs to be loose.
To extract multiple scalars out of your version vector, store the found index somewhere:
const auto a = verstr.find('.');
const std::string major = verstr.substr(0, a);
Then re-use it with one of the overloads of string::find, saying start searching at one after a:
const auto b = verstr.find ('.', a+1);
const std::string minor = verstr.substr(a+1, b);
And so forth.
If you need a syntax check, compare the returned indices against string::npos:
const auto a = verstr.find('.');
if (std::string::npos == a)
.... bad syntax ....
Pastebin style version of this answer:
#include <string>
#include <stdexcept>
#include <iostream>
struct Version
{
std::string Major, Minor, Patch;
Version(std::string const &Major)
: Major(Major), Minor("0"), Patch("0")
{}
Version(std::string const &Major, std::string const &Minor)
: Major(Major), Minor(Minor), Patch("0")
{}
Version(std::string const &Major, std::string const &Minor, std::string const &Patch)
: Major(Major), Minor(Minor), Patch(Patch)
{}
};
std::ostream& operator<< (std::ostream &os, Version const &v)
{
return os << v.Major << '.' << v.Minor << '.' << v.Patch;
}
Version parse (std::string const &verstr) {
if (verstr.empty()) throw std::invalid_argument("bad syntax");
const auto first_dot = verstr.find('.');
if (first_dot == std::string::npos)
return Version(verstr);
const auto second_dot = verstr.find('.', first_dot+1);
if (second_dot == std::string::npos)
return Version(verstr.substr(0, first_dot),
verstr.substr(first_dot+1, second_dot));
return Version(verstr.substr(0, first_dot),
verstr.substr(first_dot+1, second_dot),
verstr.substr(second_dot+1));
}
and then
int main () {
std::cout << parse("1.0") << '\n'
<< parse("1.0.4+Patches(55,322)") << '\n'
<< parse("1") << '\n';
parse(""); // expected to throw
}
try something like this instead of solution below the line
string s = "1.0.0";
string delimiters = ".";
size_t current;
size_t next = -1;
do
{
current = next + 1;
next = s.find_first_of( delimiters, current );
string current_substring = s.substr( current, next - current ); // here you have the substring
}
while (next != string::npos);
Ok, please don't use this solution below, if you really don't know what you're doing, according to discussion below this answer with #DavidSchwartz
Take a look at function strtok http://www.cplusplus.com/reference/clibrary/cstring/strtok/
char str[] = "1.0.0";
char * pch;
pch = strtok (str,".");
while (pch != NULL)
{
printf ("%s\n",pch);
pch = strtok (NULL, ".");
}
Take a look at Boost libraries, specifically String Algo.
Standard library support for string manipulation is somewhat limited in C++. And reinventing the wheel is just plain bad.
Update:
I was asked in comments why I consider all find/substr based solutions bad style.
I'll try my best.
As questions does not states otherwise, performance is not a question here. Maintainability and readability are much more important. All solutions proposed here tightly tie split algorithm semantics with a specific version parsing algorithm semantics. This hurts both.
This hurts maintainability, because when you will need to change version format, it will involve changing the very same block of code that implements splitting, making it more error-prone. Same applies to unit-tests.
This hurts readability, because due to mixed semantics I can't at once guess an intent behind this block of code. For example, when I am looking up parse algorithm to check how missing 3d version argument is handled, I'd better not waste my time digging through split implementation details.
If parsing pattern would have been slightly more difficult, I'd have advised regular expressions. But in this case splitting string by a delimiter is an action generic and often used enough to justify having it as a separate function.
if it's only simple char comparison in a small string...
char[] should not be so bad... and c functions should work... (EDIT: for some, its a blasphemy... a lot of C++ method use char* whether it's const or not).
why use an object if it's to has the same functionality with more memory to be used, and more time for the process to spend?
EDIT:
I saw that some answer suppose to create a lot of string object... i don't khnow if it's really the best way...
a little 2 line recursive C like function can do that without gasping a lot.
In c++ code I probably would do that with string object, as it's negligible gasp... but just to say it so.
In string object i would use the length property to get the last char first (with [] operator, or appropriate method).
then just need to get the two elements (in a loop, or with 2 back reference in an object accepting regex (which is less efficient))