How do I remove unwanted from an array? - ruby

Ok so I have an array that looks like this.
["Enter Sandman", "One", "Nothing Else Matters", "Master of Puppets", "The Unforgiven", "The Day That Never Comes", "For Whom the Bell Tolls", "Fade to Black", "Sad But True", "Wherever I May Roam", "Turn the Page", "I Disappear", "Fuel", "Cyanide", "Seek & Destroy", "Whiskey In the Jar", "All Nightmare Long", "Battery", "Welcome Home (Sanitarium)", "The Unforgiven III", "The Unforgiven II", "King Nothing", "Ride the Lightning", "No Leaf Clover", "Until It Sleeps", "...And Justice for All", "Blackened", "The Memory Remains", "Hero of the Day", "The Four Horsemen", "Orion", "Creeping Death", "St. Anger", "Harvester of Sorrow", "Don't Tread on Me", "Broken, Beat & Scarred", "Disposable Heroes", "Fight Fire With Fire", "The End of the Line", "Trapped Under Ice", "Of Wolf and Man", "Whiplash", "My Apocalypse", "Suicide & Redemption", "The Shortest Straw", "Tuesday's Gone"]
This array is generated by this command
artists = search_object.map{|x| x["trackName"]}.uniq.delete_if {|x| x == nil}
this works well but I need to filter out some more elements. The user will type in the textfield and as they type i need to narrow the results. So for example if the user types the string "Matters" i need to take out the elements that dont have that in the name or like the name. So it narraws down to "Nothing Else Matters". If the user enters the letter "a" then all the others in the array that dont have an "a" get deleted.
they will come in with the params[:text]
I did this and it worked but maybe there is a cleaner way
query = params[:term]
artists = search_object.map{|x| x["trackName"]}.uniq.delete_if {|x| x == nil}
filtered = []
artists.each do |artist|
filtered << artist if artist.include?(query)
end

the fast ruby variant is:
albums = ["hello kitty", "bad day", "all is good", "day is okay"]
def filter_word_in(word,array)
array.delete_if { |data| !data.match(word) }
return array
end
result1 = filter_word_in("y", albums)
puts result1.inspect # => ["hello kitty", "bad day", "day is okay"]
result2 = filter_word_in("ay", result1)
puts result2.inspect # => ["bad day", "day is okay"]
result3 = filter_word_in("day", result2)
puts result3.inspect # => ["bad day", "day is okay"]
result4 = filter_word_in("day i",result3)
puts result4.inspect # => ["day is okay"]
How you can see in this code: we just save our result in variables.
So, where we can store our data in rails?
You can use user_model for this, or you can just store this data in memory.
Create something like this:
class UserSongMemory
attr_accessor :memory
def initialize
#memory = []
end
def push(id, data)
#memory << {id => data}
end
def pop(id)
#memory.delete_if {|obj| obj.id == id}
end
end
user_memory = UserSongMemory.new
user_memory.add(#user.id, params[:inputed_string])
# after our calculations
user.pop(#user.id)
I prefer to store state memory in Class, but don't forget to clean this class-data

I would do this:
term, fname = params[:term], "trackName"
filtered = search_object.map {|x| x[fname] if x[fname].match(term) }.compact.uniq
This approach eliminates the need for two loops, one for collection and other selection. The uniq and compact methods are there as per your requirement.

The Array class offers you the "reject" method to remove unwanted elements from your array.

Here's a slightly different approach, and my reasoning.
Reason:
I'm imagining a list on a webpage with a textbox that allows you to type a substring of your desired selection to filter down to that item. As such, my assumption is that your users will ONLY ever type a substring. These aren't power users who will be regex matching for their desired track. By just using select and include? you can limit that regex power, as people suggested, without the added complexity. Regex is a bit like using a machine gun to kill a fly in this example.
Code:
#I renamed the array, since it was a list of songs, not artists
songs.select {|s| s.upcase.include?(query.upcase)}
You could leave the upcase off if you want the query to be case sensitive

Related

Randomizing key-value pairs from a hash

I'm building a simple vocabulary quiz that provides the user with a value from a predetermined hash, and takes his or her response as input. If the user's input matches the value's corresponding key, the program moves on to the next value, and repeats this process until all key-value pairs in the hash have been accounted for.
In its current state, the quiz prompts the user with values from the hash one-by-one, in order, from first to last.
However, to make the quiz more difficult, I would like the quiz to provide RANDOM values from the hash, in no particular order.
Plain English...how do I get the vocab quiz to spit out random definitions from its library, instead of printing the same definitions in the same order every time?
My code is below. Greatly appreciate everyone's help!
vocab_words = {
"class" => "Tell Ruby to make a new type of thing",
"object" => "Two meanings: The most basic type of thing, and any instance of some thing",
"instance" => "What you get when you tell Ruby to create a class",
"def" => "How you define a function inside a class"
}
vocab_words.each do |word, definition|
print vocab_words[word] + ": "
answer = gets.to_s.chomp.downcase
while answer != "%s" %word
if answer == "help"
print "The answer is \"%s.\" Type it here: " %word
answer = gets.to_s.chomp.downcase
else
print "Nope. Try again: "
answer = gets.to_s.chomp.downcase
end
end
end
Use: random_keys = vocab_words.keys.shuffle like so:
vocab_words = {
"class" => "Tell Ruby to make a new type of thing",
"object" => "Two meanings: The most basic type of thing, and any instance of some thing",
"instance" => "What you get when you tell Ruby to create a class",
"def" => "How you define a function inside a class"
}
random_keys = vocab_words.keys.shuffle
random_keys.each do |word|
print vocab_words[word] + ": "
answer = gets.to_s.chomp.downcase
if answer == "help"
print "The answer is \"%s.\" Type it here: " %word
answer = gets.to_s.chomp.downcase
else
while answer != "%s" %word
print "Nope. Try again: "
answer = gets.to_s.chomp.downcase
end
end
end

Not able to iterate properly over hash in my continuous input program (Ruby)

I am trying to see if I can create a list of user input requests with a hash in this program I am trying to create. Whats the best way to go about this?
print "would you like to begin a changeover? (y/n) "
input = gets.chomp
tracking = {
"start time" => input.Time.now,
"Type?" => input,
"shift?" => input,
"standard hrs?" => input,
"actual hrs?" => input,
"end time" => input = gets.chomp.Time.now
}
tracking.each do |key, value|
puts "great please answer the following: #{tracking}"
end
thanks in advance!
You have to remember that the evaluation is sequential, going from top to bottom (unless you are defining functions/methods). In your code:
You ask the user about a changeover
You get user input (say, y) into the variable input
You make a hash, with six values; four of them will contain y, two of them will contain current time
You iterate over the hash, printing its values (and asking the user nothing).
Or at least it would if gets.chomp.Time.now was not an error.
So, taking care about the timing:
print "would you like to begin a changeover? (y/n) "
unless gets.chomp.tolower == 'n'
puts "great please answer the following:"
tracking = {
"start time" => Time.now
}
[
"Type?",
"shift?",
"standard hrs?",
"actual hrs?"
].each do |question|
print question
tracking[question] = gets.chomp
}
tracking["end_time"] = Time.now
end
Thanks Alot! This set me on the right track. However, was not time stamping the beginning and end of the questionnaire the way I wanted. After playing with the code a bit on my own however, I was able to make it work.
print "would you like to begin a changeover? (y/n) "
unless gets.chomp. == "n"
puts Time.now
puts "great please answer the following: "
end
questionnaire = ["Type", "Shift?", "Standard hrs?", "Actual hrs?"]
.each do |question|
puts question
questionnaire = gets.chomp
end
puts "Please type 'done' when change over complete"
input = gets.chomp
puts Time.now

Unexpected boolean result using .nil? on a hash

I am working through exercises on codecademy and I came across a solution that I do not completely understand involving .nil? Here is my code :
movies = { GIS: 10.0, Phantasm: 1.5, Bourne: 4.0}
puts "Whats your movie brah?"
title = gets.chomp
puts "What's your rating brah?"
rating = gets.chomp
movies[title.to_sym] = rating.to_i
puts "Your info was saved brah!"
case movies
when 'add'
puts "What movie do you want to add son?"
title = gets.chomp
if movies[title.to_sym].nil?
puts "What's your new rating brah?"
rating = gets.chomp
movies[title.to_sym] = rating.to_i
puts "#{title} has been added with a rating of #{rating}."
else
puts "That movie already exists! Its rating is #{movies[title.to_sym]}."
end
when "update"
if movies[title.to_sym].nil?
when "display"
puts "Movies!"
when "delete"
puts "Deleted!"
else puts "Error!"
end
I am only referring to the add method. the rest of the script is a work in progress. I don't like not understanding things though and this has me in a bit of a quandry.
My question is does Ruby know not to add a title that already exists because two symbols cannot have the same name? I am curious how it determines when the hash has no value. Can anyone clarify this for me? I would really appreciate it!
The answer is a bit more complicated than that.
From the RubyDoc: "Two objects refer to the same hash key when their hash value is identical and the two objects are eql? to each other."
An object's hash value is a calculated numerical result based on the data the object contains. And the eql? method tests if two objects are equal, and this is usually aliased to == in ruby (i.e my_string1 == my_string2 is the same as my_string1.eql? my_string2).
When you say movies[title.to_sym], Ruby is saying "In the movies hash, are there any pairs currently stored where the key.eql? title.to_sym and key.hash == title.to_sym.hash? If so, return that value of the pairing, and if not return nil.
The reason Ruby doesn't add the title if it already exists is because of your if movies[title.to_sym].nil? line, which in English translates to "only do what follows if no pairing for the key title.to_sym exists."
If you had title = "GIS", and you were to just say movies[title.to_sym] = 1, Ruby would gladly over write the 10.0 you currently have stored there so that movies[:GIS] returned 1.

Ruby matching subsets and displaying set(variable)

feelings = Set["happy", "sad", "angry", "high", "low"]
euphoria = Set["happy", "high"]
dysphoria = Set["sad", "low"]
miserable = Set["sad", "angry"]
puts "How do you feel?"
str = gets.chomp
p terms = str.split(',')
if euphoria.proper_subset? feelings
puts "You experiencing a state of euphoria."
else
puts "Your experience is undocumented."
end
gets
How do I make euphoria a variable, such that if the corresponding string for miserable or dysphoria match & display the set name. Like #{Set}
Reviewing what you have, I think this is more like what you really want:
require 'set'
feelings = {
euphoria: Set.new(%w[happy high]),
dysphoria: Set.new(%w[sad low]),
miserable: Set.new(%w[sad angry])
}
puts "What are you feeling right now?"
mood = Set.new gets.scan(/\w+/)
name, _ = feelings.find{ |_,matches| matches.subset?( mood ) }
if name
puts "You are experiencing a state of #{name}"
else
puts "Your experience is undocumented."
end
Calling gets.scan(/\w+/) returns an array of strings. It's better than just .split(',') because it allows the user to put a space after commas (e.g. "sad, happy") or just use spaces (e.g. "sad happy").
As you already know, Set[] requires multiple arguments for it. Instead, we use Set.new which takes an array of values. Alternatively, you could have used mood = Set[*gets.scan(/\w+/)], where the * takes the array of values and passes them as explicit parameters.
Also, I changed from proper_subset? to just subset?, because "happy,high" is not a proper subset of "happy,high", but it is a subset.
Whenever you think you want to put the name of a variable into another variable, you probably want a Hash instead:
states = {
'euphoria' => Set["happy", "high"],
'dysphoria' => Set["sad", "low"],
'miserable' => Set["sad", "angry"]
}
Then you can say things like:
which = 'euphoria' # Or where ever this comes from...
if states[which].proper_subset? feelings
puts "You experiencing a state of #{which}."
else
puts "Your experience is undocumented."
end

Dynamically Create Arrays in Ruby

Is there a way to dynamically create arrays in Ruby? For example, let's say I wanted to loop through an array of books as input by a user:
books = gets.chomp
The user inputs:
"The Great Gatsby, Crime and Punishment, Dracula, Fahrenheit 451,
Pride and Prejudice, Sense and Sensibility, Slaughterhouse-Five,
The Adventures of Huckleberry Finn"
I turn this into an array:
books_array = books.split(", ")
Now, for each book the user input, I'd like to Ruby to create an array. Pseudo-code to do that:
x = 0
books_array.count.times do
x += 1
puts "Please input weekly sales of #{books_array[x]} separated by a comma."
weekly_sales = gets.chomp.split(",")
end
Obviously this doesn't work. It would just re-define weekly_sales over and over again. Is there a way to achieve what I'm after, and with each loop of the .times method create a new array?
weekly_sales = {}
puts 'Please enter a list of books'
book_list = gets.chomp
books = book_list.split(',')
books.each do |book|
puts "Please input weekly sales of #{book} separated by a comma."
weekly_sales[book] = gets.chomp.split(',')
end
In ruby, there is a concept of a hash, which is a key/value pair. In this case, weekly_sales is the hash, we are using the book name as the key, and the array as the value.
A small change I made to your code is instead of doing books.count.times to define the loop and then dereference array elements with the counter, each is a much nicer way to iterate through a collection.
The "push" command will append items to the end of an array.
Ruby Docs->Array->push
result = "The Great Gatsby, Crime and Punishment, Dracula, Fahrenheit 451,
Pride and Prejudice, Sense and Sensibility, Slaughterhouse-Five,
The Adventures of Huckleberry Finn".split(/,\s*/).map do |b|
puts "Please input weekly sales of #{b} separated by a comma."
gets.chomp.split(',') # .map { |e| e.to_i }
end
p result
Remove the comment if you would like the input strings converted to numbers
One way or another you need a more powerful data structure.
Your post gravitates toward the idea that weekly_sales would be an array paralleling the books array. The drawback of this approach is that you have to maintain the parallelism of these two arrays yourself.
A somewhat better solution is to use the book title as a key to hash of arrays, as several answers have suggested. For example: weekly_sales['Fahrenheit 451'] would hold an array of sales data for that book. This approach hinges on the uniqueness of the book titles and has other drawbacks.
A more robust approach, which you might want to consider, is to bundle together each book's info into one package.
At the simplest end of the spectrum would be a list of hashes. Each book would be a self-contained unit along these lines:
books = [
{
'title' => 'Fahrenheit 451',
'sales' => [1,2,3],
},
{
'title' => 'Slaughterhouse-Five',
'sales' => [123,456],
},
]
puts books[1]['title']
At the other end of the spectrum would be to create a proper Book class.
And an intermediate approach would be to use a Struct (or an OpenStruct), which occupies a middle ground between hashes and full-blown objects. For example:
# Define the attributes that a Book will have.
Book = Struct.new(:title, :weekly_sales)
books = []
# Simulate some user input.
books_raw_input = "Fahrenheit 451,Slaughterhouse-Five\n"
sales_raw_input = ['1,2,3', '44,55,66,77']
books_raw_input.chomp.split(',').each do |t|
ws = sales_raw_input.shift.split(",")
# Create a new Book.
books.push Book.new(t, ws)
end
# Now each book is a handy bundle of information.
books.each do |b|
puts b.title
puts b.weekly_sales.join(', ')
end
Are you happy to end up with an array of arrays? In which this might be useful:
book_sales = books_array.collect do |book|
puts "Please input weekly sales of #{books_array[0]} separated by a comma."
gets.chomp.split(",").collect{ |s| s.to_i }
end
Looking at it, you might prefer a hash, keyed by book. Something like this:
book_sales = books_array.inject({}) do |hash, book|
puts "Please input weekly sales of #{books_array[0]} separated by a comma."
weekly_sales = gets.chomp.split(",").collect{ |s| s.to_i }
hash[book] = weekly_sales
end
This solution assumes that there will never be a duplicate book title. I figure that is pretty safe, yes?
input = "A list of words"
hash = {}
input.split(/\s+/).collect { |word| hash[word] = [] }
# Now do whatever with each entry
hash.each do |word,ary|
ary << ...
end

Resources