This article was written by looking at the following articles. I tried to solve a simple programming problem using Ruby pattern matching --Qiita
The above article
"1, 5, 10-12, 15, 18-20"
From the character string
[1, 5, 10, 11, 12, 15, 18, 19, 20]
It describes how to get the array in Ruby.
On the other hand, this article is the opposite. In other words, given an array of positive integers, sort them and then create a comma-separated string by connecting the serial numbers with hyphens.
These processes are used, for example, to create the page number notation for a book index.
In addition, in such a notation,
4-5
even if there are only two serial numbers such as 4, 54,5
There are two possible styles, but in this article we will take the former (also write about the latter).
I also wrote test code using test-unit.
require "test/unit"
def gather(array)
array.uniq.sort
.chunk_while{ |a, b| a.succ == b }
.map{ |c| (c.size == 1) ? c : "#{c[0]}-#{c[-1]}" }
.join(", ")
end
#Below is the test code
#If you execute the script, the test described below will be performed automatically.
class TestGather < Test::Unit::TestCase
test "Dismembered" do
assert_equal "1, 3, 5", gather([3, 5, 1])
end
test "There is duplication" do
assert_equal "2, 4", gather([4, 2, 2, 4, 4])
end
test "Summary" do
assert_equal "1-2", gather([2, 1])
assert_equal "1-3", gather([2, 3, 1])
assert_equal "2-4, 7, 9, 11-12",
gather([12, 11, 7, 9, 2, 4, 3])
end
end
At first
array.uniq.sort
As a result, duplicates are omitted and sorted. I don't think it needs much explanation.
However, considering efficiency, it should be noted that it is better to ʻuniq first and then
sort` [^ uniq-sort].
[^ uniq-sort]: Not much different if there are few duplicates. However, it may be doctrinal to remember .uniq.sort
.
Next, the heart of this code is
chunk_while{ |a, b| a.succ == b }
Part of. In fact, the official reference Enumerable # chunk_while has a sample that looks exactly like the code in this article. Although it has been done.
chunk_while
is a really interesting Ruby-like method. Who is chunk_while
?
This is a decision ** for an Enumerable object such as an array, whether to lick
For example
[2, 6, 0, 3, 5, 8]
Let's say that there is an array called, and we want to divide this into even-numbered and odd-numbered categories. In other words
[[2, 6, 0], [3, 5], [8]]
I want the array.
The important thing is that only ** adjacent ** even numbers and odd numbers ** are ** catalyzed.
Therefore, the last 8
is not combined with 2, 6, 0
, but is an excursion.
By the way, how should we write the conditional expression that the two integers ʻaand
b` are both even numbers or both odd numbers?
One way is
(a.even? && b.even?) || (a.odd? && b.odd?)
But you don't have to do this kind of trouble. If you notice that "the difference between even and even is even", "the difference between odd and odd is even", and "the difference between even and odd is odd"
(a - b).even?
I know it's okay.
Now, to get an array of adjacent even and odd numbers, write as follows using chunk_while
.
numbers = [2, 6, 0, 3, 5, 8]
p numbers.chunk_while{ |a, b| (a - b).even? }.to_a
# => [[2, 6, 0], [3, 5], [8]]
How does this chunk_while
work?
chunk_while
uses ʻeachto retrieve each element from the receiver. First of all, do so to extract the two elements. In this case,
2 and
6are retrieved. Pass these two to the block and evaluate the block. Then
(2 -6) .even?Is true. At this time,
chunk_whiledecides," Hmmmm, then I won't disconnect between
2 and
6. " Next, pass the already retrieved
6 and the next retrieved
0to the block. Again, the block returns true, so it does not disconnect. Similarly, pass
0 and
3to the block, which returns false. Then,
chunk_while` decides," Okay! Cut it off here! ".
The same applies below.
The way chunk_while
picks up two adjacent elements while shifting their positions one by one is similar to ʻeach_cons (2). ʻEach_cons (2)
just picks up two elements, while chunk_while
picks up two elements to determine where to join / disconnect.
Now that you know how chunk_while
works,
chunk_while{ |a, b| a.succ == b }
Let's consider.
ʻInteger # succ is a method that returns an integer obtained by adding 1 to itself. ʻA.succ == b
expresses the condition that "after ʻa is
b` ".
Next
map{ |c| (c.size == 1) ? c : "#{c[0]}-#{c[-1]}" }
Is easy.
Oops, a word before that. Since the return value of chunk_while
is an Enumerator object, it is not necessary to make an array with to_a
, and map
can be continued as it is.
By the way, regarding this map
block, if each catamari is 1 in length, it is left as it is, and if it is 2 or more, the beginning and end are connected with a hyphen.
For example, [7]
is left as [7]
, and [2, 3, 4]
is changed to " 2-4 "
.
Well, the converted array is a bit unpleasant because the elements are arrays and strings. No, there is no problem with this (described later), but it is certain that it will be a little mushy.
Finally
join(", ")
Finish with.
Well, this is just connecting the elements with ", "
, but to be confident that I said in the previous paragraph that "elements can be arrays or strings, it doesn't matter", Array # join needs an accurate understanding.
join
returns a string that is connected with an argument in between if all the elements are strings.
If some of the elements aren't strings, they're first stringed before they're connected, but if it's an array, they're stringed using join
instead of to_s
. At that time, join
uses the same arguments as the original join
.
So
p [1, [2, 3]].join("-") # => "1-2-3"
become that way.
With the above, the operation is completely understood.
As I foretold in the "Introduction" section, how can I modify it so that it is not hyphenated when there are only two consecutive numbers such as 4,5
?
In other words
p gather([1, 2, 4, 5, 6]) # => "1, 2, 4-6"
How to make it
Actually, the sample code of Enumerable # chunk_while is written according to this style.
To do so
.map{ |c| (c.size == 1) ? c : "#{c[0]}-#{c[-1]}" }
To
.map{ |c| (c.size < 3) ? c : "#{c[0]}-#{c[-1]}" }
You can change it to.
It is clear from the specifications of join
already mentioned that this modification is sufficient.
It's unfortunate that Ruby's similar unit test library remains split into test-unit and minitest.
In my eyes, test-unit seems to be better [^ tu], but Rails has adopted minitest (a magical modification of it?), So minitest may be superior in the world. unknown.
[^ tu]: With test-unit, you can give the test name as a string as in this article, and you can also do data-driven tests.
e? RSpec? No, I don't feel like I can write it because it's too unclear to me. What is ʻit`?
Recommended Posts