type_balancer 0.1.3 → 0.1.4
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 +14 -2
- data/Gemfile.lock +1 -1
- data/examples/quality.rb +23 -0
- data/lib/type_balancer/balancer.rb +16 -19
- 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 +17 -19
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6c93fa5dcb75821b9f9ea3340f06ca63d67f84781bcf90f13ac089abac7283b7
|
4
|
+
data.tar.gz: fbfadcf9eed52f9f82a5f0a99f05fe3801c8529c32735f3d2eac5b0d42648a4b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 316f4c79fb96b3ff7362a61a4524f2d08e67be712b23f6c1a6f36d4b24332ae58266ce2fbd6dd39a7deea190f9f2f9e965c0bafe94443dd24be571ed62f302fd
|
7
|
+
data.tar.gz: 054a3439add0798dc20593ace1681734fbed87f4d162baeb9e41b6f672cbbb43c7c2b17cd67a92d2c1a4a047b9418707139c2168cdcbde08a105376af53eb39e
|
data/CHANGELOG.md
CHANGED
@@ -1,11 +1,23 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
## [0.1.
|
3
|
+
## [0.1.4] - 2025-04-29
|
4
|
+
|
5
|
+
### Fixed
|
6
|
+
- Fixed issue with providing a custom type field
|
7
|
+
|
8
|
+
## [0.1.3] - 2025-04-27
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
- Fixed type balancing behavior to properly handle edge cases where type ratios need to be maintained while respecting original collection order
|
12
|
+
- Enhanced position calculation to ensure consistent type distribution across the balanced collection
|
13
|
+
- Improved test coverage to verify correct type ratio preservation
|
14
|
+
|
15
|
+
## [0.1.2] - 2025-04-11
|
4
16
|
|
5
17
|
- Re-release of 0.1.1 due to RubyGems.org publishing issue
|
6
18
|
- No functional changes from 0.1.1
|
7
19
|
|
8
|
-
## [0.1.1] -
|
20
|
+
## [0.1.1] - 2025-04-10
|
9
21
|
|
10
22
|
### Refactoring
|
11
23
|
- Major refactoring of core components to follow SOLID principles:
|
data/Gemfile.lock
CHANGED
data/examples/quality.rb
CHANGED
@@ -23,6 +23,7 @@ class QualityChecker
|
|
23
23
|
check_available_positions_edge_cases
|
24
24
|
check_balance_method_robust
|
25
25
|
check_real_world_feed
|
26
|
+
check_custom_type_field
|
26
27
|
|
27
28
|
print_summary
|
28
29
|
exit(@issues.empty? ? 0 : 1)
|
@@ -328,6 +329,28 @@ class QualityChecker
|
|
328
329
|
puts " Balanced items with custom order: #{ordered_result.map { |i| i[:type] }.inspect}"
|
329
330
|
end
|
330
331
|
|
332
|
+
def check_custom_type_field
|
333
|
+
@examples_run += 1
|
334
|
+
puts "\nCustom Type Field Example:"
|
335
|
+
data = [
|
336
|
+
{ category: 'A', payload: 1 },
|
337
|
+
{ category: 'B', payload: 2 },
|
338
|
+
{ category: 'C', payload: 3 },
|
339
|
+
{ category: 'A', payload: 4 }
|
340
|
+
]
|
341
|
+
balanced = TypeBalancer.balance(data, type_field: :category)
|
342
|
+
found = balanced.map { |i| i[:category] }.uniq.sort
|
343
|
+
expected = %w[A B C]
|
344
|
+
if found == expected
|
345
|
+
@examples_passed += 1
|
346
|
+
puts "#{GREEN}Custom field respected: #{found.inspect}#{RESET}"
|
347
|
+
else
|
348
|
+
record_issue("Expected #{expected.inspect}, got #{found.inspect}")
|
349
|
+
puts "#{RED}Custom field test failed: #{found.inspect}#{RESET}"
|
350
|
+
end
|
351
|
+
print_section_table('custom_type_field', 1, found == expected ? 1 : 0)
|
352
|
+
end
|
353
|
+
|
331
354
|
def print_summary
|
332
355
|
puts "\n#{'-' * 50}"
|
333
356
|
puts 'Quality Check Summary:'
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require_relative 'ratio_calculator'
|
4
4
|
require_relative 'batch_processing'
|
5
5
|
require_relative 'position_calculator'
|
6
|
+
require_relative 'type_extractor_registry'
|
6
7
|
|
7
8
|
module TypeBalancer
|
8
9
|
# Handles balancing of items across batches based on type ratios
|
@@ -10,9 +11,11 @@ module TypeBalancer
|
|
10
11
|
# Initialize a new Balancer instance
|
11
12
|
#
|
12
13
|
# @param types [Array<String>, nil] Optional types
|
14
|
+
# @param type_field [Symbol] Field to use for type extraction (default: :type)
|
13
15
|
# @param type_order [Array<String>, nil] Optional order of types
|
14
|
-
def initialize(types = nil, type_order: nil)
|
16
|
+
def initialize(types = nil, type_field: :type, type_order: nil)
|
15
17
|
@types = Array(types) if types
|
18
|
+
@type_field = type_field
|
16
19
|
@type_order = type_order
|
17
20
|
validate_types! if @types
|
18
21
|
end
|
@@ -23,7 +26,18 @@ module TypeBalancer
|
|
23
26
|
# @return [Array] Balanced items
|
24
27
|
def call(collection)
|
25
28
|
validate_collection!(collection)
|
26
|
-
|
29
|
+
extractor = TypeExtractorRegistry.get(@type_field)
|
30
|
+
|
31
|
+
begin
|
32
|
+
items_by_type = extractor.group_by_type(collection)
|
33
|
+
rescue TypeBalancer::Error => e
|
34
|
+
raise TypeBalancer::Error, "Cannot access type field '#{@type_field}': #{e.message}"
|
35
|
+
end
|
36
|
+
|
37
|
+
# Remove nil types and validate
|
38
|
+
items_by_type.delete(nil)
|
39
|
+
raise TypeBalancer::Error, "Cannot access type field '#{@type_field}'" if items_by_type.empty?
|
40
|
+
|
27
41
|
validate_types_in_collection!(items_by_type)
|
28
42
|
|
29
43
|
target_counts = calculate_target_counts(items_by_type)
|
@@ -70,24 +84,7 @@ module TypeBalancer
|
|
70
84
|
raise TypeBalancer::Error, "Invalid type(s): #{invalid_types.join(', ')}" if invalid_types.any?
|
71
85
|
end
|
72
86
|
|
73
|
-
def group_items_by_type(collection)
|
74
|
-
collection.group_by do |item|
|
75
|
-
extract_type(item)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def extract_type(item)
|
80
|
-
return item[:type] || item['type'] || raise(TypeBalancer::Error, 'Cannot access type field') if item.is_a?(Hash)
|
81
|
-
|
82
|
-
begin
|
83
|
-
item.type
|
84
|
-
rescue NoMethodError
|
85
|
-
raise TypeBalancer::Error, 'Cannot access type field'
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
87
|
def calculate_target_counts(items_by_type)
|
90
|
-
items_by_type.values.sum(&:size)
|
91
88
|
items_by_type.transform_values(&:size)
|
92
89
|
end
|
93
90
|
|
@@ -20,10 +20,15 @@ module TypeBalancer
|
|
20
20
|
if item.respond_to?(@type_field)
|
21
21
|
item.send(@type_field)
|
22
22
|
elsif item.respond_to?(:[])
|
23
|
-
item[@type_field] || item[@type_field.to_s]
|
23
|
+
value = item[@type_field] || item[@type_field.to_s]
|
24
|
+
raise TypeBalancer::Error, "Cannot access type field '#{@type_field}' on item #{item.inspect}" if value.nil?
|
25
|
+
|
26
|
+
value
|
24
27
|
else
|
25
|
-
raise Error, "Cannot access type field '#{@type_field}' on item #{item}"
|
28
|
+
raise TypeBalancer::Error, "Cannot access type field '#{@type_field}' on item #{item.inspect}"
|
26
29
|
end
|
30
|
+
rescue NoMethodError, TypeError
|
31
|
+
raise TypeBalancer::Error, "Cannot access type field '#{@type_field}' on item #{item.inspect}"
|
27
32
|
end
|
28
33
|
end
|
29
34
|
end
|
@@ -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
@@ -6,6 +6,8 @@ require_relative 'type_balancer/balancer'
|
|
6
6
|
require_relative 'type_balancer/ratio_calculator'
|
7
7
|
require_relative 'type_balancer/batch_processing'
|
8
8
|
require 'type_balancer/position_calculator'
|
9
|
+
require_relative 'type_balancer/type_extractor'
|
10
|
+
require_relative 'type_balancer/type_extractor_registry'
|
9
11
|
|
10
12
|
module TypeBalancer
|
11
13
|
class Error < StandardError; end
|
@@ -33,31 +35,27 @@ module TypeBalancer
|
|
33
35
|
# Input validation
|
34
36
|
raise EmptyCollectionError, 'Collection cannot be empty' if items.empty?
|
35
37
|
|
36
|
-
#
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
38
|
+
# Use centralized extractor
|
39
|
+
extractor = TypeExtractorRegistry.get(type_field)
|
40
|
+
begin
|
41
|
+
types = extractor.extract_types(items)
|
42
|
+
raise Error, "Invalid type field: #{type_field}" if types.empty?
|
43
|
+
rescue Error => e
|
44
|
+
raise Error, "Cannot access type field '#{type_field}': #{e.message}"
|
45
|
+
end
|
42
46
|
|
43
|
-
# Initialize balancer with type order
|
44
|
-
balancer = Balancer.new(types, type_order: type_order)
|
47
|
+
# Initialize balancer with type order and type field
|
48
|
+
balancer = Balancer.new(types, type_field: type_field, type_order: type_order)
|
45
49
|
|
46
50
|
# Balance items
|
47
51
|
balancer.call(items)
|
48
52
|
end
|
49
53
|
|
54
|
+
# Backward compatibility methods
|
50
55
|
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
|
56
|
+
TypeExtractorRegistry.get(type_field).extract_types(items)
|
57
|
+
rescue Error
|
58
|
+
# For backward compatibility, return array with nil for inaccessible type fields
|
59
|
+
[nil]
|
62
60
|
end
|
63
61
|
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.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carl Smith
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-04-
|
10
|
+
date: 2025-04-29 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
|
@@ -56,6 +56,7 @@ files:
|
|
56
56
|
- lib/type_balancer/ratio_calculator.rb
|
57
57
|
- lib/type_balancer/sequential_filler.rb
|
58
58
|
- lib/type_balancer/type_extractor.rb
|
59
|
+
- lib/type_balancer/type_extractor_registry.rb
|
59
60
|
- lib/type_balancer/version.rb
|
60
61
|
- type_balancer.gemspec
|
61
62
|
homepage: https://github.com/llwebconsulting/type_balancer
|