I built this method to find the longest word in an array, but I'm wondering if there's a better way to have done it. I'm pretty new to Ruby, and just did this as an exercise for learning the inject method.
It returns either the longest word in an array, or an array of the equal longest words.
class Array
def longest_word
# Convert array elements to strings in the event that they're not.
test_array = self.collect { |e| e.to_s }
test_array.inject() do |word, comparison|
if word.kind_of?(Array) then
if word[0].length == comparison.length then
word << comparison
else
word[0].length > comparison.length ? word : comparison
end
else
# If words are equal, they are pushed into an array
if word.length == comparison.length then
the_words = Array.new
the_words << word
the_words << comparison
else
word.length > comparison.length ? word : comparison
end
end
end
end
end
I would do
class Array
def longest_word
group_by(&:size).max.last
end
end
Ruby has a standard method for returning an element in a list with the maximum of a value.
anArray.max{|a, b| a.length <=> b.length}
or you can use the max_by method
anArray.max_by(&:length)
to get all the elements with the maximum length
max_length = anArray.max_by(&:length).length
all_with_max_length = anArray.find_all{|x| x.length = max_length}
Here's one using inject (doesn't work for an empty array):
words.inject(['']){|a,w|
case w.length <=> a.last.length
when -1
a
when 0
a << w
when 1
[w]
end
}
which can be shortened to
words.inject(['']){|a,w|
[a + [w], [w], a][w.length <=> a.last.length]
}
for those who like golf.
A two liner:
vc = ['asd','s','1234','1235'].sort{|a,b| b.size <=> a.size}
vc.delete_if{|a| a.size < vc.first.size}
#Output
["1235", "1234"]
or if you want use inject, this use your idea, but its more short.
test_array.inject{ |ret,word|
ret = [ret] unless ret.kind_of?(Array)
ret << word if word.size == ret.first.size
ret = [word] if word.size > ret.first.size
ret
}
module Enumerable
def longest_word
(strings = map(&:to_s)).
zip(strings.map(&:length)).
inject([[''],0]) {|(wws, ll), (w, l)|
case l <=> ll
when -1 then [wws, ll]
when 1 then [[w], l]
else [wws + [w], ll]
end
}.first
end
end
This method only depends on generic Enumerable methods, there's nothing Array specific about it, therefore we can pull it up into the Enumerable module, where it will also be available for Sets or Enumerators, not just Arrays.
This solution uses the inject method to accumulate the longest strings in an array, then picks the ones with the highest length.
animals = ["mouse", "cat", "bird", "bear", "moose"]
animals.inject(Hash.new{|h,k| h[k] = []}) { |acc, e| acc[e.size] << e; acc }.sort.last[1]
This returns:
["mouse", "mouse"]
Related
I am working on an assignment where I have to take user input of a string and search through it to find the longest non-repeating string in it. So for example:
If the string is:
"abcabcabcdef"
My output needs to be:
"abcdef is the longest substring at the value of 6 characters"
Here is my poorly made code:
class Homework_4
puts "Enter any string of alphabetical characters: "
user_input = gets
longest_str = 0
empty_string = ""
map = {}
i = 0
j = 0
def long_substr()
while j < str_len
if map.key?(user_input[j])
i = [map[user_input[j]], i].max
end
longest_str = [longest_str, j - i + 1].max
map[user_input[j]] = j + 1
j += 1
end
longest_str
end
long_substr(user_input)
end
I have been working on this for over 6 hours today and I just can't figure it out. It seems like the internet has many ways to do it. Almost all of them confuse me greatly and don't really explain what they're doing. I don't understand the syntax they use or any of the variables or conditions.
All I understand is that I need to create two indicators that go through the inputted string searching for a non-repeating substring (sliding window method). I don't understand how to create them, what to make them do or even how to make them find and build the longest substring. It is very confusing to try and read the code that is full of random letters, symbols, and conditions. I'm sure my code is all sorts of messed up but any help or tips that could point me in the right direction would be greatly appreciated!
def uniq?(s)
# All letters of s uniq?
return s.chars.uniq == s.chars
end
def subs(s)
# Return all substrings in s.
(0..s.length).inject([]){|ai,i|
(i..s.length - i).inject(ai){|aj,j|
aj << s[i,j]
}
}.uniq
end
def longest_usub(s)
# Return first longest substring of s.
substrings(s).inject{|res, s| (uniq?(s) and s.length > res.length) ? s : res}
end
ruby's inject is actually a reduce function, where inject(optional_start_value){<lambda expression>} - and the lambda expression is similar to Python's lambda x, y: <return expression using x and y> just that lambda expressions are strangely written in Ruby as {|x, y| <return expression using x and y>}.
Python's range(i, y) is Ruby's i..y.
Python's slicing s[i:j] is in Ruby s[i..j] or s[i,j].
<< means add to end of the array.
Second solution (inspired by #Rajagopalan's answer)
def usub(s)
# Return first chunk of uniq substring in s
arr = []
s.chars do |char|
break if arr.include? char
arr << char
end
arr.join
end
def usubs(s)
# Return each position's usub() in s
(0..s.length).to_a.map{|i| usub(s[i,s.length])}
end
def longest_usub(s)
# return the longest one of the usubs() over s
usubs(s).max_by(&:length)
end
then you can do:
longest_usub("abcabcabcdef")
## "abcdef"
I have asssumed that a string is defined to be repeating if it contains a substring s of one or one more characters that is followed by the same substring s, and that a string is non-repeating if it is not repeating.
A string is seen to be repeating if and only if it matches the regular expression
R = /([a-z]+)\1/
Demo
The regular expression reads, "match one or more letters that are saved to capture group one, then match the content of capture group 1".
For convenience we can construct a simple helper method.
def nonrepeating?(str)
!str.match? R
end
I will perform a binary search to find the longest non-repeating string. First, I need a second helper method:
def find_nonrepeating(str, len)
0.upto(str.size-len) do |i|
s = str[i,len]
return s if nonrepeating?(s)
end
nil
end
find_nonrepeating("abababc", 7) #=> nil
find_nonrepeating("abababc", 6) #=> nil
find_nonrepeating("abababc", 5) #=> nil
find_nonrepeating("abababc", 4) #=> "babc"
find_nonrepeating("abababc", 3) #=> "aba"
find_nonrepeating("abababc", 2) #=> "ab"
find_nonrepeating("abababc", 1) #=> "a"
We may now implement the binary search.
def longest(str)
longest = ''
low = 0
high = str.size - 1
while low < high
mid = (low + high)/2
s = find_nonrepeating(str, mid)
if s
longest = s
low = mid + 1
else
high = mid - 1
end
end
longest
end
longest("dabcabcdef")
#=> "bcabcdef"
a = "abcabcabcdef"
arr = []
words = []
b=a
a.length.times do
b.chars.each do |char|
break if arr.include? char
arr << char
end
words << arr.join
arr.clear
b=b.chars.drop(1).join
end
p words.map(&:chars).max_by(&:length).join
Output
"abcdef"
I am working through a one line solution for the question #write a boolean function zero_sum? that takes an array of integers and returns true if 2 elements in the array sum to zero.
Here is my original answer that would also work if they would ask for a return of the indicies, but I am having trouble coming up with a one line function that works.
def zero_sum?(array)
array.each_with_index do |x, i1|
for i2 in i1 + 1..array.length - 1
if x + array[i2] == 0
return true
end
end
end
false
end
puts "\nZero Sum:\n" + "*" * 15 + "\n"
puts zero_sum?([1, -1]) == true
puts zero_sum?([1,1,0,2,1]) == false
puts zero_sum?([1,1,0,2,1,0]) == true
puts zero_sum?([2,3,4,-3,1]) == true
Here is my one line solution. I am not getting any false returns at all:
def zero_sum?(array)
array.any? {|x| array.each {|y| array.count(0) != 1 && x + y == 0}}
end
puts "\nZero Sum:\n" + "*" * 15 + "\n"
puts zero_sum?([1, -1]) == true
puts zero_sum?([1,1,0,2,1]) == false #returning true
puts zero_sum?([1,1,2,1]) == false #returning true
puts zero_sum?([1,1,0,2,1,0]) == true
puts zero_sum?([2,3,4,-3,1]) == true
Any insight would be great! (if a one line response for the return of indices comes to mind, that would be awesome!).
Try this.
require 'set'
def zero_sum?(arr)
arr.each_with_object(Set.new) do |n,st|
return true if st.include?(-n)
st << n
end
false
end
zero_sum? [1,-1] #=> true
zero_sum? [1,1,0,2,1] #=> false
zero_sum? [1,1,0,2,1,0] #=> true
zero_sum? [2,3,4,-3,1] #=> true
I've made st a set rather than an array to speed lookups.
The problem with your one line solution is that because the any? and each loops iterate over the whole array if a zero sum isn't found earlier then each element will be added to itself at some point. This means that if the array contains a 0 you'll get a 0 + 0 == 0 comparison.
I won't spoil finding a one liner for you, but if you'd like to work on finding a solution that is similar to your multiline solution then you can obtain the index of the current element whilst still using any by creating an Enumerator and using with_index i.e.
array.to_enum.with_index.any? { |x, index| ... }
and you can access the remainder of the array using slice notation:
array[index + 1..array.length - 1]
One way to do this using Array#combination:
def zero_sum? arr
arr.combination(2).any? { |pair| pair.inject(:+).zero? }
end
zero_sum? [2,3,5,2,1,-2,4] #=> true
zero_sum? [2,3,5,2,1,2,4] #=> false
zero_sum? [2,3,5,0,0,2,1,2,4] #=> true
Caveat about this answer being that Array#combination is expensive, so there's probably a better way. But interesting to know this approach nonetheless.
Many thanks to #Ursus for pointing out using any? instead of find.
Although not a one-liner, another approach is to use Enumerable#partition with parallel assignment to get two arrays, the positives and negatives of a number list arr.
def zero_sum? arr
return true if arr.count(0) > 1
a, b = arr.uniq.partition(&:positive?)
a.any? { |e| b.include? -e }
end
zero_sum? [2,3,5,2,1,-2,4] #=> true
zero_sum? [2,3,5,2,1,2,4] #=> false
zero_sum? [2,3,5,0,0,2,1,2,4] #=> true
Other key methods: Numeric#positive?, Array#any? and Array#include?
I am supposed to return the size of the largest palindrome within a given string. For example, if I pass "racecar", I should get a return of 7. If I pass "racecarveryfast" or "veryfastracecar", it should still return 7. Specs I have to pass are:
Test.assert_equals(longest_palindrome("a"), 1)
Test.assert_equals(longest_palindrome("aa"), 2)
Test.assert_equals(longest_palindrome("baa"), 2)
Test.assert_equals(longest_palindrome("aab"), 2)
Test.assert_equals(longest_palindrome("baabcd"), 4)
Test.assert_equals(longest_palindrome("baablkj12345432133d"), 9)
and I am passing the first four with this code:
def longest_palindrome s
sub_count = 0
palidrome_count = []
s_array = s.chars
puts "string: " + s
puts "string array: " + s_array.to_s
if s.reverse == s
return s.size
else
s.match('(.)\1')[0].size
end
end
My thought process from here is breaking apart the string into smaller chunks, maybe through a loop. Any help or guidance would be appreciated.
def longest_palindrome(string)
i = 0
a = []
while !string[i..-1].empty?
j = -1
while !string[i..j].empty?
s = string[i..j]
if s.reverse == s
a << s.length
end
j -= 1
end
i += 1
end
a.max
end
Suppose the string has n characters. First see if the entire string is a palindrome. If it is, return the string. Fini! If not, see if either of the two substrings of length n-1 is a palindrome. If one is, return it. If not, examine substrings of length n-2, and so on. As long as the string contains at least one letter, the longest palindrome will be found.
def longest_palindrome(str)
arr = str.downcase.chars
str.length.downto(1) do |n|
ana = arr.each_cons(n).find { |b| b == b.reverse }
return ana.join if ana
end
end
The key method here is Enumerable#each_cons.
Here are some examples1:
longest_palindrome "a" #=> "a"
longest_palindrome "aa" #=> "aa"
longest_palindrome "baa" #=> "aa"
longest_palindrome "aab" #=> "aa"
longest_palindrome "baabcd" #=> "baab"
longest_palindrome "baablkj12345432133d" #=> "123454321"
longest_palindrome "I heard tattarrattats" #=> "tattarrattat"
1 James Joyce coined the word "tattarrattat" in Ulysses, to mean a knock on the door.
I have solved the problem using normal loops and now using hashes, however I am not confident I used the hashes as well as I could have. Here is my code:
# 1-100 whats duplicated
def whats_duplicated?(array)
temp = Hash.new
output = Hash.new
# Write the input array numbers to a hash table and count them
array.each do |element|
if temp[element] >= 1
temp[element] += 1
else
temp[element] = 1
end
end
# Another hash, of only the numbers who appeared 2 or more times
temp.each do |hash, count|
if count > 1
output[hash] = count
end
end
# Return our sorted and formatted list as a string for screen
output.sort.inspect
end
### Main
# array_1 is an array 1-100 with duplicate numbers
array_1 = []
for i in 0..99
array_1[i] = i+1
end
# seed 10 random indexes which will likely be duplicates
for i in 0..9
array_1[rand(0..99)] = rand(1..100)
end
# print to screen the duplicated numbers & their count
puts whats_duplicated?(array_1)
My question is really what to improve? This is a learning excercise for myself, I am practising some of the typical brain-teasers you may get in an interview and while I can do this easily using loops, I want to learn an efficient use of hashes. I re-did the problem using hashes hoping for efficiency but looking at my code I think it isn't the best it could be. Thanks to anyone who takes an interest in this!
The easiest way to find duplicates in ruby, is to group the elements, and then count how many are in each group:
def whats_duplicated?(array)
array.group_by { |x| x }.select { |_, xs| xs.length > 1 }.keys
end
whats_duplicated?([1,2,3,3,4,5,3,2])
# => [2, 3]
def whats_duplicated?(array)
array.each_with_object(Hash.new(0)) { |val, hsh| hsh[val] += 1 }.select { |k,v| v > 1 }.keys
end
I would do it this way:
def duplicates(array)
counts = Hash.new { |h,k| h[k] = 0 }
array.each do |number|
counts[number] += 1
end
counts.select { |k,v| v > 1 }.keys
end
array = [1,2,3,4,4,5,6,6,7,8,8,9]
puts duplicates(array)
# => [4,6,8]
Some comments about your code: The block if temp[element] == 1 seems not correct. I think that will fail if a number occurs three or more times in the array. You should at least fix it to:
if temp[element] # check if element exists in hash
temp[element] += 1 # if it does increment
else
temp[element] = 1 # otherwise init hash at that position with `1`
end
Furthermore I recommend not to use the for x in foo syntax. Use foo.each do |x| instead. Hint: I like to ask in interviews about the difference between both versions.
i am trying to find if array has 2 digits number and if i find one i want to add the two digit and make it single. then add all the numbers in array to come up with a a sum. here is my code so far. and also i am a noob and learning
class Imei
attr_accessor :Imei, :split_total1, :split_total2
def initialize(imei)
#imei = imei.to_i
#split_total1 = []
#split_total2 = []
end
def check_two_digit(num)
if num.to_s.length == 2
num = num.to_s.split(//).partition.with_index{|_,i| i.odd?}
num.each do |a, b|
a.to_i + b.to_i
end
else
num.to_i
end
end
def check_imei
if #imei.to_s.length == 15
split1, split2 = #imei.to_s.split(//).partition.with_index{|_, i| i.odd?}
split1.each do |a|
#split_total1 << check_two_digit(a.to_i * 2)
end
split2.pop
split2.each do |a|
#split_total2 << a.to_i
end
else
puts "IMEI NUMBER INVALID"
end
end
end
imei = Imei.new(123456789102222)
imei.check_imei
puts imei.split_total1.inspect
puts imei.split_total2.inspect
Find below the Luhn Algorithm I wrote in ruby
def luhn_10_valid? imei
digits = imei.reverse.chars.map(&:to_i)
digits.each_with_index.inject(0) do |sum, (digit, i)|
digit *= 2 if i.odd?
digit -= 9 if digit > 9
sum += digit
end % 10 == 0
end
For Luhn algorithm I really like the divmod method, which simplifies things
array.reverse.each_slice(2).map { |x, y|
y ||= 0
[x, (y * 2).divmod(10)]
}.flatten.inject(:+) % 10 == 0
If a contains only non-negative integers, this is one Ruby-like way to compute the sum:
a.reduce(0) {|t,i| t + (((10..99).cover? i) ? i.divmod(10).reduce(:+) : i )}
Explanation:
If i => 46, (10..99).cover?(46) => true, 46.divmod(10) => [4,6], [4,6].reduce(:+) => 10. Recall that reduce is aka inject. [4,6]reduce(:+) has the same result as (but,technically, is not 'equivalent to'):
[4,6].reduce { |u,j| u+j }
The zero initial value is needed for the first reduce, as
a[46].reduce {|t,i| t+(((10..99).cover? i) ? i.divmod(10).reduce(:+):i)}
#=> 46
which is incorrect.
If a instead contains string representations of integers and/or the integers may be negative, let me know and I'll change my answer accordingly.