-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
When passing a very large number to split, it result in allocating between 2 and 3 arrays of such size. e..g `Money.new(1_000_000).split(1_000_000)` allocates an array of size 1M, then concat it with an empty array, which cause Ruby to allocate another array of 1M entries. All this cause two `8MB` mallocs, which doesn't sound like that much but if done a few times is likely to trigger Ruby GC. Instead we can return a small enumerable that will simply act like an array, but won't actually materialize it.
- Loading branch information
Showing
4 changed files
with
163 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
# frozen_string_literal: true | ||
|
||
class Money | ||
class Splitter | ||
include Enumerable | ||
|
||
attr_reader :split | ||
protected attr_writer :split | ||
|
||
def initialize(money, num) | ||
num = Integer(num) | ||
raise ArgumentError, "need at least one party" if num < 1 | ||
subunits = money.subunits | ||
low = Money.from_subunits(subunits / num, money.currency) | ||
high = Money.from_subunits(low.subunits + 1, money.currency) | ||
|
||
num_high = subunits % num | ||
|
||
@split = {} | ||
@split[high] = num_high if num_high > 0 | ||
@split[low] = num - num_high | ||
@split.freeze | ||
end | ||
|
||
alias_method :to_ary, :to_a | ||
|
||
def first(count = (count_undefined = true)) | ||
if count_undefined | ||
each do |money| | ||
return money | ||
end | ||
else | ||
if count >= size | ||
to_a | ||
else | ||
result = Array.new(count) | ||
index = 0 | ||
each do |money| | ||
result[index] = money | ||
index += 1 | ||
break if index == count | ||
end | ||
result | ||
end | ||
end | ||
end | ||
|
||
def last(count = (count_undefined = true)) | ||
if count_undefined | ||
reverse_each do |money| | ||
return money | ||
end | ||
else | ||
if count >= size | ||
to_a | ||
else | ||
result = Array.new(count) | ||
index = 0 | ||
reverse_each do |money| | ||
result[index] = money | ||
index += 1 | ||
break if index == count | ||
end | ||
result.reverse! | ||
result | ||
end | ||
end | ||
end | ||
|
||
def [](index) | ||
offset = 0 | ||
@split.each do |money, count| | ||
offset += count | ||
if index < offset | ||
return money | ||
end | ||
end | ||
nil | ||
end | ||
|
||
def reverse_each(&block) | ||
@split.reverse_each do |money, count| | ||
count.times do | ||
yield money | ||
end | ||
end | ||
end | ||
|
||
def each(&block) | ||
@split.each do |money, count| | ||
count.times do | ||
yield money | ||
end | ||
end | ||
end | ||
|
||
def reverse | ||
copy = dup | ||
copy.split = split.reverse_each.to_h.freeze | ||
copy | ||
end | ||
|
||
def size | ||
count = 0 | ||
@split.each_value { |c| count += c } | ||
count | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters