File Coverage

blib/lib/Data/WeightedRoundRobin.pm
Criterion Covered Total %
statement 91 97 93.8
branch 33 40 82.5
condition 14 20 70.0
subroutine 12 12 100.0
pod 7 7 100.0
total 157 176 89.2


line stmt bran cond sub pod time code
1             package Data::WeightedRoundRobin;
2              
3 8     8   240831 use strict;
  8         20  
  8         299  
4 8     8   39 use warnings;
  8         14  
  8         486  
5             our $VERSION = '0.06';
6              
7             our $DEFAULT_WEIGHT = 100;
8             our $BTREE_BORDER = 10;
9              
10 8     8   8119 use Scope::Guard qw(guard);
  8         6391  
  8         10617  
11              
12             sub new {
13 38     38 1 78563 my ($class, $list, $args) = @_;
14 38   100     221 $args ||= {};
15 38   66     671 my $self = bless {
      33        
16             rrlist => [],
17             weights => 0,
18             list_num => 0,
19             default_weight => $args->{default_weight} || $DEFAULT_WEIGHT,
20             btree_border => $args->{btree_border} || $BTREE_BORDER,
21             }, $class;
22 38 100       154 $self->set($list) if $list;
23 38         111 return $self;
24             }
25              
26             sub _normalize {
27 72     72   109 my ($self, $data) = @_;
28 72 100       173 return unless defined $data;
29              
30 70         82 my ($key, $value, $weight);
31              
32             # { value => 'foo', weight => 1 }
33 70 100       170 if (ref $data eq 'HASH') {
34 41         111 ($key, $value, $weight) = @$data{qw/key value weight/};
35 41 50       113 return unless defined $value;
36 41 50 66     205 return if defined $weight && $weight < 0;
37 41 100       95 $key = $value unless defined $key;
38 41 100       119 $weight = $self->{default_weight} unless defined $weight;
39             }
40             # foo
41             else {
42             # \{ foo => 'bar' }
43 29 100 66     102 if (ref $data eq 'REF' && ref $$data eq 'HASH') {
44 1         3 $data = $$data;
45             }
46 29         49 $key = $value = $data;
47 29         67 $weight = $self->{default_weight};
48             }
49              
50 70         369 return { key => $key, value => $value, weight => $weight };
51             }
52              
53             sub set {
54 39     39 1 88 my ($self, $list) = @_;
55 39 100       104 return unless $list;
56              
57 38         68 my $normalized = {};
58 38         88 for my $data (@$list) {
59 63   50     143 $data = $self->_normalize($data) || next;
60 63         225 $normalized->{$data->{key}} = $data;
61             }
62              
63 38         75 my $rrlist = [];
64 38         59 my $weights = 0;
65 38         179 for my $key (sort keys %$normalized) {
66 63         300 unshift @$rrlist, {
67             key => $key,
68             value => $normalized->{$key}{value},
69             range => $weights,
70             weight => $normalized->{$key}{weight},
71             };
72 63         155 $weights += $normalized->{$key}{weight};
73             }
74              
75 38         81 $self->{rrlist} = $rrlist;
76 38         72 $self->{weights} = $weights;
77 38         58 $self->{list_num} = scalar @$rrlist;
78              
79 38         123 return 1;
80             }
81              
82             sub add {
83 4     4 1 20 my ($self, $value) = @_;
84 4         9 my $rrlist = $self->{rrlist};
85 4   100     9 $value = $self->_normalize($value) || return;
86              
87 3         4 my $added = 1;
88 3         6 for my $data (@$rrlist) {
89 1 50       4 if ($data->{key} eq $value->{key}) {
90 1         1 $added = 0;
91 1         2 last;
92             }
93             }
94              
95 3 100       9 if ($added) {
96 2         3 push @$rrlist, $value;
97 2         6 $self->set($rrlist);
98             }
99              
100 3         14 return $added;
101             }
102              
103             sub replace {
104 5     5 1 32 my ($self, $value) = @_;
105 5         10 my $rrlist = $self->{rrlist};
106 5   100     13 $value = $self->_normalize($value) || return;
107              
108 4         5 my $replaced = 0;
109 4         9 for my $data (@$rrlist) {
110 3 50       10 if ($data->{key} eq $value->{key}) {
111 3         5 $data = $value;
112 3         7 $replaced = 1;
113 3         6 last;
114             }
115             }
116              
117 4 100       11 if ($replaced) {
118 3         8 $self->set($rrlist);
119             }
120              
121 4         22 return $replaced;
122             }
123              
124             sub remove {
125 5     5 1 58 my ($self, $value) = @_;
126 5         15 my $rrlist = $self->{rrlist};
127              
128 5         9 my $removed = 0;
129 5         10 my $newlist = [];
130 5         13 for my $data (@$rrlist) {
131 5 100       15 unless ($data->{key} eq $value) {
132 2         5 push @$newlist, $data;
133             }
134             else {
135 3         8 $removed = 1;
136             }
137             }
138              
139 5 100       17 if ($removed) {
140 3         9 $self->set($newlist);
141             }
142              
143 5         26 return $removed;
144             }
145              
146             sub next {
147 66     66 1 8932 my ($self, $key) = @_;
148 66         150 my ($rrlist, $weights, $list_num) = @$self{qw/rrlist weights list_num/};
149 66 100       158 return unless $list_num; # empty data
150 60         88 my ($start, $end) = (0, $list_num - 1);
151              
152             # if all weight is 0, choose random
153 60 100       142 return $rrlist->[int rand $list_num]->{value} if $weights == 0;
154              
155 48         144 my $rweight = rand($weights);
156 48 50       248 if ($list_num < $self->{btree_border}) {
157             # linear
158 48         70 for my $rr (@$rrlist) {
159 80 100       358 return $rr->{value} if $rweight >= $rr->{range};
160             }
161             }
162             else {
163             # b-tree
164 0         0 while ($start < $end) {
165 0         0 my $mid = int(($start + $end) / 2);
166 0 0       0 if ($rrlist->[$mid]{range} <= $rweight) {
167 0         0 $end = $mid;
168             }
169             else {
170 0         0 $start = $mid + 1;
171             }
172             }
173 0         0 return $rrlist->[$start]{value};
174             }
175             }
176              
177             sub save {
178 2     2 1 2601 my $self = shift;
179 2         5 my $orig_rrlist = $self->{rrlist};
180 2     2   15 guard { $self->set($orig_rrlist) };
  2         1023  
181             }
182              
183             1;
184             __END__