Created
July 31, 2020 16:56
-
-
Save datt/f321718efdec3ec69fd65144ccf74a55 to your computer and use it in GitHub Desktop.
Finding length of longest substring code in Ruby. Uses Ruby's core methods, may not be efficient for other languages. Probably one of the fastest solution implemented in Ruby, suggestions are welcomed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Rough Algo | |
# 1. Initialize last_distinct with first character | |
# 2. Iterate character by character | |
# 3. if no repeating char, Push current character in last_distinct | |
# 4. Repeat 3 until repeating char found | |
# 5 if repeating char found | |
# 6 if repeating char at first place, drop and rotate | |
# 7. if repeating char not at first place, reset with char and set longest. | |
class LongestDistinctSubstring # Finds length of the longest substring | |
ARG_ERROR = 'ArgumentError:: Expects a valid non blank string'.freeze | |
attr_reader :longest # just to access the found substrings with index | |
# @param [String] input string for finding longest substring length | |
def initialize(input_string) | |
raise ARG_ERROR if !input_string.is_a?(String) || input_string.length.zero? | |
@input_string = input_string.downcase # making case insensitive | |
@longest = { strings: [], length: 0 } # for storing result | |
@last_distinct = '' # storing reference to current longest string | |
@last_distinct_length = 0 # storing length of current longest string | |
end | |
# Returns the longest distinct substring length | |
def longest_length | |
find_longest if @longest[:length] == 0 | |
@longest[:length] | |
end | |
private | |
# The core logic, refer Rough Algo section at top | |
def find_longest | |
@input_string.each_char.with_index do |char, idx| | |
# if duplicate character found, either remove first char or reset | |
if @last_distinct.include?(char) | |
# # duplicate character found at first character, drop it from longest | |
if @last_distinct.index(char).zero? | |
@last_distinct = @last_distinct.slice(1..-1) << char | |
# i.e. rotate the first character to the last | |
@last_distinct_length = @last_distinct.length | |
else | |
update_new_longest(idx) if new_longest? | |
reset_last_distinct(char) # repetitive char found, reset it with char | |
end | |
else | |
@last_distinct << char | |
@last_distinct_length = @last_distinct.length | |
update_new_longest(idx) if new_longest? && idx.next == @input_string.length | |
# handling edge case e.g. 'abcabcd' must return 4 i.e. 'abcd' | |
end | |
end | |
end | |
# compares new length and current length and | |
# @return [Boolean] | |
def new_longest? | |
@last_distinct_length >= @longest[:length] | |
end | |
# resets the last_distinct with sent substring | |
# @param [String] substring | |
def reset_last_distinct(substring) | |
@last_distinct = substring | |
end | |
# Updates length of newly substring | |
# @param [Integer] number which is index of current loop | |
def update_new_longest(idx) | |
update_longest_string_data(idx) | |
@longest[:length] = @last_distinct_length | |
end | |
# @todo: Though Getting all the uniq strings of highest length, | |
# was out of scope this program. It's logic isn't perfect, needs improvement. | |
def update_longest_string_data(idx) | |
start_index = idx - @last_distinct_length | |
string_data = { | |
substring: @last_distinct, | |
sindex: start_index | |
} | |
if @last_distinct_length > @longest[:length] | |
@longest[:strings] = [string_data] | |
else # if string length is same as last_distinct substring | |
@longest[:strings] << string_data | |
end | |
end | |
end | |
# simple tests | |
string = 'abcabcd' #abcd | |
lds = LongestDistinctSubstring.new(string) | |
puts "Test Failed => abcabcd" unless lds.longest_length == 4 | |
string = 'technology' # technol | |
lds = LongestDistinctSubstring.new(string) | |
puts "Test Failed => technology" unless lds.longest_length == 7 | |
# benchmarking | |
require 'benchmark' | |
a = ('a'..'z').to_a | |
N = 1000 | |
arr = 50.times.map { N.times.map { a.sample }.join } | |
5.times do | |
Benchmark.bm do |x| | |
x.report("LDS") { arr.sum { |s| LongestDistinctSubstring.new(s).longest_length } } | |
end | |
end | |
# user system total real | |
# LDS 0.034644 0.000502 0.035146 ( 0.037816) | |
# user system total real | |
# LDS 0.028382 0.000551 0.028933 ( 0.032161) | |
# user system total real | |
# LDS 0.024485 0.000502 0.024987 ( 0.026192) | |
# user system total real | |
# LDS 0.025439 0.000338 0.025777 ( 0.026046) | |
# user system total real | |
# LDS 0.023968 0.000215 0.024183 ( 0.024395) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment