type_balancer 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +38 -2
- data/Gemfile.lock +1 -1
- data/README.md +65 -4
- data/Rakefile +19 -1
- data/docs/balance.md +113 -10
- data/docs/quality.md +10 -2
- data/examples/large_scale_balance_test.rb +289 -0
- data/examples/quality.rb +93 -0
- data/lib/type_balancer/balancer.rb +16 -19
- data/lib/type_balancer/calculator.rb +27 -91
- data/lib/type_balancer/distributor.rb +70 -17
- data/lib/type_balancer/strategies/base_strategy.rb +49 -0
- data/lib/type_balancer/strategies/sliding_window_strategy.rb +140 -0
- data/lib/type_balancer/strategies.rb +7 -0
- data/lib/type_balancer/strategy_factory.rb +41 -0
- data/lib/type_balancer/type_extractor.rb +7 -2
- data/lib/type_balancer/type_extractor_registry.rb +20 -0
- data/lib/type_balancer/version.rb +1 -1
- data/lib/type_balancer.rb +43 -27
- metadata +8 -2
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TypeBalancer
|
4
|
+
# Registry to memoize TypeExtractor per type_field in thread/request scope
|
5
|
+
class TypeExtractorRegistry
|
6
|
+
STORAGE_KEY = :type_balancer_extractors
|
7
|
+
|
8
|
+
def self.get(type_field)
|
9
|
+
cache[type_field] ||= TypeExtractor.new(type_field)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.clear!
|
13
|
+
Thread.current[STORAGE_KEY] = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.cache
|
17
|
+
Thread.current[STORAGE_KEY] ||= {}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/type_balancer.rb
CHANGED
@@ -1,11 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'type_balancer/version'
|
4
|
-
require 'type_balancer/calculator'
|
5
4
|
require_relative 'type_balancer/balancer'
|
6
5
|
require_relative 'type_balancer/ratio_calculator'
|
7
6
|
require_relative 'type_balancer/batch_processing'
|
8
|
-
|
7
|
+
require_relative 'type_balancer/position_calculator'
|
8
|
+
require_relative 'type_balancer/type_extractor'
|
9
|
+
require_relative 'type_balancer/type_extractor_registry'
|
10
|
+
require_relative 'type_balancer/strategies/base_strategy'
|
11
|
+
require_relative 'type_balancer/strategies/sliding_window_strategy'
|
12
|
+
require_relative 'type_balancer/strategy_factory'
|
9
13
|
|
10
14
|
module TypeBalancer
|
11
15
|
class Error < StandardError; end
|
@@ -14,50 +18,62 @@ module TypeBalancer
|
|
14
18
|
class EmptyCollectionError < Error; end
|
15
19
|
class InvalidTypeError < Error; end
|
16
20
|
|
21
|
+
# Register default strategies
|
22
|
+
StrategyFactory.register(:sliding_window, Strategies::SlidingWindowStrategy)
|
23
|
+
|
17
24
|
# Load Ruby implementations
|
18
25
|
require_relative 'type_balancer/distribution_calculator'
|
19
26
|
require_relative 'type_balancer/ordered_collection_manager'
|
20
|
-
require_relative 'type_balancer/
|
21
|
-
require_relative 'type_balancer/
|
27
|
+
require_relative 'type_balancer/type_extractor'
|
28
|
+
require_relative 'type_balancer/type_extractor_registry'
|
29
|
+
require_relative 'type_balancer/ratio_calculator'
|
30
|
+
require_relative 'type_balancer/position_calculator'
|
22
31
|
require_relative 'type_balancer/distributor'
|
32
|
+
require_relative 'type_balancer/sequential_filler'
|
33
|
+
require_relative 'type_balancer/alternating_filler'
|
34
|
+
require_relative 'type_balancer/balancer'
|
35
|
+
require_relative 'type_balancer/batch_processing'
|
36
|
+
require_relative 'type_balancer/calculator'
|
23
37
|
|
24
38
|
def self.calculate_positions(total_count:, ratio:, available_items: nil)
|
25
|
-
|
39
|
+
PositionCalculator.calculate_positions(
|
26
40
|
total_count: total_count,
|
27
41
|
ratio: ratio,
|
28
|
-
|
42
|
+
available_items: available_items
|
29
43
|
)
|
30
44
|
end
|
31
45
|
|
32
|
-
def self.balance(items, type_field: :type, type_order: nil)
|
46
|
+
def self.balance(items, type_field: :type, type_order: nil, strategy: nil, **strategy_options)
|
33
47
|
# Input validation
|
34
48
|
raise EmptyCollectionError, 'Collection cannot be empty' if items.empty?
|
35
49
|
|
36
|
-
#
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
50
|
+
# Use centralized extractor
|
51
|
+
extractor = TypeExtractorRegistry.get(type_field)
|
52
|
+
begin
|
53
|
+
types = extractor.extract_types(items)
|
54
|
+
raise Error, "Invalid type field: #{type_field}" if types.empty?
|
55
|
+
rescue Error => e
|
56
|
+
raise Error, "Cannot access type field '#{type_field}': #{e.message}"
|
57
|
+
end
|
42
58
|
|
43
|
-
#
|
44
|
-
|
59
|
+
# Create calculator with strategy options
|
60
|
+
calculator = Calculator.new(
|
61
|
+
items,
|
62
|
+
type_field: type_field,
|
63
|
+
types: type_order || types,
|
64
|
+
strategy: strategy,
|
65
|
+
**strategy_options
|
66
|
+
)
|
45
67
|
|
46
68
|
# Balance items
|
47
|
-
|
69
|
+
calculator.call
|
48
70
|
end
|
49
71
|
|
72
|
+
# Backward compatibility methods
|
50
73
|
def self.extract_types(items, type_field)
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
if item.is_a?(Hash)
|
56
|
-
item[type_field] || item[type_field.to_s]
|
57
|
-
else
|
58
|
-
item.public_send(type_field)
|
59
|
-
end
|
60
|
-
rescue NoMethodError
|
61
|
-
nil
|
74
|
+
TypeExtractorRegistry.get(type_field).extract_types(items)
|
75
|
+
rescue Error
|
76
|
+
# For backward compatibility, return array with nil for inaccessible type fields
|
77
|
+
[nil]
|
62
78
|
end
|
63
79
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: type_balancer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carl Smith
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-05-01 00:00:00.000000000 Z
|
11
11
|
dependencies: []
|
12
12
|
description: Balances types in collections by ensuring each type appears a similar
|
13
13
|
number of times
|
@@ -43,6 +43,7 @@ files:
|
|
43
43
|
- docs/calculate_positions.md
|
44
44
|
- docs/quality.md
|
45
45
|
- examples/balance_test_data.yml
|
46
|
+
- examples/large_scale_balance_test.rb
|
46
47
|
- examples/quality.rb
|
47
48
|
- lib/type_balancer.rb
|
48
49
|
- lib/type_balancer/alternating_filler.rb
|
@@ -55,7 +56,12 @@ files:
|
|
55
56
|
- lib/type_balancer/position_calculator.rb
|
56
57
|
- lib/type_balancer/ratio_calculator.rb
|
57
58
|
- lib/type_balancer/sequential_filler.rb
|
59
|
+
- lib/type_balancer/strategies.rb
|
60
|
+
- lib/type_balancer/strategies/base_strategy.rb
|
61
|
+
- lib/type_balancer/strategies/sliding_window_strategy.rb
|
62
|
+
- lib/type_balancer/strategy_factory.rb
|
58
63
|
- lib/type_balancer/type_extractor.rb
|
64
|
+
- lib/type_balancer/type_extractor_registry.rb
|
59
65
|
- lib/type_balancer/version.rb
|
60
66
|
- type_balancer.gemspec
|
61
67
|
homepage: https://github.com/llwebconsulting/type_balancer
|