I have an interface where I'm getting a file and the file name is the time stamp when the internal data was valid. I'm processing the files in order and providing them to another API which takes the time as milliseconds since Jan 1st, 1970.
So, I get the file name in as a string in the format of YYYYMMDD_HHmmSS.sss and have to turn this into time. I assume the processing and collection occurred in the same timezone and such and simply have to convert the characters to digits.
uint64_t foo::convertFileNameToTimestamp(const std::string& filename) const
{
const uint64_t yyyy = atoi(filename.substr(0,4).c_str());
const uint64_t MM = atoi(filename.substr(4,2).c_str());
const uint64_t DD = atoi(filename.substr(6,2).c_str());
const uint64_t HH = atoi(filename.substr(9,2).c_str());
const uint64_t MI = atoi(filename.substr(11,2).c_str());
const uint64_t SS = atoi(filename.substr(13,2).c_str());
const uint64_t sss = atoi(filename.substr(16,3).c_str());
// number of milliseconds in a day
const uint64_t DAY = 24 * 60 * 60 * 1000;
int MD = 0;
if (MM == 2) MD = 31; // currently feb, so add all of january's days
else if (MM == 3) MD = 31+28; // ...
else if (MM == 4) MD = 31+28+31;
else if (MM == 5) MD = 31+28+31+30;
else if (MM == 6) MD = 31+28+31+30+31;
else if (MM == 7) MD = 31+28+31+30+31+30;
else if (MM == 8) MD = 31+28+31+30+31+30+31;
else if (MM == 9) MD = 31+28+31+30+31+30+31+31;
else if (MM == 10) MD = 31+28+31+30+31+30+31+31+30;
else if (MM == 11) MD = 31+28+31+30+31+30+31+31+30+31;
else if (MM == 12) MD = 31+28+31+30+31+30+31+31+30+31+30;
// year 2000 wasn't a leap year
uint64_t YLD = ((yyyy-1970) / 4);
if (yyyy > 2000) --YLD;
uint64_t temp = sss;
temp += SS * 1000;
temp += MI * 60 * 1000;
temp += HH * 60 * 60 * 1000;
temp += (DD-1) * DAY;
temp += (MD) * DAY;
temp += (yyyy-1970) * 365 * DAY + YLD*DAY;
return temp;
}
Obviously, reinventing the wheel here. Seems like there should be some sort of function for this. Also.. how do I account for leap seconds? It was annoying enough to deal with leap days. The time stamps are all from 2015 and beyond and always will be, but I don't think I can just blindly add 26 seconds. Eventually we will have 27 or back up to 25. In previous functions I have validated the string to be the right format and the file to exist and all that jazz. I'm running on windows 8.1 compilation for 64 bit using VS 2010.
I've looked at Convert Epoch Time string to Time which suggested ctime(), but it doesn't seem to deal with milliseconds in the constructor or even any of the get methods and it doesn't accept generically formatted string inputs. I'm assuming I've got to call some time classes CTOR which will accept the file name string and then call some accessor on it to get the millisecond time since 1970 including leap seconds and such.
I am not using boost and don't have access/permission to use it.
Here is an answer that will work on any platform that supports C++11 or C++14. It builds off of the std::chrono library that was introduced in C++11. It also uses a free, open source, cross platform library to simplify the arithmetic (MIT license which is usually considered lawyer-friendly).
If you don't need to take leap seconds into account, you can use this date library, and it will look like this:
#include <string>
#include "date.h"
using time_stamp = std::chrono::time_point<std::chrono::system_clock,
std::chrono::milliseconds>;
time_stamp
convertFileNameToTimestamp(const std::string& filename)
{
using namespace std::chrono;
using namespace date;
const uint64_t yyyy = atoi(filename.substr(0,4).c_str());
const uint64_t MM = atoi(filename.substr(4,2).c_str());
const uint64_t DD = atoi(filename.substr(6,2).c_str());
const uint64_t HH = atoi(filename.substr(9,2).c_str());
const uint64_t MI = atoi(filename.substr(11,2).c_str());
const uint64_t SS = atoi(filename.substr(13,2).c_str());
const uint64_t sss = atoi(filename.substr(16,3).c_str());
return sys_days{year(yyyy)/MM/DD}
+ hours{HH} + minutes{MI} + seconds{SS} + milliseconds{sss};
}
After parsing the numbers out of the filename, it is very simple to create a type-safe std::chrono::time_point which simply holds an integral number of milliseconds since 1970-01-01 (as an int64_t).
If you want to take leap seconds into account, you need this higher-level library which is a complete parser of the IANA timezone database. You will also need to keep an updated copy of the IANA timezone database downloaded for my timezone/leap-second library to parse. But once set up, the source code for your converter is very similar to that above, and nearly as simple:
#include <string>
#include "tz.h"
using time_stamp_ls = std::chrono::time_point<date::utc_clock,
std::chrono::milliseconds>;
time_stamp_ls
convertFileNameToTimestamp_ls(const std::string& filename)
{
using namespace std::chrono;
using namespace date;
const uint64_t yyyy = atoi(filename.substr(0,4).c_str());
const uint64_t MM = atoi(filename.substr(4,2).c_str());
const uint64_t DD = atoi(filename.substr(6,2).c_str());
const uint64_t HH = atoi(filename.substr(9,2).c_str());
const uint64_t MI = atoi(filename.substr(11,2).c_str());
const uint64_t SS = atoi(filename.substr(13,2).c_str());
const uint64_t sss = atoi(filename.substr(16,3).c_str());
return utc_clock::sys_to_utc(sys_days{year(yyyy)/MM/DD}
+ hours{HH} + minutes{MI} + seconds{SS} + milliseconds{sss});
}
Both of these functions can be exercised with the following HelloWorld:
#include <iostream>
int
main()
{
std::string filename = "20150830_002120.123";
std::cout << convertFileNameToTimestamp (filename).time_since_epoch().count() << '\n';
std::cout << convertFileNameToTimestamp_ls(filename).time_since_epoch().count() << '\n';
}
which outputs:
1440894080123
1440894106123
Note that these timestamps are exactly 26,000ms apart.
Update
The "tz.h" header now includes a parse function which makes writing these functions much easier:
date::sys_time<std::chrono::milliseconds>
convertFileNameToTimestamp(const std::string& filename)
{
using namespace std::chrono;
using namespace date;
std::istringstream in{filename};
sys_time<milliseconds> tp;
parse(in, "%Y%m%d_%H%M%S", tp);
return tp;
}
date::utc_time<std::chrono::milliseconds>
convertFileNameToTimestamp_ls(const std::string& filename)
{
return date::to_utc_time(convertFileNameToTimestamp(filename));
}
You can use this piece of code (you don't have to worry about leap years and all the related stuff).
#Edit1: Modified the code to take leap seconds into account; also restructured it into a class:
Foo.h:
#ifndef __FOO__H__
#define __FOO__H__
#include <string>
#include <Windows.h>
#include <stdint.h>
class CFoo {
private:
const static int kLeapSecsDim = 26;
static uint64_t msecsBetweenEpochs;
static SYSTEMTIME leapSecs[kLeapSecsDim];
ULARGE_INTEGER leapSecsUi[kLeapSecsDim];
int CFoo::getLeapSeconds(ULARGE_INTEGER ui) const;
public:
CFoo();
~CFoo() {};
uint64_t toEpoch(const std::string& filename) const;
};
#endif //__FOO__H__
Foo.cpp:
#include "Foo.h"
uint64_t CFoo::msecsBetweenEpochs = 11644473600000; /* Milliseconds between 1.1.1601 and 1.1.1970 */
SYSTEMTIME CFoo::leapSecs[CFoo::kLeapSecsDim] =
{{1972, 06, 0, 30, 23, 59, 59, 999},
{1972, 12, 0, 31, 23, 59, 59, 999},
{1973, 12, 0, 31, 23, 59, 59, 999},
{1974, 12, 0, 31, 23, 59, 59, 999},
{1975, 12, 0, 31, 23, 59, 59, 999},
{1976, 12, 0, 31, 23, 59, 59, 999},
{1977, 12, 0, 31, 23, 59, 59, 999},
{1978, 12, 0, 31, 23, 59, 59, 999},
{1979, 12, 0, 31, 23, 59, 59, 999},
{1981, 06, 0, 30, 23, 59, 59, 999},
{1982, 06, 0, 30, 23, 59, 59, 999},
{1983, 06, 0, 30, 23, 59, 59, 999},
{1985, 06, 0, 30, 23, 59, 59, 999},
{1987, 12, 0, 31, 23, 59, 59, 999},
{1989, 12, 0, 31, 23, 59, 59, 999},
{1990, 12, 0, 31, 23, 59, 59, 999},
{1992, 06, 0, 30, 23, 59, 59, 999},
{1993, 06, 0, 30, 23, 59, 59, 999},
{1994, 06, 0, 30, 23, 59, 59, 999},
{1995, 12, 0, 31, 23, 59, 59, 999},
{1997, 06, 0, 30, 23, 59, 59, 999},
{1998, 12, 0, 31, 23, 59, 59, 999},
{2005, 12, 0, 31, 23, 59, 59, 999},
{2008, 12, 0, 31, 23, 59, 59, 999},
{2012, 06, 0, 30, 23, 59, 59, 999},
{2015, 06, 0, 30, 23, 59, 59, 999},
};
int CFoo::getLeapSeconds(ULARGE_INTEGER ui) const {
int ret = 0;
for (int i = 0; i < kLeapSecsDim; i++) {
if (ui.QuadPart <= this->leapSecsUi[i].QuadPart)
break;
ret++;
}
return ret;
}
CFoo::CFoo() {
FILETIME ft;
BOOL res;
for (int i = 0; i < this->kLeapSecsDim; i++) {
res = SystemTimeToFileTime(&(this->leapSecs[i]), &ft);
if (res == FALSE)
throw std::exception("SystemTimeToFileTime error", GetLastError());
this->leapSecsUi[i].LowPart = ft.dwLowDateTime;
this->leapSecsUi[i].HighPart = ft.dwHighDateTime;
}
}
uint64_t CFoo::toEpoch(const std::string& filename) const {
SYSTEMTIME st;
FILETIME ft;
ULARGE_INTEGER ui;
st.wYear = atoi(filename.substr(0, 4).c_str());
st.wMonth = atoi(filename.substr(4, 2).c_str());
st.wDay = atoi(filename.substr(6, 2).c_str());
st.wHour = atoi(filename.substr(9, 2).c_str());
st.wMinute = atoi(filename.substr(11, 2).c_str());
st.wSecond = atoi(filename.substr(13, 2).c_str());
st.wMilliseconds = atoi(filename.substr(16, 3).c_str());
BOOL result = SystemTimeToFileTime(&st, &ft);
if (result == FALSE)
throw std::exception("SystemTimeToFileTime error", GetLastError());
ui.HighPart = ft.dwHighDateTime;
ui.LowPart = ft.dwLowDateTime;
//printf("%016I64X - %I64u\n", ui.QuadPart, ui.QuadPart);
//printf("%016I64X - %I64u\n", ui.QuadPart/10000, ui.QuadPart/10000);
return (ui.QuadPart / 10000) - this->msecsBetweenEpochs + this->getLeapSeconds(ui) * 1000;
}
Notes:
For invalid dates/times SystemTimeToFileTime will fail
The constant CFoo::msecsBetweenEpochs I think it can be found on Google; I took it from Python(2.7.10)'s posixmodule.c (actually there's the seconds number; I only had to multiply it by 1000)
Your implementation yields results that are not very accurate (I used http://www.epochconverter.com for reference).
According to SystemTimeToFileTime, the timestamp is in UTC.
Related
Does C++17 (or earlier but not c++20) allow this?
I need a type_traited conditional class, like a bitset with an internal 32 or 64 unsigned integer storage, depending if the template argument N is lesser then 32 or greater (please forget about more than 64 bits).
But the constraint is to finally implement two and only two classes for all possible cases. Next source code defines the problem using static and running time asserts:
Coliru link: http://coliru.stacked-crooked.com/a/d53a5b00bd828fb5
#include <cassert>
#include <iostream>
#include <type_traits>
struct bitset32
{
bitset32() : bits(0) { }
bitset32(int _bits) : bits(_bits) { }
const int bits;
uint32_t value;
};
struct bitset64
{
bitset64() : bits(0) { }
bitset64(int _bits) : bits(_bits) { }
const int bits;
uint64_t value;
};
template <int N>
using bitset = std::conditional_t<(N<=32), bitset32, bitset64>;
int main ()
{
static_assert(std::is_same<bitset<1>, bitset<2>>::value);
static_assert(std::is_same<bitset<33>, bitset<34>>::value);
static_assert(!std::is_same<bitset<1>, bitset<33>>::value);
bitset<1> var1;
bitset<15> var2;
bitset<32> var3;
bitset<64> var4;
assert(var1.bits == 1);
assert(var2.bits == 15);
assert(var3.bits == 32);
assert(var4.bits == 64);
}
Any solution is welcomend even if it changes the basic types and uses inheritance or whatever other mechanism necessary, but please, do not offer using a template function returning an object in the style of template<int N> make_bitset { return Bitset<N>(N); } because it is needed to implement variables using this constructo Bitset<N> variable_name.
A new hypothesis, CTAD based
#include <cstdint>
#include <iostream>
#include <type_traits>
template <std::size_t>
struct BitSet;
template <>
struct BitSet<32u>
{
template <typename ... Ts>
BitSet (Ts...) {}
std::uint32_t value;
};
template <>
struct BitSet<64u>
{
template <typename ... Ts>
BitSet (Ts...) {}
std::uint64_t value;
};
template <typename ... Ts>
BitSet (Ts...) -> BitSet<(32 < sizeof...(Ts) ? 64u : 32u)>;
int main()
{
auto b1 = BitSet{1};
auto b2 = BitSet{2};
auto b3 = BitSet{0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 33, 34, 35, 36, 37, 38, 39};
auto b4 = BitSet{0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 44, 45, 46, 47, 48, 49};
static_assert( std::is_same_v<decltype(b1), decltype(b2)> );
static_assert( std::is_same_v<decltype(b1), BitSet<32u>> );
static_assert( std::is_same_v<decltype(b3), decltype(b4)> );
static_assert( std::is_same_v<decltype(b3), BitSet<64u>> );
}
You can just make bitset32 and bitset64 templates like
template <std::size_t BitsUsed>
struct bitset32
{
bitset32() : bits(BitsUsed) { }
bitset32(int _bits) : bits(_bits) { }
const int bits;
uint32_t value;
};
template <std::size_t BitsUsed>
struct bitset64
{
bitset64() : bits(BitsUsed) { }
bitset64(int _bits) : bits(_bits) { }
const int bits;
uint64_t value;
};
and now the default constructor will pull in the correct number of bits. Then you just need to modify your alias like
template <int N>
using bitset = std::conditional_t<(N<=32), bitset32<N>, bitset64<N>>;
and you get the behavior you are asking for.
This will not work if you want to store a bitset<5> in the same container as a bitset<32> though since different template instantiations create distinct type.
I have a template function as following:
using namespace std::chrono;
using namespace std::chrono_literals;
template <typename D>
time_point<system_clock, D> makeTime(
int year, int month, int day, int hour = 0, int minute = 0,
int second = 0, int ms = 0, int us = 0, int ns = 0 );
Normally, I call it like this: auto us_tp1 = makeTime<microseconds>( 2020, 5, 26, 21, 21, 21, 999, 123 );
But now I need to call it at somewhere through an alias "makeTimeUS" like this:
auto us_tp1 = makeTimeUS( 2020, 5, 26, 21, 21, 21, 999, 123 );
just like that makeTimeUS is an instance of makeTime.
I tried this:
using makeTimeUS = template time_point<system_clock, microseconds> makeTime;
and this:
using makeTimeUS = template time_point<system_clock, microseconds> makeTime(
int, int, int, int, int, int, int, int, int );
but neither can pass the compilation.
How to instantiate a template function and give an alias on it at same time?
The reason I need to do so, is that there are too many old codes calling makeTimeUS as if it be a normal function instead of a template.
Thanks!
You can get a function pointer to the function you want and then use that as your "alias". That would look like:
auto makeTimeUS = makeTime<microseconds>;
And can be used like:
auto us_tp1 = makeTimeUS( 2020, 5, 26, 21, 21, 21, 999, 123 );
But this just lets you change the name. Since it is a function pointer the default arguments no longer work and you still have to specify all of the parameters.
To get around that you can make a wrapper instead of an alias using a lambda and that would look like
auto makeTimeUS = [](int year, int month, int day, int hour = 0,
int minute = 0, int second = 0, int ms = 0)
{
return makeTime<microseconds>(year, month, day, hour, minute, second, ms);
};
I have a linux time clock, that sending me the clock with bytes, I have no idea how to get the clock out of it, I do know that it's a type of "time_t" (which is probably the total seconds since 1/1/1970), See below a few examples what I'm receiving from the clock
(I'm not sure if it's sending local or GMT time)
This bytes represents 11/09/2017 16:52(Local time) or 11/09/2017 21:52(GMT)
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,129,66,120,53,34,0,0,0
This bytes represents 11/10/2017 10:27(Local time) or 11/10/2017 15:27(GMT)
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,129,162,111,54,34,0,0,0
This bytes represents 11/13/2017 14:20(Local time) or 11/13/2017 19:20(GMT)
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,129,160,154,58,34,0,0,0
With a little reverse engineering, it's possible to see what the data from your Linux clock is doing.
If you read your lines of data, you can see that the final 7 bytes are an incrementing counter, with the least significant byte in the first position. If you do the maths, the 3 counters give the following totals:
573929538
573992866
574266016
Looking at the differences from the first number to the second and third, you get the following:
63328
336478
... which are the elapsed times between your samples, in seconds.
For some reason, the raw values aren't the time in seconds since the Unix Epoch, but the time in seconds since a day early in September 1999.
Anyway, the following source code shows the basics of how you can convert your data into the local time.
#include <time.h>
int main(int argc, char* argv[])
{
const unsigned long localEpoch = 936316800; // local epoch Sept 1999
std::vector<std::vector<int>> timeData =
{
{ 66, 120, 53, 34, 0, 0, 0 },
{ 162, 111, 54, 34, 0, 0, 0 },
{ 160, 154, 58, 34, 0, 0, 0 }
};
for (auto& data : timeData)
{
// convert to total seconds
unsigned long total = 0;
for (std::vector<int>::iterator it = data.begin(); it != data.end(); ++it)
{
total += (*it) << (std::distance(data.begin(), it) * 8);
}
std::cout << "Seconds: " << total << std::endl;
// add to local epoch and display
time_t raw = total + localEpoch;
struct tm timeinfo;
localtime_s(&timeinfo, &raw);
char fmt[100] = { 0 };
asctime_s(fmt, 100, &timeinfo);
std::cout << fmt << std::endl;
}
return 0;
}
And it gives the following outputs:
Seconds: 573929538
Thu Nov 9 16:52:18 2017
Seconds: 573992866
Fri Nov 10 10:27:46 2017
Seconds: 574266016
Mon Nov 13 14:20:16 2017
Hope this is of some help. (NB I did this in VS, so the time formatting functions are of the Windows variety, but you get the idea.)
(Edit): OK as requested, here's some equivalent C# code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp2
{
class Program
{
public static void Main(string[] args)
{
int[,] timeData = new int[,]
{
{ 66, 120, 53, 34, 0, 0, 0 },
{ 162, 111, 54, 34, 0, 0, 0 },
{ 160, 154, 58, 34, 0, 0, 0 }
};
for (int line = 0; line < timeData.GetLength(0); ++line)
{
ulong total = 0;
for (int i = 0; i < timeData.GetLength(1); ++i)
{
total += (ulong)timeData[line,i] << (8 * i);
}
System.Console.WriteLine("Seconds: " + total.ToString());
DateTime result = FromUnixTime(total + localEpoch);
System.Console.WriteLine(result.ToString());
}
System.Console.ReadKey();
}
public static DateTime FromUnixTime(ulong unixTime)
{
return epoch.AddSeconds(unixTime);
}
private static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static readonly ulong localEpoch = 936316800; // local epoch Sept 1999
}
}
(Thanks to this question for the Epoch conversion.)
WOW!!!, that helped me a lot, Here is the code what I used, Thanks #ASMackay
private DateTime convertTime(byte[] bytes)
{
ulong localEpoch = 936316800; // local epoch Sept 1999
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
ulong total = 0;
for (int i = 0; i < bytes.Count(); ++i)
{
total += (ulong)bytes[i] << (8 * i);
}
return epoch.AddSeconds(total + localEpoch);
}
This is my code
std::ifstream infile("/home/alexander/MyCompany/MyGame/Resources/res/puzzles(copia).json");
std::string line;
std::ofstream ofs("/home/alexander/MyCompany/MyGame/Resources/res/temporal.json", std::ofstream::out);
Document d;
while (std::getline(infile, line))
{
d.Parse(line.c_str());
if (d.HasParseError()) CCLOG("GetParseError %s\n", d.GetParseError());
if (d.IsObject() && d.HasMember("Lados"))
{
rapidjson::Value& a = d["Lados"]; // Using a reference for consecutive access is handy and faster.
rapidjson::Document::AllocatorType& allocator = d.GetAllocator();
assert(a.IsArray()); // explotar si no es un arreglo
a.PushBack(4, allocator).PushBack(8, allocator).PushBack(15, allocator).PushBack(16, allocator).PushBack(23, allocator).PushBack(42, allocator);
}
// Convertir JSON a string e Insertar en archivo
StringBuffer strbuf;
Writer<StringBuffer> writer(strbuf);
d.Accept(writer);
ofs << strbuf.GetString() << "\n";
}
ofs.close();
infile.close();
// ACTUALIZA ARCHIVO PRINCIPAL & LIMPIA TEMPORAL
std::ifstream src("/home/alexander/MyCompany/MyGame/Resources/res/puzzles(copia).json");
std::ofstream dst("/home/alexander/MyCompany/MyGame/Resources/res/temporal.json");
dst << src.rdbuf();
src.close();
dst.close();
if (remove("/home/alexander/MyCompany/MyGame/Resources/res/temporal.json") != 0) CCLOG("Error deleting file");
CCLOG("save");
As you can see I'm creating a new file called temporal in which'll put my modified file, then pass it back to the original file.
the problem is that when I do that the file does not change,it is created and properly cleared but the file orginal is not modify and don't know why?.
I am using cocos2d-x, c ++ and rapidjson.
not if I need to give permission for my program to modififque arhivos or something like that
JSON has several lines in this format:
{ "N" : 5, "Rotacion" : 42, "Igual" : 20, "Inverso" : 0, "RotacionE" : 47, "Espejo" : 22, "Puntuacion" : 0, "_id" : "563b7b4756ab632f47fe6d7f" , "Lados" : [], "Camino" : [ 6, 5, 4, 21, 22, 7, 2, 3, 20, 23, 8, 1, 18, 19, 24, 9, 0, 17, 16, 15, 10, 11, 12, 13, 14 ], "__v" : 0 }
as I can see your code is ok, what you have to do is reverse the direction of your files .json right now you have it like so:
std::ifstream src("/home/alexander/MyCompany/MyGame/Resources/res/puzzles(copia).json");
std::ofstream dst("/home/alexander/MyCompany/MyGame/Resources/res/temporal.json");
but you have to put it like this :
std::ifstream src("/home/alexander/MyCompany/MyGame/Resources/res/temporal.json");
std::ofstream dst("/home/alexander/MyCompany/MyGame/Resources/res/puzzles(copia).json");
Consider following code
struct VersionData
{
VersionData();
VersionData(VersionData&& rhs);
int m_versionId;
int m_weight;
int m_pId;
bool m_hdi;
};
struct VersionId{};
typedef boost::multi_index_container<
VersionData,
bmi::indexed_by<
bmi::ordered_non_unique<
bmi::tag<VersionId>,
bmi::member<VersionData, int, &VersionData::m_versionId>
>
>
> VersionDataContainer;
struct VersionsData
{
VersionsData();
VersionsData(VersionsData&& rhs);
int m_sdGroupId;
int m_retId;
VersionDataContainer m_versionData;
};
struct mvKey{};
typedef boost::multi_index_container<
VersionsData,
bmi::indexed_by<
bmi::ordered_unique<
bmi::tag<mvKey>,
bmi::composite_key<
VersionsData,
bmi::member<VersionsData,int, &VersionsData::m_subdeliveryGroupId>,
bmi::member<VersionsData,int, &VersionsData::m_retargetingId>
>
>
>
> mvDataContainer;
mvDataContainer m_data;
The intention is to lookup using the composite key in mvDataContainer but in some cases I need to lookup in VersionData across all VersionsData. Something like m_data.get<mvKey>.equal_range(make_tuple(ignore, ignore, ignore)).get<VersionId>.equal_range(123456);
First question, is it achievable?
Second, is this the right approach to use nested multi_index_containers? any performance impacts/gains?
In addition to the other answer suggesting a single container for the whole table, here's the idea based on Boost Intrusive multiset
See it Live On Coliru
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/composite_key.hpp>
// for intrusive multiset
#include <boost/intrusive/set.hpp>
#include <boost/range/iterator_range.hpp>
#include <iostream>
namespace bmi = boost::multi_index;
namespace bi = boost::intrusive;
struct VersionData : bi::set_base_hook<bi::link_mode<bi::auto_unlink> > {
VersionData(int versionId, int weight=0, int pId=0, bool hdi=false) :
m_versionId(versionId), m_weight(weight), m_pId(pId), m_hdi(hdi) { }
int m_versionId;
int m_weight;
int m_pId;
bool m_hdi;
friend std::ostream& operator<<(std::ostream& os, VersionData const& vd) {
return os << "{ " << vd.m_versionId << " " << vd.m_weight << " " << vd.m_pId << " " << vd.m_hdi << " }";
}
struct ById {
bool operator()(VersionData const& a, VersionData const& b) const { return a.m_versionId < b.m_versionId; }
};
};
typedef bi::multiset<VersionData, bi::constant_time_size<false>, bi::compare<VersionData::ById> > VersionIndex;
typedef boost::multi_index_container<
VersionData,
bmi::indexed_by<
bmi::ordered_non_unique<
bmi::tag<struct VersionId>,
bmi::member<VersionData, int, &VersionData::m_versionId>
>
>
> VersionDataContainer;
struct VersionsData
{
int m_subdeliveryGroupId;
int m_retargetingId;
VersionDataContainer m_versionData;
};
typedef boost::multi_index_container<
VersionsData,
bmi::indexed_by<
bmi::ordered_unique<
bmi::tag<struct mvKey>,
bmi::composite_key<
VersionsData,
bmi::member<VersionsData,int, &VersionsData::m_subdeliveryGroupId>,
bmi::member<VersionsData,int, &VersionsData::m_retargetingId>
>
>
>
> mvDataContainer;
void insert(
mvDataContainer& into, VersionIndex& global_version_index,
int subdeliveryGroupId, int retargetingId, int
versionId, int weight, int pId, bool hdi)
{
auto& mainIdx = into.get<mvKey>();
auto insertion = mainIdx.insert(VersionsData { subdeliveryGroupId, retargetingId, VersionDataContainer {} });
mainIdx.modify(insertion.first, [&](VersionsData& record) {
auto insertion = record.m_versionData.insert(VersionData { versionId, weight, pId, hdi });
global_version_index.insert(const_cast<VersionData&>(*insertion.first));
});
}
int main() {
VersionIndex global_version_index;
mvDataContainer table;
insert(table, global_version_index, 21, 10, 1, 100, 123, false);
insert(table, global_version_index, 9, 27, 2, 90, 123, false);
insert(table, global_version_index, 12, 25, 3, 110, 123, true);
insert(table, global_version_index, 10, 33, /*version 8:*/ 8, 80, 123, false);
insert(table, global_version_index, 4, 38, 5, 101, 124, false);
insert(table, global_version_index, 33, 7, 6, 91, 124, false);
insert(table, global_version_index, 34, 27, 7, 111, 124, true);
insert(table, global_version_index, 9, 11, /*version 8:*/ 8, 81, 124, false);
insert(table, global_version_index, 0, 12, 9, 99, 125, false);
insert(table, global_version_index, 35, 39, /*version 8:*/ 8, 89, 125, false);
insert(table, global_version_index, 15, 15, 11, 109, 125, true);
insert(table, global_version_index, 25, 32, /*version 8:*/ 8, 79, 125, false);
// debug table output
assert(table.size()==12);
// so now you can do:
std::cout << "---\nQuerying for version id 8:\n";
for (auto& record : boost::make_iterator_range(global_version_index.equal_range(8)))
std::cout << record << "\n";
table.erase(table.find(boost::make_tuple(10,33))); // auto unlinks from global_version_index
std::cout << "---\nQuerying for version id 8:\n";
for (auto& record : boost::make_iterator_range(global_version_index.equal_range(8)))
std::cout << record << "\n";
}
Prints:
---
Querying for version id 8:
{ 8 80 123 0 }
{ 8 81 124 0 }
{ 8 89 125 0 }
{ 8 79 125 0 }
---
Querying for version id 8:
{ 8 81 124 0 }
{ 8 89 125 0 }
{ 8 79 125 0 }
So indeed, instead of using nested containers, like that (live on Coliru)
You could/should implement it as a single /table/ (after all, this is a table with several indices):
Live On Coliru
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/composite_key.hpp>
namespace bmi = boost::multi_index;
struct VersionRecord {
int m_subdeliveryGroupId;
int m_retargetingId;
int m_versionId;
int m_weight;
int m_pId;
bool m_hdi;
friend std::ostream& operator<<(std::ostream& os, VersionRecord const& record) {
return os << "{ " << record.m_subdeliveryGroupId << " " << record.m_retargetingId << " "
<< record.m_versionId << " " << record.m_weight << " " << record.m_pId << " " << record.m_hdi << " }";
}
};
typedef boost::multi_index_container<
VersionRecord,
bmi::indexed_by<
bmi::ordered_unique<
bmi::tag<struct mvKey>,
bmi::composite_key<
VersionRecord,
bmi::member<VersionRecord,int, &VersionRecord::m_subdeliveryGroupId>,
bmi::member<VersionRecord,int, &VersionRecord::m_retargetingId>
>
>,
bmi::ordered_non_unique<
bmi::tag<struct VersionId>,
bmi::member<VersionRecord, int, &VersionRecord::m_versionId>
>
>
> VersionTable;
#include <iostream>
#include <boost/range/iterator_range.hpp>
int main() {
auto table = VersionTable {
{ 21, 10, 1, 100, 123, false },
{ 9, 27, 2, 90, 123, false },
{ 12, 25, 3, 110, 123, true },
{ 10, 33, /*version 8:*/ 8, 80, 123, false },
{ 4, 38, 5, 101, 124, false },
{ 33, 7, 6, 91, 124, false },
{ 34, 27, 7, 111, 124, true },
{ 9, 11, /*version 8:*/ 8, 81, 124, false },
{ 0, 12, 9, 99, 125, false },
{ 35, 39, /*version 8:*/ 8, 89, 125, false },
{ 15, 15, 11, 109, 125, true },
{ 25, 32, /*version 8:*/ 8, 79, 125, false },
};
// debug table output
assert(table.size()==12);
for (auto& record : table) std::cout << record << "\n";
// so now you can do:
auto& idx = table.get<VersionId>();
std::cout << "---\nQuerying for version id 8:\n";
for (auto& record : boost::make_iterator_range(idx.equal_range(8)))
std::cout << record << "\n";
}
Which prints:
{ 0 12 9 99 125 0 }
{ 4 38 5 101 124 0 }
{ 9 11 8 81 124 0 }
{ 9 27 2 90 123 0 }
{ 10 33 8 80 123 0 }
{ 12 25 3 110 123 1 }
{ 15 15 11 109 125 1 }
{ 21 10 1 100 123 0 }
{ 25 32 8 79 125 0 }
{ 33 7 6 91 124 0 }
{ 34 27 7 111 124 1 }
{ 35 39 8 89 125 0 }
---
Querying for version id 8:
{ 25 32 8 79 125 0 }
{ 35 39 8 89 125 0 }
{ 10 33 8 80 123 0 }
{ 9 11 8 81 124 0 }
Alternatively, you can bolt an intrusive container on top of the VersionsData records. This however, complicates the design (you either have to use auto_unlink node hooks (sacrificing thread safety control) or you have to make sure the containers are in synch all the time.
It is not the exact answer which I originally asked but since the performance issue was mentioned and in light of discussion with #sehe this is what I found.
1) use flat structure, you can save wasting memory on the same keys using boost::flyweight
2) use MIC instead of tailored containers, MIC might be slightly slower (depends on test scenario) when searching on simple indexes, but once you use composite keys (and implement similar behavior for your tailored datastructure) it is from slightly to significantly faster than tailored DS
My previous statement that tailored one is faster is wrong, since I was using MIC from boost 1.52 and looks like there was a bug when using composite keys with strings (5 orders of magnitude slower than composite without string). When switched to 1.57 everything started to work as expected.
Tests on Coliru
Have a nice indexing, guys! :)