Exchanging multiple pairs of characters in a Haske

2019-07-08 08:14发布

I'm trying to write a Haskell function that takes a string of pairs of letters, and exchanges the letters of the pair in a string of all letters, but what I've come up with feels awkward and unidiomatic.

I have

swap a b = map (\x-> if x == a then b else if x == b then a else x)
sub n = foldr (.) id (zipWith swap (head <$> splitOn "." n) (last <$> splitOn "." n)) ['A'..'Z']

which works well enough giving

> sub "RB.XD.EU.ZM.IJ"
"ARCXUFGHJIKLZNOPQBSTEVWDYM"

and

> sub "YC.LU.EB.TZ.RB.XD.IJ"
"ARYXBFGHJIKUMNOPQESZLVWDCT"

but I'm new to Haskell and feel like my approach — especially my swap helper function (which I only use here) — is more elaborate than it needs to be.

Is there a better, more idiomatic, approach to this problem; especially one that takes advantage of a language feature, builtin, or library function that I've missed?

3条回答
SAY GOODBYE
2楼-- · 2019-07-08 08:24

Doing a left fold over the substitution list makes the code shorter:

import Data.List
import Data.List.Split

sub = foldl' swap ['A'..'Z'] . splitOn "." . reverse
  where
    swap az [a,b] = map (\x -> if x == a then b else if x == b then a else x) az

Drop the reverse if you don't care whether EB or RB is swapped first.

If you'd want to replace instead of a swap:

import Data.List
import Data.List.Split

replace needle replacement haystack =
    intercalate replacement (splitOn needle haystack)

rep = foldl' replace' ['A'..'Z'] . splitOn "."
    where
        replace' az [a,b] = replace [a] [b] az
查看更多
Explosion°爆炸
3楼-- · 2019-07-08 08:45

Some things I noticed from reading your code (I haven't tried to rewrite it). My first suggestion involves separation of concerns:

I'm trying to write a Haskell function that takes a string of pairs of letters, and exchanges the letters of the pair in a string of all letters

That means the a more natural type for your function would be:

sub :: [(Char, Char)] -> String -> String

Or, using Data.Map for more efficient lookups:

sub :: Map Char Char -> String -> String

Which is a lot more precise than taking a string with dot-separated pairs. You can then generate the associations between Chars in a separate step:

parseCharPairs :: String -> Map Char Char

Ideally you should also handle invalid inputs (e.g. AB.CDE) and empty input strings.

my swap helper function (which I only use here)

Then you probably should define it in a where clause. I would also avoid the name swap, as there is a relatively common function in Data.Tuple with the same name (swapLetters might be a nice choice).

sub n = foldr (.) id -- etc.

foldr (.) id (fmap f xs) y is the same thing as foldr f y xs. I'm almost certain this can be rewritten in a simpler way.

查看更多
Fickle 薄情
4楼-- · 2019-07-08 08:48

I'd break the problem down a bit more. It's important to remember that shorter code is not necessarily the best code. Your implementation works, but it's too compact for me to quickly understand. I'd recommend something more like

import Data.Maybe (mapMaybe)

swap = undefined -- Your current implementation is fine,
                 -- although you could rewrite it using
                 -- a local function instead of a lambda

-- |Parses the swap specification string into a list of
-- of characters to swap as tuples
parseSwap :: String -> [(Char, Char)]
parseSwap = mapMaybe toTuple . splitOn "."
    where
        toTuple (first:second:_) = Just (first, second)
        toTuple _ = Nothing

-- |Takes a list of characters to swap and applies it
-- to a target string
sub :: [(Char, Char)] -> String -> String
sub charsToSwap = foldr (.) id (map (uncurry swap) charsToSwap)

The equivalent to your sub function would be

sub swapSpec = foldr (.) id (map (uncurry swap) $ parseSwap swapSpec)

But the former is probably easier to understand for most haskellers. You could also do more transformations more easily to your swap specification as a list of tuples making it more powerful overall. You essentially decouple the representation of the swap specification and the actual swapping. Even for small programs like this it's important to maintain loose coupling so that you develop a habit for when you write larger programs!

This implementation also avoids recalculating splitOn for the swap specification string.

(I wasn't able to execute this code because I'm on a computer without Haskell installed, if anyone notices any bugs please edit to fix.) Tried it out in FPComplete, output matches @raxacoricofallapatorius'.

查看更多
登录 后发表回答