Strange result when using #each_with_index to change an array - arrays

I'm trying to write some code that will loop through an array of strings, clean up the entries, and then add the cleaned up entries to a hash that tracks the frequency with which each word appears. This was my first solution:
puts("Give me your text.")
text = gets.chomp
words = text.split
frequencies = Hash.new(0)
words.map! do |word|
word.tr("\",.", "")
end
words.each do |word|
frequencies[word] += 1
end
It works fine, but looping through the array twice feels very inefficient, so I've been trying to find a way to do it one go and stumbled upon the following:
puts("Give me your text.")
text = gets.chomp
words = text.split
frequencies = Hash.new(0)
words.each_with_index do |word, index|
words[index].tr!("\",.", "")
frequencies[word] += 1
end
Based on my understanding of each_with_index, this shouldn't work, but somehow it does, and the hash receives the clean version of each string: https://repl.it/B9Gw. What's going on here? And is there a different way to solve this problem without looping twice?
EDIT: After some reading, I was able to solve the problem using just one loop in the following way:
puts("Give me your text.")
text = gets.chomp
words = text.split
frequencies = Hash.new(0)
for i in 0..words.length-1
words[i].tr!("\",.", "")
frequencies[words[i]] += 1
end
However, this is more of a JS or C++ solution and doesn't seem like idiomatic Ruby. Are there any other options? Also, why does the each_with_index approach even work?

You are using the String#tr! method, which modifies the string destructively instead of returning a new string. The fact that you are looking it up on the hash again (using words[index]) doesn't change anything, because the string object is still the same - so the word you use to modify the frequencies hash is also modified.
And is there a different way to solve this problem without looping twice?
An obvious way would be to use the same logic that you used, but without the with_index (which isn't making any difference here anyway). I would advise using the non-destructive String#tr instead of String#tr!, to make it more clear which strings have been cleaned and which have not.
frequencies = Hash.new(0)
words.each do |word|
cleaned = word.tr("\",.", "")
frequencies[cleaned] += 1
end
If you want to make clear the map phase of the process and still only loop once, you can leverage ruby's lazy enumerators:
frequencies = Hash.new(0)
cleaned_words = words.lazy.map { |word| word.tr("\",.", "") }
cleaned_words.each do |cleaned|
frequencies[cleaned] += 1
end
Here, even though we do a map and then an each, the collection is only traversed once, and ruby doesn't create any intermediary arrays.

Related

How to continue iteration over a codition even after it has been met once or even multiple times in ruby

I am writing a small package manager in ruby, and while working on its' package searching functionality, I want to continue iterating over a list of matches, even if it has found a package or sting identical to the inputted string.
def makelist(jsn, searchterm)
len = jsn.length
n = 0
while n < len do
pkname = jsn[n]["Name"]
pkdesc = jsn[n]["Description"]
pkver = jsn[n]["Version"]
unless pkname != nil || pkdesc != nil
# skip
else
puts "#{fmt(fmt("aur/", 6),0)}#{fmt(pkname,0)} [#{fmt(pkver,8)}]\n #{pkdesc}"
n += 1
end
end
end
I have tried using an if statement, unless statement and a case statement in which I gave conditions for what it should do if it specifically finds a packages that matches searchterm but when I use this condition, it always skips all other conditions and ends the loop, printing only that result. This block of code works, but I want to use my fmt function to format the text of matches differently in the list. Anyone have any ideas of how I could accomplish this?
EDIT:
Based on some back and forth the desired behavior is to print matched results differently from unmatched results.
So, in Ruby you have access to "functional" patterns like select, map, reduce, etc. that can be useful for what you're doing here.
It's also advantageous to separate functionality into different methods (e.g. one that searches, one that turns them into a string, one that prints the output). This is just an example of how to break it down, but splitting up the responsibilities makes it much easier to test and see which function isn't doing what you want by examining the intermediate structures.
Also, I'm not sure how you want to "match" the search term, so I used String#include? but you can replace that with whatever matching algorithm you like.
But I think you're looking for something like this:
def makelist(jsn, searchterm)
jsn.select do |package|
package["Name"] && package["Description"]
end.map do |package|
if matches?(package, searchterm)
matched_package_to_string(package)
else
package_to_string(package)
end
end.join("\n")
end
def matches?(package, searchterm)
package['Name'].include?(searchterm) || package('Description').include?(searchterm)
end
def matched_package_to_string(package)
pkname = package["Name"]
pkdesc = package["Description"]
pkver = package["Version"]
"#{fmt(fmt("aur/", 6),0)}#{fmt(pkname,0)} [#{fmt(pkver,8)}]\n #{pkdesc}"
end
def package_to_string(package)
# Not sure how these should print
end

Ruby partner pairing program not working?

I am new to ruby and have this program that takes in a number of names and sorting them into pairs of two, and throwing the odd person in a random group. Sometimes it works perfect, sometimes it throws the extra person into an array of their own, and im not sure why. I know there is a cleaner way to do this but Im just trying to understand how the code works. For example it should return "Apple" "Banana" "Orange" as ["Banana", "Orange", "Apple"] and will most of the time, but sometimes it give me ["Banana","Orange",] ["Apple"] Any advice?
def randomArray
classNames = []
puts "Please enter a list of names to sort"
while true
input = gets.chomp
break if input.empty?
classNames << input
end
classRandom = classNames.shuffle
splitNames = classRandom.each_slice(2).to_a
arrayPos = 0
splitNames.length.times do
if splitNames[arrayPos].length == 2
arrayPos+=1
else splitNames[arrayPos].length == 1
splitNames.sample << splitNames[arrayPos].pop
arrayPos+=1
end
end
x = 0
splitNames.length.times do
break if splitNames[x].empty?
puts "Group number #{x+1} is #{splitNames[x]}"
x+=1
end
end
randomArray
Your problem is this: splitNames.sample << splitNames[arrayPos].pop
sample can return any element of the array, including the element that has the odd person you're trying to assign! So if it samples that person, it removes them from their group of 1 and then adds them right back in.
To fix it, take advantage of the fact that either all groups will be pairs, or the last group will have a single person. Don't iterate over the array, just check splitNames[-1]. If they are alone, add them to splitNames[0...-1].sample.

How do only add item to an array if it doesn't exist already (in a case insensitive way)? [duplicate]

I want to know what's the best way to make the String.include? methods ignore case. Currently I'm doing the following. Any suggestions? Thanks!
a = "abcDE"
b = "CD"
result = a.downcase.include? b.downcase
Edit:
How about Array.include?. All elements of the array are strings.
Summary
If you are only going to test a single word against an array, or if the contents of your array changes frequently, the fastest answer is Aaron's:
array.any?{ |s| s.casecmp(mystr)==0 }
If you are going to test many words against a static array, it's far better to use a variation of farnoy's answer: create a copy of your array that has all-lowercase versions of your words, and use include?. (This assumes that you can spare the memory to create a mutated copy of your array.)
# Do this once, or each time the array changes
downcased = array.map(&:downcase)
# Test lowercase words against that array
downcased.include?( mystr.downcase )
Even better, create a Set from your array.
# Do this once, or each time the array changes
downcased = Set.new array.map(&:downcase)
# Test lowercase words against that array
downcased.include?( mystr.downcase )
My original answer below is a very poor performer and generally not appropriate.
Benchmarks
Following are benchmarks for looking for 1,000 words with random casing in an array of slightly over 100,000 words, where 500 of the words will be found and 500 will not.
The 'regex' text is my answer here, using any?.
The 'casecmp' test is Arron's answer, using any? from my comment.
The 'downarray' test is farnoy's answer, re-creating a new downcased array for each of the 1,000 tests.
The 'downonce' test is farnoy's answer, but pre-creating the lookup array once only.
The 'set_once' test is creating a Set from the array of downcased strings, once before testing.
user system total real
regex 18.710000 0.020000 18.730000 ( 18.725266)
casecmp 5.160000 0.000000 5.160000 ( 5.155496)
downarray 16.760000 0.030000 16.790000 ( 16.809063)
downonce 0.650000 0.000000 0.650000 ( 0.643165)
set_once 0.040000 0.000000 0.040000 ( 0.038955)
If you can create a single downcased copy of your array once to perform many lookups against, farnoy's answer is the best (assuming you must use an array). If you can create a Set, though, do that.
If you like, examine the benchmarking code.
Original Answer
I (originally said that I) would personally create a case-insensitive regex (for a string literal) and use that:
re = /\A#{Regexp.escape(str)}\z/i # Match exactly this string, no substrings
all = array.grep(re) # Find all matching strings…
any = array.any?{ |s| s =~ re } # …or see if any matching string is present
Using any? can be slightly faster than grep as it can exit the loop as soon as it finds a single match.
For an array, use:
array.map(&:downcase).include?(string)
Regexps are very slow and should be avoided.
You can use casecmp to do your comparison, ignoring case.
"abcdef".casecmp("abcde") #=> 1
"aBcDeF".casecmp("abcdef") #=> 0
"abcdef".casecmp("abcdefg") #=> -1
"abcdef".casecmp("ABCDEF") #=> 0
class String
def caseinclude?(x)
a.downcase.include?(x.downcase)
end
end
my_array.map!{|c| c.downcase.strip}
where map! changes my_array, map instead returns a new array.
To farnoy in my case your example doesn't work for me. I'm actually looking to do this with a "substring" of any.
Here's my test case.
x = "<TD>", "<tr>", "<BODY>"
y = "td"
x.collect { |r| r.downcase }.include? y
=> false
x[0].include? y
=> false
x[0].downcase.include? y
=> true
Your case works with an exact case-insensitive match.
a = "TD", "tr", "BODY"
b = "td"
a.collect { |r| r.downcase }.include? b
=> true
I'm still experimenting with the other suggestions here.
---EDIT INSERT AFTER HERE---
I found the answer. Thanks to Drew Olsen
var1 = "<TD>", "<tr>","<BODY>"
=> ["<TD>", "<tr>", "<BODY>"]
var2 = "td"
=> "td"
var1.find_all{|item| item.downcase.include?(var2)}
=> ["<TD>"]
var1[0] = "<html>"
=> "<html>"
var1.find_all{|item| item.downcase.include?(var2)}
=> []

Looping over array values in Lua

I have a variable as follows
local armies = {
[1] = "ARMY_1",
[2] = "ARMY_3",
[3] = "ARMY_6",
[4] = "ARMY_7",
}
Now I want to do an action for each value. What is the best way to loop over the values? The typical thing I'm finding on the internet is this:
for i, armyName in pairs(armies) do
doStuffWithArmyName(armyName)
end
I don't like that as it results in an unused variable i. The following approach avoids that and is what I am currently using:
for i in pairs(armies) do
doStuffWithArmyName(armies[i])
end
However this is still not as readable and simple as I'd like, since this is iterating over the keys and then getting the value using the key (rather imperatively). Another boon I have with both approaches is that pairs is needed. The value being looped over here is one I have control over, and I'd prefer that it can be looped over as easily as possible.
Is there a better way to do such a loop if I only care about the values? Is there a way to address the concerns I listed?
I'm using Lua 5.0 (and am quite new to the language)
The idiomatic way to iterate over an array is:
for _, armyName in ipairs(armies) do
doStuffWithArmyName(armyName)
end
Note that:
Use ipairs over pairs for arrays
If the key isn't what you are interested, use _ as placeholder.
If, for some reason, that _ placeholder still concerns you, make your own iterator. Programming in Lua provides it as an example:
function values(t)
local i = 0
return function() i = i + 1; return t[i] end
end
Usage:
for v in values(armies) do
print(v)
end

Ruby Array/Loop/.each do - Simplest Answer

In Ruby I'm looking to create a short program where a user is able to enter a certain number of words (5 for now) and the program needs to spit the words back out to the user in alphabetical order, and each word must alternate from ALL CAPS to all lowercase letters.
This program can only use methods like (.each do, .sort, .upcase, .downcase), arrays, loops, and if/else statements. NOTHING ELSE ... so no indexes, dictionaries, modules, arguments, etc. The syntax shouldn't diverge too much from what I've already written.
This is the code I have so far ... I keep getting a no method error ... any help would be greatly appreciated.
words = []
5.times do
puts "Please enter a word"
words << gets.chomp
end
words.sort.each do |odd, even|
if odd puts words.upcase
elsif even puts words.downcase
end
end
The first loop, filling the array, is fine.
words.sort.each do |odd, even|
if odd puts words.upcase
elsif even puts words.downcase
end
end
This does not work. each takes one word from the sorted array and "feeds" it to the block. The block uses the variable "odd" for it. Since the variable "even" did not get a value, it will be nil.
In the next line Ruby decides if odd is true, and since it is not falseor nil, Ruby decides it is, so puts words.upcase is executed. words however, being an Array, does not know about upcasing things - and tells you so by giving an error.
The following code uses a variable as a "toggle"; it is switched on if it's off and vice versa.
words = []
5.times do
puts "Please enter a word"
words << gets.chomp
end
up = true # the toggle
words.sort.each do |word|
if up then
puts word.upcase
up = false
else
puts word.downcase
up = true
end
end
Edit: I had not seen the post by #steenslag when I posted this answer, but it is slightly different in the way the input is generated so I will leave it unless it is desired that I remove it, in which case I will.
puts "Enter 5 words separated by spaces: "
words = gets.chomp!
results = words.split(" ").sort!
caps = true
results.each do |word|
if caps == true
puts word.upcase
caps = false
elsif caps == false
puts word.downcase
caps = true
end
end
I agree that the idea with the toggle is the way to go as other anwers already suggest.
I would just simplify the block a bit, because in Ruby is no need to check the toggle explicitly against true or false nor you need change the toggle explicitly to true or false in each iteration, just toggle it:
upcase = true
words.sort.each do |word|
puts(upcase ? word.upcase : word.downcase)
upcase = !upcase
end

Resources