(What I don’t like about my via Data.Set
approach is it needs building the set then deleting elements, to ensure there’s no duplicates in the result. Deleting is computationally expensive, in terms of rebalancing the tree.)
So here’s a more Listiferous approach, along the lines of the o.p. “secondary index”: scan down the input, maintaining a parallel/secondary list with wanted pairs. Assuming the input is strictly ascending, the wanteds will also be strictly ascending, so we only go tailward along them/there’s no destructive update.
pllPairs :: (Num a, Ord a) => a -> [a] -> [(a,a)]
pllPairs k xs = go xs []
where
go [] _ = []
go (x:xs) [] = go xs [(x, x+k)] -- want x+k in xs
go xxs@(x:xs) ( yyk@(_, yk) : ys) =
case compare x yk of
EQ -> yyk : go xs ys -- matched a wanted
LT -> go xs (yyk: ordInsert (x, x+k) ys) -- yyk not yet matched
GT -> go xxs ys -- yyk not matched, drop it
ordInsert :: Ord a => (a,a) -> [(a,a)] -> [(a,a)]
ordInsert yyk [] = [yyk]
ordInsert yyk@(_, yk) zzs@(zzk@(_, zk): zs) =
case compare yk zk of
EQ -> error "duplicates in List"
LT -> yyk: zzs
GT -> zzk: ordInsert yyk zs
This has some (crude) protection against deviant lists: it copes with Lists that don’t wholly pair up; but is fairly erratic faced with non-ascending or non-unique elements.