|
| 1 | +{-# LANGUAGE LambdaCase #-} |
| 2 | + |
| 3 | +----------------------------------------------------------------------------- |
| 4 | +-- | |
| 5 | +-- Module : Data.Set.Interval |
| 6 | +-- Copyright : (c) Alice Rixte 2025 |
| 7 | +-- License : BSD-style |
| 8 | +-- |
| 9 | +-- Maintainer : masahiro.sakai@gmail.com |
| 10 | +-- Stability : provisional |
| 11 | +-- |
| 12 | +-- Manipulate the keys of a "Set" (it works for both strict and lazy Sets) using |
| 13 | +-- "Interval"s and "IntervalSet"s |
| 14 | +----------------------------------------------------------------------------- |
| 15 | + |
| 16 | +module Data.Set.Interval |
| 17 | + ( restrictInterval |
| 18 | + , restrictIntervals |
| 19 | + , deleteInterval |
| 20 | + , deleteIntervals |
| 21 | + ) where |
| 22 | + |
| 23 | +import Data.Set as Set |
| 24 | +import qualified Data.Interval.Internal as I |
| 25 | +import Data.Interval.Internal (Interval(..)) |
| 26 | +import qualified Data.IntervalSet as IS |
| 27 | +import Data.IntervalSet (IntervalSet) |
| 28 | + |
| 29 | + |
| 30 | +-- | \(O(\log n)\). Restrict a 'Set' to the keys contained in a given |
| 31 | +-- 'Interval'. |
| 32 | +-- |
| 33 | +-- >>> restrictInterval s i == filterKeys (\k -> Interval.member k i) s |
| 34 | +-- |
| 35 | +-- [Usage:] |
| 36 | +-- |
| 37 | +-- >>> s = Set.fromList [-2.5, 3.1, 5 , 8.5] :: Set Rational |
| 38 | +-- >>> restrictInterval s (3 <=..< 8.5) |
| 39 | +-- fromList [31 % 10,5 % 1] |
| 40 | +-- |
| 41 | +-- [Performance:] |
| 42 | +-- This functions performs better than 'filterKeys' which is \(O(n)\). |
| 43 | +-- |
| 44 | +restrictInterval :: Ord k => Set k -> Interval k -> Set k |
| 45 | +restrictInterval s = \case |
| 46 | + Whole -> s |
| 47 | + Empty -> Set.empty |
| 48 | + Point k -> if Set.member k s then Set.singleton k else Set.empty |
| 49 | + LessThan k -> fst $ Set.split k s |
| 50 | + LessOrEqual k -> let (lt, eq, _) = Set.splitMember k s in |
| 51 | + if eq then |
| 52 | + Set.insert k lt |
| 53 | + else |
| 54 | + lt |
| 55 | + GreaterThan k -> snd $ Set.split k s |
| 56 | + GreaterOrEqual k -> let (_, eq, gt) = Set.splitMember k s in |
| 57 | + if eq then |
| 58 | + Set.insert k gt |
| 59 | + else |
| 60 | + gt |
| 61 | + BothClosed lk uk -> |
| 62 | + restrictInterval (restrictInterval s (I.GreaterOrEqual lk)) |
| 63 | + (I.LessOrEqual uk) |
| 64 | + LeftOpen lk uk -> |
| 65 | + restrictInterval (restrictInterval s (I.GreaterThan lk)) |
| 66 | + (I.LessOrEqual uk) |
| 67 | + RightOpen lk uk -> |
| 68 | + restrictInterval (restrictInterval s (I.GreaterOrEqual lk)) |
| 69 | + (I.LessThan uk) |
| 70 | + BothOpen lk uk -> |
| 71 | + restrictInterval (restrictInterval s (I.GreaterThan lk)) |
| 72 | + (I.LessThan uk) |
| 73 | + |
| 74 | + |
| 75 | +-- | \(O(n \log n)\). |
| 76 | +-- |
| 77 | +-- Restrict a 'Set' to the keys contained in a given 'Interval'. |
| 78 | +-- |
| 79 | +-- >>> restrictIntervals s i == filterKeys (\k -> IntervalSet.member k i) s |
| 80 | +-- |
| 81 | +-- [Usage:] |
| 82 | +-- |
| 83 | +-- >>> s = Set.fromList [-2.5, 3.1, 5 , 8.5] :: Set Rational |
| 84 | +-- >>> restrictIntervals s (IntervalSet.fromList [ -inf <..<= 3, 8 <..< inf]) |
| 85 | +-- fromList [(-5) % 2,17 % 2] |
| 86 | +-- |
| 87 | +-- [Performance:] |
| 88 | +-- In most cases, this function performs better than 'filterKeys', especially |
| 89 | +-- when the 'IntervalSet' contains few large intervals. However, in cases where |
| 90 | +-- the 'IntervalSet' consists of many small intervals and covers a significant |
| 91 | +-- portion of the Set, 'filterKeys' may outperform 'restrictIntervals' (see |
| 92 | +-- benchmark). |
| 93 | +-- |
| 94 | +restrictIntervals :: Ord k => Set k -> IntervalSet k -> Set k |
| 95 | +restrictIntervals s is = IS.foldr f Set.empty is |
| 96 | + where |
| 97 | + f i acc = Set.union acc (restrictInterval s i) |
| 98 | + |
| 99 | +-- complexity proof: |
| 100 | +-- |
| 101 | +-- This is not a tight bound, and a better analysis should be done. |
| 102 | +-- |
| 103 | +-- Set.union is O(s log(n/s + 1)) where s <= n, and we know that all intevals |
| 104 | +-- within the IntervalSet are disjoint. So the complexity is, provided that the |
| 105 | +-- accumulator size is bigger than the current interval restriction size: |
| 106 | +-- |
| 107 | +-- O( Σ s log(n/s + 1)) |
| 108 | +-- |
| 109 | +-- where i is the number of intervals in the IntervalSet, and n is the size of |
| 110 | +-- the original Set, and s is the size of the current interval restriction. |
| 111 | +-- |
| 112 | +-- But log(n/s + 1) < log(n + 1) for all s >= 1, so we have |
| 113 | +-- |
| 114 | +-- Σ s log(n/s + 1) |
| 115 | +-- < Σ s log(n + 1) |
| 116 | +-- = log(n + 1) Σ s |
| 117 | +-- < n log(n + 1) |
| 118 | +-- |
| 119 | +-- since the sum of sizes of all interval restrictions is less than n. |
| 120 | + |
| 121 | + |
| 122 | +-- | \(O(n)\). Delete keys contained in a given 'Interval' from a 'Set'. |
| 123 | +-- |
| 124 | +-- >>> deleteInterval i s == filterKeys (\k -> not (Interval.member k i)) s |
| 125 | +-- |
| 126 | +-- [Usage:] |
| 127 | +-- |
| 128 | +-- >>> s = Set.fromList [-2.5, 3.1, 5 , 8.5] :: Set Rational |
| 129 | +-- >>> deleteInterval (3 <=..< 8.5) s |
| 130 | +-- fromList [(-5) % 2,17 % 2] |
| 131 | +-- |
| 132 | +-- [Performance:] In practice, this function performs a lot better than |
| 133 | +-- 'filterKeys' (see benchmark). |
| 134 | +-- |
| 135 | +deleteInterval :: Ord k => Interval k -> Set k -> Set k |
| 136 | +deleteInterval i s = restrictIntervals s (IS.complement (IS.singleton i)) |
| 137 | + |
| 138 | +-- | \(O(n \log n)\). |
| 139 | +-- |
| 140 | +-- Delete keys contained in a given 'IntervalSet' from a 'Set'. |
| 141 | +-- |
| 142 | +-- >>> deleteIntervals i s == filterKeys (\k -> not (IntervalSet.member k i)) s |
| 143 | +-- |
| 144 | +-- [Usage:] |
| 145 | +-- |
| 146 | +-- >>> s = Set.fromList [-2.5, 3.1, 5 , 8.5] :: Set Rational |
| 147 | +-- >>> deleteIntervals (IntervalSet.fromList [ -inf <..<= 3, 8 <..< inf]) s |
| 148 | +-- fromList [31 % 10,5 % 1] |
| 149 | +-- |
| 150 | +-- See performance note of 'restrictIntervals'. |
| 151 | +deleteIntervals :: Ord k => IntervalSet k -> Set k -> Set k |
| 152 | +deleteIntervals is s = restrictIntervals s (IS.complement is) |
0 commit comments