avoiding cost of (++)

Pal-Kristian Engstad engstad@naughtydog.com
Thu, 16 Jan 2003 13:03:16 -0800


On Thursday 16 January 2003 08:10 am, Hal Daume III wrote:
> I have a function which behaves like map, except instead of applying the
> given function to, say, the element at position 5, it applies it to the
> entire list *without* the element at position 5.  An implementation looks
>
> like:
> > mapWithout :: ([a] -> b) -> [a] -> [b]
> > mapWithout f = mapWith' []
> >     where mapWith' pre [] = []
> >           mapWith' pre (x:xs) = f (pre ++ xs) : mapWith' (x:pre) xs
>
> Unfortunately, this is very slow, due to the overhead of (++).

Sometimes, I find it easier to think about problems in C/C++. If I had a list 
in C, I would simply run f from the head of the list to the current split 
point, skip this one, then continue until the end of the list.

Notice that this gives O(n^2) complexity.

It struck me though, if you have a function that calculates something on a 
list 'lst', and then you calculate something on 'lst ++ [a]', then surely one 
should be able to cache the results from the previous calculation. 

For the same reason, if you have calculated f on '[a] + lst', you should be 
able to calculate the result on 'lst'.

For instance, if f = sum, then the basic operation is (+), so your mapWithout 
function should be equivalent to:

mapWithoutSum lst = let tot = sum lst in map (\x -> tot - x) lst

So, 

mapWithout :: ([a] -> b) -> (b -> a -> b) -> [a] -> [b]
mapWithout f g lst = 
	let total = f lst in
	map (g total) lst

mapWithoutSum' = mapWithout sum (\total x -> total - x)

Perhaps some Haskell expert can expand on this idea?

PKE.