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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
//! # Day 4: High-Entropy Passphrases
//!
//! A new system policy has been put in place that requires all accounts to use a **passphrase**
//! instead of simply a pass**word**. A passphrase consists of a series of words (lowercase letters)
//! separated by spaces.
//!
//! To ensure security, a valid passphrase must contain no duplicate words.
//!
//! For example:
//!
//! - `aa bb cc dd ee` is valid.
//! - `aa bb cc dd aa` is not valid - the word `aa` appears more than once.
//! - `aa bb cc dd aaa` is valid - `aa` and `aaa` count as different words.
//!
//! The system's full passphrase list is available as your puzzle input. **How many passphrases are
//! valid?**
//!
//! ## Part Two
//!
//! For added security, yet another system policy has been put in place. Now, a valid passphrase
//! must contain no two words that are anagrams of each other - that is, a passphrase is invalid if
//! any word's letters can be rearranged to form any other word in the passphrase.
//!
//! For example:
//!
//! - `abcde fghij` is a valid passphrase.
//! - `abcde xyz ecdab` is not valid - the letters from the third word can be rearranged to form the
//!   first word.
//! - `a ab abc abd abf abj` is a valid passphrase, because **all** letters need to be used when
//!   forming another word.
//! - `iiii oiii ooii oooi oooo` is valid.
//! - `oiii ioii iioi iiio` is not valid - any of these words can be rearranged to form any other
//!   word.
//!
//! Under this new system policy, **how many passphrases are valid?**

use std::{collections::HashSet, iter::FromIterator};

use anyhow::Result;
use itertools::Itertools;

pub const INPUT: &str = include_str!("d04.txt");

pub fn solve_part_one(input: &str) -> Result<u32> {
    let input = parse_input(input);

    Ok(input.iter().filter(|l| l.iter().unique().count() == l.len()).count() as u32)
}

pub fn solve_part_two(input: &str) -> Result<u32> {
    let input = parse_input(input);

    Ok(input
        .iter()
        .filter(|l| l.iter().map(|s| s.chars().sorted().collect_vec()).unique().count() == l.len())
        .count() as u32)
}

fn parse_input(input: &str) -> Vec<Vec<&str>> {
    input.lines().map(|l| l.split_whitespace().collect()).collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn part_one() {
        assert_eq!(1, solve_part_one("aa bb cc dd ee").unwrap());
        assert_eq!(0, solve_part_one("aa bb cc dd aa").unwrap());
        assert_eq!(1, solve_part_one("aa bb cc dd aaa").unwrap());
    }

    #[test]
    fn part_two() {
        assert_eq!(1, solve_part_two("abcde fghij").unwrap());
        assert_eq!(0, solve_part_two("abcde xyz ecdab").unwrap());
        assert_eq!(1, solve_part_two("a ab abc abd abf abj").unwrap());
        assert_eq!(1, solve_part_two("iiii oiii ooii oooi oooo").unwrap());
        assert_eq!(0, solve_part_two("oiii ioii iioi iiio").unwrap());
    }
}