I want to reverse a sentence. For example, my string is like follows.
str = "I am a good boy"
I want the result "boy good a am I". I can reverse the string by using built in Ruby methods like:
str.split(" ").reverse.join(" ") #=> "boy good a am I"
Is there any way to do this without using Ruby built in methods?
Sure, there is the way. In fact my favorite way. You said you don't want to use Ruby's builtins. Well, we won't. What about native extensions? I know people love them.
Firstly, create reverse/reverse.c file. Most of the source I took here.
#include "ruby/ruby.h"
void reverseWords(char *s)
{
char *word_begin = NULL;
char *temp = s;
while( *temp )
{
if (( word_begin == NULL ) && (*temp != ' ') )
{
word_begin=temp;
}
if(word_begin && ((*(temp+1) == ' ') || (*(temp+1) == '\0')))
{
reverse(word_begin, temp);
word_begin = NULL;
}
temp++;
}
reverse(s, temp-1);
}
void reverse(char *begin, char *end)
{
char temp;
while (begin < end)
{
temp = *begin;
*begin++ = *end;
*end-- = temp;
}
}
VALUE reverse_words(VALUE str)
{
char *s;
s = RSTRING_PTR(str);
reverseWords(s);
return str;
}
void Init_reverse_words()
{
VALUE string = rb_const_get(rb_cObject, rb_intern("String"));
rb_define_method(string, "reverse_words!", reverse_words, 0);
}
Secondly, create reverse/extconf.rb file:
require 'mkmf'
create_makefile('reverse_words')
Thirdly, in terminal cd to reverse folder and run:
$ ruby extconf.rb
$ make && make install
Finally, test it at irb.
irb(main):001:0> require 'reverse_words'
=> true
irb(main):002:0> "foo bar baz".reverse_words!
=> "baz bar foo"
That's the way to reverse words order without using builtins.
You can reverse an Array by pop ing each element into a new Array
arr, new_arr = ["I", "am", "a", "good", "boy"], []
for i in 0...arr.length do
new_arr << arr[arr.length - 1 - i]
end
new_arr
# => ["boy", "good", "a", "am", "I"]
TRy this
def reverse(string)
reverse = ""
index = 0
while index < string.length
reverse = string[index] + reverse
index += 1
end
return reverse
end
reverse_sent = reverse(""I am a good boy"")
reverse_sent.split.map{|word| reverse(word)}.join(" ")
You can not.
In ruby, you'll always end up using just an other methods, or reimplementing some that already exist (like other answer did).
In C maybe you could, using dynamic length array, but in ruby you MUST use the builtins methods.
Is there any way to do this without using Ruby built in methods?
No.
Ruby is an object-oriented language. In OO, you do things by calling methods. There is no other way to perform any action.
Okay, technically, there are some corners where Ruby is not object-oriented: if, &&, ||, and and or have no corresponding methods. So, that's all you can use.
Related
I'd like to modify the n first occurrences of a letter in a ruby string.
For example :
> "abcabcabcabc".some_magic_method("a", "0", 3)
=> "0bc0bc0bcabc"
I'm pretty sure I'm missing something but I'm not able to find an easy way! Maybe with regexes ?
You can use the basic split/join method:
"abcabcabcabc".split('a', 4).join('0')
Note that the split limit must be: number of replacements + 1
My first idea was:
3.times.inject("abcabcabcabc") { |str, _| str.sub!('a','0') }
But I like Casimir's answer more, especially because this solution is only O(mn).
Here's another single-pass method:
class String
def some_magic_method(c_before, c_after, ntimes)
gsub(/#{c_before}/) { ntimes -= 1; (ntimes>=0) ? c_after : c_before }
end
end
"abcabcabcabc".some_magic_method('a', '0', 3)
#=> "0bc0bc0bcabc"
"abcabcabcabc".some_magic_method('ab', 'xyz', 2)
#=> "xyzcxyzcabcabc"
I'd do the simple thing:
str = "abcabcabcabc"
3.times { str.sub!("a", "0") }
str # => "0bc0bc0bcabc"
If you have to have a method:
class String
def some_magic_method(n, target, replacement)
n.times { self.sub!(target, replacement) }
self
end
end
str = "abcabcabcabc"
str.some_magic_method(3, 'a', '0') # => "0bc0bc0bcabc"
I thought that doing puts #{a} would result in the same output as puts a, but found this not to be the case. Consider:
irb(main):001:0> a = [1,2]
=> [1, 2]
irb(main):002:0> puts a
1
2
=> nil
irb(main):003:0> puts "#{a}"
12
=> nil
irb(main):004:0>
In the above example it doesn't matter much, but it may matter when I want to print multiple variables on one line, such as (psudocode):
puts "There are #{a.size} items in the whitelist: #{a}"
Why is the output different here? Do they actually do different things, or have different semantics?
That's because "#{a}" calls the #to_s method on the expression.
So:
puts a # equivalent to, well, puts a
puts "#{a}" # equivalent to the next line
puts a.to_s
Update:
To elaborate, puts eventually calls #to_s but it adds logic in front of the actual output, including special handling for arrays. It just happens that Array#to_s doesn't use the same algorithm. (See puts docs here.) Here is exactly what it does...
rb_io_puts(int argc, VALUE *argv, VALUE out)
{
int i;
VALUE line;
/* if no argument given, print newline. */
if (argc == 0) {
rb_io_write(out, rb_default_rs);
return Qnil;
}
for (i=0; i<argc; i++) {
if (TYPE(argv[i]) == T_STRING) {
line = argv[i];
goto string;
}
line = rb_check_array_type(argv[i]);
if (!NIL_P(line)) {
rb_exec_recursive(io_puts_ary, line, out);
continue;
}
line = rb_obj_as_string(argv[i]);
string:
rb_io_write(out, line);
if (RSTRING_LEN(line) == 0 ||
!str_end_with_asciichar(line, '\n')) {
rb_io_write(out, rb_default_rs);
}
}
return Qnil;
}
I have this array of hashes:
results = [
{"day"=>"2012-08-15", "name"=>"John", "calls"=>"5"},
{"day"=>"2012-08-15", "name"=>"Bill", "calls"=>"8"},
{"day"=>"2012-08-16", "name"=>"Bill", "calls"=>"11"},
]
How can I search the results to find how many calls Bill made on the 15th?
After reading the answers to "Ruby easy search for key-value pair in an array of hashes", I think it might involve expanding upon the following find statement:
results.find { |h| h['day'] == '2012-08-15' }['calls']
You're on the right track!
results.find {|i| i["day"] == "2012-08-15" and i["name"] == "Bill"}["calls"]
# => "8"
results.select { |h| h['day'] == '2012-08-15' && h['name'] == 'Bill' }
.reduce(0) { |res,h| res += h['calls'].to_i } #=> 8
A Really clumsy implementation ;)
def get_calls(hash,name,date)
hash.map{|result| result['calls'].to_i if result['day'] == date && result["name"] == name}.compact.reduce(:+)
end
date = "2012-08-15"
name = "Bill"
puts get_calls(results, name, date)
=> 8
Or another possible way, but a little worse, using inject:
results.inject(0) { |number_of_calls, arr_element| arr_element['day'] == '2012-08-15' ? number_of_calls += 1 : number_of_calls += 0 }
Note that you have to set number_of_calls in each iteration, otherwise it will not work, for example this does NOT work:
p results.inject(0) { |number_of_calls, arr_element| number_of_calls += 1 if arr_element['day'] == '2012-08-15'}
Actually, "reduce" or "inject" is specifically for this exact operation (To reduce the contents of an enumerable down into a single value:
results.reduce(0) do |count, value|
count + ( value["name"]=="Bill" && value["day"] == "2012-08-15" ? value["calls"].to_i : 0)
end
Nice writeup here:
"Understanding map and reduce"
Developing a little survey webapp, ran into problem that deals with ranges for rating type questions.
So a rating's range could be:
1..10
-5..0
-5..5
'a'..'z'
'E'..'M'
and so on
The range is stored as a pair of varchars in database (start and end of range). So range always starts off as a string input.
What is the best way to take these string values and build a Ruby Range accordingly.
I can't just go value.to_i as this won't work for string iteration. Having a bunch of if's seems ugly. Any better way?
Not as important, but worth asking:
Also what if I wanted to make it all work with reversed range? Say 5-to-0 or G-to-A. I know that Ruby doesn't support reverse range (since it uses succ() to iterate). What would be the best way here?
Thanks in advance!
Update:
Based on Wouter de Bie's suggestion I've settled for this:
def to_int_or_string(str)
return str.match(/^-?\d+$/) ? str.to_i : str.strip
end
def ratings_array(from, to)
from = to_int_or_string(from)
to = to_int_or_string(to)
from > to ? Range.new(to, from).to_a.reverse : Range.new(from, to).to_a
end
Any thoughts?
Use Range.new:
Range.new("a","z")
=> "a".."z"
Range.new(-5,5)
=> -5..5
If you're varchars contain quotes, you can use eval to get the right ranges:
from = "'a'"
to = "'z'"
eval("Range.new(#{from},#{to})")
Otherwise you could use value.to_i to figure out if it was a number or a string in the varchar:
a = "x"
a = (a.to_i == 0 && a != "0") ? a : a.to_i
=> "x"
a = "5"
a = (a.to_i == 0 && a != "0") ? a : a.to_i
=> 5
Which of course can be nicely extracted into a method:
def to_int_or_string(value)
return (value.to_i == 0 && value != "0") ? value : value.to_i
end
def to_range(from, to)
return Range.new(to_int_or_string(from), to_int_or_string(to))
end
To reverse your range, you have to convert it to an array first:
Range.new("a","g").to_a.reverse
=> ["g", "f", "e", "d", "c", "b", "a"]
You can do something like the following.
str = 'Z..M'
v1 = str[0,str.index('.')]
v2 = str[str.index('.')+2, str.length]
unless v1.to_i == 0
v1 = v1.to_i
v2 = v2.to_i
end
if v2>v1
final_arr = (v1..v2).to_a
else
final_arr = (v2..v1).to_a.reverse
end
puts final_arr
This takes care of both the positive and the negative ranges
I have a String and I want to get another string out of it which has only characters at odd occuring positions.
For example if i have a string called ABCDEFGH, the output I expect is ACEG since the character indexes are at 0,2,4,6 respectively. I did it using a loop, but there should be one line implementation in Ruby (perhaps using Regex?).
>> "ABCDEFGH".gsub /(.)./,'\1'
=> "ACEG"
Here is one-line solution:
"BLAHBLAH".split('').enum_for(:each_with_index).find_all { |c, i| i % 2 == 0 }.collect(&:first).join
Or:
''.tap do |res|
'BLAHBLAH'.split('').each_with_index do |char, index|
res << c if i % 2 == 0
end
end
One more variant:
"BLAHBLAH".split('').enum_slice(2).collect(&:first).join
Some other ways:
Using Enumerable methods
"BLAHBLAHBLAH".each_char.each_slice(2).map(&:first).join
Using regular expressions:
"BLAHBLAHBLAH".scan(/(.).?/).join
Not sure about the run-time speed but it's one line of processing.
res = "";
"BLAHBLAH".scan(/(.)(.)/) {|a,b| res += a}
res # "BABA"
(0..string.length).each_with_index { |x,i| puts string[x] if i%2 != 0 }