# Practice Problems: Substrings Manipulation

## Example 1

=begin

Give an array of strings made only from lowecase letters, return an array of
all characters that show up in all strings within the given array (including
duplicates).
For example, if a character occurs 3 times in all strings but not 4 times, you
need to include that character three times in the final answer.

# PEDAC

## Problem

Input: an array of one or more strings (lowecase English letters)
Output: an array containing letters included in every string of the given
array, as many as they occurs in each string

Clarifying:

- Array consist of one or more strings
- English alphabet
- Orders of the letters matter

## Examples

See results

## Data

Array of strings

## Algorithm

. Init analyzed_letter array to contain letters already analyzed
. Init result array to contain the result

. Iterate over each letter of the first element of the given array
. Continue to next if the current letter is already in analyzed_letter
. Add the current letter to analyzed_letter
. Check if current letter appears in each string of the array
. Count the occurence of the current letter in each string and return min
. Add current letter * min to result
. Retrun result

## Code

=end

def common_chars?(array, char)
array.all? do |string|
string.chars.include?(char)
end
end

def common_chars(array)
analyzed_letter = Array.new
result = Array.new

array[0].chars.each do |char|
next if analyzed_letter.include?(char)
analyzed_letter << char

is_common = common_chars?(array, char)

if is_common
occurence = Array.new

array.each do |string|
occurence << string.chars.count(char)
end

occurence.min.times do
result << char
end
end
end

result
end

p common_chars(["bella", "label", "roller"]) == ["e", "l", "l"]
p common_chars(["cool", "lock", "cook"]) == ["c", "o"]
p common_chars(["hello", "goodbye", "booya", "random"]) == ["o"]
p common_chars(["aabbaaaa", "ccdddddd", "eeffee", "ggrrrrr", "yyyzzz"]) == []

Time: 25:15

## Example 2

=begin

You have to create a method that takes a positive integer number and returns
the next bigger number formed by the same digits:

12 ==> 21
513 ==> 531
2017 ==> 2071

If no bigger number can be composed using those digits, return -1:

9 ==> -1
111 ==> -1
531 ==> -1

# PEDAC

## Problem

Input: a positive integer number
Output: the next bigger number formed by the same digits, or -1 if it's
impossible

Clarifying:

- Number should be bigger than 11
- Number which are all the same digit returns -1
- Number's digits ascending returns -1
- Next biggest number is swapping number from the right if the rightmost is
greater

## Data

Arrays

## Algorithm

. Return -1 if number is smaller or equal to 11
. Return -1 if number to string is all the same digit
. Return -1 if number to string's digits are ascending

. Loop on each digit descending for digits size -1
. Check if current digit and current digit -1 are in ascending order
. Flip them
. Return
. Return new array, joined and integerized

=end

def next_bigger_num(number)
return -1 if number <= 11
digits = number.to_s.chars
return -1 if digits.all?(number.to_s.chars[0])
return -1 if digits.sort { |a, b| b.to_i <=> a.to_i } == digits

index = digits.size - 1

loop do
break if index == 0

if digits[index] > digits[index - 1]
digits[index], digits[index - 1] = digits[index - 1], digits[index]
break
end
index -= 1
end

digits.join.to_i
end

p next_bigger_num(9) == -1
p next_bigger_num(12) == 21
p next_bigger_num(513) == 531
p next_bigger_num(2017) == 2071
p next_bigger_num(111) == -1
p next_bigger_num(531) == -1
p next_bigger_num(123456789) == 123456798

Time: 54:32

## Example 3

=begin

Write a function scramble(str1, str2) taht returns true if a portion of str1
characters can be rearranged to match str2, otherwise returns false.

For example:
str1 is 'rkqodlw' and str2 is 'world' the output should return true.
str1 is 'cedewaraaossoqqyt' and str2 is 'codewars' should return true.
str1 is 'katas' and str2 is 'steak' should return false.

Only lower case letters will be used (a-z). No punctuation or digits will be
included.

# PEDAC

## Problem

Create a method that return true if the letters from str1 can be rearranged to
match str2.

Input: Two strings (a-z)
Output: true if condition above met, false otherwise

Clarify:

- Only a-z English letters
- str1 needs to have more letters than str2 for it to be true

## Data

Array of strings

## Algorithm

. Return false if str1 is smaller than str2

. Init local variable selected_chars
. Convert str1 and str2 to their array of chars
. Loop for as many char that arr2 have
. Check for each letters of arr2 if it appears in arr1
. If yes, remove it from arr1 and add to selected_chars
. If no, return false
. Return true

=end

def scramble(str1, str2)
arr1 = str1.chars
arr2 = str2.chars
selected_chars = Array.new

arr2.each do |char2|
if arr1.include?(char2)
selected_chars << char2
arr1.delete_at(arr1.index(char2))
else
return false
end
end

true
end

p scramble('javaass', 'jjss') == false
p scramble('rkqodlw', 'world') == true
p scramble('cedewaraaossoqqyt', 'codewars') == true
p scramble('katas', 'steak') == false
p scramble('scriptjava', 'javascript') == true
p scramble('scriptingjava', 'javascript') == true

Time: 17:55

## Example 4

=begin

PROBLEM:

Given a string, write a method change_me which returns the same string but with
all the words in it that are palindromes uppercased.

change_me("We will meet at noon") == "We will meet at NOON"
change_me("No palindromes here") == "No palindromes here"
change_me("") == ""
change_me("I LOVE my mom and dad equally") == "I LOVE my MOM and DAD equally"

# PEDAC

## Problem

Return the given string with palindromes in uppercase.

Input: a string
Output: a string with palindromes in uppercase

Clarify:

- Does it have to be the same string? (no)
- Does case have an inpact on the string being a palindrome or not? (no)
- Return empty string if empty string

## Data

Arrays of strings

## Algorithm

. Create an array which each word of the given string
. Create a new result array
. Iterate over the array
. For each word, check PALINDROME
. Uppcase the result and add to new array if it is
. Only add to new result array otherwise
. Return joined array

=end

def palindrome?(word)
word.downcase.reverse == word.downcase
end

def change_me(string)
candidate_list = string.split

result = candidate_list.map do |word|
if palindrome?(word)
word.upcase
else
word
end
end

result.join(' ')
end

p change_me("We will meet at noon") == "We will meet at NOON"
p change_me("No palindromes here") == "No palindromes here"
p change_me("") == ""
p change_me("I LOVE my mom and dad equally") == "I LOVE my MOM and DAD equally"

Time: 10:40

## Example 5

=begin
# Reverse a string, without using String#reverse

# PEDAC

## Problem

Reverse string manually

Input: a string
Output: reversed string

Clarify:

- Case not changed
- Emptry string returns empty? (Yes)
- Shall returns the same string? (No)

## Data

Arrays

## Algorithm

. Init a new local var reversed to String object
. Transform the string into array of its characters
. Loop for array length
. Take the last characters and add it to reversed
. Continue until array is empty
. Return reversed as a string

=end

def string_reverser(string)
reversed = ''

array = string.chars

loop do
reversed << array.pop
break if array.empty?
end

reversed
end

p string_reverser('hello') == 'olleh'
p string_reverser('Launch School') == 'loohcS hcnuaL'

Time: 07:22

## Example 6

=begin

Write a method that converts an english phrase into a mathematical expression,
step by step.

# PEDAC

## Problem

Write a method that convert string to integer and do the calculation.

Input: a string with two numbers and an operation, in English
Output: an Integer, the result of the operation

Clarification:

- English phrase are words separated by spaces? (Yes)
- Only one operation per string? (Yes)
- Operations are 'plus', 'minus', 'multipy', 'divide' ? (Yes)
- Take care of case

## Data

Arrays

## Algorithm

. Init a constant NUMBERS to a new Array of elements zero, one etc until nine
. Init a constant OPERATIONS to a new Array of elements plus, minus etc

. FIND_INTEGERS
. Init a local variable integers to a new empty Array
. Create an array of strings by separating by space
. Iterate over the array
. Check if the current word is in NUMBERS
. Save the index of NUMBERS for current word in integers
. Repeat
. DO_OPERATION, passing in integers and the sting array
. Return return value of DO_OPERATION

. DO_OPERATION
. Iterate over the array
. Check if the current word is in OPERATIONS
. Do the correct operation accordingly
. Return result

=end

NUMBERS = %w(zero one two three four five six seven eight nine)
OPERATIONS = %w(plus minus)

def do_operation(integers, expression_list)
operation = ''

expression_list.each do |word|
if OPERATIONS.include?(word)
operation = word
break
end
end

if operation == 'plus'
integers.reduce(:+)
elsif operation == 'minus'
integers.reduce(:-)
end
end

def computer(expression)
integers = []
expression_list = expression.downcase.split(' ')

expression_list.each do |word|
if NUMBERS.include?(word)
integers << NUMBERS.index(word)
end
end

result = do_operation(integers, expression_list)

result
end

p computer("two plus two") == 4
p computer('seven minus six') == 1
p computer('zero plus eight') == 8

Time: 23:00

## Example 7

=begin

Write a method that takes a single String argument and returns a new string
that contains the original value of the argument with the first character of
every word capitalized and all other letters lowercase. You may assume that
words are any sequence of non-blank characters.

# PEDAC

## Problem

Given a String, for every sequence of non-blank characters, capitalized their

- Input: a string of one or more words
- Output: a new string with each word capitalized

Clarification:

- New String
- Only first letter of word
- Word is a sequence of non-blank characters
- Every other letter must be lowercase

## Data

Array

## Algorithm

. Init a new local variable capitalized to an empty Array object
. From the string, create an array of downcase "word" (separate by blank
characters)
. Iterate over this array
. For each iteration, then upcase its first letter
. Return the return value of the iteration as a string separated by whitespaces

=end

def word_cap(string)
string.downcase.split(' ').map(&:capitalize).join(' ')
end

p word_cap('four score and seven') == 'Four Score And Seven'
p word_cap('the javaScript language') == 'The Javascript Language'
p word_cap('this is a "quoted" word') == 'This Is A "quoted" Word'

Time: 12:35

## Example 8

=begin

Consider the word "abode". We can see that the letter a is in position 1 and b
is in position 2. In the alphabet, a and b are also in positions 1 and 2.
Notice also that d and e in abode occupy the positions they would occupy in the
alphabet, which are positions 4 and 5.

Given an array of words, return an array of the number of letters that occupy
their positions in the alphabet for each word. For example,

solve(["abode","ABc","xyzD"]) = [4, 3, 1]

Input will consist of alphabet characters, both uppercase and lowercase. No
spaces.

# PEDAC

## Problem

Count the numbers of letters that are at their right place in the string and in
the alphabet.

Input: an array of strings with letters at their corresponding place in the
alphabet, or not
Output: an array of numbers representing the number of letters in their right
place in the alphabet

Clarification:

-  Case does not matter
-  Alphabet has 26 letters
-  If the string is more than 26 letters, everything above can be omited? (Yes)
-  Return 0 if none found
-  Returned array and given array should be the same size

## Data

Arrays

## Algorithm

.  MAIN
. Init ALPHABET whith every letter of the alphabet
.  Init local variable results
.  Iterate over the given array passing in string
.  Call COUNT_LETTER_IN_ORDER passing in string (downcase) as an arg
.  Save return value to results
.  Return results

.  COUNT_LETTER_IN_ORDER
.  Init local var counter
.  Iterate from 0 upto string size
.  Check if the current character of the string is equal to the character
at the same index in constant ALPHABET
.  Increment counter if true
.  Return counter

=end

ALPHABET = ('a'..'z').to_a

def count_letter_in_order(string)
counter = 0

0.upto(string.size) do |position|
if string[position] == ALPHABET[position]
counter += 1
end
end

counter
end

def solve(array)
results = []

array.each do |string|
results << count_letter_in_order(string.downcase)
end

results
end

p solve(["abode", "ABc", "xyzD"]) == [4, 3, 1]
p solve(["abide", "ABc", "xyz"]) == [4, 3, 0]
p solve(["IAMDEFANDJKL", "thedefgh", "xyzDEFghijabc"]) == [6, 5, 7]
p solve(["encode", "abc", "xyzD", "ABmD"]) == [1, 3, 1, 3]

Time: 12:35

## Example 9

=begin

Given a string of integers, return the number of odd-numbered substrings that
can be formed.

For example, in the case of "1341", they are 1, 1, 3, 13, 41, 341, 1341, a
total of 7 numbers.

# PEDAC

## Problem

Return the number of odd-numbered substrings that can be formed from a given
string.

Input: a string of a number
Output: the number of substring from the given string that are odd

Clarification:

- Assume always a well formed string? (Yes)
- Return 0 if nothing found? Yes
- Return an integer (not string)
- Substrings are part of the string, minimum 1 char

## Data

Arrays

## Algorithm

.  MAIN
.  Init local variable substr_integer to return value of FIND_EVERY_SUBSTRING
.  Call SELECT_ONLY_ODD passing in substr_integer to it
.  Return the count of the return value of SELECT_ONLY_ODD

.  FIND_EVERY_SUBSTRING
.  Init local variable substr_int
.  Iterate from 0 upto string size, passing in char_start as a param
.  Iterate from char_start upto string size, passing in char_end as a param
.  Slice string from char_start to char_end, convert to integer and save
the result to substr_int
. Return substr_int

.  SELECT_ONLY_ODD
.  Iterate over the given integers array and select only odd numbers

=end

def find_every_substring(string)
substr_int = []

0.upto(string.size - 1) do |char_start|
char_start.upto(string.size - 1) do |char_end|
substr_int << string[char_start..char_end].to_i
end
end

substr_int
end

def select_only_odd(integers)
integers.select(&:odd?)
end

def solve(num_candidate)
substr_integer = find_every_substring(num_candidate)

select_only_odd(substr_integer).count
end

p solve("1341") == 7
p solve("1357") == 10
p solve("13471") == 12
p solve("134721") == 13
p solve("1347231") == 20
p solve("13472315") == 28

Time: 18:02

## Example 10

=begin

Complete the function that takes an array of words.

You must concatenate the nth letter from each word to construct a new word
which should be returned as a string, where n is the position of the word in
the list.

For example:

["yoda", "best", "has"]  -->  "yes"
^        ^        ^
n=0     n=1     n=2

# PEDAC

## Problem

Given an array, return the character at the index of the string for each string
and concatenate the result.

Input: an array of valid string
Output: the new word based on each character of each string of the array

Clarification:

-  Empty array return empty string
-  Case matters
-  Assume each strings has enough character
-  Assume each array only contains strings

## Data

Arrays

## Algorithm

.  Init local variable new_string to local variable string
.  Iterate through the array, passing in index and string as parameters
.  Save the character of string at index in new_string
.  Return new_string

=end

def nth_char(array)
new_string = ''

array.each_with_index do |string, index|
new_string << string[index]
end

new_string
end

p nth_char(['yoda', 'best', 'has']) == 'yes'
p nth_char([]) == ''
p nth_char(['X-ray']) == 'X'
p nth_char(['No', 'No']) == 'No'
p nth_char(['Chad', 'Morocco', 'India', 'Algeria', 'Botswana',
'Bahamas', 'Ecuador', 'Micronesia']) == 'Codewars'

Time: 06:38

## Example 11

=begin

Write a function that takes in a string of one or more words, and returns the
same string, but with all five or more letter words reversed. Strings passed
in will consist of only letters and spaces.
Spaces will be included only when more than one word is present.

Examples:

spin_words( "Hey fellow warriors" ) => returns "Hey wollef sroirraw"

# PEDAC

## Problem

Given a string, return it with every 5+ letters words inveresed.

Input: a string (letters and spaces only, one or more word)
Output: same string with 5+ letters words inversed

Clarification:

-  Same string? (no)
-  Case matter
-  No ponctuation
-  one word possible

## Data

Arrays

## Algorithm

.  From the string, create an array of words (words being characters delimited
by spaces)
.  Iterate over the array
.  Count the number of characters in the word
.  If 5 or more characters, return it spinned
.  Otherwise return the word
.  Return the new array, joined by spaces

=end

def spin_words(sentence)
sentence.split.map do |word|
word.length >= 5 ? word.reverse : word
end.join(' ')
end

p spin_words('Hey fellow warriors') == 'Hey wollef sroirraw'
p spin_words('This is a test') == 'This is a test'
p spin_words('This is another test') == 'This is rehtona test'
p spin_words('test') == 'test'

Time: 07:33

## Example 12

=begin

A string is considered to be in title case if each word in the string is either
(a) capitalised (that is, only the first letter of the word is in upper case)
or (b) considered to be an exception and put entirely into lower case unless it
is the first word, which is always capitalised.

Write a function that will convert a string into title case, given an optional
list of exceptions (minor words). The list of minor words will be given as a
string with each word separated by a space. Your function should ignore the
case of the minor words string -- it should behave in the same way even if the
case of the minor word string is changed.

# PEDAC

## Problem

Given a string, return the same string in title case, following the optional
list of exceptions.

Input: a string to title case and a string of exceptions (optional)
Output: the first string, with title case, minus the exceptions (if exists)

Clarification:

-  All case should be modified in the given string
-  Exceptions are optional
-  No punctuations? (yes)
-  Same string? (no)
-  First char always capitalized

## Data

Arrays of strings

## Algorithm

.  Downcase the optional exceptions string
.  From the optional exceptions string, make an array of strings
.  Downcase the whole given string
.  Make an array of strings from the given string
.  Iterate over the new array
.  Capitalize the first letter of each element unless it's in the exceptions
list
.  Join the array with one space char
.  Capitalize the new string and return

=end

def title_case(string, exceptions='')
exceptions_list = exceptions.downcase.split(' ')
strings_list = string.downcase.split(' ')

title = strings_list.map do |word|
if exceptions_list.include?(word)
word
else
word.capitalize
end
end

title[0][0] = title[0][0].capitalize

title.join(' ')
end

p title_case('a clash of KINGS', 'a an the of') == 'A Clash of Kings'
p title_case('THE WIND IN THE WILLOWS', 'The In') == 'The Wind in the Willows'
p title_case('the quick brown fox') == 'The Quick Brown Fox'

Time: 16:12