File Coverage

blib/lib/Data/WeightedRoundRobin.pm
Criterion Covered Total %
statement 94 100 94.0
branch 33 40 82.5
condition 14 20 70.0
subroutine 13 13 100.0
pod 7 7 100.0
total 161 180 89.4


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