First, few transformations:
positions2 :: (Eq a) => a -> [a] -> [Int]
positions2 = f where
f aa aas = filter (g aa aas) [0 .. (length aas - 1)]
g aa aas it = aa == aas !! it
-- ==
positions2 aa aas = filter (g aa aas) [0 .. (length aas - 1)]
g aa aas it = aa == aas !! it
-- ==
positions2 aa aas = [it | it <- [0 .. (length aas - 1)], aa == (aas !! it)]
{-
positions2 aa aas = for each it in [0 .. (length aas - 1)]
if (aas !! it) == aa
emit it
-}
Now you can clearly see that for a given it
the traversal of the list aas
is repeated anew, from its very start, by (!!)
. This is a classic quadratic behaviour, you perform 1+2+3+4+...+n = O(n2) steps by the time the results of positions2 aa aas
are explored in full (the returned list is traversed to its very end).
But you explore progressively increasing indices, so you could as well start from the previous point along the list (and not from its start), and only advance by 1 position each time (and not by full it
indices, as (!!)
is doing):
positions2 aa aas = g 0 aas
where
g i (a:as) | a == aa = i : g (i+1) as
| otherwise = g (i+1) as
g _ [] = []
Here you can see how the incrementing of the index by 1 (i --> i+1
) is weaved together with the advancement along the list by one position ((a:as) --> as
).
With list comprehensions again, the weaving (or actually, more like zipping) is achieved by using zip
:
positions2 aa aas = [it | (a,it) <- zip aas [0 .. (length aas - 1)]
, a == aa]
{-
= for each a in aas while incrementing it by 1 from 0 to (length aas - 1),
if a == aa
emit it
-}
This is clearly and visibly an O(n) solution now. And because Haskell's lists are lazy and zip
stops when the shortest list ends,
positions2 aa aas = [it | (a,it) <- zip aas [0 ..], a == aa]
(which is equivalent to the code in this answer here).