Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

“replace” a 3-tuple

Tags:

haskell

I have the following list (it’s a length 2 list, but in my assignment I have a length +n list)

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

I’m trying to “replace” one 3-tuple (p1 or p2 or p3 or p4 from the image bellow) by list index (n) and by sub-list index (p).

Visual of list breakdown

The function, at the end, should be like:

fooo newtuple n p = (…)

For example: (replace p3 for (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 planned the code like following this steps:

  1. Access the pn that I want to change. I manage to achieve it by:

    fob n p = ((aux2 xxs)!!n)!!p
       where aux2 [] = []
             aux2 ((_,_,c):xs) = c:aux2 xs
    
  2. “replace” the 3-tuple. I really need some help here. I’m stuck. the best code (in my head it makes some sense) that I’ve done: (remember: please don’t be too bad on my code, I’ve only been studying Haskell only for 5 weeks)

    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. Finally I will put all back together, using splitAt.

Is my approach to the problem correct? I really would appreciate some help on step 2.

like image 465
Nomics Avatar asked Dec 13 '11 04:12

Nomics


3 Answers

I'm a bit new to Haskell too, but lets see if we can't come up with a decent way of doing this.

So, fundamentally what we're trying to do is modify something in a list. Using functional programming I'd like to keep it a bit general, so lets make a function update.

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

That will now take an index, a function and a list and replace the nth element in the list with the result of the function being applied to it.

In our bigger problem, however, we need to update in a nested context. Luckily our update function takes a function as an argument, so we can call update within that one, too!

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

All fooo has to do is call update twice (once within the other call) and do a little "housekeeping" work (putting the result in the tuple correctly). The (n-1) and (p-1) were because you seem to be indexing starting at 1, whereas Haskell starts at 0.

Lets just see if that works with our 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)])]
like image 172
mange Avatar answered Nov 08 '22 19:11

mange


First, we need a general function to map a certain element of a list, e.g.:

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

We can use this function twice, for the outer list and the inner lists. There is a little complication as the inner list is part of a tuple, so we need another helper function:

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

Now we have everything we need to apply the replace function to our use case:

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

Of course in the inner list, we don't need to consider the old value, so we can use const :: a -> (b -> a) to ignore that argument.

like image 45
Landei Avatar answered Nov 08 '22 19:11

Landei


So you've tried using some ready-made function, (!!). It could access an item in a list for you, but forgot its place there, so couldn't update. You've got a solution offered, using another ready-made function split, that tears a list into two pieces, and (++) which glues them back into one.

But to get a real feel for it, what I suspect your assignment was aiming at in the first place (it's easy to forget a function name, and it's equally easy to write yourself a new one instead), you could try to write the first one, (!!), yourself. Then you'd see it's real easy to modify it so it's able to update the list too.

To write your function, best think of it as an equivalence equation:

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

when n is zero, we just take away the head element. What do we do when it's not? We try to get nearer towards the zero. You can fill in the blanks.

So here we returned the element found. What if we wanted to replace it? Replace it with what? - this calls another parameter into existence,

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

Now you can complete the rest, I think.

Lastly, Haskell is a lazy language. That means it only calls into existence the elements of a list that are needed, eventually. What if you replace the 7-th element, but only first 3 are later asked for? The code using split will actually demand the 7 elements, so it can return the first 3 when later asked for them.

Now in your case you want to replace in a nested fashion, and the value to replace the old one with is dependent on the old value: newVal = let (a,b,ls)=oldVal in (a,b,myRepl p ls newtuple). So indeed you need to re-write using functions instead of values (so that where y was used before, const y would go):

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

and your whole call becomes myUpd n xxs (\(a,b,c)->(a,b,myUpd ... (const ...) )).

like image 4
Will Ness Avatar answered Nov 08 '22 18:11

Will Ness