Adding/Deleting numbers in an array via user input - ruby

I want to add/remove numbers in an array based on user input. Here's what I tried:
a = %w[1 2 3 4 5 6 7 8 9]
delete_list = []
puts a
puts "pick 1-9 to del"
input = gets.to_i
input << a
puts a
The last line is to check if it worked, and I get "no implicit conversion of Array into Integer". Is this because I used %w and the array isn't integer based?
a = %w[1 2 3 4 5 6 7 8 9]
a.map! {|e| e.to_i}
puts a
puts "pick 1-9 to del"
input = gets.chomp
a.delete(input)
puts a
Well, I changed it up like so. But I don't seem to be having success with the a.delete(input) command, as my array still prints out 1-9. What am I doing wrong?

To remove an element at specific position use Array#delete_at:
input = gets.to_i
a.delete_at(input - 1) # index starts from `0`
If you want to delete item not by position, but by value, use Array#delete.
input = gets.chomp # `a` contains strings; used `.chomp` instead of `.to_i`
a.delete(input)

Yes. It is because the argument to Fixnum#<< has to be an integer, not an array.

Focusing on the key lines of code:
a = %w[1 2 3 4 5 6 7 8 9]
This makes the variable "a" an array of string elements:
=> ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
Then you set this variable:
input = gets.to_i
This gets a string from the user ("gets" - like an abbreviation of the name getStringFromUser) and then .to_i turns it to an integer.
This would have likely resulted in a "0" (if letters entered) or whatever integer was entered:
=>0 OR => #some integer
Then you tried to put an array into the integer:
input << a
Ruby tried to take the "a" array of elements (class Array) and cram it into that integer (aka: class Fixnum) variable "input". This is where you got your error - Ruby can't put an array into an integer using a method like "<<".
If you replaced the line:
input << a
With:
a << input
You'll at least get a functional result.
If the "gets" was say, input=9, then your last puts a would give you:
=> ["1", "2", "3", "4", "5", "6", "7", "8", "9", 9]
Which is an Array element that consists of a bunch of string elements and an integer element that was pushed to the end.
Now, from your puts "pick 1-9 to del", it seems like you want to delete an element from the array.
First, you'll want your array to be integers and not strings... something like:
a.map! {|e|e.to_i}
=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
(if you hadn't converted the input to an integer, you could skip that last step... or oddly convert the "input" back to a string with input.to_s)
Now that "a" is an array of integers, you can delete one using the "delete" method for Arrays and telling it to delete the value of the "input" variable:
a.delete(input)
=> 9
#it returns the value you deleted.
Your last puts a would return:
=> [1, 2, 3, 4, 5, 6, 7, 8]
It's a long step-wise answer, but hopefully that helps.

Related

Is there ruby methods to select string between other strings?

I'm starting in programming and I'm looking to make a program for extracting all the words contained between two words within a text (in order store them in a variable )
For example with the words "START" & "STOP":
"START 1 2 3 STOP 5 6 START 7 8 STOP 9 10"
I would like to store in variables: 1 2 3 7 8
I started to do it with Ruby as you can see in the code below, my current idea was to convert the string "global" into an array and then number the position of string1 and string2; then create an array ‘string1’ with the values of the initial array # string1 + 1,… string2 -1.
Unfortunately, it works only once because the .index function only works on the first occurence...would there be a better way to do that ?
Thank you in advance for your help
text = "0 start 2 3 4 stop 6 7 start 9 10 stop 12"
start= text.split(' ')
a = start.index('start')
b = start.index('stop')
puts a
puts b
puts c = start[a+1,b-a-1].join(" ")
# returns
#1
#5
#2 3 4 ```
You could start with the scan-method and a regular expression:
text = "0 start 2 3 4 stop 6 7 start 9 10 stop 12"
res1 = text.scan(/start\s*(.*?)\s*stop/) #[["2 3 4"], ["9 10"]]
res2 = res1.flatten #["2 3 4", "9 10"]
or without the intermediate variables:
res = text.scan(/start(.*?)stop/).flatten #["2 3 4", "9 10"]
Explanation:
See https://apidock.com/ruby/String/scan for the scan method.
The regular expression /start\s*(.*?)\s*stop/ is the combination of
start
\s*: any space character
(.*?):
The (and ) is responsible to remember the content.
. means any character, * means a repetition (zero or more characters), ? restrict the result to the shortest possibility (see below for details)
\s*: any space character
stop
The result is an array with hits of the regular expression. The regular expression could contain different parts to detect (multiple ()-pairs). So it is an array of arrays. In our case, each inner array has one element, so you can use flatten to get a 'flat' array.
If you would not use the ? in the regular expression, then you would find 2 3 4 stop 6 7 start 9 10 instead of the shorter parts.
You are not exactly getting an error, codereview might be a better place to ask. But since you are new in the community, here is a regular expression with lookaround assertions that does the job:
text = "0 start 2 3 4 stop 6 7 start 9 10 stop 12"
text.scan(/start ((?:(?!start).)*?) stop/).join(' ')
# => "2 3 4 9 10"
Btw, a great place to test you regular expressions in Ruby is https://rubular.com/
I hope you find this helpful.
A One-Line Method Chain
Here's an approach based on String#scan:
text = "0 start 2 3 4 stop 6 7 start 9 10 stop 12"
text.scan(/\bstart\s+(.*?)\s+stop\b/i).flat_map { _1.flat_map &:split }
#=> ["2", "3", "4", "9", "10"]
The idea here is to:
Extract all string segments that are bracketed between case-insensitive start and stop keywords.
text.scan /\bstart\s+(.*?)\s+stop\b/i
#=> [["2 3 4"], ["9 10"]]
Extract words separated by whitespace from between your keywords.
[["2 3 4"], ["9 10"]].flat_map { _1.flat_map &:split }
#=> ["2", "3", "4", "9", "10"]
Caveats
Notable caveats to the approach outlined above include:
String#scan creates nested arrays, and the repeated calls to Enumerable#flat_map used to handle them are less elegant than I might prefer.
\b is a zero-width assertion, so looking for word boundaries can cause #scan to include leading and trailing whitespace in the results that then need to be handled by String#strip or String#split.
Substituting \s+ for \b handles some edge cases while creating others.
It doesn't do anything to guard against unbalanced pairs, e.g. "start 0 start 2 3 4 stop 6 stop".
For simple use cases, String#scan with a tuned regex is probably all you need. The more varied and unpredictable your input and data structures are, the more edge cases your parsing routines will need to handle.
Option using array: as a starting point I could suggest using Enumerable#slice_before after String#split
Given your command and the stop-words:
command = "START 1 2 3 STOP 5 6 START 7 8 STOP 9 10"
start = 'START'
stop = 'STOP'
You can use it something like that:
grouped_cmd = command.split.slice_before { |e| [start, stop].include? e } # .to_a
#=> [["START", "1", "2", "3"], ["STOP", "5", "6"], ["START", "7", "8"], ["STOP", "9", "10"]]
Then you can manipulate as you like, for example:
grouped_cmd.select { |first, *rest| first == start }
#=> [["START", "1", "2", "3"], ["START", "7", "8"]]
Or
grouped_cmd.each_with_object([]) { |(first, *rest), ary| ary << rest if first == start }
#=> [["1", "2", "3"], ["7", "8"]]
Or even
grouped_cmd.each_slice(2).map { |(start, *stt), (stop, *stp)| { start.downcase.to_sym => stt, stop.downcase.to_sym => stp } }
#=> [{:start=>["1", "2", "3"], :stop=>["5", "6"]}, {:start=>["7", "8"], :stop=>["9", "10"]}]
And so on.

Remove empty value before converting string to array

ids = "1,4,5,"
ids.split(',') => ["1", " 4", " 5", " "]
ids.split(',').map(&:to_i) => [1, 4, 5, 0]
How do I remove that empty value before it becomes a zero?
You can use #scan also
ids = "1,4,5,"
ids.scan(/\d+/).map(&:to_i)
# => [1, 4, 5]
This doesn't happen in Ruby 2.2+:
ids = "1,4,5,"
ids.split(',')
# => ["1", "4", "5"]
RUBY_VERSION # => "2.2.0"
The simple thing to do is run a preflight check on your data and normalize it to what it's supposed to be, BEFORE trying to process it:
ids = "1,4,5,"
ids.chop! if ids[-1] == ','
ids # => "1,4,5"
ids.split(',')
# => ["1", "4", "5"]
You could be a bit more rigorous in the test, since the end of the line might also contain whitespace which would throw off the cleanup.
Also, you're dealing with comma-delimited data, so consider using the built in CSV class, which is designed to work with such strings.

Working on an array method

In my homework, I had to create a digit_sum method for the Integer class. Outcome:
class Integer
def digit_sum
to_s.split("").inject(0) { |sum, n| sum + n.to_i }
end
end
Now I have to create something similar for an Array:
[1, 2, 3, "words"].sum #=> 6
[12, 13, 14].sum_up_digit_sums #=> 12
But I don't know how to work with the Array Class. A simple start should look like that.
class Array
def sum
#Code
end
end
An Array has variables and I don't know how to include those variables into my code. I hope someone can explain how I should work with this.
Implementation of sum_up_digit_sums
class Array
def sum_up_digit_sums
join.chars.map(&:to_i).reduce(:+)
end
end
puts [12, 13, 14].sum_up_digit_sums # => 12
Your sum requirements also satisfied with it.
Explanation for [12, 13, 14]:
.join will create a string from array given: [12, 13, 14] => "121314"
.chars will create an array of characters from this string "121314" => ["1", "2", "1", "3", "1", "4"]
.map(&:to_i) will call to_i on each character ["1", "2", "1", "3", "1", "4"] => [1, 2, 1, 3, 1, 4](on this point, if you had any word characters, they'll become zeros)
Then simply sum everything with .reduce(:+)(reduce is an alias of inject)
Let's start with the second one, a Array#sum method. This is a commonly desired method and as such a practical homework question.
First, take a look at your existing implementation of Integer#digit_sum:
# Here you're taking a number, turning it into a string, and splitting it
# on an empty string, resulting in an array of characters (same effect as
# `String#chars`)
to_s.split("").
# The result of this? An array, to which you're then sending `inject` to
# calculate the sum. As you see you've already written a rudimentary
# `Array#sum` (for ints) right here.
inject(0) { |sum, n| sum + n.to_i }
Simply move that to an instance method of Array
class Array
# whereas in your other method you send `inject` to an array you created,
# here you're sending it to *this* array instance `self`, which is the
# implicit target of instance methods. `self.inject` would be equivalent.
def sum
inject(0) {|sum, n| sum + n.to_i }
end
end
You could then use this to refactor your existing Integer#digit_sum
class Integer
def digit_sum
to_s.split("").sum
end
end
Now take what you learned here to write the 2nd method.
class Array
# There are plenty of ways to accomplish the task here, but for the sake
# of pedagogy let's use the method you already wrote yourself.
# This (rather fragile) method would assume you have an array of objects
# that respond to `digit_sum`
def sum_up_digit_sums
# Exactly like plain `sum` but now we're using the other method you wrote,
# `digit_sum` to determine the value instead of `to_i`
inject(0) {|sum, n| sum + n.digit_sum }
end
end

Ruby multiple arrays comparison

I am trying to replace a component in a legacy system with a Ruby script. One piece of this system accepts a string that contains ASCII '0's and '1's apparently to represent a bitfield of locations. It then converts these location to a string of comma separated 2 two codes (mostly US states).
I have a Ruby method that does this but it doesn't seem like I am doing it the best way Ruby could. Ruby has a ton of ways built in to iterate over and manipulated array and I feel I am not using them to their fullest:
# input "0100010010" should return "AZ,PR,WY"
def locations(bits)
# Shortened from hundreds for this post. :u? is for locations I have't figured out yet.
fields = [ :u?, :az, :de, :mi, :ne, :wy, :u?, :u?, :pr, :u? ]
matches = []
counter = 0
fields.each { |f|
case bits[counter]
when '1' then matches << f
when '0' then nil
else raise "Unknown value in location bit field"
end
counter += 1
}
if matches.include(:u?) then raise "Unknown field bit set" end
matches.sort.join(",").upcase
end
What would be a better way to do this?
It seems counter to the "Ruby way" to have counter variables floating around. I tried looking at ways to use Array#map, and I could find nothing obvious. I also tried Googling for Ruby Idioms pertaining to Arrays.
matches = fields.select.with_index { |_,i| bits[i] == '1' }
# => [:az, :wy, :pr]
To verify bits only holds 0s and 1s, you can still do
raise "Unknown value in location bit field" if !bits.match(/^[01]*$/)
Use Array#zip and Array#reduce
bits.split('').zip(fields).reduce([]) do |a, (k, v)|
k == '1' ? a << v.to_s.upcase : a
end.sort.join(',')
# => "AZ,PR,WY
Explanation:
1) split bits into an array of chars:
bits.split('') # => ["0", "1", "0", "0", "0", "1", "0", "0", "1", "0"]
2) zip both arrays to generate an array of pairs (by position)
bits.split('').zip(fields) # => [["0", :u?], ["1", :az], ["0", :de], ["0", :mi],
# ["0", :ne], ["1", :wy], ["0", :u?], ["0", :u?], ["1", :pr], ["0", :u?]]
3) reduce the array taking the desired elements according to the conditions
.reduce([]) do |a, (k, v)|
k == '1' ? a << v.to_s.upcase : a
end # => "[AZ,WY,PR]
4) sort the resulting array and join their elements to get the expected string
.sort.join(',') # => "AZ,PR,WY"
You could combine each_with_index, map andcompact:
fields.each_with_index.map do |v,i|
v if bits[i] == '1'
end.compact
each_with_index returns an iterator for each each value and its integer index.
map uses the return value of a passed block to yield an output value for each input value. The block returns the value if its corresponding bit is set, and implicitly returns nil if it is not.
compact returns a copy of the output array with all of the nil values removed.
For more detail see the docs for Enumerable and Array.

Only push specific substring to array in Ruby

I have an array that I am looping through and pushing specific values to a separate array. EX:
first_array = ["Promoter: 8", "Passive: 7"]
I want to push every value that is an integer to a separate array, that would look like this in the end:
final_array = [8,7]
It would be nice for the values in the new array to be integers. I can't think of a way to push all numeric values within a string to a new array, but what would be the best option to do what I am wanting?
first_array.map{|s| s[/\d+/].to_i}
# => [8, 7]
first_array.map{|a| a.match(/\d+/)}.compact.map{|a| a[0].to_i }
Use a regex to grab the integers,
compact the blank spaces from the strings with no integers, and
convert them all to ints
And I have to add this super short but complicated one-liner solution:
a = ["Promoter: 8", "Passive: 7"]
p a.grep(/(\d+)/){$&.to_i} #=> [8,7]
Your question, as formulated, has an easy practical answer, already provided by others. But it seems to me, that your array of strings
a = ["Promoter: 8", "Passive: 7"]
envies being a Hash. So, from broader perspective, I would take freedom of converting it to a Hash first:
require 'pyper' # (type "gem install pyper" in your command line to install it)
hsh = Hash[ a.τBmm2dτ &/(\w+): *(\d+)/.method( :match ) ]
#=> {"Promoter"=>"8", "Passive"=>"7"}
# (The construction of #τBmm2dτ Pyper method will be explained in the appendix.)
Now, having your input data in a hash, you can do things with them more easily, eg.
hsh.τmbtiτ
#=> [8, 7]
APPENDIX: Explanation of the Pyper methods.
Pyper methods are similar to Lisp #car/#cdr methods in that, that a combination of
letters controls the method behavior. In the first method, #τBmm2dτ:
τ - opening and ending character
m - means #map
B - means take a block
2 - means first 3 elements of an array
d - means all elements except the first one (same meaning as in #cdr, btw.)
So, in #τBmm2dτ, Bm applies the block as follows:
x = ["Promoter: 8", "Passive: 7"].map &/(\w+): *(\d+)/.method( :match )
#=> [#<MatchData "Promoter: 8" 1:"Promoter" 2:"8">, #<MatchData "Passive: 7" 1:"Passive" 2:"7">]
# Which results in an array of 2 MatchData objects.
Then, m2d chars map (m) the MatchData objects using 2 and d chars. Character 2 gives
x = x.map { |e| e.to_a.take 3 }
#=> [["Promoter: 8", "Promoter", "8"], ["Passive: 7", "Passive", "7"]]
and d removes the first element from each:
x = x.map { |e| e.drop 1 }
#=> [["Promoter", "8"], ["Passive", "7"]]
In the secon method, #τmbtiτ, m means again #map, b means take the second element, and ti means convert it to Integer:
{"Promoter"=>"8", "Passive"=>"7"}.to_a.map { |e| Integer e[1] }
#=> [8, 7]
If the integer part of each string in (which look like members of a hash) is always preceded by at least one space, and there is no other whitespace (other than possibly at the beginning of the string), you could do this:
first_array = ["Promoter: 8", "Passive: 7"]
Hash[*first_array.map(&:split).flatten].values.map(&:to_i) # => [8,7]
map first_array => [["Promoter:", "8"], ["Passive:", "7"]]
flatten => ["Promoter:", "8", "Passive:", "7"]
convert to hash => {"Promoter:" => "8", "Passive:" => "7"}
get hash values => ["8", "7"]
convert to ints => [8, 7]
Note the need for the splat:
Hash[*["Promoter:", "8", "Passive:", "7"]]
=> Hash["Promoter:", "8", "Passive:", "7"]
=> {"Promoter:" => "8", "Passive:" => "7"}

Resources