在Haskell字符串中交换多对字符

时间:2015-08-31 18:04:07

标签: string haskell replace

我正在尝试编写一个带有一对字母的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辅助函数(我在这里只使用它) - 比它需要的更精细。

这个问题是否有更好,更惯用的方法;特别是利用我错过的语言功能,内置功能或库功能的功能?

3 个答案:

答案 0 :(得分: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'。

答案 1 :(得分:2)

在替换列表上进行左侧折叠会缩短代码:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:isScrollContainer="false"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="fill_vertical"
        android:clipToPadding="false"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:paddingTop="1dp">

        <ListView
            android:id="@+id/list"
            android:layout_width="fill_parent"
            android:layout_height="match_parent"
            android:divider="@null" />
        </LinearLayout>
</android.support.v4.widget.NestedScrollView>

如果您不在乎是先交换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

答案 2 :(得分:1)

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

  

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

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

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

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

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

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

parseCharPairs :: String -> Map Char Char

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

  

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

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

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

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