簡體   English   中英

haskell 列表中的唯一元素

[英]unique elements in a haskell list

好的,這可能會出現在序曲中,但是:是否有標准庫 function 用於查找列表中的唯一元素? 為了澄清起見,我的(重新)實施是:

has :: (Eq a) => [a] -> a -> Bool
has [] _ = False
has (x:xs) a
  | x == a    = True
  | otherwise = has xs a

unique :: (Eq a) => [a] -> [a]
unique [] = []
unique (x:xs)
  | has xs x  = unique xs
  | otherwise = x : unique xs

我在Hoogle上搜索了(Eq a) => [a] -> [a]

第一個結果是nub (從列表中刪除重復的元素)。

Hoogle很棒。

Data.Listnub函數(不,實際上它不在Prelude中)絕對可以實現您想要的功能,但是它與您的unique函數並不完全相同。 它們都保留元素的原始順序,但unique保留每個元素的最后一次出現,而nub保留第一次的出現。

如果很重要,您可以執行此操作以使nub行為完全像unique一樣(盡管我覺得不是):

unique = reverse . nub . reverse

另外, nub僅適用於小型列表。 它的復雜度是二次的,因此如果您的列表可以包含數百個元素,則它開始變慢。

如果將類型限制為具有Ord實例的類型,則可以使其擴展性更好。 nub上的這種變化仍然保留了列表元素的順序,但是其復雜度為O(n * log n)

import qualified Data.Set as Set

nubOrd :: Ord a => [a] -> [a] 
nubOrd xs = go Set.empty xs where
  go s (x:xs)
   | x `Set.member` s = go s xs
   | otherwise        = x : go (Set.insert x s) xs
  go _ _              = []

實際上,已經建議nubOrd添加到Data.Set

import Data.Set (toList, fromList)
uniquify lst = toList $ fromList lst

我認為unique應該返回僅在原始列表中出現一次的元素列表; 也就是說,原始列表中出現多次的任何元素都不應包含在結果中。

我可以建議一個替代定義unique_alt:

    unique_alt :: [Int] -> [Int]
    unique_alt [] = []
    unique_alt (x:xs)
        | elem x ( unique_alt xs ) = [ y | y <- ( unique_alt xs ), y /= x ]
        | otherwise                = x : ( unique_alt xs )

以下是一些示例,這些示例突出了unique_alt和unqiue之間的區別:

    unique     [1,2,1]          = [2,1]
    unique_alt [1,2,1]          = [2]

    unique     [1,2,1,2]        = [1,2]
    unique_alt [1,2,1,2]        = []

    unique     [4,2,1,3,2,3]    = [4,1,2,3]
    unique_alt [4,2,1,3,2,3]    = [4,1]

我認為這可以做到。

unique [] = []
unique (x:xs) = x:unique (filter ((/=) x) xs)

刪除重復項的另一種方法:

unique :: [Int] -> [Int]
unique xs = [x | (x,y) <- zip xs [0..], x `notElem` (take y xs)]

Haskell中創建唯一列表的算法:

data Foo = Foo { id_ :: Int
               , name_ :: String
               } deriving (Show)

alldata = [ Foo 1 "Name"
          , Foo 2 "Name"
          , Foo 3 "Karl"
          , Foo 4 "Karl"
          , Foo 5 "Karl"
          , Foo 7 "Tim"
          , Foo 8 "Tim"
          , Foo 9 "Gaby"
          , Foo 9 "Name"
          ]

isolate :: [Foo] -> [Foo]
isolate [] = []
isolate (x:xs) = (fst f) : isolate (snd f)
  where
    f = foldl helper (x,[]) xs
    helper (a,b) y = if name_ x == name_ y
                     then if id_ x >= id_ y
                          then (x,b)
                          else (y,b)
                     else (a,y:b)

main :: IO ()
main = mapM_ (putStrLn . show) (isolate alldata)

輸出:

Foo {id_ = 9, name_ = "Name"}
Foo {id_ = 9, name_ = "Gaby"}
Foo {id_ = 5, name_ = "Karl"}
Foo {id_ = 8, name_ = "Tim"}

基於庫的解決方案:

我們可以使用 Haskell 編程風格,其中所有循環和遞歸活動都被推出用戶代碼並進入合適的庫函數。 所述庫函數通常以超出 Haskell 初學者技能的方式進行優化。

將問題分解為兩遍的方法如下:

  1. 生成與輸入列表平行的第二個列表,但具有適當標記的重復元素
  2. 從第二個列表中刪除標記為重復的元素

對於第一步,重復元素根本不需要值,所以我們可以使用[Maybe a]作為第二個列表的類型。 所以我們需要一個 function 類型:

pass1 :: Eq a => [a] -> [Maybe a]

Function pass1有狀態列表遍歷的示例,其中state是到目前為止看到的不同元素的列表(或集合)。 對於這類問題,庫提供了mapAccumL:: (s -> a -> (s, b)) -> s -> [a] -> (s, [b]) function。

這里的mapAccumL function 除了初始的 state 和輸入列表之外,還需要一個類型為s -> a -> (s, Maybe a)步驟 function參數。

如果當前元素x不是重復項,則步驟 function 的 output 是Just x並且x被添加到當前 state。如果 x 是重復項,則步驟 function 的 output 是Nothing ,並且 8827141483740 未更改。

ghci解釋器下測試:

$ ghci
 GHCi, version 8.8.4: https://www.haskell.org/ghc/  :? for help
 λ> 
 λ> stepFn s x = if (elem x s) then (s, Nothing) else (x:s, Just x)
 λ> 
 λ> import Data.List(mapAccumL)
 λ> 
 λ> pass1 xs = mapAccumL stepFn [] xs
 λ> 
 λ> xs2 = snd $ pass1 "abacrba"
 λ> xs2
 [Just 'a', Just 'b', Nothing, Just 'c', Just 'r', Nothing, Nothing]
 λ> 

寫一個pass2 function 就更容易了。 要過濾掉Nothing非值,我們可以使用:

import Data.Maybe( fromJust, isJust)
pass2 = (map fromJust) . (filter isJust)

但為什么要打擾呢? - 因為這正是catMaybes庫 function 所做的。

 λ> 
 λ> import Data.Maybe(catMaybes)
 λ> 
 λ> catMaybes xs2
 "abcr"
 λ> 

把它們放在一起:

總的來說,源碼可以寫成:

import Data.Maybe(catMaybes)
import Data.List(mapAccumL)

uniques :: (Eq a) => [a] -> [a]
uniques = let  stepFn s x = if (elem x s) then (s, Nothing) else (x:s, Just x)
          in   catMaybes . snd . mapAccumL stepFn []

這段代碼與無限列表相當兼容,有時被稱為“惰性友好”:

 λ> 
 λ> take 5 $ uniques $ "abacrba" ++ (cycle "abcrf")
 "abcrf"
 λ> 

效率說明:如果我們預計可以在輸入列表中找到許多不同的元素並且我們可以有一個Ord a實例,則state可以實現為一個Set object 而不是一個普通列表,這無需改變整體解決方案的結構。

這是一個僅使用 Prelude 函數的解決方案:

uniqueList theList =
if not (null theList)
    then head theList : filter (/= head theList) (uniqueList (tail theList))
    else []

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM