optimize bfs

This commit is contained in:
JasterV 2025-03-23 20:40:09 +01:00
commit d5778ab5e5
5 changed files with 65 additions and 43 deletions

View file

@ -38,6 +38,9 @@ library
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints
build-depends: build-depends:
base >=4.7 && <5 base >=4.7 && <5
, bytestring
, hashable
, unordered-containers
default-language: Haskell2010 default-language: Haskell2010
executable ladder-exe executable ladder-exe
@ -51,7 +54,10 @@ executable ladder-exe
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N
build-depends: build-depends:
base >=4.7 && <5 base >=4.7 && <5
, bytestring
, hashable
, ladder , ladder
, unordered-containers
default-language: Haskell2010 default-language: Haskell2010
test-suite ladder-test test-suite ladder-test
@ -66,5 +72,8 @@ test-suite ladder-test
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N
build-depends: build-depends:
base >=4.7 && <5 base >=4.7 && <5
, bytestring
, hashable
, ladder , ladder
, unordered-containers
default-language: Haskell2010 default-language: Haskell2010

View file

@ -21,6 +21,9 @@ description: Please see the README on GitHub at <https://github.com/gith
dependencies: dependencies:
- base >= 4.7 && < 5 - base >= 4.7 && < 5
- unordered-containers
- hashable
- bytestring
ghc-options: ghc-options:
- -Wall - -Wall

View file

@ -22,57 +22,58 @@ module Graph
) )
where where
import qualified Data.AssocMap as M
import Data.Bifunctor (second) import Data.Bifunctor (second)
import Data.Function ((&)) import Data.Function ((&))
import qualified Data.HashMap.Lazy as M
import Data.Hashable (Hashable)
import qualified Data.List as L import qualified Data.List as L
type DiGraph a = M.AssocMap a [a] type DiGraph a = M.HashMap a [a]
empty :: DiGraph a empty :: DiGraph a
empty = M.empty empty = M.empty
hasNode :: (Eq a) => DiGraph a -> a -> Bool hasNode :: (Hashable a) => DiGraph a -> a -> Bool
hasNode = flip M.member hasNode = flip M.member
addNode :: (Eq a) => DiGraph a -> a -> DiGraph a addNode :: (Hashable a) => DiGraph a -> a -> DiGraph a
addNode graph node = M.insert node [] graph addNode graph node = M.insert node [] graph
addEdge :: (Eq a) => (a, a) -> DiGraph a -> DiGraph a addEdge :: (Hashable a) => (a, a) -> DiGraph a -> DiGraph a
addEdge (node, child) = M.alter insertEdge node addEdge (node, child) = M.alter insertEdge node
where where
insertEdge Nothing = Just [child] insertEdge Nothing = Just [child]
insertEdge (Just nodes) = Just $ L.nub (child : nodes) insertEdge (Just nodes) = Just $ L.nub (child : nodes)
addEdges :: (Eq a) => [(a, a)] -> DiGraph a -> DiGraph a addEdges :: (Hashable a) => [(a, a)] -> DiGraph a -> DiGraph a
addEdges edges graph = foldr addEdge graph edges addEdges edges graph = foldr addEdge graph edges
buildDiGraph :: (Eq a) => [(a, [a])] -> DiGraph a buildDiGraph :: (Hashable a) => [(a, [a])] -> DiGraph a
buildDiGraph = foldr (\(node, childs) -> M.insert node (L.nub childs)) M.empty buildDiGraph = foldr (\(node, childs) -> M.insert node (L.nub childs)) M.empty
children :: (Eq a) => a -> DiGraph a -> [a] children :: (Hashable a) => a -> DiGraph a -> [a]
children = M.findWithDefault [] children = M.findWithDefault []
deleteNode :: (Eq a) => a -> DiGraph a -> DiGraph a deleteNode :: (Hashable a) => a -> DiGraph a -> DiGraph a
deleteNode = M.delete deleteNode = M.delete
deleteNodes :: (Eq a) => [a] -> DiGraph a -> DiGraph a deleteNodes :: (Hashable a) => [a] -> DiGraph a -> DiGraph a
deleteNodes nodes graph = foldr deleteNode graph nodes deleteNodes nodes graph = foldr deleteNode graph nodes
deleteEdge :: (Eq a) => (a, a) -> DiGraph a -> DiGraph a deleteEdge :: (Hashable a) => (a, a) -> DiGraph a -> DiGraph a
deleteEdge (node, child) = M.alter aux node deleteEdge (node, child) = M.alter aux node
where where
aux Nothing = Nothing aux Nothing = Nothing
aux (Just nodes) = Just (L.delete child nodes) aux (Just nodes) = Just (L.delete child nodes)
deleteEdges :: (Eq a) => [(a, a)] -> DiGraph a -> DiGraph a deleteEdges :: (Hashable a) => [(a, a)] -> DiGraph a -> DiGraph a
deleteEdges edges graph = foldr deleteEdge graph edges deleteEdges edges graph = foldr deleteEdge graph edges
type SearchState a = ([a], DiGraph a, DiGraph a) type SearchState a = ([a], DiGraph a, DiGraph a)
type SearchResult a = Maybe (DiGraph a) type SearchResult a = Maybe (DiGraph a)
bfsSearch :: forall a. (Eq a) => DiGraph a -> a -> a -> Maybe [a] bfsSearch :: forall a. (Hashable a) => DiGraph a -> a -> a -> Maybe [a]
bfsSearch initialGraph start end bfsSearch initialGraph start end
| start == end = Just [start] | start == end = Just [start]
| otherwise = findSolution <$> bfsSearch' ([start], initialGraph, empty) | otherwise = findSolution <$> bfsSearch' ([start], initialGraph, empty)
@ -113,7 +114,7 @@ bfsSearch initialGraph start end
else else
bfsSearch' (frontier', graph', predecessors') bfsSearch' (frontier', graph', predecessors')
dfsSearch :: forall a. (Eq a) => DiGraph a -> a -> a -> Maybe [a] dfsSearch :: forall a. (Hashable a) => DiGraph a -> a -> a -> Maybe [a]
dfsSearch initialGraph start end = dfsSearch initialGraph start end =
case dfsSearch' initialGraph start of case dfsSearch' initialGraph start of
Right path -> Just path Right path -> Just path

View file

@ -1,39 +1,47 @@
module Ladder (readDictionary, ladderSolve) where module Ladder (readDictionary, ladderSolve) where
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as C
import Data.Char (isLower) import Data.Char (isLower)
import qualified Data.List as L import qualified Data.List as L
import qualified Graph as G import qualified Graph as G
import qualified PermutationMap as PM import qualified PermutationMap as PM
import Prelude hiding (lines, words) import Prelude hiding (lines, words)
type Dictionary = [String] type Dictionary = [BS.ByteString]
ladderSolve :: Dictionary -> String -> String -> Maybe [String] ladderSolve :: Dictionary -> String -> String -> Maybe [BS.ByteString]
ladderSolve dict start end = ladderSolve dict start end =
let graph = mkLadderGraph dict let graph = mkLadderGraph dict
in G.bfsSearch graph start end in G.bfsSearch graph (C.pack start) (C.pack end)
readDictionary :: String -> IO Dictionary readDictionary :: String -> IO Dictionary
readDictionary filename = do readDictionary filepath = do
text <- readFile filename text <- C.readFile filepath
let lines = L.lines text let lines = C.lines text
let lowercaseWords = map (filter isLower) lines let lowercaseWords = map (C.filter isLower) lines
return (L.nub lowercaseWords) return lowercaseWords
mkLadderGraph :: Dictionary -> G.DiGraph String mkLadderGraph :: Dictionary -> G.DiGraph BS.ByteString
mkLadderGraph dict = G.buildDiGraph [(word, computeCandidates word) | word <- dict] mkLadderGraph dict = G.buildDiGraph [(word, computeCandidates word) | word <- dict]
where where
permMap = PM.createPermutationMap dict permMap = PM.createPermutationMap dict
computeCandidates :: String -> [String] computeCandidates :: BS.ByteString -> [BS.ByteString]
computeCandidates word = computeCandidates word =
-- Delete the original word from the permutations list -- Delete the original word from the permutations list
L.delete word permutations L.delete word permutations
where where
removed = [L.delete c word | c <- word] added = [C.cons c word | c <- ['a' .. 'z']]
added = [c : word | c <- ['a' .. 'z']] removed = [delete c word | c <- C.unpack word]
modified = [x : L.delete y word | x <- ['a' .. 'z'], y <- word, x /= y] modified =
[C.cons x (delete y word) | x <- ['a' .. 'z'], y <- C.unpack word, x /= y]
-- Sort and deduplicate all the candidates -- Sort and deduplicate all the candidates
candidates = L.nub $ map L.sort (added ++ removed ++ modified ++ [word]) candidates = added ++ removed ++ modified ++ [word]
-- For each candidate, lookup all its permutations -- For each candidate, lookup all its permutations
permutations = L.concatMap (\w -> PM.findWithDefault [] w permMap) candidates permutations = L.concatMap (\w -> PM.findWithDefault [] w permMap) candidates
delete :: Char -> BS.ByteString -> BS.ByteString
delete ch string = case C.uncons string of
Just (x, xs) -> if ch == x then xs else C.cons x (delete ch xs)
Nothing -> C.empty

View file

@ -11,35 +11,36 @@ module PermutationMap
) )
where where
import qualified Data.AssocMap as M import qualified Data.ByteString as BS
import Data.List (nub, sort) import qualified Data.HashMap.Lazy as M
import Data.List (nub)
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Prelude hiding (lookup) import Prelude hiding (lookup)
type PermutationMap = M.AssocMap String [String] type PermutationMap = M.HashMap BS.ByteString [BS.ByteString]
empty :: PermutationMap empty :: PermutationMap
empty = M.empty empty = M.empty
member :: String -> PermutationMap -> Bool member :: BS.ByteString -> PermutationMap -> Bool
member key = M.member (sort key) member key = M.member (BS.sort key)
alter :: (Maybe [String] -> Maybe [String]) -> String -> PermutationMap -> PermutationMap alter :: (Maybe [BS.ByteString] -> Maybe [BS.ByteString]) -> BS.ByteString -> PermutationMap -> PermutationMap
alter f key = M.alter f (sort key) alter f key = M.alter f (BS.sort key)
delete :: String -> PermutationMap -> PermutationMap delete :: BS.ByteString -> PermutationMap -> PermutationMap
delete key = M.delete (sort key) delete key = M.delete (BS.sort key)
insert :: String -> [String] -> PermutationMap -> PermutationMap insert :: BS.ByteString -> [BS.ByteString] -> PermutationMap -> PermutationMap
insert key = M.insert (sort key) insert key = M.insert (BS.sort key)
lookup :: String -> PermutationMap -> Maybe [String] lookup :: BS.ByteString -> PermutationMap -> Maybe [BS.ByteString]
lookup key = M.lookup (sort key) lookup key = M.lookup (BS.sort key)
findWithDefault :: [String] -> String -> PermutationMap -> [String] findWithDefault :: [BS.ByteString] -> BS.ByteString -> PermutationMap -> [BS.ByteString]
findWithDefault defaultValue key pmap = fromMaybe defaultValue (PermutationMap.lookup key pmap) findWithDefault defaultValue key pmap = fromMaybe defaultValue (PermutationMap.lookup key pmap)
createPermutationMap :: [String] -> PermutationMap createPermutationMap :: [BS.ByteString] -> PermutationMap
createPermutationMap = aux empty createPermutationMap = aux empty
where where
aux permMap [] = permMap aux permMap [] = permMap