<< Operator Appending String to List Acting Strange - Ruby - arrays

I'm trying to append a string (made up of characters from another string iterated through a for loop) to an array. But for some reason when I output the items of the array in the end they're all off.
Here's my output for the code I have:
r
racecar
a
aceca
c
cec
e
c
a
r
Final list:
racecar
racecar
acecar
acecar
cecar
cecar
ecar
car
ar
r
I tried adding a empty string to the end of my statement like so,
list << string_current + ""
and it seems to fix the problem. Does anyone know why this is?
def longest_palindrome(s)
n = s.length
max_string = ""
max_string_current = ""
string_current = ""
list = []
counter = 0
for i in (0...n)
for j in (i...n)
string_current << s[j]
# puts "string_current: #{string_current}"
if is_palindrome(string_current)
counter += 1
puts string_current
list << string_current
end
end
string_current = ""
end
puts "Final list:"
list.each do |item|
puts "#{item}"
end
end
def is_palindrome(string)
for i in (0..string.length/2)
if string[i] != string[string.length-1-i]
return false
end
end
return true
end
longest_palindrome("racecar")
I thought that the items in my final list should be idential to those being put into the list during.
list << string_current

This:
string_current << s[j]
modifies the string in-place. That means that this:
list << string_current
can put a correct string into list and then that string can be modified later.
When you append your blank string:
list << string_current + ""
you're creating a brand new string (string_current + "") and then pushing that new string onto list so you're doing this:
tmp = string_current + ""
list << tmp
A couple simple ways around your problem would be explicitly dup'ing the string:
if is_palindrome(string_current)
counter += 1
puts string_current
list << string_current.dup # <--------------------
end
or using += instead of << on string_current:
for i in (0...n)
string_current = ''
for j in (i...n)
string_current += s[j]
You should also consider replacing your for loops (which are rare in Ruby) with methods from Enumerable. I don't want to go down a refactoring rabbit hole right now so I'll leave that as an exercise for the reader.

You could also have fixed it by
list << string_current.dup
but the real problem is your
string_current << s[j]
Try out (for instance in irb) the following example:
list=[]
str='ab'
list << str
str << 'x'
puts list
You will see that the list contains now 'abx', not 'ab'.
The reason is that the list contains an object reference (pointer) to the string, and when you do the str << 'x', you don't create a new object, but modify the existing one, and list hence sees the updated version.

Related

Changing an element in a multidimensional array doesen't work in Ruby

I made a class that represents a matrix using a multidimensional array. The initialize method goes through every position and sets the value of the elements to the one specified, and the to_s method does the same but it just concatenates every element to a string. I'm writing an insert method that changes the value of the element of a given position to a given value, but it changes a whole value instead of just an element.
class Matrix
attr_accessor :rows, :cols, :arr
# Goes through the matrix setting everything with the "n" value
def initialize(r, c, n = "a")
#rows = r
#cols = c
#arr = Array.new(#rows)
tmpArray = Array.new(#cols)
i = 0
while i < #rows
j = 0
while j < #cols
tmpArray[j] = n
j += 1
end
#arr[i] = tmpArray
i += 1
end
return #arr
end
def to_s
i = 0
str = String.new
while i < #rows
j = 0
str << "("
while j < #cols
str << " "
if #arr[i][j].is_a?String
str << #arr[i][j]
else
str << #arr[i][j].to_s
end
j += 1
end
str << " )\n"
i += 1
end
return str
end
# Calls and prints to_s
def write
print self.to_s
return self.to_s
end
# Rewrites the element (r, c) as the value in "n"
def insert(r, c, n)
#arr[r][c] = n
self.write
return self
end
end
The thing is that, when I print the matrix I notice that I didn't change just an element, but a whole column of the matrix.
a = Matrix.new(2, 2, 0)
a.insert(0, 0, 1)
a.write
# Expected output: ( 1 0 )
# ( 0 0 )
# Real output: ( 1 0 )
# ( 1 0 )
The to_s method isn't failing. I already made a trace of it and tested it. I'm printing the real values that are in the positions of the matrix.
Your program calls Array.new only twice, and it doesn't create arrays using any other methods, and it doesn't use dup or clone to copy any arrays. Therefore, you only have two different array objects in your program. Each element of #arr actually points to the same row array.
One idea for a solution would be to replace this line:
#arr[i] = tmpArray
with this:
#arr[i] = tmpArray.dup
However, your code is pretty long and I have not tried running it, so I do not know if that would be a complete solution.

pushing a value into an array within a hash

Ive been working at this code for a radix sort for awhile now. Everything has seemed to be coming together fine until i try to push a value into an array within a hash.
Im getting an error message that the value is nil, however ive checked all the values in question just prior to trying to store them in the array and its still not allowing me to do so. Any ideas? buckets[sdigit].push(num) is the line that tells me one of my values is nil.
arr = []
while arr.size < 100
arr.push(rand(1000))
end
for outer_index in arr
puts "for outer index(#{outer_index} in arr"
buckets = Hash.new()
puts "buckets = Hash.new()"
puts "for j in 0..9"
for j in 0..9
buckets[j.to_s] = Array.new()
#buckets[j.to_s] = [j]
puts "buckets[j.to_s(#{buckets[j.to_s]})"
end
for inner_index in arr
puts "for inner_index(#{inner_index}) in arr"
num = inner_index
puts "num(#{num}) = inner_index(#{inner_index})"
sdigit = num.to_s[-1]
puts "sdigit(#{sdigit}) = num.to_s[-1](#{num.to_s[-1]})"
digit = sdigit.to_i
puts "digit(#{digit}) = sdigit.to_i(#{sdigit.to_i})"
puts "buckets[digit] = #{buckets[sdigit]}"
puts "#{buckets["1"]}"
puts "o#{num}"
puts buckets
buckets[sdigit].push(num)
puts "buckets[digit].push(num)(#{buckets[digit].push(num)})"
end
arr = buckets.values.flatten
end `
buckets[sdigit].push(num) is the line that tells me one of my values
is nil.
If you look at the error message :
top.rb:30:in `block (2 levels) in <main>': undefined method `push' for nil:NilClass (NoMethodError)
and your code :
you see that line 30 is the puts, not buckets[sdigit].push(num).
Cause : there is a discrepancy between the values displayed by the puts and those used by the expression : [digit] instead of [sdigit], and that's the trace which causes the error.
Personally I write the puts traces before the statement to trace, because it shows the values that will be used by the expression before the statement which could cause the error is executed. It usually helps ... except when the trace itself is in error.
I have slightly rearranged your code :
arr = []
100.times { arr << rand(1000) }
puts arr.join(', ')
arr.each do | outer_index |
puts "===== for outer_index=#{outer_index} in arr"
buckets = Hash.new()
puts "buckets = Hash.new()"
puts "for j in 0..9"
(0..9).each do | j |
buckets[j.to_s] = Array.new()
#buckets[j.to_s] = [j]
puts "buckets[#{j.to_s}]=#{buckets[j.to_s]}"
end
arr.each do | inner_index |
puts "----- for inner_index=#{inner_index} in arr"
num = inner_index
puts "num(#{num}) = inner_index(#{inner_index})"
sdigit = num.to_s[-1]
puts "sdigit(#{sdigit}) = num.to_s[-1](#{num.to_s[-1]})"
digit = sdigit.to_i
puts "digit(#{digit}) = sdigit.to_i(#{sdigit.to_i})"
puts "buckets[digit] = #{buckets[sdigit]}"
puts "#{buckets["1"]}"
puts "o#{num}"
puts buckets
puts "buckets[sdigit].push(num)=buckets[#{sdigit}].push(#{num})"
buckets[sdigit].push(num)
# puts "buckets[digit].push(num)(#{buckets[digit].push(num)})"
end
arr = buckets.values.flatten
end

VBA - Only run if statement when array is not empty still runs even when array is empty,

I have code that creates an array and enters "supplier names" or "null" (actual string null) into an array if certain conditions are met. If certain conditions are not met, the array will not be filled with any data and is thus empty (or so I believe).
The next thing I want to do is print out only the supplier names listed in that array. Hence I have to create an If statement that will only be entered when the item in the array does not have the value "null" and when the array is not empty.
I'm experiencing the following problem in the code below. The string array supplierCategoryP(r) did not meet the conditions and thus was never filled with any information. So I assume this is an empty array. Yet when I debug, the code shows that this first If is still entered:
If supplierCategoryP(r) <> "null" And Not IsEmpty(supplierCategoryP(r)) Then
...while it shouldn't, since the array is empty.
k = 1
If countNoNull > 0 Then
moveDownBy = countNoNull
For r = 1 To nP
If supplierCategoryP(r) <> "null" And Not IsEmpty(supplierCategoryP(r)) Then
Cells(9 + k + moveDownBy, 5) = supplierCategoryP(r)
k = k + 1
countNoNull = countNoNull + 1
End If
Next r
Else
For r = 1 To nP
If supplierCategoryP(r) <> "null" And Not IsEmpty(supplierCategoryP(r)) Then
Cells(9 + k, 5) = supplierCategoryP(r)
k = k + 1
countNoNull = countNoNull + 1
End If
Next r
End If
Code that creates the array:
Worksheets("PEMCO").Activate
comNO = CLng(Range("commoditiesAmount").Text)
nP = CLng(Range("supplierAmount").Text)
ReDim supplierCategoryP(1 To nP) As String
For c = 1 To comNO
commodityLoop = Cells(3, 1 + c)
If commodity = commodityLoop Then
For r = 1 To nP
cellX = Cells(3 + r, 1 + c)
If cellX = "x" Then
supplierCategoryP(r) = Cells(3 + r, 1)
Else
supplierCategoryP(r) = "null"
End If
Next r
End If
Next c
Note that the IsEmpty function doesn't work on a null string, it tests for empty numeric value. You can verify this in the Immediate pane:
?IsEmpty("")
False
since you've ReDim your array to a specific number of items, all of those items are initialized as an empty string by the ReDim statement. Later, you assign to (overwrite) some those items with either the value from the cell, or the "null" value. The other cases will still retain the vbNullString from initialization.
To check for an empty string, you'd need to test whether supplierCategoryP(r) = vbNullString (this is the built-in constant which expresses "").
Or, if you consider spaces or sequence of spaces " " to be empty, you'd use Trim:
Trim(supplierCategoryP(r)) = vbNullString
Note also, and this may seem pedantic, but it's important: an empty array is not the same as an array that's been initialized which contains "empty" values. Your array is never empty, even if it contains nothing but "empty" (vbNullString) values.

Dynamically deleting elements from an array while enumerating through it

I am going through my system dictionary and looking for words that are, according to a strict definition, neither subsets nor supersets of any other word.
The implementation below does not work, but if it did, it would be pretty efficient, I think. How do I iterate through the array and also remove items from that same array during iteration?
def collect_dead_words
result = #file #the words in my system dictionary, as an array
wg = WordGame.new # the class that "knows" the find_subset_words &
# find_superset_words methods
result.each do |value|
wg.word = value
supersets = wg.find_superset_words.values.flatten
subsets = wg.find_subset_words.values.flatten
result.delete(value) unless (matches.empty? && subsets.empty?)
result.reject! { |cand| supersets.include? cand }
result.reject! { |cand| subsets.include? cand }
end
result
end
Note: find_superset_words and find_subset_words both return hashes, hence the values.flatten bit
It is inadvisable to modify a collection while iterating over it. Instead, either iterate over a copy of the collection, or create a separate array of things to remove later.
One way to accomplish this is with Array#delete_if. Here's my run at it so you get the idea:
supersets_and_subsets = []
result.delete_if do |el|
wg.word = el
superset_and_subset = wg.find_superset_words.values.flatten + wg.find_subset_words.values.flatten
supersets_and_subsets << superset_and_subset
!superset_and_subset.empty?
end
result -= supersets_and_subsets.flatten.uniq
Here's what I came up with based on your feedback (plus a further optimization by starting with the shortest words):
def collect_dead_words
result = []
collection = #file
num = #file.max_by(&:length).length
1.upto(num) do |index|
subset_by_length = collection.select {|word| word.length == index }
while !subset_by_length.empty? do
wg = WordGame.new(subset_by_length[0])
supermatches = wg.find_superset_words.values.flatten
submatches = wg.find_subset_words.values.flatten
collection.reject! { |cand| supermatches.include? cand }
collection.reject! { |cand| submatches.include? cand }
result << wg.word if (supermatches.empty? && submatches.empty?)
subset.delete(subset_by_length[0])
collection.delete(subset_by_length[0])
end
end
result
end
Further optimizations are welcome!
The problem
As I understand, string s1 is a subset of string s2 if s1 == s2 after zero or more characters are removed from s2; that is, if there exists a mapping m of the indices of s1 such that1:
for each index i of s1, s1[i] = s2[m(i)]; and
if i < j then m(i) < m(j).
Further s2 is a superset of s1 if and only if s1 is a subset of s2.
Note that for s1 to be a subset of s2, s1.size <= s2.size must be true.
For example:
"cat" is a subset of "craft" because the latter becomes "cat" if the "r" and "f" are removed.
"cat" is not a subset of "cutie" because "cutie" has no "a".
"cat" is not a superset of "at" because "cat".include?("at") #=> true`.
"cat" is not a subset of "enact" because m(0) = 3 and m(1) = 2, but m(0) < m(1) is false;
Algorithm
Subset (and hence superset) is a transitive relation, which permit significant algorithmic efficiencies. By this I mean that if s1 is a subset of s2 and s2 is a subset of s3, then s1 is a subset of s3.
I will proceed as follows:
Create empty sets neither_sub_nor_sup and longest_sups and an empty array subs_and_sups.
Sort the words in the dictionary by length, longest first.
Add w to neither_sub_nor_sup, where w is longest word in the dictionary.
For each subsequent word w in the dictionary (longest to shortest), perform the following operations:
for each element u of neither_sub_nor_sup determine if w is a subset of u. If it is, move u from neither_sub_nor_sup to longest_sups and append u to subs_and_sups.
if one or more elements were moved from from neither_sub_nor_sup to longest_sups, append w to subs_and_sups; else add w to neither_sub_nor_sup.
Return subs_and_sups.
Code
require 'set'
def identify_subs_and_sups(dict)
neither_sub_nor_sup, longest_sups = Set.new, Set.new
dict.sort_by(&:size).reverse.each_with_object([]) do |w,subs_and_sups|
switchers = neither_sub_nor_sup.each_with_object([]) { |u,arr|
arr << u if w.subset(u) }
if switchers.any?
subs_and_sups << w
switchers.each do |u|
neither_sub_nor_sup.delete(u)
longest_sups << u
subs_and_sups << u
end
else
neither_sub_nor_sup << w
end
end
end
class String
def subset(w)
w =~ Regexp.new(self.gsub(/./) { |m| "#{m}\\w*" })
end
end
Example
dict = %w| cat catch craft cutie enact trivial rivert river |
#=> ["cat", "catch", "craft", "cutie", "enact", "trivial", "rivert", "river"]
identify_subs_and_sups(dict)
#=> ["river", "rivert", "cat", "catch", "craft"]
Variant
Rather than processing the words in the dictionary from longest to shortest, we could instead order them shortest to longest:
def identify_subs_and_sups1(dict)
neither_sub_nor_sup, shortest_sups = Set.new, Set.new
dict.sort_by(&:size).each_with_object([]) do |w,subs_and_sups|
switchers = neither_sub_nor_sup.each_with_object([]) { |u,arr|
arr << u if u.subset(w) }
if switchers.any?
subs_and_sups << w
switchers.each do |u|
neither_sub_nor_sup.delete(u)
shortest_sups << u
subs_and_sups << u
end
else
neither_sub_nor_sup << w
end
end
end
identify_subs_and_sups1(dict)
#=> ["craft", "cat", "rivert", "river"]
Benchmarks
(to be continued...)
1 The OP stated (in a later comment) that s1 is not a substring of s2 if s2.include?(s1) #=> true. I am going to pretend I never saw that, as it throws a spanner into the works. Unfortunately, subset is no longer a transitive relation with that additional requirement. I haven't investigate the implications of that, but I suspect it means a rather brutish algorithm would be required, possibly requiring pairwise comparisons of all the words in the dictionary.

Functions and loops

I am trying to make the function _EncryptionProcess() take arrays 1 by 1 and process them. I realized you can't have functions inside For loops.
The location in which the arrays need to be taken is where $aArray is typed, the arrays are stored in this value. The other variable defines the key size and value.
;Cuts the input up into piece;
$VariableToBeCut = "12345678"
$aArray = StringRegExp($VariableToBeCut, ".{2}", 3)
MsgBox(0, "die", $aArray[0]) ; personal check to make sure array works
$DataToBeEncrypted=_EncryptionProcess($aArray, $keyvalue, $keysize, 1) ;$aArray needs to be where the different arrays are processed
MsgBox(0, "Encrypted data", $DataToBeEncrypted)
This is how you should process array elements.
;Cuts the input up into piece;
$VariableToBeCut = "12345678"
$aArray = StringRegExp($VariableToBeCut, ".{2}", 3)
ConsoleWrite("Array element 0: " & $aArray[0] & #LF) ; personal check to make sure array works
For $i = 0 To UBound($aArray)-1
$DataToBeEncrypted = _EncryptionProcess($aArray[$i], $keyvalue, $keysize, 1)
ConsoleWrite("Element " & $i & " : " & $aArray[$i] & " DataToBeEncrypted: " & $DataToBeEncrypted & #LF)
Next

Resources