patternrubyMinor
Split string into 160-character chunks while adding text to each part
Viewed 0 times
while160eachintochunkstextcharacterpartaddingsplit
Problem
The
The trick is that if
The problem is that we don't know the length of the extra text. It can be 14 characters for part 1 of 2 or 16 for part 11 of 99 and so on.
This code can handle up to 99 parts. How can I make it generic to handle more parts without adding more conditions?
send_sms_message method takes text as an argument. The length of text is unknown and can be 0 or as large as 1070 (after that we will have no space for the actual text, because multi-part message text will be so long).The trick is that if
text is larger then 160 characters, we need to split it into parts and to each part add text part 1 of 2.The problem is that we don't know the length of the extra text. It can be 14 characters for part 1 of 2 or 16 for part 11 of 99 and so on.
This code can handle up to 99 parts. How can I make it generic to handle more parts without adding more conditions?
SMS_LENGTH = 160
MPM_SIZE_LONG = 16
MPM_SIZE_SHORT = 14
MPM_SHORT_LIMIT = 1314
def send_sms_message(text, to, from)
unless text.length > SMS_LENGTH
deliver_message_via_carrier(text, to, from)
else
parts = text.scan(/.{1,#{SMS_LENGTH - (text.length > MPM_SHORT_LIMIT ? MPM_SIZE_LONG : MPM_SIZE_SHORT)}}/)
parts.to_enum.with_index(1) do |message_part, index|
deliver_message_via_carrier("#{message_part} - Part #{index} of #{parts.length}", to, from)
end
end
endSolution
Some notes:
-
Is your code inside a module or a class, I guess? You can include it in the question.
-
As a general rule, favour the user of affirmative statements (
-
If you spend enough time with a pencil and a paper, maybe you'd get to a nice formula
I'd write:
-
Is your code inside a module or a class, I guess? You can include it in the question.
-
As a general rule, favour the user of affirmative statements (
if instead of unless).-
If you spend enough time with a pencil and a paper, maybe you'd get to a nice formula
get_total_parts(message_size, sms_size, parts_message_min_size), but it's not trivial, that's for sure. A pre-computing of the max_size (along with some extra info you'll need) is a bit tedious to write but fast. For example, what's the maximum size if you have a total count with exactly 3 digits (100-999)? (SMS_SIZE - PARTS_MSG_MIN_SIZE - 2) 9 + (SMS_SIZE - PARTS_MSG_MIN_SIZE - 3) 90 + (SMS_SIZE - PARTS_MSG_MIN_SIZE - 4) * 900. You get the idea.I'd write:
module SMS
SMS_LENGTH = 160
PARTS_MESSAGE = " - Part %{n} of %{total}"
SPLIT_INFO = (1..70).map do |ndigits|
base_size = (PARTS_MESSAGE % {n: "1", total: "1"}).size
parts_per_digit = (0...ndigits).map do |n|
(SMS_LENGTH - base_size - (ndigits + n - 1)) * (9 * (10 ** n))
end
{
max_size: parts_per_digit.reduce(0, :+),
size_of_first_parts: parts_per_digit[0...-1].reduce(0, :+),
min_parts: 10**(ndigits - 1) - 1,
msgsize_for_last_parts: (SMS_LENGTH - base_size - 2 * (ndigits - 1))
}
end
def self.get_total_parts_for_long_message(text)
info = SPLIT_INFO.detect { |h| text.size #{to}: #{text} - #{text.size} bytes")
end
endCode Snippets
module SMS
SMS_LENGTH = 160
PARTS_MESSAGE = " - Part %{n} of %{total}"
SPLIT_INFO = (1..70).map do |ndigits|
base_size = (PARTS_MESSAGE % {n: "1", total: "1"}).size
parts_per_digit = (0...ndigits).map do |n|
(SMS_LENGTH - base_size - (ndigits + n - 1)) * (9 * (10 ** n))
end
{
max_size: parts_per_digit.reduce(0, :+),
size_of_first_parts: parts_per_digit[0...-1].reduce(0, :+),
min_parts: 10**(ndigits - 1) - 1,
msgsize_for_last_parts: (SMS_LENGTH - base_size - 2 * (ndigits - 1))
}
end
def self.get_total_parts_for_long_message(text)
info = SPLIT_INFO.detect { |h| text.size <= h[:max_size] } or
raise ValueError("Message text too large")
info[:size_of_first_parts] +
Rational(text.size - info[:min_size], info[:msgsize_for_last_parts]).ceil
end
def self.send_sms_message(text, to, from)
if text.length <= SMS_LENGTH
deliver_message_via_carrier(text, to, from)
else
total_parts = get_total_parts_for_long_message(text)
idx = 0
(1..total_parts).each do |part_index|
split_message = PARTS_MESSAGE % {n: part_index, total: total_parts}
user_message_size = SMS_LENGTH - split_message.size
message_text = text[idx, SMS_LENGTH - user_message_size]
deliver_message_via_carrier(message_text + split_message, to, from)
idx += user_message_size
end
end
end
def self.deliver_message_via_carrier(text, to, from)
puts("Sending #{from} -> #{to}: #{text} - #{text.size} bytes")
end
endContext
StackExchange Code Review Q#133551, answer score: 2
Revisions (0)
No revisions yet.