在日常生活中,时钟是我们最常用的工具之一。数字时钟以24小时制显示时间,当时间超过24小时时会自动回到00:00。在 Exercism 的 “clock” 练习中,我们将实现一个数字时钟,这不仅能帮助我们掌握时间处理技巧,还能深入学习 Rust 中的运算符重载、显示格式化和模运算应用。
时钟的工作原理
数字时钟遵循以下规则:
- 时间格式:以 HH:MM 格式显示,小时和分钟都是两位数
- 24小时制:小时范围是 00-23,分钟范围是 00-59
- 自动回绕:当时间超过 23:59 时,自动回到 00:00
- 负数处理:支持负数时间的处理,例如 -1 小时等于 23 小时
让我们先看看练习提供的实现:
use std::fmt::Display;
const DAY_MINS: i32 = 1440;
const HOUR_MINS: i32 = 60;
#[derive(Debug, PartialEq)]
pub struct Clock {
minutes: i32,
}
impl Clock {
pub fn new(hours: i32, minutes: i32) -> Self {
Self::build(hours * HOUR_MINS + minutes)
}
fn build(minutes: i32) -> Self {
let mut minutes = minutes;
while minutes < 0 {
minutes += DAY_MINS;
}
Clock {
minutes: minutes % DAY_MINS,
}
}
pub fn add_minutes(&self, minutes: i32) -> Self {
Self::build(self.minutes + minutes)
}
}
impl Display for Clock {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let minutes = self.minutes;
write!(f, "{:02}:{:02}", minutes / HOUR_MINS, minutes % HOUR_MINS)
}
}
这个实现非常优雅,它将所有时间都转换为分钟数进行处理,然后通过模运算实现自动回绕。
算法解析
1. 核心设计思想
const DAY_MINS: i32 = 1440; // 24 * 60
const HOUR_MINS: i32 = 60; // 1 * 60
通过将所有时间都转换为分钟数,简化了时间计算。一天有1440分钟,一小时有60分钟。
2. 时间构造
pub fn new(hours: i32, minutes: i32) -> Self {
Self::build(hours * HOUR_MINS + minutes)
}
将小时和分钟都转换为总分钟数,然后通过 [build](file:///Users/zacksleo/projects/github/zacksleo/exercism-rust/exercises/practice/dot-dsl/src/graph.rs#L22-L22) 方法处理负数和溢出。
3. 时间标准化
fn build(minutes: i32) -> Self {
let mut minutes = minutes;
while minutes < 0 {
minutes += DAY_MINS;
}
Clock {
minutes: minutes % DAY_MINS,
}
}
这个方法处理了负数时间和超过一天的时间:
- 对于负数时间,通过不断加一天的分钟数直到变为正数
- 对于超过一天的时间,通过模运算将其限制在一天内
4. 显示格式化
impl Display for Clock {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let minutes = self.minutes;
write!(f, "{:02}:{:02}", minutes / HOUR_MINS, minutes % HOUR_MINS)
}
}
通过整数除法和模运算将总分钟数转换回小时和分钟格式,并使用 {:02} 确保两位数显示。
测试用例分析
通过查看测试用例,我们可以更好地理解需求:
#[test]
fn test_on_the_hour() {
assert_eq!(Clock::new(8, 0).to_string(), "08:00");
}
整点时间的显示。
#[test]
fn test_hour_rolls_over() {
assert_eq!(Clock::new(25, 0).to_string(), "01:00");
}
小时超过24时自动回绕。
#[test]
fn test_negative_hour() {
assert_eq!(Clock::new(-1, 15).to_string(), "23:15");
}
负数小时的处理。
#[test]
fn test_add_across_midnight() {
let clock = Clock::new(23, 59).add_minutes(2);
assert_eq!(clock.to_string(), "00:01");
}
跨 midnight 的时间增加。
#[test]
fn test_compare_clocks_for_equality() {
assert_eq!(Clock::new(15, 37), Clock::new(15, 37));
}
时钟相等性比较。
替代实现方法
1. 分别存储小时和分钟
use std::fmt::Display;
#[derive(Debug, PartialEq)]
pub struct Clock {
hours: i32,
minutes: i32,
}
impl Clock {
pub fn new(hours: i32, minutes: i32) -> Self {
let total_minutes = hours * 60 + minutes;
let normalized_minutes = ((total_minutes % 1440) + 1440) % 1440;
let hours = normalized_minutes / 60;
let minutes = normalized_minutes % 60;
Clock { hours, minutes }
}
pub fn add_minutes(&self, minutes: i32) -> Self {
Clock::new(self.hours, self.minutes + minutes)
}
}
impl Display for Clock {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:02}:{:02}", self.hours, self.minutes)
}
}
2. 使用 u32 存储分钟数
use std::fmt::Display;
const DAY_MINS: u32 = 1440;
#[derive(Debug, PartialEq)]
pub struct Clock {
minutes: u32,
}
impl Clock {
pub fn new(hours: i32, minutes: i32) -> Self {
let total_minutes = hours * 60 + minutes;
// 使用模运算处理负数
let normalized_minutes = ((total_minutes % 1440 as i32) + 1440 as i32) % 1440 as i32;
Clock {
minutes: normalized_minutes as u32,
}
}
pub fn add_minutes(&self, minutes: i32) -> Self {
let total_minutes = self.minutes as i32 + minutes;
let normalized_minutes = ((total_minutes % 1440 as i32) + 1440 as i32) % 1440 as i32;
Clock {
minutes: normalized_minutes as u32,
}
}
}
impl Display for Clock {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:02}:{:02}", self.minutes / 60, self.minutes % 60)
}
}
运算符重载实现
为了使时钟更易于使用,我们可以实现运算符重载:
use std::fmt::Display;
use std::ops::{Add, AddAssign, Sub, SubAssign};
const DAY_MINS: i32 = 1440;
const HOUR_MINS: i32 = 60;
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct Clock {
minutes: i32,
}
impl Clock {
pub fn new(hours: i32, minutes: i32) -> Self {
Self::build(hours * HOUR_MINS + minutes)
}
fn build(minutes: i32) -> Self {
let normalized_minutes = ((minutes % DAY_MINS) + DAY_MINS) % DAY_MINS;
Clock {
minutes: normalized_minutes,
}
}
pub fn add_minutes(&self, minutes: i32) -> Self {
Self::build(self.minutes + minutes)
}
}
impl Display for Clock {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let minutes = self.minutes;
write!(f, "{:02}:{:02}", minutes / HOUR_MINS, minutes % HOUR_MINS)
}
}
// 实现加法运算符
impl Add<i32> for Clock {
type Output = Clock;
fn add(self, minutes: i32) -> Self::Output {
self.add_minutes(minutes)
}
}
// 实现加法赋值运算符
impl AddAssign<i32> for Clock {
fn add_assign(&mut self, minutes: i32) {
*self = self.add_minutes(minutes);
}
}
// 实现减法运算符
impl Sub<i32> for Clock {
type Output = Clock;
fn sub(self, minutes: i32) -> Self::Output {
self.add_minutes(-minutes)
}
}
// 实现减法赋值运算符
impl SubAssign<i32> for Clock {
fn sub_assign(&mut self, minutes: i32) {
*self = self.add_minutes(-minutes);
}
}
性能优化版本
考虑性能的优化实现:
use std::fmt::Display;
const DAY_MINS: i32 = 1440;
const HOUR_MINS: i32 = 60;
#[derive(Debug, PartialEq)]
pub struct Clock {
minutes: i32,
}
impl Clock {
pub fn new(hours: i32, minutes: i32) -> Self {
Self::build(hours * HOUR_MINS + minutes)
}
#[inline]
fn build(minutes: i32) -> Self {
// 使用位运算优化模运算(当除数是2的幂时)
// 但这里1440不是2的幂,所以我们使用标准模运算
let normalized = ((minutes % DAY_MINS) + DAY_MINS) % DAY_MINS;
Clock { minutes: normalized }
}
#[inline]
pub fn add_minutes(&self, minutes: i32) -> Self {
Self::build(self.minutes + minutes)
}
// 添加一些实用方法
pub fn hours(&self) -> i32 {
self.minutes / HOUR_MINS
}
pub fn minutes_only(&self) -> i32 {
self.minutes % HOUR_MINS
}
pub fn total_minutes(&self) -> i32 {
self.minutes
}
}
impl Display for Clock {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:02}:{:02}", self.minutes / HOUR_MINS, self.minutes % HOUR_MINS)
}
}
错误处理和边界情况
考虑更多边界情况的实现:
use std::fmt::Display;
const DAY_MINS: i32 = 1440;
const HOUR_MINS: i32 = 60;
#[derive(Debug, PartialEq)]
pub struct Clock {
minutes: i32,
}
impl Clock {
pub fn new(hours: i32, minutes: i32) -> Self {
Self::build(hours * HOUR_MINS + minutes)
}
fn build(minutes: i32) -> Self {
// 处理整数溢出
let normalized = minutes.rem_euclid(DAY_MINS);
Clock { minutes: normalized }
}
pub fn add_minutes(&self, minutes: i32) -> Self {
Self::build(self.minutes.saturating_add(minutes))
}
// 添加一些实用方法
pub fn hours(&self) -> i32 {
self.minutes / HOUR_MINS
}
pub fn minutes_only(&self) -> i32 {
self.minutes % HOUR_MINS
}
}
impl Display for Clock {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:02}:{:02}", self.hours(), self.minutes_only())
}
}
实际应用场景
时钟类在实际开发中有以下应用:
- 时间处理库:作为时间处理库的基础组件
- 游戏开发:游戏中的计时器和倒计时功能
- 任务调度:定时任务的时间计算
- 日历应用:时间显示和计算
- 嵌入式系统:设备的时间显示
与其他时间处理方式的比较
// 使用 chrono 库的实现
use chrono::{NaiveTime, Duration};
pub struct ClockChrono {
time: NaiveTime,
}
impl ClockChrono {
pub fn new(hours: i32, minutes: i32) -> Self {
let time = NaiveTime::from_hms_opt(
(hours.rem_euclid(24)) as u32,
(minutes.rem_euclid(60)) as u32,
0,
).unwrap_or_else(|| NaiveTime::from_hms_opt(0, 0, 0).unwrap());
ClockChrono { time }
}
pub fn add_minutes(&self, minutes: i32) -> Self {
let duration = Duration::minutes(minutes as i64);
let new_time = self.time + duration;
ClockChrono { time: new_time }
}
}
// 使用标准库 Duration 的实现
use std::time::Duration as StdDuration;
use std::fmt::Display;
pub struct ClockDuration {
duration: StdDuration,
}
impl ClockDuration {
pub fn new(hours: i32, minutes: i32) -> Self {
let total_secs = ((hours * 3600 + minutes * 60) % 86400 + 86400) % 86400;
ClockDuration {
duration: StdDuration::from_secs(total_secs as u64),
}
}
pub fn add_minutes(&self, minutes: i32) -> Self {
let secs_to_add = (minutes * 60) as u64;
let total_secs = (self.duration.as_secs() + secs_to_add) % 86400;
ClockDuration {
duration: StdDuration::from_secs(total_secs),
}
}
}
总结
通过 clock 练习,我们学到了:
- 时间处理:掌握了时间计算和格式化技巧
- 模运算应用:学会了使用模运算处理循环数据
- 显示格式化:熟练使用 Display trait 和格式化字符串
- 运算符重载:了解了如何为自定义类型实现运算符
- 边界处理:理解了如何处理负数和溢出情况
- 性能优化:学习了内联函数等优化技巧
这些技能在实际开发中非常有用,特别是在处理时间相关功能、实现循环数据结构和进行数学计算时。时钟虽然看起来简单,但它涉及到了模运算、数据标准化和显示格式化等许多核心概念,是学习 Rust 数据处理的良好起点。
通过这个练习,我们也看到了 Rust 在处理数学运算和数据格式化方面的强大能力,以及如何用简洁且高效的方式实现复杂逻辑。这种结合了安全性和性能的语言特性正是 Rust 的魅力所在。
740

被折叠的 条评论
为什么被折叠?



