2013-01-03 13 views
5

Ich möchte ein Element in einer Liste mit einem neuen Wert nur beim ersten Auftreten ersetzen. Ich habe den folgenden Code geschrieben, aber bei der Verwendung ändern sich alle übereinstimmenden Elemente.Ersetzen Sie ein Element in einer Liste nur einmal - Haskell

replaceX :: [Int] -> Int -> Int -> [Int] 
replaceX items old new = map check items where 
check item | item == old = new 
      | otherwise = item 

Wie kann ich den Code so ändern, dass die Änderung nur beim ersten übereinstimmenden Element erfolgt?

Vielen Dank für Ihre Hilfe!

Antwort

11

Der Punkt ist, dass map und f (check in Ihrem Beispiel) nur in Bezug auf die Kommunikation, wie die einzelnen Elemente zu transformieren. Sie kommunizieren nicht darüber, wie weit die Liste zum Transformieren von Elementen reicht: map geht immer den ganzen Weg bis zum Ende weiter.

map :: (a -> b) -> [a] -> [b] 
map _ []  = [] 
map f (x:xs) = f x : map f xs 

Lassen Sie uns eine neue Version von map schreiben --- ich es mapOnce nennen werde, weil ich nicht einen besseren Namen denken kann.

mapOnce :: (a -> Maybe a) -> [a] -> [a] 

Es gibt zwei Dinge über diese Art Signatur zu beachten:

  1. Weil wir f Teil Art und Weise der Anwendung auf der Liste stoppen kann, die Eingangsliste und die Ausgabeliste müssen den gleichen Typ haben . (Mit map, weil die gesamte Liste wird immer abgebildet werden, kann die Art ändern.)

  2. Die Art der f nicht zu a -> a verändert, aber zu a -> Maybe a.

    • Nothing bedeutet "lassen Sie dieses Element unverändert, weiter unten die Liste"
    • Just y bedeutet "dieses Element ändern, und lassen Sie die übrigen Elemente unverändert"

So:

mapOnce _ []  = [] 
mapOnce f (x:xs) = case f x of 
     Nothing -> x : mapOnce f xs 
     Just y -> y : xs 

Ihr Beispiel ist jetzt:

replaceX :: [Int] -> Int -> Int -> [Int] 
replaceX items old new = mapOnce check items where 
    check item | item == old = Just new 
       | otherwise = Nothing 
+1

Vielen Dank für die genaue Erklärung! Ich habe viel gelernt. – Afflatus

4

Sie können ganz einfach dies als eine rekursive Iteration schreiben wie folgt:

rep :: Eq a => [a] -> a -> a -> [a] 
rep items old new = rep' items 
    where rep' (x:xs) | x == old = new : xs 
         | otherwise = x : rep' xs 
      rep' [] = [] 
+0

Ich bin ziemlich neu in Haskell. Darf ich fragen, was bedeutet "Eq a =>"? – Afflatus

+0

Das ist eine Art Einschränkung; es bedeutet, dass "a" eine Instanz der Eq-Typklasse sein muss. Es wird benötigt, damit '==' verwendet werden kann. –

4

Eine direkte Implementierung würde

rep :: Eq a => a -> a -> [a] -> [a] 
rep _ _ [] = [] 
rep a b (x:xs) = if x == a then b:xs else x:rep a b xs 

sein mag ich Liste als letztes Argument etwas wie

myRep = rep 3 5 . rep 7 8 . rep 9 1 
2

Vielleicht nicht die schnellste Lösung zu tun, aber leicht zu verstehen:

rep xs x y = 
    let (left, (_ : right)) = break (== x) xs 
    in left ++ [y] ++ right 

[Edi t]

Wie Dave kommentierte, wird dies fehlschlagen, wenn x nicht in der Liste ist. Eine sichere Version wäre:

rep xs x y = 
    let (left, right) = break (== x) xs 
    in left ++ [y] ++ drop 1 right 

[Bearbeiten]

Arrgh !!!

rep xs x y = left ++ r right where 
    (left, right) = break (== x) xs 
    r (_:rs) = y:rs 
    r [] = [] 
+0

Obwohl die Musterübereinstimmung fehlschlägt, sollte 'x' nicht in' xs' vorkommen. – dave4420

+0

Zum Bearbeiten: Sollte 'x' nicht in' xs' vorkommen, gibt Ihre sichere Version 'xs ++ [y]' zurück. Dies ist nicht das, was die anderen Lösungen tun. Ich sage nicht, dass das falsch ist (obwohl ich nicht denke, dass es das ist, was das OP will, zweifellos gibt es einige Situationen, in denen dieses Verhalten korrekt ist), aber ich denke, es ist erwähnenswert. – dave4420

+0

Hier ist ein Hack: 'rep xs x y = lassen (links, rechts) = break (== x) xs; (ja, nein) = splitAt 1 rechts in links ++ [y | _ <- ja] ++ nein. –

3

Um ehrlich zu sein, mag ich die meisten der Antworten bisher nicht. dave4420 präsentiert einige nette Einblicke auf map, dass ich Sekunde, aber ich mag auch nicht seine Lösung.

Warum mag ich diese Antworten nicht? Denn Sie sollten lernen, Probleme wie diese zu lösen, indem Sie sie in kleinere Probleme aufteilen, die durch einfachere Funktionen, vorzugsweise Bibliotheksfunktionen, gelöst werden können. In diesem Fall ist die Bibliothek Data.List, und die Funktion ist break:

break, zu einem Prädikat angewandt p und eine Liste xs, ein Tupel zurück, wo die erste Element am längsten Präfix ist (möglicherweise leere) von xs von Elementen das nicht und zweites Element ist der Rest der Liste.

Bewaffnet mit, dass, können wir das Problem wie folgt angreifen:

  1. Split die Liste in zwei Teile: alle Elemente vor dem ersten Auftreten von old, und der Rest.
  2. Die Liste "rest" ist entweder leer oder ihr erstes Element ist das erste Vorkommen von old. Beide Fälle sind einfach zu handhaben.

So haben wir diese Lösung:

import Data.List (break) 

replaceX :: Eq a => a -> a -> [a] -> [a] 
replaceX old new xs = beforeOld ++ replaceFirst oldAndRest 
    where (beforeOld, oldAndRest) = break (==old) xs 
      replaceFirst [] = [] 
      replaceFirst (_:rest) = new:rest 

Beispiel:

*Main> replaceX 5 7 ([1..7] ++ [1..7]) 
[1,2,3,4,7,6,7,1,2,3,4,5,6,7] 

Also mein Rat an Sie:

  1. Erfahren Sie, wie Bibliotheken zu importieren.
  2. Studieren Sie die Bibliotheksdokumentation und lernen Sie Standardfunktionen. Data.List ist ein großartiger Ort, um anzufangen.
  3. Versuchen Sie, diese Bibliotheksfunktionen so oft wie möglich zu verwenden.
  4. Als eine Selbstlernübung können Sie einige der Standardfunktionen von Data.List auswählen und Ihre eigenen Versionen davon schreiben.
  5. Wenn Sie auf ein Problem stoßen, das nicht mit einer Kombination von Bibliotheksfunktionen gelöst werden kann, versuchen Sie, Ihre eigene generische Funktion zu erfinden, die nützlich wäre.

EDIT: Ich habe erkannt, dass break tatsächlich eine Prelude Funktion ist, und muss nicht importiert werden. Dennoch ist Data.List eine der besten zu studierenden Bibliotheken.

2

Eine Alternative mit der Lens library.

>import Control.Lens 
>import Control.Applicative 

>_find :: (a -> Bool) -> Simple Traversal [a] a         
>_find _ _ [] = pure []               
>_find pred f (a:as) = if pred a             
>      then (: as) <$> f a          
>      else (a:) <$> (_find pred f as) 

Diese Funktion nimmt ein (a -> Bool), die eine Funktion ist, die Wahr auf einer Art zurückgeben sollte 'a', die Sie ändern wan.

Wenn die erste Zahl größer als 5 muss verdoppelt werden, dann könnten wir schreiben:

>over (_find (>5)) (*2) [4, 5, 3, 2, 20, 0, 8] 
[4,5,3,2,40,0,8] 

Die große Sache über Objektiv ist, dass man sie zusammen mit Komponieren sie kombinieren kann (.). Also, wenn wir wollen, die erste Nummer < 100 in der 2. Teilliste auf Null konnten wir:

>over ((element 1).(_find (<100))) (const 0) [[1,2,99],[101,456,50,80,4],[1,2,3,4]] 
[[1,2,99],[101,456,0,80,4],[1,2,3,4]] 
0

Hier ist ein Imperativ Weg, es zu tun, mit State Monad:

import Control.Monad.State             

replaceOnce :: Eq a => a -> a -> [a] -> [a] 
replaceOnce old new items = flip evalState False $ do 
    forM items $ \item -> do 
    replacedBefore <- get 
    if item == old && not replacedBefore 
     then do 
     put True 
     return new 
     else 
     return old 
0
replaceValue :: Int -> Int -> [Int] -> [Int] 
replaceValue a b (x:xs) 
     |(a == x) = [b] ++ xs 
     |otherwise = [x] ++ replaceValue a b xs 
Verwandte Themen