Skip to content

Commit 4bf6bb1

Browse files
Added 52 kata in Ruby
1 parent c6cc5aa commit 4bf6bb1

File tree

52 files changed

+2428
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+2428
-0
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
=begin
2+
Codewars. 19/05/20. 'Most frequently used words in a text'. 4kyu. Here we create
3+
a method that takes a string of text - which can include punctuation or line breaks -
4+
and returns the top 3 most occuring words in the text. For example,
5+
"e e e e DDD ddd DdD: ddd ddd aa aA Aa, bb cc cC e e e" should return ["e", "ddd", "aa"].
6+
Words may contain one or more apostrophes and words should be treated case-insensitively
7+
and returned lower case. Here is the solution I used to solve the challenge, after having
8+
to look up solutions on Github.
9+
1) We define our method top_3_words, which takes a string of text as its
10+
argument.
11+
2) We downcase the string as we should treat matches case insensitively.
12+
3) We then call the scan method to create an array of all the words in the
13+
text. In our regex, [a-z']+ matches 1 or more letters or apostrophes and
14+
placing word boundaries \b either side of it ensures that apostrophes by
15+
themselves are not matched. Essentially, our regex creates an array of
16+
words which may or may not contain apostrophes.
17+
4) We then call the group_by method and group all of the same words together.
18+
5) We then turn the values of the hash into their size, thereby leaving us with
19+
a hash where the key is the word and the value is the count of the word in
20+
the text.
21+
6) We then sort the hash according to the count's as if they were negative
22+
integers, which puts the largest takes first and leaves us with an array of
23+
arrays rather than a hash.
24+
7) We then use the take method to grab the first 3 elements of the array of
25+
arrays, which gives us the top 3 counts.
26+
8) We map over the array and keep only the words.
27+
=end
28+
29+
def top_3_words(s)
30+
s.downcase.scan(/\b[a-z']+\b/).group_by(&:itself).transform_values(&:size).sort_by {|k,v,| -v}.take(3).map(&:first)
31+
end
32+
33+
=begin
34+
Here is another solution.
35+
1) We downcase the string then call the scan method to generate an array of
36+
all the words in the text.
37+
2) In scan's regex, we match a single letter [a-z] followed by 0 or more
38+
letters or apostrophes [a-z']*.
39+
3) We then call inject on the array of words and initialize a counter hash
40+
inside inject's initializer.
41+
4) In our block we make the keys of our hash the word and increment its count
42+
by 1 every time that word is found. We then return the hash from inside
43+
the inject block, without this the hash would not automatically return.
44+
5) We then sort the hash with counts in descending order thereby converting it
45+
from a hash to an array of arrays.
46+
6) We grab the first 3 sub-arrays, then map over them and keep only the first
47+
element from each. Now we have an array with the top 3 most frequently used
48+
words in the text.
49+
=end
50+
51+
def top_3_words_x(s)
52+
s.downcase.scan(/[a-z][a-z']*/).inject(Hash.new(0)) {|h,w| h[w] += 1 ; h}.sort_by {|k,v| -v}.first(3).map(&:first)
53+
end
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
=begin
2+
Codewars. 02/06/20. "Recover a secret string from random triplets". 4kyu. We are given a collection of random triplets from which we must
3+
recover a secret string. In each random triplet, we are given a (not necessarily consecutive) order of appearance of letters in the secret
4+
string. No letter occurs more than once in the secret string. After seeing Owen Patrick F. Falculan's Python solution in his YouTube
5+
tutorial, here is the adaptation I built to solve the challenge.
6+
1) We define our method recover_secret_ms, which takes an array of arrays where each sub-array contains not necessarily consecutive but
7+
ordered random triplets from the secret string.
8+
2) We intialize an empty string, where we will store our secret string.
9+
3) We then iterate over each sub-array, making a block variable for each triplet, x (the 1st), y (the 2nd) and z (the 3rd).
10+
4) In our block, if x is not included in the secret string s, we concatenate it to the front of s using prepend. Likewise, if y is not
11+
included in s, we concatenate it to the front of s.
12+
5) We then get to our first sorting part. If y is included in s but its index position is less than that of x, we remove y from s, then
13+
reinsert it into the string 1 position after x. Remember, a y always comes after an x in the secret string.
14+
6) Now we get to z, if this is not included in s, we concatenate it to the front of the string using prepend. Then if the index position of
15+
z in s is less than the index position of y in s, we remove z from s then reinsert 1 position after y.
16+
7) This process is performed as we iterate each triplet, because our triplets always have x, y and z in order of appearance in the secret
17+
string, once we've iterated all triplets we'll have a correctly sorted secret string. Our use of the index method with s is fine because
18+
no letter occurs more than once in the secret string.
19+
8) [['t','u','p'],['w','h','i'],['t','s','u'],['a','t','s'],['h','a','p'],['t','i','s'],['w','h','s']] returns "whatisup" but let's take
20+
a look at how our algorithm on it to see how it works.
21+
9) 1) t", s = "t"; "u", s = "ut"; index position of "u" is less than "t", so we slice then reinsert and s = "tu"; "p", s = "ptu"; index
22+
position of "p" is less than that of "u" so we slice and reinsert then s = "tup".
23+
2) "w", s = "wtup"; "h", s = "hwtup"; index position of "h" is less than that of "w", so we slice and reinsert and then s = "whtup";
24+
"i", s = "iwhtup"; index position of "i" is less than that of "h" so we slice and reinsert then s = "whitup".
25+
3) "t" is already included so no need to add it; "s", s = "swhitup"; index position of "s" is lower than that of "t", so we slice and
26+
reinsert then s = "whitsup"; "u" is already in s so no need to add it; index position of "u" is not lower than "s" so no sorting
27+
needed.
28+
4) "a", s = "awhitsup"; "t" is already included so no need to add it; index position of "t" is not less than that of "a" so no need for
29+
sorting; "s" already included; index position of "s" is not lower than that of "t" so no need for sorting.
30+
5) "h" already included; "a" already included; index position of "a" lower than "h", slice and reinsert then s = "whaitsup"; "p" is
31+
already included; index position of "p" not lower than "a".
32+
6) "t" already included; "i" already included; index position of "i" less than "t", slice and reinsert then s = "whatisup"; "s" is
33+
already included; "s" index not lower than "i" index.
34+
7) "w" already included; "h" already included; "h" index not lower than "w" index; "s" already included; "s" index not lower than "h"
35+
index.
36+
=end
37+
38+
def recover_secret_ms(t)
39+
s = ""
40+
t.each do |x,y,z|
41+
if s !~ /#{x}/ ; s.prepend(x) ; end
42+
if s !~ /#{y}/ ; s.prepend(y) ; end
43+
if s =~ /#{y}/ && s.index(y) < s.index(x) ; s.slice!(s.index(y)) ; s.insert((s.index(x)+1), y) ; end
44+
if s !~ /#{z}/ ; s.prepend(z) ; end
45+
if s =~ /#{z}/ && s.index(z) < s.index(y) ; s.slice!(s.index(z)) ; s.insert(s.index(y)+1, z) ; end
46+
end
47+
s
48+
end

ruby/4kyu/strip-comments.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
=begin
2+
Codewars. 20/05/20. 'Strip Comments'. 4kyu. Here we create a method that strips
3+
all text that follows any comment markers passed in, any whitespace at the end of
4+
the line should also be stripped out. Here is an example input and output:
5+
apples, pears # and bananas apples, pears
6+
grapes grapes
7+
bananas !apples bananas
8+
In code the input will look like ("apples, pears # and bananas\ngrapes\nbananas
9+
!apples", ["#", "!"]) and the output should be "apples, pears\ngrapes\nbananas".
10+
Here is the solution I used to solve the challenge after seeing a solution on Github.
11+
1) We define our method strip_comments, which takes a string which includes
12+
newlines and an array containing comment markers.
13+
2) We call gsub on the string.
14+
3) In our regex, we match 1 or more spaces \s+, followed by any one of our
15+
comment markers [#{m.join}], followed by one or more of any character
16+
except newlines .+. We remove all of these.
17+
4) This ensures that the space, the comment marker and all the text or space
18+
that follows it, up to a newline, is stripped from the string.
19+
=end
20+
21+
def strip_comments(s,m)
22+
s.gsub(/\s+[#{m.join}].+/, '')
23+
end

ruby/5kyu/#hashtag.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
=begin
2+
Codewars. 05/05/20. '#Hashtag'. 5kyu. Here we create a method that takes a string
3+
and returns only the words that have 1 or more # symbols at the beginning, returning
4+
the words without the # symbol. Here is the solution I used to solve the challenge.
5+
1) We define our method get_hashtags_ms, which takes a string of a "social
6+
media post" as an argument.
7+
2) We split the string into an array of words.
8+
3) We call the grep method, which is like the select method, returning an
9+
array of all elements which match our pattern. Our regex matches for every
10+
word that starts with one or more hashtags.
11+
4) In our regex, \A asserts position at the start of the string, \Z asserts
12+
position at the end of the string.
13+
5) (\#)+ is capture group 1, matching for 1 or more occurences of a hashtag, +
14+
being the 1 or more quantifier.
15+
6) ([a-zA-Z]+) is capture group 2, any series of letters which follow 1 or more
16+
hashtags.
17+
7) In our block we return $2 (capture group 2), which with grep gives us an
18+
array of words that originally started with 1 or more hashtags.
19+
8) When we call $1 we get 1 hashtag from every word that started with 1 or more
20+
hashtags, so if there was 3 we don't get 3, we get 1. If we want to get all
21+
the preceding hashtags we simply do (\#+)+, placing a 1 or more quantifier
22+
inside the hashtag capture group.
23+
=end
24+
25+
def get_hashtags_ms(post)
26+
post.split.grep(/\A(\#)+([a-zA-Z]+)\Z/) {$2}
27+
end
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
=begin
2+
Codewars. 20/05/20. 'Bob's reversing obfuscator'. 5kyu. Here we create a method
3+
that takes an encoded string and uses just a marker and our wits to decode the string.
4+
An input of ("Lor-.tile gnicsipida rutetcesnoc ,tema tis rolod muspi me", "-") should
5+
return "Lorem ipsum dolor sit amet, consectetur adipiscing elit." An input of
6+
("q.qSusqsitanenev cen lsin sillom subinif ecsuF .itnetop essidnep", "q") should return
7+
"Suspendisse potenti. Fusce finibus mollis nisl nec venenatis." Here is the solution I
8+
developed to solve the challenge. My solution occasionally fails a random test, delivering
9+
the error message "'tr' : invalid range "|-q" in string transliteration (ArgumentError)."
10+
1) We define our method decoder_ms, which takes an encoded string and a marker,
11+
the marker can feature once or multiple times in the string.
12+
2) We create an empty array called d, where we will store segments of the
13+
decoded message.
14+
3) Based on a pattern that I figured out by working with the string
15+
"Dq.silucaiqonec mollq odommoc qis ipsum qlsin lev" and its marker "q". If
16+
we split each sub-section at q boundaries we get the following:
17+
["D", ".silucai", "onec moll", " odommoc ", "is ipsum ", "lsin lev"].
18+
4) Comparing with final output "Donec mollis ipsum vel nisl commodo iaculis.",
19+
we see that everything in an even index position doesn't need reversing,
20+
they only need to be appended together. Everything in an odd index position
21+
however, needs to be reversed and then shifted to position -1, then -2,
22+
then -3 etc as they are encounterd in the array.
23+
5) With this in mind, we create an empty array where we will store our
24+
sub-sections, then we initialize the position counters for sub-sections
25+
that will be added to the new array. The first even will be placed in
26+
position 0 and the first odd will be placed in position -1.
27+
6) We then split the encoded string by its markers, then iterate over it with
28+
index. If the index of the segment is even, we insert it in d, wherever
29+
even position counter is at (initially 0), then increment ev by 1.
30+
7) If the index of the segment is odd, we insert a reversed version of that
31+
string in the position where the odd position counter is currently at
32+
(initially -1), then decrement od by 1.
33+
8) Now our array of strings has been decoded, we return it joined back into
34+
a string.
35+
9) To provide one more example, when ("q.qSusqsitanenev cen lsin sillom subinif
36+
ecsuF .itnetop essidnep", "q") is split into sub-sections it produces:
37+
["",".","Sus","sitanenev cen lsin sillom subinif ecsuF .itnetop essidnep"].
38+
"" and "Sus" are appended together, "." is shifted to position -1, and
39+
then "sitanenev cen lsin sillom subinif ecsuF .itnetop essidnep" reversed is
40+
placed in position -2. Then it is all joined into the decoded string.
41+
=end
42+
43+
def decoder_ms(e,m)
44+
d = [] ; ev = 0 ; od = -1
45+
e.split(m).each_with_index do |s,i|
46+
d.insert(ev, s) && ev += 1 if i.even?
47+
d.insert(od, s.reverse) && od -= 1 if i.odd?
48+
end
49+
d.join
50+
end
51+
52+
=begin
53+
Here is a superior solution, which doesn't fail any random tests.
54+
1) We split the encoded string at its markers, then we call the partition
55+
method with index.
56+
2) The partition method returns an array of 2 sub-arrays where the first
57+
contains the elements which evaluate to true and the second with the
58+
elements which evaluate to false.
59+
3) In our block, we partition the elements which are in even index positions
60+
(first sub-array) and the elements which are in odd index positions (second
61+
sub-array). We store these sub-arrays in variables called ev and od.
62+
4) We join the even strings and then concatenate the odd strings joined and
63+
reversed, leaving us with our decoded string.
64+
=end
65+
66+
def decoder(e, m)
67+
ev, od = e.split(m).partition.with_index {|s, i| i.even?} ; ev.join + od.join.reverse
68+
end

ruby/5kyu/break-the-caesar!.rb

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
=begin
2+
Codewars. 18/05/20. "Break the Caesar!". 5kyu. The Caesar cipher encrypts a
3+
message by shifting each letter a certain number of places in the alphabet. For
4+
example, applying a shift of 5 to "Hello, world!" yields "Mjqqt, btwqi!". Here
5+
we create a method that takes an encrypted message, and then returns the most
6+
likely shift that was used to decode it. The decoding is verified by checking
7+
if all or most of the decoded string's words are in a set called WORDS,
8+
provided in the kata. Here is the solution I developed to solve the challenge,
9+
which occasionally fails 1 or 2 random tests. While my solution may not be
10+
perfect for this kata, it still provides a perfectly good caesar decoder.
11+
1) We define our method break_caesar_ms, which takes an encrypted message as
12+
its argument.
13+
2) We sanitise the string by removing everything which isn't a letter or a
14+
space - thereby removing punctuation - and then we downcase the string, as
15+
all the words in the WORDS set are lower case.
16+
3) We create a for loop from 0 to 25, which covers all the possible shifts.
17+
4) In our for loop, we call gsub on the string and match each letter. If the
18+
ASCII value of the letter minus i (the shift) falls outside the bounds of
19+
of letter ASCII values, we subtract i from the ASCII value of the letter,
20+
then add 26 so that it loops back around to an alphabetical ASCII value.
21+
5) For example, if a letter is "d", and the correct shift is 7, the ASCII value
22+
of "d" is 100 and 100 - 7 = 93, but 93 is "]". But when we add 26 to 93 we
23+
get 119 and the corresponding character to that ASCII value is "w", which is
24+
7 places back from "d" in the alphabet.
25+
6) If the ASCII value minus the shift does not fall below the (lower cased)
26+
letter ASCII range, we simply do the ASCII minus the shift.
27+
7) We are subtracting because we are decoding the cipher and hence moving
28+
letters back to their original position. If we were encoding, we'd be
29+
adding the shift to the ASCII value instead.
30+
8) On each loop the shifted string gets stored in a variable d (for decoded),
31+
we return i if all the words in an array of words of d, are included in the
32+
set, or, if the count of d's included words in WORDS is all but one of them.
33+
We also check with this condition that d doesn't contain only 1 word,
34+
because this would make one the basic tests "DAM!" fail.
35+
9) If all or all but one of d's words are included in WORDS, we return i
36+
because we assume we have uncovered the correct shift.
37+
10) This manages to pass all the tests most of the time, but occasionally we
38+
get 1 random fail, or 2 random fails, probably because there are random
39+
cases where the decoded string has 2 words which aren't included in WORDS.
40+
=end
41+
42+
def break_caesar_ms(s)
43+
s.gsub!(/([^a-z|\s])/i, '').downcase!
44+
for i in 0..25
45+
d = s.gsub(/([a-z])/) {$1.ord - i < 97 ? (($1.ord - i) + 26).chr : ($1.ord - i).chr}
46+
return i if d.split.all? {|w| WORDS.include?(w)} ||
47+
d.split.count {|w| WORDS.include?(w)} == d.split.size - 1 && d.split.size != 1
48+
end
49+
end
50+
51+
=begin
52+
Here is an improved version of my solution, which doesn't fail any random
53+
tests because it returns the shift which produces a string containing the
54+
highest amount of words in WORDS. This is more precise than returning the shift
55+
which has all or all but one words in WORDS.
56+
=end
57+
58+
def break_caesar(s)
59+
s.gsub!(/([^a-z|\s])/i, '').downcase!
60+
(0..25).max_by do |i|
61+
s.gsub(/([a-z])/) {$1.ord - i < 97 ? (($1.ord - i) + 26).chr : ($1.ord - i).chr}.split.count {|w| WORDS.include?(w)}
62+
end
63+
end

0 commit comments

Comments
 (0)