summaryrefslogtreecommitdiff
path: root/src/Data/Bitfield.hs
blob: 546c68e959776971226d52e79cd809f864df5fac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
-- |
--   Copyright   :  (c) Sam T. 2013
--   License     :  MIT
--   Maintainer  :  pxqr.sta@gmail.com
--   Stability   :  experimental
--   Portability :  portable
--
--   This modules provides all necessary machinery to work with
--   bitfields. Bitfields are used to keep track indices of complete
--   pieces either peer have or client have.
--
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE RecordWildCards #-}
module Data.Bitfield
       ( Bitfield, PieceCount

         -- * Construction
       , empty
       , insert
       , haveAll, haveNone, have

         -- * Query
       , haveCount, totalCount, completeness
       , findMin, findMax
       , frequencies, rarest

         -- * Combine
       , union
       , intersection
       , difference

         -- * Serialization
       , getBitfield, putBitfield
       , bitfieldByteCount

       , -- * Debug
         mkBitfield
       ) where

import Control.Monad
import Control.Monad.ST
import           Data.Vector.Unboxed (Vector)
import qualified Data.Vector.Unboxed as V
import qualified Data.Vector.Unboxed.Mutable as VM
import           Data.IntervalSet (IntSet)
import qualified Data.IntervalSet as S
import           Data.List (foldl')
import           Data.Monoid
import           Data.Ratio
import           Data.Serialize
import Network.BitTorrent.PeerWire.Block


type PieceCount = Int

-- TODO cache some operations

-- | Bitfields are represented just as integer sets but with
-- restriction: the each set should be within given interval (or
-- subset of). Size is used to specify interval, so bitfield of size
-- 10 might contain only indices in interval [0..9].
--
data Bitfield = Bitfield {
    bfSize :: !PieceCount
  , bfSet  :: !IntSet
  } deriving (Show, Read, Eq)

-- Invariants: all elements of bfSet lie in [0..bfSize - 1];

instance Monoid Bitfield where
  {-# SPECIALIZE instance Monoid Bitfield #-}
  mempty  = empty 0
  mappend = union
  mconcat = unions

-- TODO documentation
{-----------------------------------------------------------------------
    Construction
-----------------------------------------------------------------------}

empty :: PieceCount -> Bitfield
empty s = Bitfield s S.empty

insert :: PieceIx -> Bitfield -> Bitfield
insert ix Bitfield {..}
  | 0 <= ix && ix < bfSize = Bitfield bfSize (S.insert ix bfSet)
  |      otherwise         = Bitfield bfSize bfSet

haveNone :: PieceCount -> Bitfield
haveNone = empty

haveAll :: PieceCount -> Bitfield
haveAll s = Bitfield s (S.interval 0 (s - 1))

have :: PieceIx -> Bitfield -> Bitfield
have = insert

{-----------------------------------------------------------------------
    Query
-----------------------------------------------------------------------}

haveCount :: Bitfield -> PieceCount
haveCount = S.size . bfSet

totalCount :: Bitfield -> PieceCount
totalCount = bfSize

-- |
--
--   > forall bf. 0 <= completeness bf <= 1
--
completeness :: Bitfield -> Ratio PieceCount
completeness b = haveCount b % totalCount b

findMin :: Bitfield -> Maybe PieceIx
findMin Bitfield {..}
  | S.null bfSet = Nothing
  |   otherwise  = Just (S.findMin bfSet)

findMax :: Bitfield -> Maybe PieceIx
findMax Bitfield {..}
  | S.null bfSet = Nothing
  |   otherwise  = Just (S.findMax bfSet)

type Frequency = Int

frequencies :: [Bitfield] -> Vector Frequency
frequencies [] = V.fromList []
frequencies xs = runST $ do
    v <- VM.new size
    VM.set v 0
    forM_ xs $ \ Bitfield {..} -> do
      forM_ (S.toList bfSet) $ \ x -> do
        fr <- VM.read v x
        VM.write v x (succ fr)
    V.unsafeFreeze v
  where
    size = maximum (map bfSize xs)

rarest :: [Bitfield] -> Maybe PieceIx
rarest xs
    | V.null freqMap = Nothing
    |     otherwise  = Just $ fst $ V.ifoldr minIx (0, freqMap V.! 0) freqMap
  where
    freqMap = frequencies xs

    minIx :: PieceIx -> Frequency -> (PieceIx, Frequency) -> (PieceIx, Frequency)
    minIx ix fr acc@(_, fra)
      | fr < fra && fr > 0 = (ix, fr)
      |     otherwise      = acc



{-----------------------------------------------------------------------
    Combine
-----------------------------------------------------------------------}

union :: Bitfield -> Bitfield -> Bitfield
union a b = Bitfield {
    bfSize = bfSize a `max` bfSize b
  , bfSet  = bfSet a `S.union` bfSet b
  }

intersection :: Bitfield -> Bitfield -> Bitfield
intersection a b = Bitfield {
    bfSize = bfSize a `min` bfSize b
  , bfSet  = bfSet a `S.intersection` bfSet b
  }

difference :: Bitfield -> Bitfield -> Bitfield
difference a b = Bitfield {
    bfSize = bfSize a -- FIXME is it more reasonable?
  , bfSet  = bfSet a `S.difference` bfSet b
  }

unions :: [Bitfield] -> Bitfield
unions = foldl' union (empty 0)

{-----------------------------------------------------------------------
    Serialization
-----------------------------------------------------------------------}

getBitfield :: Int -> Get Bitfield
getBitfield = error "getBitfield"

putBitfield :: Bitfield -> Put
putBitfield = error "putBitfield"

bitfieldByteCount :: Bitfield -> Int
bitfieldByteCount = error "bitfieldByteCount"

{-----------------------------------------------------------------------
    Debug
-----------------------------------------------------------------------}

mkBitfield :: PieceCount -> [PieceIx] -> Bitfield
mkBitfield s ixs = Bitfield {
    bfSize = s
  , bfSet  = S.splitLT s $ S.fromList ixs
  }