2011-12-13 9 views
8

Ho la seguente lista (si tratta di un elenco di lunghezza 2, ma nel mio incarico ho avere una lunghezza + n elenco)“sostituire” un 3-tuple

xxs = [(11,22,[(33,33,33),(44,44,44)]),(55,66,[(77,77,77),(88,88,88)])] 

Sto cercando di “sostituire” uno 3-tupla (p1 o p2 o p3 o p4 dall'immagine qui sotto) per indice di lista (n) e per indice di sottoelenco (p).

Visual of list breakdown

La funzione, alla fine, dovrebbe essere come:

fooo newtuple n p = (…) 

Ad esempio: (sostituire p3 per (98,98,98):

fooo (98,98,98) 2 1 
[(11, 22, [(33,33,33) , (44,44,44)]) , (55, 66, [(98,98,98),(88,88,88)])] 

I pianificato il codice come seguendo questi passi:

  1. Accedere al pn che voglio modificare. Riesco a raggiungerlo da:

    fob n p = ((aux2 xxs)!!n)!!p 
        where aux2 [] = [] 
         aux2 ((_,_,c):xs) = c:aux2 xs 
    
  2. “sostituire” il 3-tupla. Ho davvero bisogno di aiuto qui. Sono bloccato. il codice migliore (nella mia testa fa un certo senso) che ho fatto: (ricordate: si prega di non essere troppo male il mio codice, ho solo studiato Haskell solo per 5 settimane)

    foo n p newtuple = fooAux newtuple fob 
        where fooAux _ [] = [] 
          fooAux m ((_):ds) = m:ds 
          fob n p = ((aux2 xxs)!!n)!!p 
           where aux2 [] = [] 
            aux2 ((_,_,c):xs) = c:aux2 xs 
    
  3. Infine, rimetterò tutti insieme, usando splitAt.

Il mio approccio al problema è corretto? Apprezzerei davvero qualche aiuto sul passaggio 2.

risposta

7

Sono un po 'nuovo anche a Haskell, ma vediamo se non riusciamo a trovare un modo decente per farlo.

Quindi, fondamentalmente ciò che stiamo cercando di fare è modificare qualcosa in una lista. Usando la programmazione funzionale mi piacerebbe tenerlo un po 'generale, quindi facciamo una funzione update.

update :: Int -> (a -> a) -> [a] -> [a] 
update n f xs = pre ++ (f val) : post 
    where (pre, val:post) = splitAt n xs 

che saranno ora prendere un indice, una funzione e una lista e sostituire l'elemento nth nell'elenco con il risultato della funzione ad esso applicata.

Nel nostro problema più grande, tuttavia, è necessario aggiornare in un contesto nidificato. Fortunatamente la nostra funzione update accetta una funzione come argomento, quindi possiamo chiamare anche update all'interno di quello!

type Triple a = (a,a,a) 
type Item = (Int, Int, [Triple Int]) 

fooo :: Triple Int -> Int -> Int -> [Item] -> [Item] 
fooo new n p = update (n-1) upFn 
    where upFn (x,y,ps) = (x,y, update (p-1) objFn ps) 
     objFn _ = new 

Tutto fooo ha a che fare è aggiornare chiamata due volte (una volta dentro l'altro di chiamata) e fare un po 'di lavoro "di pulizia" (ponendo il risultato in tupla correttamente). I numeri (n-1) e (p-1) erano perché sembra che si stia indicizzando a partire da 1, mentre Haskell inizia da 0.

Consente solo vedere se funziona con il nostro test case:

*Main> fooo (98,98,98) 2 1 [(11,22,[(33,33,33),(44,44,44)]),(55,66,[(77,77,77),(88,88,88)])] 
[(11,22,[(33,33,33),(44,44,44)]),(55,66,[(98,98,98),(88,88,88)])] 
+0

Prima di tutto, la tua spiegazione è stata davvero notevole.Secondo: ho ancora molto da imparare, ma ho imparato molto con la tua risposta. Terzo: trascorro metà della mia giornata cercando di risolvere questo problema, e lo risolvi in ​​pochi minuti (ho davvero molto da imparare e studiare). Finalmente un GRANDE ringraziamento! _o_ – Nomics

4

Quindi hai provato con qualche funzione ready-made, (!!). Potrebbe accedere a un elemento in un elenco per te, ma ha dimenticato il suo posto lì, quindi non ha potuto aggiornare. Hai una soluzione offerta, usando un'altra funzione già pronta split, che strappa una lista in due pezzi, e (++) che li incolla in uno solo.

Ma per ottenere una vera e propria sensazione di esso, ciò che ho il sospetto il vostro compito mirava a in primo luogo (è facile dimenticare un nome di funzione, ed è altrettanto facile per scrivere una nuova, invece), è potrei provare a scrivere il primo, (!!), da solo. Quindi vedrai che è davvero facile modificarlo, quindi è anche in grado di aggiornare la lista.

Per scrivere la vostra funzione, meglio pensare ad esso come un'equazione di equivalenza:

myAt 1 (x:xs) = x 
myAt n (x:xs) | n > 1 = ... 

quando n è zero, dobbiamo solo togliere l'elemento di testa. Cosa facciamo quando non lo è? Cerchiamo di avvicinarci allo zero. È possibile compilare gli spazi vuoti.

Quindi qui abbiamo restituito l'elemento trovato. E se volessimo sostituirlo? Sostituirlo con cosa? - questo chiama un altro parametro,

myRepl 1 (x:xs) y = (y:xs) 
myRepl n (x:xs) y | n > 1 = x : myRepl ... 

Ora puoi completare il resto, credo.

Infine, Haskell è una lingua pigra. Ciò significa che alla fine richiama solo gli elementi di una lista che sono necessari. Che cosa succede se si sostituisce l'elemento 7-th, ma solo i primi 3 vengono successivamente richiesti? Il codice che utilizza split richiederà effettivamente i 7 elementi, quindi può restituire i primi 3 quando in seguito li ha richiesti.

Ora nel tuo caso si desidera sostituire in modo annidato, e il valore per sostituire quello vecchio con dipende dal vecchio valore: newVal = let (a,b,ls)=oldVal in (a,b,myRepl p ls newtuple). Così in effetti è necessario riscrivere utilizzando le funzioni invece di valori (in modo che dove y è stato utilizzato prima, const y sarebbe andato):

myUpd 1 (x:xs) f = (f x:xs) 
myUpd n ... = ... 

e tutta la vostra chiamata diventa myUpd n xxs (\(a,b,c)->(a,b,myUpd ... (const ...))).

+0

È la soluzione più vicina a quella che ho provato. Proverò a completare il (...). La spiegazione è tutto lì. :) Grazie!! – Nomics

+1

@Nomics quello che intendevo era, per scrivere la tua funzione, pensarla come se lo avessi già scritto, come se lo avessi già disponibile per il tuo uso, e solo annotare alcune equazioni di equivalenza basate sulle qualità che dovrebbe avere, leggi che dovrebbe seguire. Queste equazioni diventeranno la definizione della funzione stessa - purché seguiate le leggi di essa, mantenete gli invarianti prima e dopo il suo uso, per esempio _ la lista con 1 ° elt sostituito è proprio questo; l'elenco con _n_th elt sostituito è il 1 ° elt prefisso al resto di esso con _ (n-1) _- th elt sostituito._ Questo è tutto. –

4

In primo luogo, abbiamo bisogno di una funzione generale per mappare un certo elemento di una lista, per es .:

mapN :: (a -> a) -> Int -> [a] -> [a] 
mapN f index list = zipWith replace list [1..] where 
    replace x i | i == index = f x 
       | otherwise = x 

Possiamo usare questa funzione per due volte, per la lista esterno e le liste interne. C'è un po 'di complicazioni come la lista interna è parte di una tupla, quindi abbiamo bisogno di un altro funzione di supporto:

mapTuple3 :: (c -> c) -> (a,b,c) -> (a,b,c) 
mapTuple3 f (x,y,z) = (x,y,f z) 

Ora abbiamo tutto quello che serve per applicare la funzione di sostituire al nostro caso d'uso:

fooo :: Int -> Int -> (Int,Int,Int) -> [(Int,Int,[(Int,Int,Int)])] 
fooo n p newTuple = mapN (mapTuple3 (mapN (const newTuple) p)) n xxs 

Ovviamente nella lista interna, non è necessario considerare il vecchio valore, quindi possiamo usare const :: a -> (b -> a) per ignorare quell'argomento.