Composing slices of slices - ruby

I've been thinking about this problem for a couple of days now and I can't find an elegant solution for the life of me.
In my app I have a Text class which is just a wrapper around String:
class Text < Struct.new(:string, :style)
def [](start, len)
Text.new(string[start, len], style)
end
def length
string.length
end
def to_s
case style
when :bold then "**" + string + "**"
when :italic then "_" + string +"_"
else string
end
end
def inspect
"<[#{style}] #{string}>"
end
end
I also have a Line class that basically is an array of Text objects:
class Line < Struct.new(:texts)
def [](start, len)
# TODO Should return a new Line object.
end
def length
texts.map(&:length).reduce(&:+)
end
def to_s
texts.map(&:to_s).join
end
def inspect
texts.map(&:inspect).join(" ")
end
end
The question is, how can I implement #[] in Line so that it returns a new Line object which "correctly" slices the contained Text objects?
The idea is to imitate the slicing behavior of String. For example:
line = Line.new([Text.new("abcdef", :bold), Text.new("ghijkl", :default)])
puts line[0, 2] # => **ab**
p line[0, 2] # => "<[:bold] ab>"
puts line[3, 6] # => **def**ghi
p line[3, 6] # => "<[:bold] def> <[:default] ghi>"
Keep in mind that the length of a Text object is the length of its string member:
a = Text.new("abc", :bold)
puts a # => **abc**
puts a.length # => 3
And the length of a Lineobject is just the sum of the lengths of its texts:
line = Line.new([Text.new("abcdef", :bold), Text.new("ghijkl", :default)])
puts line.length # => 12
Everything I've tried involves an stupid amount of complicated conditionals and convoluted temporary variables, and I feel there's a simpler solution lurking underneath it all.

Here's a snippet that may help you:
class Line
def pos_to_index_and_offset(pos)
raise ArgumentError if !texts or texts.empty?
index = 0
offset = pos
while offset >= (size = texts[index].length)
offset -= size
index += 1
raise ArgumentError if index > texts.length
end
return [index, offset]
end
end

Related

How to compare strings for 3/4 matches

Need to compare numbers in an array to 'winning' number. But I have to see if 3 out of 4 match. eg "1234" is my number and winning=["4356","8312","4820","7623"] . In this case "8312" should alert a win because they have 1 2&3 in common. I have to define the numbers in a unit test, then write a function in a separate file then pass that function back into the unit test. Any help would be greatly appreciated. I already wrote a function and test comparing for an exact match and dont have any idea on what next step to take.
function_file
def match(my_num,arr)
matches = []
arr.each_with_index do |v,i|
if my_num == v
matches << my_num
end
end
matches
end
test_file
require "minitest/autorun"
require_relative "close_but_no_func.rb"
class TestWinningNumbers < Minitest::Test
def test_1_equals_1
assert_equal(10-5, 3+2)
end
def test_winning_num
my_num = "1134"
arr=["6028","2088","3058","3476","8740","1134"]
assert_equal(["1134"], match(my_num, arr))
end
end
So let us divide this problem into two seperate problems.
You want a function that counts the number of matching characters.
Then you want to collect these strings together, if they have enough matching characters.
You could for example write a function that checks how many characters of the both strings match.
def count_matching_chars(str1,str2)
# counts how many characters of str1 appear in str2
matching_chars_count = 0
str1.each_char do |char|
matching_chars_count += 1 if str2.include?(char)
end
matching_chars_count
end
puts count_matching_chars("1234", "1134") => 3
puts count_matching_chars("1111", "1134") => 4
puts count_matching_chars("1234", "1111") => 1
This one here ignores the positioning, it just checks, how many of the characters of str1 match one character of str2.
Now you can easily collect these numbers in an array.
def matches(my_num, arr)
result = []
arr.each do |num|
result << arr if count_matching_chars(my_num,num) >= 3
end
result
end
You can write both functions in a more compact way, by using enumerator functions like count and select:
def count_matching_chars(str1,str2)
str1.each_char.count do |char|
str2.include?(char)
end
end
def matches(my_num, arr)
arr.select do |num|
return true if count_matching_chars(num,my_num) >= 3
end
end
Or you then combine everything into one function
def matches(my_num, arr)
arr.select do |num|
true if my_num.each_char.count { |char| num.include?(char)} >= 3
end
end
Now if you just want to check, if it is a winning number. You just return true as soon as you find a match:
def winning_number?(my_num, arr)
arr.select do |num|
return true if my_num.each_char.count { |char| num.include?(char)} >= 3
end
end

Simplify similar loops into one

I am writing a braille converter. I have this method to handle the top line of a braille character:
def top(input)
braille = ""
#output_first = ""
#top.each do |k, v|
input.chars.map do |val|
if k.include?(val)
braille = val
braille = braille.gsub(val, v)
#output_first = #output_first + braille
end
end
end
#output_first
end
I'm repeating the same each loop for the middle and bottom lines of a character. The only thing that is different from the method above is that the #top is replaced with #mid and #bottom to correspond to the respective lines.
Trying to figure a way to simplify the each loop so I can call it on top, mid and bottom lines.
You can put the loop in a separate method.
def top(input)
#output_first = handle_line(#top)
end
def handle_line(line)
result = ''
line.each do |k, v|
input.chars.map do |val|
if k.include?(val)
braille = val
braille = braille.gsub(val, v)
result = result + braille
end
end
end
result
end
You can then call handle_line in your #mid and #bottom processing
I'm not sure whats in the #top var but I believe braille has limited number of characters and therefore I would consider some map structure
BRAILLE_MAP = {
'a' => ['..',' .','. '], # just an example top,mid,bot line for character
'b' => ['..','..',' '],
# ... whole map
}
def lines(input)
top = '' # representation of each line
mid = ''
bot = ''
input.each_char do |c|
representation = BRAILLE_MAP[c]
next unless representation # handle invalid char
top << representation[0] # add representation to each line
mid << representation[1]
bot << representation[2]
end
[top,mid,bot] # return the lines
end
There may be better way to handle those 3 variables, but I cant think of one right now

Ruby: how to split a string on a regex while keeping the delimiters? [duplicate]

This question already has answers here:
How do I keep the delimiters when splitting a Ruby string?
(5 answers)
Closed 7 years ago.
This has been asked multiple times around here, but never got a generic answer, so here we go:
Say you have a string, any string, but let's go with "oruh43451rohcs56oweuex59869rsr", and you want to split it with a regular expression. Any regular expression, but let's go with a sequence of digits: /\d+/. Then you'd use split:
"oruh43451rohcs56oweuex59869rsr".split(/\d+/)
# => ["oruh", "rohcs", "oweuex", "rsr"]
That's lovely and all, but I want the digits. So for that we have scan:
"oruh43451rohcs56oweuex59869rsr".scan(/\d+/)
# => ["43451", "56", "59869"]
But I want it all! Is there, say, a split_and_scan? Nope.
How about I split and scan then zip them? Let me stop you right there.
Ok, so how?
If split's pattern contains a capture group, the group will be included in the resulting array.
str = "oruh43451rohcs56oweuex59869rsr"
str.split(/(\d+)/)
# => ["oruh", "43451", "rohcs", "56", "oweuex", "59869", "rsr"]
If you want it zipped,
str.split(/(\d+)/).each_slice(2).to_a
# => [["oruh", "43451"], ["rohcs", "56"], ["oweuex", "59869"], ["rsr"]]
I'm glad you asked… well, there's String#shatter from Facets. I don't love it because it's implemented using trickery (look at the source, it's cute clever trickery, but what if your string actually contains a "\1"?).
So I rolled my own. Here's what you get:
"oruh43451rohcs56oweuex59869rsr".unjoin(/\d+/)
# => ["oruh", "43451", "rohcs", "56", "oweuex", "59869", "rsr"]
And here's the implementation:
class Object
def unfold(&f)
(m, n = f[self]).nil? ? [] : n.unfold(&f).unshift(m)
end
end
class String
def unjoin(rx)
unfold do |s|
next if s.empty?
ix = s =~ rx
case
when ix.nil?; [s , ""]
when ix == 0; [$&, $']
when ix > 0; [$`, $& + $']
end
end
end
end
(verbosier version at the bottom)
And here are some examples of corner cases being handled:
"".unjoin(/\d+/) # => []
"w".unjoin(/\d+/) # => ["w"]
"1".unjoin(/\d+/) # => ["1"]
"w1".unjoin(/\d+/) # => ["w", "1"]
"1w".unjoin(/\d+/) # => ["1", "w"]
"1w1".unjoin(/\d+/) # => ["1", "w", "1"]
"w1w".unjoin(/\d+/) # => ["w", "1", "w"]
And that's it, but here's more…
Or, if you don't like mucking with the built-in classes… well, you could use Refinements… but if you really don't like it, here it is as functions:
def unfold(x, &f)
(m, n = f[x]).nil? ? [] : unfold(n, &f).unshift(m)
end
def unjoin(s, rx)
unfold(s) do |s|
next if s.empty?
ix = s =~ rx
case
when ix.nil?; [s , ""]
when ix == 0; [$&, $']
when ix > 0; [$`, $& + $']
end
end
end
It also occurs to me that it may not always be clear which are the separators and which are the separated bits, so here's a little addition that lets you query a string with #joint? to know what role it played before the split:
class String
def joint?
false
end
class Joint < String
def joint?
true
end
end
def unjoin(rx)
unfold do |s|
next if s.empty?
ix = s =~ rx
case
when ix.nil?; [s, ""]
when ix == 0; [Joint.new($&), $']
when ix > 0; [$`, $& + $']
end
end
end
end
and here it is in use:
"oruh43451rohcs56oweuex59869rsr".unjoin(/\d+/)\
.map { |s| s.joint? ? "(#{s})" : s }.join(" ")
# => "oruh (43451) rohcs (56) oweuex (59869) rsr"
You can now easily reimplement split and scan:
class String
def split2(rx)
unjoin(rx).reject(&:joint?)
end
def scan2(rx)
unjoin(rx).select(&:joint?)
end
end
"oruh43451rohcs56oweuex59869rsr".split2(/\d+/)
# => ["oruh", "rohcs", "oweuex", "rsr"]
"oruh43451rohcs56oweuex59869rsr".scan2(/\d+/)
# => ["43451", "56", "59869"]
And if you hate match globals and general brevity…
class Object
def unfold(&map_and_next)
result = map_and_next.call(self)
return [] if result.nil?
mapped_value, next_value = result
[mapped_value] + next_value.unfold(&map_and_next)
end
end
class String
def unjoin(regex)
unfold do |tail_string|
next if tail_string.empty?
match = tail_string.match(regex)
index = match.begin(0)
case
when index.nil?; [tail_string, ""]
when index == 0; [match.to_s, match.post_match]
when index > 0; [match.pre_match, match.to_s + match.post_match]
end
end
end
end

Turn a hash symbol to string and apply .ord method (for an offset)

I am doing a coding exercise(just to clear up any questions beforehand). My objective is to be able to offset the hash key by a specified amount. The problem I am having is if the hash key is a symbol. My approach is to turn it into a string and go from there. Here is my code:
class :: Hash
def transpose_key(offset)
self.each_key{|key|
t = (key.to_s.ord - "a".ord + offset)
key = (t % 26) + "a".ord.chr
}
return self
end #def
end #class
wrong_keys = { :a => "rope", :b => "knife", :x => "life-jacket", :z => "raft" }
puts wrong_keys.transpose_key(2)
I am getting the following error:
test.rb:6:in `+': String can't be coerced into Fixnum (TypeError)
I'm confused because I would think (key.to_s.ord) would give me a string letter on which to convert to ascii? I will later add functionality for numbered keys. Most of all I would like to, if at possible, use the approach I have started and make it work. Any ideas?
UPDATED
Here is my new code:
def transpose(string, offset)
#string = string.chars
string.each_codepoint {|c| puts (c + offset) > 122 ? (((c - 97) + offset) % 26 + 97).chr : (c + offset).chr}
end
transpose('xyz', 5)
...the output is correct, but it puts every character on different line. I have tried a various ways to try to join it, but can't seem to. If I use print in the iteration instead of puts, the output is joined, but I don't get a new line, which I want. Why is that and how can I fix it?
I'm confused because I would think (key.to_s.ord) would ...
That's the wrong line.
The next line is the line raising the error, and you're not doing .to_s.ord, you're doing .ord.to_s:
key = (t % 26) + "a".ord.chr
"a".ord.chr has no meaning, you're converting a character to an ordinal and back to a character, and then trying to add an integer and a character, hence your error. Replace "a".ord.chr with "a".ord
If I understand your question correctly, I think this gives you want you want:
class Hash
def transpose_key(offset)
map do |key, value|
t = (key.to_s.ord - "a".ord + offset) % 26
[(t + "a".ord).chr.to_sym, value]
end.to_h
end
end
wrong_keys = { :a => "rope", :b => "knife", :x => "life-jacket", :z => "raft" }
puts wrong_keys.transpose_key(2)
# {:c=>"rope", :d=>"knife", :z=>"life-jacket", :b=>"raft"}
Array#to_h (v2.0+) is an alternative to the class method Hash::[] (v1.0+)for converting an array of two-element arrays to a hash:
a = [[1,2],[3,4]]
Hash[a] #=> {1=>2, 3=>4}
a.to_h #=> {1=>2, 3=>4}
If we removed .to_h from the method we would find that the value returned by map (to which to_h is applied) is:
[[:c, "rope"], [:d, "knife"], [:z, "life-jacket"], [:b, "raft"]]
To use Hash#each_key, you could do this:
class Hash
def transpose_key(offset)
each_key.with_object({}) do |key,h|
t = (key.to_s.ord - "a".ord + offset) % 26
h[(t + "a".ord).chr.to_sym] = self[key]
end
end
end
puts wrong_keys.transpose_key(2)
# {:c=>"rope", :d=>"knife", :z=>"life-jacket", :b=>"raft"}
On reflection, I prefer the latter method.

Reverse words of a string in Ruby?

I am trying to reverse the words of a string in Ruby, without using the reverse method. I want to implement the known algorithm of:
Reverse the whole string
Reverse each word in the reversed string.
Here is what I have come up with:
class String
def custom_reverse(start, limit)
i_start = start
i_end = limit - 1
while (i_start <= i_end)
tmp = self[i_start]
self[i_start] = self[i_end]
self[i_end] = tmp
i_start += 1
i_end -= 1
end
return self
end
def custom_reverse_words
self.custom_reverse(0, self.size)
i_start = 0
i_end = 0
while (i_end <= self.length)
if (i_end == self.length || self[i_end] == ' ')
self.custom_reverse(i_start, i_end)
i_start += 1
end
i_end += 1
end
end
end
test_str = "hello there how are you"
p test_str.custom_reverse_words
But the results are "yahthello ow ou er ereh"
What am I missing?
The gist of any reverse operation is to iterate over elements in the reverse order of what you'd normally do. That is, where you'd usually use the set (0..N-1) you'd instead go through (N-1..0) or more specifically N-1-i where i is 0..N-1:
class String
def reverse_words
split(/\s+/).map{|w|wl=w.length-1;(0..wl).map{|i|w[wl-i]}.join}.join(' ')
end
end
puts "this is reverse test".reverse_words.inspect
# => "siht si esrever tset"
The same principle can be applied to the words in a given string.
Interview questions of this sort are of highly dubious value. Being "clever" in production code is usually a Very Bad Idea.
Here's one way to reverse an array without using the built-in reverse:
class Array
def reverse
tmp_ary = self.dup
ret_ary = []
self.size.times do
ret_ary << tmp_ary.pop
end
ret_ary
end
end
%w[a b c].reverse # => ["c", "b", "a"]
tmp_ary.pop is the secret. pop removes elements from the end of the array.
The cleanest solution I could think of is:
class Array
def my_reverse
sort_by.with_index {|_, i| -i}
end
end
class String
def words
split(/\W+/)
end
def revert_words
words.my_reverse.join(' ')
end
def revert_each_word
words.map {|w| w.chars.my_reverse.join}.join(' ')
end
end
Once you define a simple and efficient array reverser:
def reverse_array(a)
(a.length / 2).times {|i| a[i],a[-(i+1)] = a[-(i+1)],a[i]}
a
end
You can reverse a sentence pretty straightforwardly:
def reverse_sentence(s)
reverse_array(s.split('')).join.split(" ").map{|w| reverse_array(w.split('')).join}.join(" ")
end
reverse_sentence "Howdy pardner" # => "pardner Howdy"
Here's another way:
class String
def reverse_words
split.inject([]){|str, word| str.unshift word}.join(' ')
end
def reverse_chars
each_char.inject([]){|str, char| str.unshift char}.join('')
end
end
Revised
Carey raises a good point, reverse_chars can be simplified, since string is already an Enumerable:
class String
def reverse_chars
each_char.inject(""){|str, char| str.insert(0, char) }
end
end

Resources