Skip to content

Commit 4ee3163

Browse files
committed
Restrict and delete interval for Data.Set
1 parent 29bc7f0 commit 4ee3163

File tree

2 files changed

+153
-0
lines changed

2 files changed

+153
-0
lines changed

data-interval.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ Library
6565
Data.IntervalSet
6666
Data.IntegerInterval
6767
Data.Map.Interval
68+
Data.Set.Interval
6869
Other-Modules:
6970
Data.Interval.Internal
7071
Data.IntegerInterval.Internal

src/Data/Set/Interval.hs

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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

Comments
 (0)