2015-12-17 19 views
12

Ho un compito in Haskell (no, non sono i miei compiti, sto imparando per l'esame).rendere la funzione con 'se' point-free

Il compito è:

Write funzione senza punto numocc che conta le occorrenze di elemento in determinati liste. Ad esempio: numocc 1 [[1, 2], [2, 3, 2, 1, 1], [3]] = [1, 2, 0]

Questo è il mio codice:

addif :: Eq a => a -> Int -> a -> Int 
addif x acc y = if x == y then acc+1 else acc 

count :: Eq a => a -> [a] -> Int 
count = flip foldl 0 . addif 

numocc :: Eq a => a -> [[a]] -> [Int] 
numocc = map . count 

numocc e count sono 'point-libera', ma stanno usando la funzione addif che non è.

Non ho idea di come posso fare la funzione addif senza punti. C'è un modo per fare l'istruzione if senza punti? Forse c'è un trucco che non usa if?

+1

è possibile eseguire math-chenigans come 'let fxy = 1 - ceiling (fromIntegral (xy)/fromIntegral y) :: Int'? (che non è esattamente quello che ti serve;)) – Carsten

+0

ma '1 - ceiling (abs $ fromIntegral (xy)/fromIntegral (max xy)) :: Int' dovrebbe farlo se non mi mancassi qualche brutto caso di confine da qualche parte - forse vorrete ragionarci sopra o eseguire alcuni quickchecks;) (beh, mi mancano alcuni negativi quindi dovrete avere più addominali in ... ma il principio dovrebbe essere ovvio ... la vera soluzione è * banale * e sinistra per il lettore **: D **) – Carsten

+3

In generale è possibile sostituire un'istruzione 'if' con la funzione' bool :: Bool -> a -> a -> a; bool False f _ = f; bool True _t = t', nel qual caso puoi sempre formare un'espressione "normale" che può essere resa punto libero con i metodi regolari. – user2407038

risposta

10

userei il fatto che è possibile convertire facilmente un Bool a un Int utilizzando fromEnum:

addif x acc y = acc + fromEnum (x == y) 

Ora è possibile iniziare ad applicare i soliti trucchi per renderlo punto-libera

-- Go prefix and use $ 
addif x acc y = (+) acc $ fromEnum $ (==) x y 
-- Swap $ for . when dropping the last argument 
addif x acc = (+) acc . fromEnum . (==) x 

E così via. Non toglierò tutto il divertimento di renderlo punto libero, specialmente quando ci sono strumenti per farlo per te.

In alternativa, si potrebbe scrivere una funzione come

count x = sum . map (fromEnum . (==) x) 

che è quasi Point gratuito, e ci sono trucchi che ci si avvicina, anche se ottengono piuttosto brutta in fretta:

count = fmap fmap fmap sum map . fmap fmap fmap fromEnum (==) 

Here I pensa che in realtà sembra più bello usare fmap anziché (.), anche se è possibile sostituire ogni fmap con (.) e sarebbe lo stesso codice esatto.In sostanza, il (fmap fmap fmap) compone un singolo argomento e una funzione a due argomenti insieme, se invece dà il nome .: si potrebbe scrivere questo come

count = (sum .: map) . (fromEnum .: (==)) 

Ripartiti:

> :t fmap fmap fmap sum map 
Num a => (a -> b) -> [a] -> b 

quindi ci vuole una funzione da b a un numero a, un elenco di b s e restituisce un a, non troppo male.

> :t fmap fmap fmap fromEnum (==) 
Eq a => a -> a -> Int 

E questo tipo può essere scritto come Eq a => a -> (a -> Int), che è una cosa importante da notare. Questo rende il tipo di ritorno di questa funzione corrispondente all'ingresso a fmap fmap fmap sum map con b ~ Int, quindi possiamo comporli per ottenere una funzione di tipo Eq a => a -> [a] -> Int.

6

Un trucco sarebbe quello di importare uno dei molti if functions, ad es. Data.Bool.bool 1 0 (disponibile anche in Data.Bool.Extras).

Un trucco più arcano sarebbe quello di utilizzare Foreign.Marshal.Utils.fromBool, che fa esattamente quello che ti serve qui. O la stessa cosa, meno arcano: fromEnum (grazie @bheklilr).

Ma penso che il trucco più semplice sarebbe semplicemente quello di evitare di contare te stesso, e basta applicare la funzione standarddopo il numero filter per il numero.

+1

Perché non usare semplicemente 'fromEnum' invece della funzione' Foreign.Marshal'? Fa la stessa cosa ed esiste già in 'Preludio'. – bheklilr

+0

@bheklilr: Uh, non mi rendevo conto che 'Bool' è un'istanza di' Enum' - hai già avuto il mio upvote :-) 'Bool' mi è passato per la mente, l'avevo visto da qualche parte nel passato ... – Bergi

8

perché non

numocc x 
    = map (length . filter (== x)) 
    = map ((length .) (filter (== x))) 
    = map (((length .) . filter) (== x)) 
    = map (((length .) . filter) ((==) x)) 
    = map (((length .) . filter . (==)) x) 
    = (map . ((length .) . filter . (==))) x 
    = (map . (length .) . filter . (==)) x 

e poi il banale eta-contrazione.

+1

Penso che questo sia più conforme al compito descritto. –

+0

@ AndrásKovács l'approccio step-back ... :) cf. [questa recente voce Lisp] (http://stackoverflow.com/questions/34207933/print-adjacent-duplicates-of-a-list-scheme) per alcuni equivalenti complicati della testa della mappa. filter (not.null.drop 1). group'. –

1

Utilizzando l'istanza Enum per Bool, è possibile costruire una sostituzione pointfree per se che può essere utilizzato nei casi più generali:

chk :: Bool -> (a,a) -> a 
chk = ([snd,fst]!!) . fromEnum 

Uso chk possiamo definire una versione diversa di addIf:

addIf' :: Eq a => a -> a -> Int -> Int 
addIf' = curry (flip chk ((+1),id) . uncurry (==)) 

Ora possiamo semplicemente sostituire chk in addIf':

addIf :: Eq a => a -> a -> Int -> Int 
addIf = curry (flip (([snd,fst]!!) . fromEnum) ((+1),id) . uncurry (==)) 
0

Penso che stiate cercando Data.Bool 's bool, che esiste dal 4.7.0.0 (2014-04-08).

incif :: (Eq a, Enum counter) => a -> a -> counter -> counter 
incif = ((bool id succ) .) . (==) 

L'ulteriore . permette == prendere due parametri, prima di passare l'espressione bool.

Dal momento che l'ordine dei parametri è diverso, è necessario utilizzare incif come questo:

(flip . incif) 

(Integrazione che inincif è lasciato come esercizio per il lettore [Traduzione:. Non è banale, e non so ancora come;;)

Problemi correlati