1#![doc = include_str!("../README.md")]
2
3pub mod thirteen_strings;
5
6use fnv::FnvHashSet as HashSet;
7use num_traits::FromPrimitive;
8use once_cell::sync::OnceCell;
9use std::fmt::Debug;
10use std::ops::Rem;
11use thirteen_strings::THIRTEEN_STRINGS;
12
13pub trait IsThirteen {
16 fn thirteen(&self) -> bool;
18}
19
20macro_rules! impl_for_integer {
21 ($type:ty) => {
22 impl IsThirteen for $type {
23 fn thirteen(&self) -> bool {
25 *self == 13
26 }
27 }
28 };
29}
30
31impl_for_integer!(i8);
32impl_for_integer!(i16);
33impl_for_integer!(i32);
34impl_for_integer!(i64);
35impl_for_integer!(i128);
36impl_for_integer!(isize);
37impl_for_integer!(u8);
38impl_for_integer!(u16);
39impl_for_integer!(u32);
40impl_for_integer!(u64);
41impl_for_integer!(u128);
42impl_for_integer!(usize);
43
44macro_rules! impl_for_float {
45 ($type:ty) => {
46 impl IsThirteen for $type {
47 fn thirteen(&self) -> bool {
49 (self - 13.0).abs() < <$type>::EPSILON
50 }
51 }
52 };
53}
54
55impl_for_float!(f64);
56impl_for_float!(f32);
57
58impl IsThirteen for &str {
59 fn thirteen(&self) -> bool {
64 matches!(*self, "13" | "B")
65 || (self.len() == 13 && self.bytes().all(|b| matches!(b, b'I' | b'l' | b'1')))
66 || is_thirteen_equal_chars(self)
67 || THIRTEEN_STRINGS.contains(self.to_lowercase().as_str())
69 }
70}
71
72fn is_thirteen_equal_chars(s: &str) -> bool {
73 if let Some(first_char) = s.chars().next() {
74 if s.chars().count() == 13 {
75 s.chars().all(|c| c == first_char)
76 } else {
77 false
78 }
79 } else {
80 false
81 }
82}
83
84impl IsThirteen for String {
85 fn thirteen(&self) -> bool {
86 self.as_str().thirteen()
87 }
88}
89
90impl IsThirteen for char {
91 fn thirteen(&self) -> bool {
93 matches!(*self, 'B' | 'ß' | 'β' | '阝')
94 }
95}
96
97macro_rules! impl_always_false {
98 ($type:ty) => {
99 impl IsThirteen for $type {
100 fn thirteen(&self) -> bool {
102 false
103 }
104 }
105 };
106}
107
108impl_always_false!(bool);
109impl_always_false!(());
110
111#[derive(Debug, Copy, Clone)]
113pub struct Roughly(pub f64);
114
115impl IsThirteen for Roughly {
116 fn thirteen(&self) -> bool {
117 (12.5..13.5).contains(&self.0)
118 }
119}
120
121#[derive(Debug, Clone)]
123pub struct Returns<T>(pub T);
124
125impl<F, R> IsThirteen for Returns<F>
126where
127 F: Fn() -> R,
128 R: IsThirteen,
129{
130 fn thirteen(&self) -> bool {
131 self.0().thirteen()
132 }
133}
134
135#[derive(Debug, Copy, Clone)]
137pub struct DivisibleBy<T>(pub T);
138
139impl<T, RemOutput> IsThirteen for DivisibleBy<T>
140where
141 T: Rem<Output = RemOutput> + FromPrimitive + Copy,
142 RemOutput: PartialEq + FromPrimitive,
143{
144 fn thirteen(&self) -> bool {
145 self.0 % FromPrimitive::from_u64(13).unwrap() == FromPrimitive::from_u64(0).unwrap()
146 }
147}
148
149#[derive(Debug, Copy, Clone)]
151pub struct GreaterThan<T>(pub T);
152
153impl<T> IsThirteen for GreaterThan<T>
154where
155 T: PartialOrd + FromPrimitive,
156{
157 fn thirteen(&self) -> bool {
158 self.0 > FromPrimitive::from_u64(13).unwrap()
159 }
160}
161
162#[derive(Debug, Copy, Clone)]
164pub struct LessThan<T>(pub T);
165
166impl<T> IsThirteen for LessThan<T>
167where
168 T: PartialOrd + FromPrimitive,
169{
170 fn thirteen(&self) -> bool {
171 self.0 < FromPrimitive::from_u64(13).unwrap()
172 }
173}
174
175#[derive(Debug, Copy, Clone)]
177pub struct Within {
178 value: f64,
179 radius: f64,
180}
181
182impl Within {
183 pub fn new(value: f64, radius: f64) -> Self {
185 Self { value, radius }
186 }
187}
188
189impl IsThirteen for Within {
190 fn thirteen(&self) -> bool {
191 (self.value - 13.0).abs() <= self.radius
192 }
193}
194
195#[derive(Debug, Clone)]
197pub struct CanSpell {
198 letters: HashSet<u8>,
199}
200
201impl CanSpell {
202 pub fn new(s: &str) -> Self {
203 Self {
204 letters: s.bytes().map(|b| b.to_ascii_lowercase()).collect(),
205 }
206 }
207}
208
209impl IsThirteen for CanSpell {
210 fn thirteen(&self) -> bool {
211 [b't', b'h', b'i', b'r', b't', b'e', b'e', b'n']
212 .iter()
213 .all(|b| self.letters.contains(b))
214 }
215}
216
217#[derive(Debug, Clone)]
220pub struct AnagramOf {
221 bytes: HashSet<u8>,
222}
223
224impl AnagramOf {
225 pub fn new(s: &str) -> Self {
226 Self {
227 bytes: s.bytes().map(|b| b.to_ascii_lowercase()).collect(),
228 }
229 }
230}
231
232const THIRTEEN_STR: &str = "thirteen";
233static THIRTEEN_LETTERS: OnceCell<HashSet<u8>> = OnceCell::new();
234
235impl IsThirteen for AnagramOf {
236 fn thirteen(&self) -> bool {
237 self.bytes == *THIRTEEN_LETTERS.get_or_init(|| THIRTEEN_STR.bytes().collect())
238 }
239}
240
241#[derive(Debug, Clone)]
244pub struct Backwards<'s>(pub &'s str);
245
246impl IsThirteen for Backwards<'_> {
247 fn thirteen(&self) -> bool {
248 self.0.eq_ignore_ascii_case("neetriht")
249 }
250}
251
252#[derive(Debug, Clone)]
254pub struct AtomicNumber<'s>(pub &'s str);
255
256impl IsThirteen for AtomicNumber<'_> {
257 fn thirteen(&self) -> bool {
258 self.0.eq_ignore_ascii_case("aluminum")
259 }
260}
261
262#[cfg(test)]
263mod lib_test;