Skip to content

Instantly share code, notes, and snippets.

@Shikugawa
Created May 18, 2020 09:40
Show Gist options
  • Save Shikugawa/10b1f5152d794d4c781fcc65e2e61fbf to your computer and use it in GitHub Desktop.
Save Shikugawa/10b1f5152d794d4c781fcc65e2e61fbf to your computer and use it in GitHub Desktop.
#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 &current_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 &current_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 &current_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 &current_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