Select specific character positions from string - ruby

I'm looking for a way to select pseudo random characters from a string.
For example, I have a 64 character string. I would like to pick positions 0, 1, 4, 5, 8, 9.
Or a harder one would be with the same string, I would pick positions 0, 1, 2, 4, 6, 8, 10, 11, 12 and so on.
Is there a quick way to do this?

Here's something that may work well for you:
#!/usr/bin/env ruby
# Returns a string whose length is a random number between 0
# and the string length, and whose values are characters from
# random positions in the input string.
def random_string_char_subset(string)
chars = string.chars.shuffle
char_count = Random.rand(string.length + 1)
subset = ''
char_count.times { subset << chars.pop }
subset
end
puts random_string_char_subset 'hello' # => lhl
puts random_string_char_subset '0123456789' # => 821097634
puts random_string_char_subset 'bye' # => b

Yes, you could use Array#values_at
> "64charstring".chars.values_at(*[0, 1, 4])
=> ["6", "4", "a"]
Update:
And if you'd like to get string result - join the result.
> "64charstring".chars.values_at(*[0, 1, 4]).join
=> "64a"

This can be done using []
a = 'test string'
a[1] #=>e, assuming you are using a known value
a[Random.rand(a.length)] #assuming you want a random value

Related

Data corrupted when passed from one Ruby method to another?

I'm trying to pass several parameters of class Integer to one of two methods—the pick is determined by the final parameter passed to the initial method.
The problem is described in full here, but in short, my methods should correctly count the no. of chains of 2+ identical digits when multiple digits are passed in with a final param of :problem => :count_clumps.
i.e. problem_14(1, 2, 2, 2, 2, 3, 4, 4, :problem => :count_clumps) should return 2, as it contains 2 chains of 2+ identical digits.
I'm failing an rspec test for the following:
problem_14(1, 2, 2, 3, 4, 4, :problem => :count_clumps)
My methods should return 2, but they return 0.
I think the problem is with whatever problem_14 is passing to count_clumps. count_clumps passes rspec tests when tested directly, but returns the wrong result when invoked via problem_14.
def problem_14(*parameters)
if parameters[-1].is_a?(Hash)
parameters.pop[:problem] == :same_ends ? same_ends(parameters) : count_clumps(parameters)
else
count_clumps(parameters)
end
end
def same_ends(n, *array)
return true if n == 0
array[0..n-1] == array[-n..-1]
end
def count_clumps(*array)
count = 0
clump = false
array.each_index do |x|
if array[x] == array[x+1]
clump = true
else
count += 1 if clump
clump = false
end
end
return count
end
I'd appreciate any pointers on where I'm going wrong with this.
In ruby, the splat (*) operator takes all parameters and turns them into an array. So when you do this:
def problem_14(*parameters)
You're taking a list of params and putting them into the array called parameters. Then when you call count_clumps(parameters) you're passing in an array, but since your count_clumps method also expects a list of parameters which gets turned into an array:
def count_clumps(*array)
What you end up with is a double array, like this:
[[1, 2, 2, 2, 2, 3, 4, 4]]
The fix is actually pretty simple. When you have an array, you can use splat (*) to turn it back into a parameter list. Just do this:
parameters.pop[:problem] == :same_ends ? same_ends(*parameters) : count_clumps(*parameters)
There's a couple problem's with this. First the array needs to be flattened and your not comparing the value with the next index instead your comparing value to value + 1.
def count_clumps(*array)
count = 0
clump = false
array = array.flatten
array.each_with_index do |x, a|
if x == array[a+1]
clump = true
else
count += 1 if clump
clump = false
end
end
return count
end
puts problem_14(1, 2, 2, 3, 4, 4, :problem => :count_clumps)
=> 2
puts problem_14(1, 1, 2, 1, 1, problem: :count_clumps)
=> 2
puts problem_14(1, 1, 1, 1, problem: :count_clumps)
=> 1

Ruby reverse histogram

So I'm new to ruby and while I was reading about histograms I had a thought. Is there was a way to have a histogram convert a number into a line of symbols? For example, 12 would be converted into ############.
You can multiply a string by an integer in Ruby.
value = 12
result = '#' * value
=> "############"
So for [1, 3, 5, 4] you could do
[1, 3, 5, 4].map{|value| '#' * value}
=> ["#", "###", "#####", "####"]

Getting an array of integers from console

I am trying to take in an array of integers from console user input. I thought that the function I need to use was gets, but now I am remembering that this function is going to take in a string, not an array. Can someone help me with how to convert this string into an array? Here is what I have tried to do so far:
print "Enter array: "
a = Array.new
a = gets.chomp
my_function(a)
Expected input: [1,2,3,4]
You could always eval it, but dangerous as heck...
>> foo = "[0,5,3,2,20,10]"
=> "[0,5,3,2,20,10]"
>> a = eval foo
=> [0, 5, 3, 2, 20, 10]
>> a
=> [0, 5, 3, 2, 20, 10]
>> a.class
=> Array
>>
could they could do something like this..
>> foo = "[0,5,3,2,20,10] - a"
=> "[0,5,3,2,20,10] - a"
>> a = eval foo
=> []
>>
or worse
Your input format seems to be JSON or YAML, so you could just parse it with a JSON parser or YAML parser:
require 'json'
a = JSON.parse(gets) # [1, 2, 3, 4]
# => [1, 2, 3, 4]
This is a contrived example that takes a user-inputted string of single-digit integers, creates an array via the String::split method, and converts each element from string to integer:
puts "Enter array: " # enter this string: 1234
str = gets.chomp
puts str.class #=> String
arr = str.split(//).map(&:to_i)
puts arr.class #=> Array
puts arr.size #=> 4
puts arr.inspect #=> [1, 2, 3, 4]
This is far from a full solution (i.e. assumes no delimiter in string, would only work for single-digit integers, etc.), but it demonstrates the basic concept.
print "Enter array: "
a = gets.chomp
a.gsub!(/\[|\]/, "").split(',').map(&:to_i)
my_function(a)
In your example you are doing a = ... twice. The first assignment of a is overwritten by the second assignment, so the first assignment is doing nothing. Just so you know.
What I am doing in the third line here is I am mutating the string "[1,2,3,4]" into an Array of integers.
Sources:
http://ruby-doc.org/core-2.2.0/String.html#method-i-split
http://ruby-doc.org/core-2.2.0/String.html#method-i-gsub-21
http://ruby-doc.org/core-2.2.0/Array.html#method-i-map

How to create two seperate arrays from one input?

DESCRIPTION:
The purpose of my code is to take in input of a sequence of R's and C's and to simply store each number that comes after the character in its proper array.
For Example: "The input format is as follows: R1C4R2C5
Column Array: [ 4, 5 ] Row Array: [1,2]
My problem is I am getting the output like this:
[" ", 1]
[" ", 4]
[" ", 2]
[" ", 5]
**How do i get all the Row integers following R in one array, and all the Column integers following C in another seperate array. I do not want to create multiple arrays, Rather just two.
Help!
CODE:
puts 'Please input: '
input = gets.chomp
word2 = input.scan(/.{1,2}/)
col = []
row = []
word2.each {|a| col.push(a.split(/C/)) if a.include? 'C' }
word2.each {|a| row.push(a.split(/R/)) if a.include? 'R' }
col.each do |num|
puts num.inspect
end
row.each do |num|
puts num.inspect
end
x = "R1C4R2C5"
col = []
row = []
x.chars.each_slice(2) { |u| u[0] == "R" ? row << u[1] : col << u[1] }
p col
p row
The main problem with your code is that you replicate operations for rows and columns. You want to write "DRY" code, which stands for "don't repeat yourself".
Starting with your code as the model, you can DRY it out by writing a method like this to extract the information you want from the input string, and invoke it once for rows and once for columns:
def doit(s, c)
...
end
Here s is the input string and c is the string "R" or "C". Within the method you want
to extract substrings that begin with the value of c and are followed by digits. Your decision to use String#scan was a good one, but you need a different regex:
def doit(s, c)
s.scan(/#{c}\d+/)
end
I'll explain the regex, but let's first try the method. Suppose the string is:
s = "R1C4R2C5"
Then
rows = doit(s, "R") #=> ["R1", "R2"]
cols = doit(s, "C") #=> ["C4", "C5"]
This is not quite what you want, but easily fixed. First, though, the regex. The regex first looks for a character #{c}. #{c} transforms the value of the variable c to a literal character, which in this case will be "R" or "C". \d+ means the character #{c} must be followed by one or more digits 0-9, as many as are present before the next non-digit (here a "R" or "C") or the end of the string.
Now let's fix the method:
def doit(s, c)
a = s.scan(/#{c}\d+/)
b = a.map {|str| str[1..-1]}
b.map(&:to_i)
end
rows = doit(s, "R") #=> [1, 2]
cols = doit(s, "C") #=> [4, 5]
Success! As before, a => ["R1", "R2"] if c => "R" and a =>["C4", "C5"] if c => "C". a.map {|str| str[1..-1]} maps each element of a into a string comprised of all characters but the first (e.g., "R12"[1..-1] => "12"), so we have b => ["1", "2"] or b =>["4", "5"]. We then apply map once again to convert those strings to their Fixnum equivalents. The expression b.map(&:to_i) is shorthand for
b.map {|str| str.to_i}
The last computed quantity is returned by the method, so if it is what you want, as it is here, there is no need for a return statement at the end.
This can be simplified, however, in a couple of ways. Firstly, we can combine the last two statements by dropping the last one and changing the one above to:
a.map {|str| str[1..-1].to_i}
which also gets rid of the local variable b. The second improvement is to "chain" the two remaining statements, which also rids us of the other temporary variable:
def doit(s, c)
s.scan(/#{c}\d+/).map { |str| str[1..-1].to_i }
end
This is typical Ruby code.
Notice that by doing it this way, there is no requirement for row and column references in the string to alternate, and the numeric values can have arbitrary numbers of digits.
Here's another way to do the same thing, that some may see as being more Ruby-like:
s.scan(/[RC]\d+/).each_with_object([[],[]]) {|n,(r,c)|
(n[0]=='R' ? r : c) << n[1..-1].to_i}
Here's what's happening. Suppose:
s = "R1C4R2C5R32R4C7R18C6C12"
Then
a = s.scan(/[RC]\d+/)
#=> ["R1", "C4", "R2", "C5", "R32", "R4", "C7", "R18", "C6", "C12"]
scan uses the regex /([RC]\d+)/ to extract substrings that begin with 'R' or 'C' followed by one or more digits up to the next letter or end of the string.
b = a.each_with_object([[],[]]) {|n,(r,c)|(n[0]=='R' ? r : c) << n[1..-1].to_i}
#=> [[1, 2, 32, 4, 18], [4, 5, 7, 6, 12]]
The row values are given by [1, 2, 32, 4, 18]; the column values by [4, 5, 7, 6, 12].
Enumerable#each_with_object (v1.9+) creates an array comprised of two empty arrays, [[],[]]. The first subarray will contain the row values, the second, the column values. These two subarrays are represented by the block variables r and c, respectively.
The first element of a is "R1". This is represented in the block by the variable n. Since
"R1"[0] #=> "R"
"R1"[1..-1] #=> "1"
we execute
r << "1".to_i #=> [1]
so now
[r,c] #=> [[1],[]]
The next element of a is "C4", so we will execute:
c << "4".to_i #=> [4]
so now
[r,c] #=> [[1],[4]]
and so on.
rows, cols = "R1C4R2C5".scan(/R(\d+)C(\d+)/).flatten.partition.with_index {|_, index| index.even? }
> rows
=> ["1", "2"]
> cols
=> ["4", "5"]
Or
rows = "R1C4R2C5".scan(/R(\d+)/).flatten
=> ["1", "2"]
cols = "R1C4R2C5".scan(/C(\d+)/).flatten
=> ["4", "5"]
And to fix your code use:
word2.each {|a| col.push(a.delete('C')) if a.include? 'C' }
word2.each {|a| row.push(a.delete('R')) if a.include? 'R' }

How to initialize an array in one step using Ruby?

I initialize an array this way:
array = Array.new
array << '1' << '2' << '3'
Is it possible to do that in one step? If so, how?
You can use an array literal:
array = [ '1', '2', '3' ]
You can also use a range:
array = ('1'..'3').to_a # parentheses are required
# or
array = *('1'..'3') # parentheses not required, but included for clarity
For arrays of whitespace-delimited strings, you can use Percent String syntax:
array = %w[ 1 2 3 ]
You can also pass a block to Array.new to determine what the value for each entry will be:
array = Array.new(3) { |i| (i+1).to_s }
Finally, although it doesn't produce the same array of three strings as the other answers above, note also that you can use enumerators in Ruby 1.8.7+ to create arrays; for example:
array = 1.step(17,3).to_a
#=> [1, 4, 7, 10, 13, 16]
Oneliner:
array = [] << 1 << 2 << 3 #this is for fixnums.
or
a = %w| 1 2 3 4 5 |
or
a = [*'1'..'3']
or
a = Array.new(3, '1')
or
a = Array[*'1'..'3']
Along with the above answers , you can do this too
=> [*'1'.."5"] #remember *
=> ["1", "2", "3", "4", "5"]
To prove There's More Than One Six Ways To Do It:
plus_1 = 1.method(:+)
Array.new(3, &plus_1) # => [1, 2, 3]
If 1.method(:+) wasn't possible, you could also do
plus_1 = Proc.new {|n| n + 1}
Array.new(3, &plus_1) # => [1, 2, 3]
Sure, it's overkill in this scenario, but if plus_1 was a really long expression, you might want to put it on a separate line from the array creation.
You can do
array = ['1', '2', '3']
As others have noted, you can also initialize an array with %w notation like so:
array = %w(1 2 3)
or
array = %w[1 2 3]
Please note that in both cases each element is a string, rather than an integer.
So if you want an array whose elements are integers, you should not wrap each element with apostrophes:
array_of_integers = [1, 2, 3]
Also, you don't need to put comma in between the elements (which is necessary when creating an array without this %w notation). If you do this (which I often did by mistake), as in:
wrong_array = %w(1, 2, 3)
its elements will be three strings ---- "1,", "2,", "3". So if you do:
puts wrong_array
the output will be:
1,
2,
3
=>nil
which is not what we want here.
Hope this helps to clarify the point!
To create such an array you could do:
array = ['1', '2', '3']
If you have an Array of strings, you can also initialize it like this:
array = %w{1 2 3}
just separate each element with any whitespace
You can initialize an array in one step by writing the elements in [] like this:
array = ['1', '2', '3']
You can simply do this with %w notation in ruby arrays.
array = %w(1 2 3)
It will add the array values 1,2,3 to the arrayand print out the output as ["1", "2", "3"]

Resources