Skip to content

Commit a2a6ef5

Browse files
Added 10 kata in Ruby
1 parent 118d07a commit a2a6ef5

10 files changed

+488
-0
lines changed

ruby/5kyu/valid-parentheses.rb

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
=begin
2+
Codewars. 30/04/20. "Valid Parentheses". 5kyu. Here we create a method that
3+
takes a string of parentheses and determines whether the order of the
4+
parentheses is valid. For example, "(())((()())())" would be valid and so we
5+
return true, ")(()))" would be false and so we return false. Here is the
6+
solution I used to solve the challenge.
7+
1) We define our method valid_parentheses_ms?, which takes a string as an
8+
argument.
9+
2) We initialise a counter called bal (for balance), this will keep track of
10+
the balance of our parentheses as we iterate over our string.
11+
3) We call the each_char method, which allows us to iterate over each character
12+
of our string.
13+
4) If our character is an open parenthesis "(" our count increments by 1, if
14+
our character is a close parenthesis ")", we decrement by 1. If the balance
15+
is ever -1, that means we have a new close parenthesis coming before any
16+
open parenthesis and so we can return false right away.
17+
5) If our loop has finished, we didn't encounter a close coming before an open,
18+
but we may have more open's than closes, in which case bal > 0 and we return
19+
false.
20+
6) If bal is 0, our parentheses are valid because there are an equal and
21+
correctly ordered amount of open and closed parentheses, so we return true.
22+
=end
23+
24+
def valid_parentheses_ms?(str)
25+
bal = 0
26+
str.each_char do |c|
27+
return false if bal == -1
28+
bal += 1 if c == "("
29+
bal -= 1 if c == ")"
30+
end
31+
bal == 0 ? true : false
32+
end
33+
34+
=begin
35+
Here is another solution, submitted by a Codewars user.
36+
1) We convert our string to an array of characters, then we call
37+
each_with_object on it and iterate over every character.
38+
2) If we encounter an open parenthesis, we add it to the new array (stack)
39+
using push.
40+
3) If we encounter a close parenthesis after an open, we delete that open
41+
parenthesis from the stack.
42+
4) If we encounter a close parenthesis and there is no open parenthesis to
43+
remove from the stack, pop will return nil, which means we have a close
44+
before an open and so we return false immediately.
45+
5) Once we've iterated through the entire array of characters, if stack is
46+
empty the parentheses were balanced and so true is returned. If stack is
47+
not empty, that means it contains 1 or more open parenthesis, with of course
48+
no close's to match, in which case the parentheses are not valid, so false
49+
is correctly returned.
50+
=end
51+
52+
def valid_parentheses_x(str)
53+
str.chars.each_with_object([]) do |c, stack|
54+
if c == '('
55+
stack.push(c)
56+
elsif c == ')'
57+
stack.pop or return false
58+
end
59+
end.empty?
60+
end
61+
62+
=begin
63+
Here is another solution, which uses gsub and a regex.
64+
1) We delete every character apart from parentheses from the string.
65+
2) We start a while loop and call the gsub! method with open and closed
66+
parentheses as the pattern to match. gsub continues to remove pairs of open
67+
and closed parentheses until there is no pattern left to match.
68+
3) If after gsub has performed its work the string is empty, the string only
69+
consisted of balanced pairs and so the condition is true, if the string is
70+
not empty, all of the parentheses were not balanced and so the condition is
71+
false.
72+
=end
73+
74+
def valid_parentheses(str)
75+
str = str.delete("^()")
76+
while str.gsub!("()","") ; end
77+
str == ""
78+
end

ruby/6kyu/codwars-or-codewars.rb

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
=begin
2+
Codewars. 06/06/20. "Codwars or Codewars?". 6kyu. Here we create a method that accepts a URL as a string and determines if it would result
3+
in a request to codwars.com. The URLs are all valid but may or may not contain a scheme, they may or may not contain subdirectories, and
4+
they may or may not contain query strings. The subdomain may be "codewars.com" in a URL with a codwars domain and visa versa e.g.
5+
"http://codewars.com.codwars.com". The directory in the path may contain "codewars.com" and visa versa e.g.
6+
"https://this.is.an.unneccesarily.long.subdomain.codwars.com/katas.are.really.fun.codewars.com/". The query string may also contain
7+
"codewars.com" or "codwars.com". Here is the solution I developed to solve the challenge.
8+
1) We require the the URI (Univeral Resource Identifier) library from Ruby's built-in URI class, which allows us to parse URLs. A URL
9+
(Uniform Resource Locator) is a specific type of URI which normally locates an existing resource on the Internet.
10+
2) A URL consists of a scheme, a host (which may include a port number), a path and a query string.
11+
1) Format: scheme://host:port/path?query
12+
2) Example without port: http://www.codewars.com/path?this=is&a=querystring
13+
3) Example with port: http://www.example.com:1030/software/index.html?this=is&a=querystring
14+
2) We define our method codwars_ms?, with takes a string URL. The Ruby developer convention is to place a question mark at the end of
15+
methods which return true or false.
16+
3) Using the URI.parse method with the string URL passed in, we check if the URL doesn't contain a scheme, if not, we add one to it.
17+
Taking this step will allow us to extract the host much easier.
18+
4) Using the host method from the URI class, we extract the host from the url. Now the scheme, path and querystring have all been removed
19+
from the URL, making it much easier for us to use a regex to check whether it's a codwars domain.
20+
5) In a URL, the host consists of the subdomain, domain name and the top level domain, here are 2 examples:
21+
1) freedomplatform.londonreal.tv: freedomplatform (subdomain), londonreal (domain), .tv (TLD).
22+
2) www.codewars.com: www. (subdomain), codewars (domain), .com (TLD).
23+
6) Using the case equality operator, we then use a regex to check whether the URL will result in a request to codwars.com. If so, our
24+
method will return true, if not, it will return false.
25+
7) In our regex, ^ asserts the start of the string; (www\.|.+\.)? matches 0 or 1 of either "www." or 1 or more characters followed by a
26+
dot, these are the potential subdomains; the subdomain - or nothing - should be followed by codwars.com at the end of the string, which
27+
we match with (codwars\.com)$.
28+
=end
29+
30+
require "uri"
31+
32+
def codwars_ms?(url)
33+
url = "http://#{url}" if URI.parse(url).scheme.nil?
34+
host = URI.parse(url).host
35+
/^(www\.|.+\.)?(codwars\.com)$/ === host
36+
end
37+
38+
=begin
39+
Here is another method which solves the challenge solely with a regex and the case equality operator.
40+
1) ^ asserts the start of the string.
41+
2) (https?:\/\/)? matches 0 or 1 scheme. Because the scheme could be http:// or https://, inside this capture group we place a 0 or 1
42+
quantifier after s (s?).
43+
3) ([a-z]+\.)* matches the subdomain, which can be 1 or more groups of letters followed by a dot. Because a subdomain may not even exist,
44+
we place a 0 or more quantifier after the capture group.
45+
4) codwars\.com matches the domain name and top level domain.
46+
5) ([?\/]|$) matches either the beginning of a querystring or the beginning of a path, or the end of the string. This comes straight after
47+
codwars.com.
48+
=end
49+
50+
def codwars?(url)
51+
/^(https?:\/\/)?([a-z]+\.)*codwars\.com([?\/]|$)/ === url
52+
end

ruby/6kyu/compare-versions.rb

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
=begin
2+
Codewars. 21/07/20. 'Compare Versions'. 6kyu. Here we create a method that takes two arguments, version 1 and version 2, it returns true
3+
if v1 is a newer or equal version than v2, and false if not. Here is the solution I developed to solve the challenge.
4+
1) We define our function compare_versions_ms, which takes two versions as its arguments.
5+
2) Our versions come in the form of strings, so first we split them at their dots, then map over the numbers and convert them to integers.
6+
Now v1 and v2 are arrays of something like a major, minor, patch e.g. [10, 4, 6], although some inputs contain 2 numbers, 4 numbers or
7+
more.
8+
3) Now we map over v1 with index, and we subtract the number in the same index position in v2. If v2 is shorter than v1 and so a number in
9+
the corresponding index position doesn't exist, we subtract 0.
10+
4) Now we create a variable called con for condition, if every value in v1 is 0 or above, we can almost assume that it is the newer version,
11+
provided we do one more check, that is, whether v2 has more numbers than v1, if that is the case, v2 is the newer version. So if all of
12+
the numbers in v1 are 0 or greater, con is currently set to true.
13+
5) At this point, if con is false, we know for sure that v1 is not the newer version. For example, comparing 10.2.3 with 9.2.3 would give us
14+
[-1, 0, 0].
15+
6) If con is currently true, but v2 is longer than v1, we return false because v1 is not the newer version. Otherwise, we return true.
16+
=end
17+
18+
def compare_versions_ms(v1,v2)
19+
v2 = v2.split('.').map(&:to_i)
20+
v1 = v1.split('.').map(&:to_i)
21+
v1 = v1.map.with_index {|n,i| n - (v2[i] || 0)}
22+
con = v1.all? {|n| n >= 0}
23+
con == false ? false : v2.size > v1.size ? false : true
24+
end
25+
26+
=begin
27+
Here is a shorter solution which uses the spaceship operator.
28+
1) We split v1 into an array of its numbers, then map over the numbers and convert them to integers. We do the same for v2.
29+
2) We compare both arrays using the spaceship operator. The spaceship operator works in the following manner:
30+
1) v1 < v2 // -1
31+
2) v1 == v2 // 0
32+
3) v1 > v2 // 1
33+
4) v1 and v2 are not comparable // nil
34+
3) Thus if our spaceship operator comparison is greater than or equal to 0, we return true, if it's -1, we return false.
35+
=end
36+
37+
def compare_versions(v1,v2)
38+
(v1.split('.').map(&:to_i) <=> v2.split('.').map(&:to_i)) >= 0
39+
end
40+
41+
=begin
42+
Here is another solution, which uses Ruby's built-in Version class to create version objects and then compare them with the greater than
43+
or equal to comparison operator.
44+
=end
45+
46+
def compare_versions_x(v1,v2)
47+
Gem::Version.new(v1) >= Gem::Version.new(v2)
48+
end
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
=begin
2+
Codewars. 24/04/20. 'Simple Fun #170: Sum Groups'. 6kyu. Given an array of
3+
integers, sum consecutive even numbers and consecutive odd numbers. Repeat
4+
the process while it can be done and return the length of the final array.
5+
Here is a refined version of the top solution on Codewars.
6+
1) We define our method sum_groups, which takes an array of integers as its
7+
argument.
8+
2) We return the size of the array unless the array still contains any
9+
consecutive even numbers. a.even? == b.even? essentially evaluates whether
10+
both a.even? and b.even? are equal to true or false so if both are true we
11+
don't return arr.size because we have consecutive evens. Likewise, if both
12+
are false we don't return the array size, because we have consecutive odds.
13+
3) If there are consecutives, we call sum_groups recursively with the chunking
14+
and summing process performed on the array.
15+
4) arr.chunk(&:even?) returns an array of arrays where each sub-array's first
16+
element is a true (if it's even) and a false (if it's odd); the second
17+
element in the sub-array is an array with all the consecutive values that
18+
match the conditions of the first element.
19+
5) We then call map(&:last) to remove those first elements (booleans) and now
20+
we have an array of arrays containing consecutive evens and consecutive odds.
21+
6) We map over the array of arrays and sum each sub-array, which essentially
22+
sums all the odd consecutives and all the even consecutives.
23+
7) Now we have a new array of integers. If there are no consecutives, the size
24+
of it is returned. If not, the chunk and sum process runs again. This
25+
process continues until we have no consecutive odds or evens, then we
26+
return the length of the final array.
27+
9) If our starting array is [2,1,2,2,6,5,0,2,0,5,5,7,7,4,3,3,9], after the
28+
first recursive call it will be [2,1,10,5,2,24,4,15]. We have consecutives
29+
[2,24,4] so a second recursive call is done, after which we have
30+
[2,1,10,5,30,15]. Now we have no consecutives, so we return the array size,
31+
6.
32+
10) In this method, we could substitute a.even? == a.even? with
33+
a.odd? == b.odd?, and it would still work, because what we're comparing is
34+
equal boolean values, equal boolean values mean we have a consecutive,
35+
either even or odd.
36+
11) We could also substitute arr.chunk(&:even?) with arr.chunk(&:odd?),
37+
because regardless of which we use, consecutive odds and evens are grouped
38+
together as a result of either argument being placed in the chunk method.
39+
12) Also, all 3 uses of even? could simply be uses of odd? and our method
40+
still would work.
41+
=end
42+
43+
def sum_groups(arr)
44+
return arr.size unless arr.each_cons(2).any? {|a,b| a.even? == b.even?}
45+
sum_groups(arr.chunk(&:even?).map(&:last).map(&:sum))
46+
end
47+
48+
=begin
49+
Here is a refined version of another solution submitted by a Codewars user,
50+
which uses the slice_when method instead of the chunk method.
51+
1) We call the slice_when method on the array. The slice_when method takes 2
52+
block variables representing element before and element after and groups an
53+
array based on the block. It is similar to the chunk method.
54+
2) In our slice_when block we group together consecutive evens and odds in an
55+
array of arrays and store this in a variable groups.
56+
3) We return the array size if every sub-array in groups contains 1 element,
57+
which means we have no consecutives.
58+
4) If not, we resursively call the method with the sub-arrays - and hence
59+
consecutives - summed.
60+
5) Once there are no consecutive odds or evens, every sub-array in groups will
61+
contain 1 element, in which case the array size is returned.
62+
=end
63+
64+
def sum_groups_sw(arr)
65+
groups = arr.slice_when {|a,b| a.even? != b.even?}.to_a
66+
return arr.size if groups.all? {|g| g.size == 1}
67+
sum_groups_sw(groups.map(&:sum))
68+
end
69+
70+
=begin
71+
Here is the method I found online that allowed me to pass the challenge, it's
72+
clever in that it doesn't sum the numbers at all, but it's not a method I
73+
would voluntarily use.
74+
1) We call the chunk method on our array of integers with odd? passed in as an
75+
argument and turn this from an enumerator into an array. We essentially
76+
create an array of arrays where the first element of each sub-array is a
77+
boolean (true if odd, false if even) and the second element of the sub-array
78+
is all the consecutive values matching the condition of the first element.
79+
We store this in a variable chunks.
80+
2) We return the size of the array passed in if it is equal to the size of it
81+
chunked, this basically means our original array has been chunked and
82+
mapped down to contain no consecutives. If not, we run the "mapped" process
83+
on chunks.
84+
3) In the "mapped" process - stored in the variable mapped - we call the map
85+
method on the chunks array of arrays. If the first value is true (meaning
86+
this was an odd number) and the size of the consecutive terms of this
87+
number is odd also, we convert that entire sub-array to 1.
88+
4) In all other cases, i.e. if the first element if false (meaning the
89+
number was even) or the first element is true but its consecutive terms are
90+
even in quantity, the sub-array is converted to 0.
91+
5) If our original array we're [2,1,2,2,6,5,0,2,0,5,5,7,7,4,3,3,9], after being
92+
chunked and mapped it would be [0,1,0,1,0,0,0,1]. Our method is then called
93+
resursively with mapped passed in, mapped becomes arr and the size of mapped
94+
is 8, so arr.size is 8. The size of mapped (arr) chunked is 6. So in this
95+
instance the array size is not returned and the mapped process runs again.
96+
6) Once mapped is chunked and mapped again it becomes [0,1,0,1,0,1], this new
97+
mapped is passed into the method again. This time mapped (arr) size (6) is
98+
equal to its chunks size (6). This gives us the size of the final array, 6.
99+
=end
100+
101+
def sum_groups_ms(arr)
102+
chunks = arr.chunk(&:odd?).to_a
103+
return arr.size if arr.size == chunks.size
104+
mapped = chunks.map {|odd,terms| odd && terms.size.odd? ? 1 : 0}
105+
sum_groups(mapped)
106+
end

ruby/6kyu/simple-string-indices.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
=begin
2+
Codewars. 11/05/20. "Simple string indices". 6kyu. Here we create a method
3+
that takes a string with brackets and an index of an opening bracket and we
4+
must return the index position of the matching closing bracket. We return -1 if
5+
there is no opening bracket at the given index position. Here is the solution I
6+
developed to solve the challenge.
7+
1) We define our method closing_bracket_ms, which takes a string with brackets
8+
and the position of the opening bracket as its arguments.
9+
2) If there isn't an opening bracket in the n index position, we return -1.
10+
3) We initialize a counter variable bal (for balance).
11+
4) We iterate over each character in the string with index and skip iterations
12+
before the index position of n (our opening bracket).
13+
5) We increment bal by 1 if we encounter an opening bracket.
14+
6) We decrement bal by 1 if we encounter a closing bracket.
15+
7) If bal becomes 0, we have found the matching closing bracket of our opening
16+
bracket and so we return its index position.
17+
=end
18+
19+
def closing_bracket_ms(s,n)
20+
return -1 if s[n] != "("
21+
bal = 0
22+
s.each_char.with_index do |c,i|
23+
next if i < n
24+
bal += 1 if c == "("
25+
bal -= 1 if c == ")"
26+
return i if bal == 0
27+
end
28+
end

ruby/6kyu/strip-url-params.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
=begin
2+
Codewars. 06/06/20. 'Strip Url Params'. 6kyu. Here we create a method that takes a url string and removes all duplicate query strings
3+
keeping only the first as well as removes any query string parameter specified within the 2nd argument (an optional array). Here is the
4+
solution I developed to solve the challenge.
5+
1) We define our method strip_url_query_ms, which takes a url and an optional array holding query string parameters that should also be
6+
removed (if the array contains any query string parameters).
7+
2) First we slice out the host from the url using a regex and store it in a variable host.
8+
3) In our regex, (.+\..+\.\w+) is the one and only capture group, which extracts the host. .+\. matches one or more of any character
9+
followed by a dot, this is the subdomain. .+ is the domain name. \.\w+ is the top level domain. After the host name we have \??|$ which
10+
matches 0 or 1 question marks (denoting the start of the query string) or the end of the string $ (if no query string is the url).
11+
4) Using the scan method, we generate an array of each query string parameter. In our regex, \w\=\d+ matches the likes of "a=1", "b=2",
12+
"c=33", "d=76" etc.
13+
5) We then call reject on the query string paramter array, for each query string parameter, we check if its first letter is included in
14+
a string of our parameters to strip (the optional array), if so, that query string parameter is removed from the array.
15+
6) We then call the group_by method on the array and group together all query string paramters which have the same letter.
16+
7) We then call the values method on this hash, now we have an array of arrays where each sub-array holds duplicate query string parameters.
17+
8) We map over the array and keep the first element from each sub-array thereby keeping only the first occurrence of all query string
18+
parameters, then we join the array delimited by an ampersand, which seperated each query string parameter in the original URL.
19+
9) If the query string is empty, we return the host, if not, using string interpolation, we return the host, plus the question mark denoting
20+
the start of the query string, plus the query string.
21+
=end
22+
23+
def strip_url_query_ms(url, ps = [])
24+
host = url[/^(.+\..+\.\w+)\??|$/, 1]
25+
qs = url.scan(/\w\=\d+/).reject {|q| ps.join =~ /#{q[0]}/}.
26+
group_by {|q| q[0]}.values.map(&:first).join("&")
27+
qs.empty? ? host : "#{host}?#{qs}"
28+
end

0 commit comments

Comments
 (0)