我正在尝试编写一个带有一对字母的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
辅助函数(我在这里只使用它) - 比它需要的更精细。
这个问题是否有更好,更惯用的方法;特别是利用我错过的语言功能,内置功能或库功能的功能?
答案 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) y
与foldr f y xs
相同。我几乎可以肯定这可以用更简单的方式重写。