Created
May 18, 2020 09:40
-
-
Save Shikugawa/10b1f5152d794d4c781fcc65e2e61fbf to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <string> | |
#include <vector> | |
class Record { | |
public: | |
// continueフラグは、msecに変換する際、初期値から換算するか否かを区別するのに用いる | |
Record(const std::string &record_str) : record_str_(record_str) { | |
parse(); | |
calculateTimestamp(); | |
} | |
struct Time { | |
uint16_t hour_{0}; | |
uint16_t min_{0}; | |
uint16_t sec_{0}; | |
uint16_t msec_{0}; | |
uint32_t timestamp_{0}; // 00:00:00.000 から何msec経ったか | |
static bool isNight(Time &t) { | |
// continueしている場合もあるので剰余を取る | |
const auto current_timestamp = t.timestamp_ % 86400000; | |
return current_timestamp >= 0 && current_timestamp < 21600000; | |
} | |
static bool isPeak(Time &t) { | |
// continueしている場合もあるので剰余を取る | |
const auto current_timestamp = t.timestamp_ % 86400000; | |
return (current_timestamp >= 21600000 && current_timestamp < 34200000) || | |
(current_timestamp >= 72000000 && current_timestamp < 86400000); | |
} | |
static uint16_t time_to_uint16_t(std::string &time, | |
uint8_t upper_digits_size = 2) { | |
if (time.size() > upper_digits_size) { | |
throw "不正なフォーマットです"; | |
} | |
std::string tmp; | |
uint8_t start_index = 0; | |
// 最初の0を飛ばす | |
while (time[start_index] == 0) { | |
++start_index; | |
} | |
for (size_t i = start_index; i < time.size(); ++i) { | |
tmp += time[i]; | |
} | |
return std::stoi(tmp); | |
} | |
}; | |
private: | |
// タイムスタンプを計算する | |
void calculateTimestamp() { | |
time_.timestamp_ += 3600000 * time_.hour_; | |
time_.timestamp_ += 60000 * time_.min_; | |
time_.timestamp_ += 1000 * time_.sec_; | |
time_.timestamp_ += time_.msec_; | |
} | |
enum TimeParseState { HOUR, MINUTE, SECOND, MSECOND, TERMINATED }; | |
void parse() { | |
std::string time, distance; | |
std::string tmp; | |
size_t length = 0; | |
for (const auto &c : record_str_) { | |
if (length > 2) { | |
throw "レコードのフォーマットが不正です"; | |
} | |
if (c == ' ') { | |
time = tmp; | |
tmp.clear(); | |
length += 1; | |
continue; | |
} | |
if (c == '\n') { | |
distance = tmp; | |
tmp.clear(); | |
length += 1; | |
continue; | |
} | |
tmp += c; | |
} | |
parseTime(time); | |
parseDistance(distance); | |
} | |
void parseTime(std::string &time_str) { | |
std::string tmp; | |
TimeParseState state{Record::TimeParseState::HOUR}; | |
for (const auto &c : time_str) { | |
if (state == Record::TimeParseState::TERMINATED) { | |
throw "フォーマットが不正です"; | |
} | |
if (c == ':') { | |
switch (state) { | |
case Record::TimeParseState::HOUR: | |
time_.hour_ = Time::time_to_uint16_t(tmp); | |
tmp.clear(); | |
state = Record::TimeParseState::MINUTE; | |
break; | |
case Record::TimeParseState::MINUTE: | |
time_.min_ = Time::time_to_uint16_t(tmp); | |
tmp.clear(); | |
state = Record::TimeParseState::SECOND; | |
break; | |
default: | |
throw "不正なフォーマットです"; | |
} | |
continue; | |
} | |
if (c == '.' && state == Record::TimeParseState::SECOND) { | |
time_.sec_ = Time::time_to_uint16_t(tmp); | |
tmp.clear(); | |
state = Record::TimeParseState::MSECOND; | |
continue; | |
} | |
if (c == '\n' && state == Record::TimeParseState::MSECOND) { | |
time_.msec_ = Time::time_to_uint16_t(tmp); | |
tmp.clear(); | |
state = Record::TimeParseState::TERMINATED; | |
continue; | |
} | |
tmp += c; | |
} | |
} | |
void parseDistance(std::string &distance_str) { | |
try { | |
distance_ = std::stod(distance_str); | |
} catch (const std::invalid_argument &) { | |
throw "不正なフォーマットです"; | |
} | |
} | |
public: | |
std::string record_str_; | |
Time time_; | |
double distance_; | |
}; | |
class DistanceMeter { | |
public: | |
enum DistanceState { | |
FIRST_RIDE, // 初乗距離 | |
SHORT_RIDE, // 短距離 | |
LONG_RIDE, // 長距離 | |
}; | |
void update(Record current_record) { | |
distance_ += current_record.distance_; | |
if (distance_ >= 1000) { | |
current_state_ = DistanceMeter::DistanceState::SHORT_RIDE; | |
} else if (distance_ >= 10200) { | |
current_state_ = DistanceMeter::DistanceState::LONG_RIDE; | |
} | |
} | |
void updateState() { | |
if (current_state_ == DistanceMeter::DistanceState::FIRST_RIDE) { | |
current_state_ = DistanceMeter::DistanceState::SHORT_RIDE; | |
} else if (current_state_ == DistanceMeter::DistanceState::SHORT_RIDE) { | |
current_state_ = DistanceMeter::DistanceState::LONG_RIDE; | |
} | |
} | |
DistanceState currentState() const { return current_state_; } | |
double currentDistance() const { return distance_; } | |
private: | |
double distance_ = 0; | |
DistanceMeter::DistanceState current_state_{ | |
DistanceMeter::DistanceState::FIRST_RIDE}; | |
}; | |
class LowSpeedTimeMeter { | |
public: | |
void update(uint32_t timespan_msec) { sec_ += timespan_msec / 1000; } | |
uint64_t currentSec() const { return sec_; } | |
private: | |
uint64_t sec_ = 0; | |
}; | |
class DistanceFareCalculator { | |
public: | |
// 現在いくらなのかを計算 | |
void update(DistanceMeter &meter, Record current_record) { | |
// 初乗りの時は加算しない | |
if (meter.currentState() == DistanceMeter::DistanceState::FIRST_RIDE) { | |
previous_point_distance_ = meter.currentDistance(); | |
return; | |
} else if (meter.currentState() == | |
DistanceMeter::DistanceState::SHORT_RIDE) { | |
assert(meter.currentDistance() > 1000); | |
const auto next_checkpoint = 1000 + current_point_ * 400; | |
if (previous_point_distance_ < next_checkpoint && | |
meter.currentDistance() >= next_checkpoint) { | |
distance_fare_ += pickFare(current_record.time_); | |
++current_point_; | |
} | |
previous_point_distance_ = meter.currentDistance(); | |
} else if (meter.currentState() == | |
DistanceMeter::DistanceState::LONG_RIDE) { | |
assert(meter.currentDistance() > 10200); | |
if (previous_point_distance_ < 10200) { | |
current_point_ = 0; | |
} | |
const auto next_checkpoint = 10200 + current_point_ * 350; | |
if (previous_point_distance_ < next_checkpoint && | |
meter.currentDistance() >= next_checkpoint) { | |
distance_fare_ += pickFare(current_record.time_); | |
++current_point_; | |
} | |
previous_point_distance_ = meter.currentDistance(); | |
} | |
} | |
uint32_t fare() const { return distance_fare_; } | |
private: | |
uint32_t pickFare(Record::Time ¤t_time) { | |
if (Record::Time::isNight(current_time)) { | |
return 60; | |
} | |
if (Record::Time::isPeak(current_time)) { | |
return 52; | |
} | |
return 40; | |
} | |
size_t current_point_{0}; | |
uint32_t previous_point_distance_{0}; // 前回のレコード時の総距離 | |
uint32_t distance_fare_{0}; | |
}; | |
class LowSpeedTimeFareCalculator { | |
public: | |
// 低速走行した秒数 | |
void update(LowSpeedTimeMeter &meter, Record ¤t_record) { | |
const auto next_checkpoint = 45 * current_point_; | |
if (next_checkpoint > previous_point_second_ && | |
meter.currentSec() >= next_checkpoint) { | |
low_speed_fare_ += pickFare(current_record.time_); | |
++current_point_; | |
} | |
current_point_ = meter.currentSec(); | |
} | |
uint32_t fare() const { return low_speed_fare_; } | |
private: | |
uint32_t pickFare(Record::Time ¤t_time) { | |
if (Record::Time::isNight(current_time)) { | |
return 60; | |
} | |
if (Record::Time::isPeak(current_time)) { | |
return 52; | |
} | |
return 40; | |
} | |
size_t current_point_{0}; | |
uint32_t previous_point_second_{0}; // 前回の記録時、0から何秒経ったか | |
uint32_t low_speed_fare_{0}; | |
}; | |
class FareMeter { | |
public: | |
FareMeter(Record start_record) { setDefaultFare(start_record.time_); } | |
void update(Record current_record, Record prev_record, | |
DistanceMeter &distance_meter, | |
LowSpeedTimeMeter &low_speed_meter) { | |
distance_fare_calc_.update(distance_meter, current_record); | |
low_speed_fare_calc_.update(low_speed_meter, current_record); | |
fare_ = default_fare_ + distance_fare_calc_.fare() + | |
low_speed_fare_calc_.fare(); | |
} | |
uint32_t fare() const { return fare_; } | |
private: | |
void setDefaultFare(Record::Time ¤t_time) { | |
if (Record::Time::isNight(current_time)) { | |
default_fare_ = 600; | |
fare_ = default_fare_; | |
return; | |
} | |
if (Record::Time::isPeak(current_time)) { | |
default_fare_ = 520; | |
fare_ = default_fare_; | |
return; | |
} | |
default_fare_ = 400; | |
fare_ = default_fare_; | |
} | |
DistanceFareCalculator distance_fare_calc_; | |
LowSpeedTimeFareCalculator low_speed_fare_calc_; | |
uint32_t default_fare_; | |
uint32_t fare_{0}; | |
}; | |
int main() { | |
std::vector<std::string> records{"13:50:11.123 4.0\n", "13:50:12.125 10.2\n", | |
"13:50:13.100 8.7\n"}; | |
DistanceMeter dm; | |
LowSpeedTimeMeter lstm; | |
const auto first_record = Record("13:50:00.245 0.0\n"); | |
FareMeter fm(first_record); | |
Record prev_record = first_record; | |
std::cout << fm.fare() << std::endl; | |
for (const std::string record_str : records) { | |
Record record(record_str); | |
const double timespan_ms = | |
record.time_.timestamp_ - prev_record.time_.timestamp_; | |
const double timespan_hour = timespan_ms / 3600000.0; | |
const double average_speed = | |
static_cast<double>(record.distance_) / timespan_hour / 1000.0; | |
if (average_speed <= 10) { | |
lstm.update(timespan_ms); | |
} | |
fm.update(record, prev_record, dm, lstm); | |
std::cout << fm.fare() << std::endl; | |
prev_record = record; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment