【问题标题】:Exchanging multiple pairs of characters in a Haskell string在 Haskell 字符串中交换多对字符
【发布时间】:2015-08-31 18:04:07
【问题描述】:

我正在尝试编写一个 Haskell 函数,该函数接受一串成对的字母,并在所有字母的字符串中交换成对的字母,但我想出的东西感觉很尴尬和不习惯。

我有

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']

效果很好,给予

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

和

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

但我是 Haskell 的新手,我觉得我的方法——尤其是我的 swap 辅助函数(我只在这里使用)——比它需要的更复杂。

有没有更好、更惯用的方法来解决这个问题?尤其是利用了我错过的语言特性、内置函数或库函数的函数?

【问题讨论】:

  • 请注意,您可以将"ABCDE..." 写为['A'..'Z'],因为Char 是Enum 的一个实例。

标签: string haskell replace


【解决方案1】:

在替换列表上进行左折叠使代码更短:

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

如果您不在乎是先交换 EB 还是 RB,请放弃反向操作。

如果您想替换而不是交换:

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

【讨论】:

  • 没错。这也更优雅地突出了逻辑(即对的作用)。
【解决方案2】:

我会进一步分解问题。重要的是要记住较短的代码不一定是最好的代码。您的实现有效,但它太紧凑了,我无法快速理解。我会推荐类似的东西

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)

相当于您的sub 函数将是

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

但是对于大多数haskellers来说,前者可能更容易理解。您还可以更轻松地对交换规范进行更多转换,作为元组列表,使其整体更强大。您基本上将交换规范的表示与实际交换解耦。即使对于像这样的小程序,保持松散耦合也很重要,这样你才能养成编写大型程序的习惯!

此实现还避免为交换规范字符串重新计算 splitOn。

(我无法执行此代码,因为我在没有安装 Haskell 的计算机上,如果有人发现任何错误,请编辑修复。) 在 FPComplete 中尝试过,输出匹配@raxacoricofallapatorius'。

【讨论】:

  • 这有点太过分了(请参阅我对duplode's question 的评论)。
  • @raxacoricofallapatorius 在几乎所有情况下,您都会发现普遍接受的智慧是首先将您的输入解析为有用的数据结构,然后在另一个步骤中使用它。这是为了将执行解析的代码与执行处理的代码分开,因为通常更容易单独担心每个问题。另外,请注意我给出的最后一行代码,它显示了如何从. 分隔的字符串直接转到执行交换。它是由parseSwap 的小工具和sub :: [(Char, Char)] -&gt; String String 的定义构建而成的。
  • @raxacoricofallapatorius 为了用户方便,您仍然只能导出或使用采用交换规范而不是 [(Char, Char)] 的版本,但在内部您可以使用任何您想要的表示形式。这为您提供了实现程序内部的灵活性和能力。再举一个例子,你很少看到解析 JSON 或 XML 的代码与使用从 JSON 或 XML 文档获得的值交错,在任何处理之前总是有一个解析步骤。
【解决方案3】:

我在阅读您的代码时注意到了一些事情(我没有尝试重写它)。我的第一个建议涉及关注点分离:

我正在尝试编写一个 Haskell 函数,该函数接受一串成对的字母,并在所有字母的字符串中交换成对的字母

这意味着您的函数更自然的类型是:

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

或者,使用Data.Map 进行更高效的查找:

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

这比使用点分隔对获取字符串要精确得多。然后,您可以在单独的步骤中生成 Chars 之间的关联:

parseCharPairs :: String -> Map Char Char

理想情况下,您还应该处理无效输入(例如AB.CDE)和空输入字符串。

我的swap 辅助函数(我只在这里使用)

那么您可能应该在where 子句中定义它。我也会避免使用名称 swap,因为在 Data.Tuple 中有一个相对常见的功能,名称相同(swapLetters 可能是一个不错的选择)。

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

foldr (.) id (fmap f xs) y 与 foldr f y xs 相同。我几乎可以肯定这可以用更简单的方式重写。

【讨论】:

  • 这里的上下文主要是关于将由句点分隔的成对的字母字符串(这是一个约束)转换为已进行替换的字符串。在这种情况下,拥有中间结构(在我看来)没有帮助。这是为方便起见而提供的(用户)输入步骤的一部分。
  • @raxacoricofallapatorius 这可能对最终用户没有帮助,但肯定对作者(即您)有用。分解成更小的函数可以更容易地指定、理解和测试代码。对于最终用户,功能组合意味着您始终可以提供一个endUserSub = sub . parseCharPairs,一步完成所有操作。如果你认为你的用户不需要中间函数,你甚至不需要从你的模块中导出它们。
猜你喜欢
  • 2012-02-28
  • 2019-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-22
  • 1970-01-01
  • 2018-07-07
相关资源
最近更新 更多