2019-12-18 16:32:23 +01:00
use grid ::* ;
use std ::char ;
2019-12-18 21:03:14 +01:00
use std ::collections ::{ HashMap , HashSet } ;
2019-12-18 16:32:23 +01:00
use std ::io ::{ self , BufRead } ;
2019-12-18 21:03:14 +01:00
use std ::sync ::Mutex ;
use std ::ops ::Mul ;
2019-12-18 16:32:23 +01:00
#[ macro_use ]
extern crate lazy_static ;
2019-12-18 21:03:14 +01:00
#[ derive(Hash, PartialEq, Eq, Clone, Debug) ]
struct Door ( char ) ;
#[ derive(Hash, PartialEq, Eq, Clone, Debug) ]
struct Key ( char ) ;
2019-12-18 16:32:23 +01:00
lazy_static! {
2019-12-18 21:03:14 +01:00
static ref MAP : Mutex < HashMap < Position2D , char > > = Mutex ::new (
io ::stdin ( )
. lock ( )
. lines ( )
2019-12-18 16:32:23 +01:00
. enumerate ( )
2019-12-18 21:03:14 +01:00
. flat_map ( move | ( y , l ) | l
. unwrap ( )
. to_owned ( )
. chars ( )
. enumerate ( )
. map ( move | ( x , c ) | ( ( x , y ) . into ( ) , c ) )
. collect ::< Vec < _ > > ( ) )
. collect ( )
) ;
}
fn get ( p : & Position2D ) -> Option < char > {
MAP . lock ( ) . unwrap ( ) . get ( p ) . copied ( )
2019-12-18 16:32:23 +01:00
}
2019-12-18 21:03:14 +01:00
fn visit_neighbors (
position : Position2D ,
steps : usize ,
distances : & mut HashMap < Position2D , usize > ,
dependencies : & mut HashMap < Key , ( HashSet < Key > , HashSet < Door > ) > ,
door_dependencies : & mut HashMap < Door , HashSet < Door > > ,
mut doors : HashSet < Door > ,
mut keys : HashSet < Key > ,
) {
let c = get ( & position ) . unwrap ( ) ;
if c . is_alphabetic ( ) {
if c . is_lowercase ( ) {
dependencies . insert ( Key { 0 : c } , ( keys . clone ( ) , doors . clone ( ) ) ) ;
keys . insert ( Key { 0 : c } ) ;
}
if c . is_uppercase ( ) {
door_dependencies . insert ( Door { 0 : c } , doors . clone ( ) ) ;
doors . insert ( Door { 0 : c } ) ;
}
}
2019-12-18 16:32:23 +01:00
let mut unvisited = Vec ::new ( ) ;
// TODO: fix ownership and make this a single filter().inspect().for_each()
for ( _ , p ) in & position . neighbors ( ) {
2019-12-18 21:03:14 +01:00
match get ( p ) {
Some ( '#' ) = > ( ) ,
2019-12-18 16:32:23 +01:00
_ = > {
if distances . get ( p ) . is_none ( ) {
unvisited . push ( p . to_owned ( ) ) ;
}
2019-12-18 21:03:14 +01:00
}
2019-12-18 16:32:23 +01:00
}
2019-12-18 21:03:14 +01:00
if get ( p ) ! = Some ( '#' ) & & distances . get ( p ) . is_none ( ) {
2019-12-18 16:32:23 +01:00
unvisited . push ( p . to_owned ( ) ) ;
}
}
/*
unvisited . into_iter ( )
. inspect ( | p | { distances . insert ( p . to_owned ( ) , steps + 1 ) ; } )
. for_each ( | p | visit_neighbors ( p . to_owned ( ) , steps + 1 , distances ) ) ;
* /
for p in & unvisited {
2019-12-18 21:03:14 +01:00
distances . insert ( p . to_owned ( ) , steps + 1 ) ;
2019-12-18 16:32:23 +01:00
}
for p in & unvisited {
2019-12-18 21:03:14 +01:00
visit_neighbors (
p . to_owned ( ) ,
steps + 1 ,
distances ,
dependencies ,
door_dependencies ,
doors . clone ( ) ,
keys . clone ( ) ,
) ;
2019-12-18 16:32:23 +01:00
}
}
2019-12-18 21:03:14 +01:00
fn traverse (
start : Position2D ,
) -> (
HashMap < Position2D , usize > ,
HashMap < Key , ( HashSet < Key > , HashSet < Door > ) > ,
HashMap < Door , HashSet < Door > > ,
) {
2019-12-18 16:32:23 +01:00
let mut distances = HashMap ::new ( ) ;
let mut dependencies = HashMap ::new ( ) ;
2019-12-18 21:03:14 +01:00
let mut door_dependencies = HashMap ::new ( ) ;
2019-12-18 16:32:23 +01:00
distances . insert ( start , 0 usize ) ;
2019-12-18 21:03:14 +01:00
visit_neighbors (
start ,
0 ,
& mut distances ,
& mut dependencies ,
& mut door_dependencies ,
HashSet ::new ( ) ,
HashSet ::new ( ) ,
) ;
( distances , dependencies , door_dependencies )
}
fn find_keys_at_dead_ends ( deps : & HashMap < Key , ( HashSet < Key > , HashSet < Door > ) > ) -> HashSet < Key > {
let keydeps : HashSet < _ > = deps . iter ( ) . flat_map ( | ( _ , ( keys , _ ) ) | keys ) . collect ( ) ;
deps . keys ( )
. collect ::< HashSet < _ > > ( )
. difference ( & keydeps )
. into_iter ( )
. map ( | k | k . to_owned ( ) . to_owned ( ) )
. collect ( )
}
fn find ( c : char ) -> Option < Position2D > {
let map = MAP . lock ( ) . unwrap ( ) ;
map . keys ( ) . find ( | p | map . get ( & p ) = = Some ( & c ) ) . copied ( )
}
fn clear_field ( p : & Position2D ) {
MAP . lock ( ) . unwrap ( ) . insert ( * p , '.' ) ;
}
fn remove ( k : Key ) {
let key_pos = find ( k . 0 ) . unwrap ( ) ;
clear_field ( & key_pos ) ;
if let Some ( door_pos ) = find ( k . 0. to_uppercase ( ) . next ( ) . unwrap ( ) ) {
clear_field ( & door_pos ) ;
}
}
fn distance_to_door ( k : & Key ) -> usize {
let ( distances , _ , _ ) = traverse ( find ( k . 0 ) . unwrap ( ) ) ;
let door_pos = find ( k . 0. to_uppercase ( ) . next ( ) . unwrap ( ) ) ;
* door_pos . map ( | p | distances . get ( & p ) . unwrap ( ) ) . unwrap_or ( & 10 )
}
fn distance_to_locked_door ( k : & Key ) -> usize {
let ( distances , _ , doors ) = traverse ( find ( k . 0 ) . unwrap ( ) ) ;
* doors . keys ( )
. map ( | d | find ( d . 0 ) . unwrap ( ) )
. map ( | p | distances . get ( & p ) . unwrap_or ( & 9999999999999 ) )
. min ( )
. unwrap_or ( & 0 )
2019-12-18 16:32:23 +01:00
}
fn main ( ) {
2019-12-18 21:03:14 +01:00
let mut pos = find ( '@' ) . unwrap ( ) ;
let mut steps = 0 ;
let ( _ , dependencies , _ ) = traverse ( pos . to_owned ( ) ) ;
let mut i = 0 ;
while i < dependencies . len ( ) {
println! ( " {} " , i ) ;
let ( distances , dependencies , door_dependencies ) = traverse ( pos . to_owned ( ) ) ;
//dbg!(distances.get(&find('c').unwrap()));
let reachable_doors : HashSet < _ > = door_dependencies
. iter ( )
. filter_map ( | ( door , doors ) | if doors . is_empty ( ) { Some ( door ) } else { None } )
. collect ( ) ;
let useful_keys = dependencies
. iter ( )
. filter ( | ( _ , ( _ , d ) ) | d . is_empty ( ) )
. filter ( | ( k , _ ) | {
reachable_doors . contains ( & Door {
0 : k . 0. to_uppercase ( ) . next ( ) . unwrap ( ) ,
} )
} )
//.min_by_key(|(k, _)| distances.get(&find(k.0).unwrap()))
//.unwrap()
. map ( | ( k , _ ) | ( k , distances . get ( & find ( k . 0 ) . unwrap ( ) ) . unwrap ( ) . mul ( 2 ) ) ) ;
//.min_by_key(|k| )
let next : Key = find_keys_at_dead_ends ( & dependencies )
. iter ( )
. filter ( | k | dependencies . get ( k ) . unwrap ( ) . 1. is_empty ( ) )
. map ( | k | ( k , distances . get ( & find ( k . 0 ) . unwrap ( ) ) . unwrap ( ) . mul ( 1 ) ) )
//.min_by_key(|k| distances.get(&find(k.0).unwrap()))
. chain ( useful_keys )
. min_by_key ( | ( k , n ) | * n ) // + distance_to_locked_door(&k))// * 100 + k.0 as usize)
. expect ( " no candidate found " )
. 0
. to_owned ( ) ;
dbg! ( & next ) ;
let next_pos = find ( next . 0 ) . unwrap ( ) ;
steps + = distances . get ( & next_pos ) . unwrap ( ) ;
pos = next_pos ;
for k in & dependencies . get ( & next ) . unwrap ( ) . 0 {
dbg! ( & k ) ;
remove ( k . to_owned ( ) ) ;
i + = 1 ;
}
remove ( next ) ;
i + = 1 ;
dbg! ( & i ) ;
dbg! ( & steps ) ;
}
println! ( " {} " , steps ) ;
2019-12-18 16:32:23 +01:00
}