How would I modify (add/remove elements) an array while iterating over it and have the iterator be aware of it?
For example I would think this code:
a = "1234567890".split("")
a.each_with_index{|d, i|
if d.eql?('5')
a.delete_at(i)
a.insert(i, ['A', 'B', 'C'] )
end
print d
}
would produce: 1234ABC67890 but instead produces 1234567890
Is there a workaround or different method to make this work?
(I know this example is pretty simple example but I am doing some complicated text processing where I need to insert some text when I hit a key word. I do a bunch of functions before and after I would do the expansion so doing the insert outside of the each loop [aka map!] would really complicate my code)
Actually, your code works, you just need to replace print d with print a[i] since what you're printing is the variable d not the actual array element at index i
Rather than deleting and inserting, why not change the element on that index?
a = "1234567890".split("")
a.each_with_index{|d, i|
if d.eql?('5')
a[i] = ['A','B','C']
end
print a[i] #a[i] rather than d, since d contains the old value
}
or
...
if d.eql?('5')
a[i] = ['A','B','C']
d = a[i]
end
print d
Deleting/Inserting on an Array during iterations is discourage since it may cause headaches haha... Resort to other methods if possible :)
Note:
I've just used the current logic in your code based on my understanding, and the given desired output
the array will become [1,2,3,4,['A','B','C'],6,7,8,9,0] and not [1,2,3,4,'A','B','C',6,7,8,9,0]. If you want the other, just leave a comment :)
If what you want is just to change a value in the string, you can just use .tr or .gsub to do the job
Here is one option. If you want to return array then remove join otherwise keep it to return a String
a = "1234567890".split("")
a.collect! {|i| i == '5' ? ['A', 'B', 'C'] : i }.flatten!.join #=> "1234ABC67890"
Inserting and deleting while iterating is best avoided. Some problems disappear however when iterating in reverse order:
a = "1234567890".split("")
a.reverse_each.with_index{|d, i|
if d.eql?('5')
a.delete_at(i)
a.insert(i, ['A', 'B', 'C'] )
end
}
puts a.join # => 12345ABC7890
You can't in general modify an Enumerable while iterating over its members. In most such cases, you need to construct a new Enumerable as you go.
b = []
a.each_with_index do |d, i|
if d.eql?('5')
b << 'A' << 'B' << 'C'
else
b << d
end
end
Related
I have one array like below
[["GJ","MP"],["HR","MH"],["MP","KL"],["KL","HR"]]
And I want result like below
"GJ, MP, KL, HR, MH"
First element of array ["GJ","MP"]
Added is in the answer_string = "GJ, MP"
Now Find MP which is the last element of this array in the other where is should be first element like this ["MP","KL"]
after this I have to add KL in to the answer_string = "GJ, MP, KL"
This is What I want as output
Given
ary = [["GJ","MP"],["HR","MH"],["MP","KL"],["KL","HR"]]
(where each element is in fact an edge in a simple graph that you need to traverse) your task can be solved in a quite straightforward way:
acc = ary.first.dup
ary.size.times do
# Find an edge whose "from" value is equal to the latest "to" one
next_edge = ary.find { |a, _| a == acc.last }
acc << next_edge.last if next_edge
end
acc
#=> ["GJ", "MP", "KL", "HR", "MH"]
Bad thing here is its quadratic time (you search through the whole array on each iteration) that would hit you badly if the initial array is large enough. It would be faster to use some auxiliary data structure with the faster lookup (hash, for instance). Smth. like
head, *tail = ary
edges = tail.to_h
tail.reduce(head.dup) { |acc, (k, v)| acc << edges[acc.last] }
#=> ["GJ", "MP", "KL", "HR", "MH"]
(I'm not joining the resulting array into a string but this is kinda straightforward)
d = [["GJ","MP"],["HR","MH"],["MP","KL"],["KL","HR"]]
o = [] # List for output
c = d[0][0] # Save the current first object
loop do # Keep looping through until there are no matching pairs
o.push(c) # Push the current first object to the output
n = d.index { |a| a[0] == c } # Get the index of the first matched pair of the current `c`
break if n == nil # If there are no found index, we've essentially gotten to the end of the graph
c = d[n][1] # Update the current first object
end
puts o.join(',') # Join the results
Updated as the question was dramatically changed. Essentially, you navigating a graph.
I use arr.size.times to loop
def check arr
new_arr = arr.first #new_arr = ["GJ","MP"]
arr.delete_at(0) # remove the first of arr. arr = [["HR","MH"],["MP","KL"],["KL","HR"]]
arr.size.times do
find = arr.find {|e| e.first == new_arr.last}
new_arr << find.last if find
end
new_arr.join(',')
end
array = [["GJ","MP"],["HR","MH"],["MP","KL"],["KL","HR"]]
p check(array)
#=> "GJ,MP,KL,HR,MH"
Assumptions:
a is an Array or a Hash
a is in the form provided in the Original Post
For each element b in a b[0] is unique
First thing I would do is, if a is an Array, then convert a to Hash for faster easier lookup up (this is not technically necessary but it simplifies implementation and should increase performance)
a = [["GJ","MP"],["HR","MH"],["MP","KL"],["KL","HR"]]
a.to_h
#=> {"GJ"=>"MP", "HR"=>"MH", "MP"=>"KL", "KL"=>"HR"}
UPDATE
If the path will always be from first to end of the chain and the elements are always a complete chain, then borrowing from #KonstantinStrukov's inspiration: (If you prefer this option then please given him the credit ✔️)
a.to_h.then {|edges| edges.reduce { |acc,_| acc << edges[acc.last] }}.join(",")
#=> "GJ,MP,KL,HR,MH"
Caveat: If there are disconnected elements in the original this result will contain nil (represented as trailing commas). This could be solved with the addition of Array#compact but it will also cause unnecessary traversals for each disconnected element.
ORIGINAL
We can use a recursive method to lookup the path from a given key to the end of the path. Default key is a[0][0]
def navigate(h,from:h.keys.first)
return unless h.key?(from)
[from, *navigate(h,from:h[from]) || h[from]].join(",")
end
Explanation:
navigation(h,from:h.keys.first) - Hash to traverse and the starting point for traversal
return unless h.key?(key) if the Hash does not contain the from key return nil (end of the chain)
[from, *navigate(h,from:h[from]) || h[from]].join(",") - build a Array of from key and the recursive result of looking up the value for that from key if the recursion returns nil then append the last value. Then simply convert the Array to a String joining the elements with a comma.
Usage:
a = [["GJ","MP"],["HR","MH"],["MP","KL"],["KL","HR"]].to_h
navigate(a)
#=> "GJ,MP,KL,HR,MH"
navigate(a,from: "KL")
#=> "KL,HR,MH"
navigate(a,from: "X")
#=> nil
I want to count how much D and E and F contains the array ary in total.
I can do it like
ary.count('D') + ary.count('E') + ary.count('F')
or like
count = 0
'DEF'.split('').each do |letter|
count += ary.count(letter)
end
count
but both of it don't look very smart to me, is there a better way in ruby? Unfortunately, .count('D','E','F') does not work.
You could use a regex.
ary.grep(/\A(D|E|F)\z/).size
You can easily build this regex from array ['D', 'E', 'F'], but I'll leave that up to you.
It's pretty straightforward if you're simply counting letters:
letters = 'AEIOU'.chars
matches = 'FISSION'.chars.grep(Regexp.union(letters)).count
Where this is useful for matching single letter instances in a case-sensitive manner. Here Regexp.union creates a single regular expression that matches any of those letters.
You can pass a block to Array#count, as follows:
ary.count { |item| %w(D E F).include?(item) }
This will return a count of how many elements for which the block returns a truthy (not nil or false) value.
%w() is a nice syntax for defining an array of strings - i.e. the following are equivalent:
['D', 'E', 'F'] == %w(D E F)
You were close. It's actually very simple, using a little-known feature of String#count:
'DEF'.count('DEF') #=> 3
'FEED ME'.count('DEF') #=> 5
If I have the following hash and array
hash = {'i' => 'i', 'av' => 'av', 'deviceName' => 'Genymotionvbox86p'}
array = ['i', 'av', 'Genymotionvbox86p']
How could I compare that each item in the array matches the hashes value in the same order
So far I have
array.each do |value|
hash.each do |k, v|
expect(v).to eq(value), "expected #{k} to equal #{v}, instead got #{value}"
end
end
This is failing as I get
expected av to equal av, instead got i (RSpec::Expectations::ExpectationNotMetError)
I'm not quite there yet and imagine that a loop within a loop is not the best thing to do either?
I would like to know how to efficiently approach this.
The reason this fails is because you compare every array value with every hash value. To solve this, you can take advantage of the fact that two arrays arrays are equal if all their values in order are equal:
expect(array).to eq hash.values
If you would really want to compare item-by-item, you rightfully noticed that a loop within a loop is not the way to go. You need a single loop to iterate both structures.
For that, you can, for example, use zip, to combine hash and array:
hash.zip(array).each do |(hash_key, hash_value), array_item|
expect(hash_value).to eq array_item
end
or you can resort to using an index:
hash.each_with_index do |(k, v), i|
expect(v).to eq array[i]
end
How could I compare that each item in the array matches the hashes
value in the same order
how about this?
> array == hash.values
#=> true
> array = ["i", "Genymotionvbox86p", "av"] # change the order
> array == hash.values
#=> false
I am learning Ruby and one issue I have come across in a few practice problems is working with combinations from an array, but getting the original array indices instead of the actual combination as a result. To keep it simple let's just talk about pairs. I know a fast way to get all possible pairs is:
array = [a, b, c]
array.combination(2).to_a
# => [[a, b], [a, c], [b, c]]
Now let's say I want to iterate over these combinations and choose a pair that fits an arbitrary condition. It would be easy enough to return the pair itself:
...select{|pair| condition}
# => [a, c]
# assuming this pair fits the condition
but what if I want to return the indices from the original array instead?
# => [0, 2]
Is there a way of doing this using #combination ? Or would you have to find the combinations yourself in this case? If so is there a more elegant way to do it than the following(which is what I ended up doing to solve these problems)?
array.each.with_index do |s1, i1|
array[(i1 + 1)..-1].each.with_index do |s2, i2|
if condition
result = [i1, (i2 + i1 + 1)]
end
end
end
Try this:
array = ['a', 'b', 'c', 'd']
array.combination(s).to_a.reduce do |memo, pair|
if condition # I tested with pair[0] == 'a' && pair[1] == 'c'
memo = pair.map {|e| array.index(e)}
else
memo
end
end
My test of this yielded:
[0, 2]
edit
To avoid using a call to index, just compute the indexes ahead of time and then create a combination of them, selecting the one(s) that meet the condition:
(0..array.length).to_a.combination(2).to_a.select {|pair| condition}
At first: Apologies for my not-so-good English, I'm a 17-year old German ;-)
I do my apprentice as a web-developer and just stumbled upon a problem:
I need to re-arrange the indexes of an array. The array is basically like that:
#array = ( 'a', 'b', 'c' );
But: it changes dynamically, so it doesnt always have 3 elements, but something over 20. Now, what I need to do is to get it to:
#array = ( 'c', 'a', 'b' );
So, I thought I just needed to do something like that (in a for-loop that loops once for every element in the array and increments $counter)
my $last = $array[$#array];
for($#array)
{
$array[$counter] = $array[$counter + 1];
unshift(#array, $last);
pop(#array);
$counter++;
}
I thought it would do this:
For each element in array (lets say, we are at element 0): Set element index = element index + 1; then add the last array to the beginning and remove the now "real" last array (which is just a duplicate of the beginning now). Then adding 1 to the counter and redo that for the array. But it doesnt really do what I want.
Well, that was my "newbie" try, so again, thats what I just need:
I need to "move" all the elements indexes of the array +1 and cut off the last one then to be at the beginning. The maybe more complicated thing is, that the arrays length is just variable.
In another form:
I need to turn
( 'a', 'b', 'c', 'd', ... 'z');
to
( 'z', 'a', 'b', 'c', ... 'y' );
Ive got no idea how to go on and I would appreciate any help.
Thanks :-)
Already thanks for all your anwers! I forgot to say that I cant test it before monday, because Im not at work anymore and I dont have the source with me right now. But thanks, Im sure at least one of your solutions will work!
#array = { 'c', 'a', 'b' };
assigns a single element (a hash reference) to #array; I think you mean
#array = ( 'c', 'a', 'b' );
This for loop:
for ($#array)
only loops over the single value $#array (the last index of #array).
I'm guessing you meant something like:
for ( 0..$#array )
(loop over all the array indicies).
Inside your loop, you seem to be doing two different things; one rotating things with unshift/pop and one moving things with an assignment of elements. Either approach would work (though in both you have errors), but both will not.
You can do:
my $last = $array[$#array];
for ( 0..$#array-1 ) {
$array[$_+1] = $array[$_];
}
$array[0] = $last;
(no separate counter needed; the loop provides one)
or rotate by one (moving the first element to be last) the array one fewer times than there are elements:
for ( 1..$#array ) {
push #array, shift #array;
}
or just do this to take the last element and make it first:
unshift #array, pop #array;
Or you can reassign all the elements at once, using a slice:
#array = #array[ $#array, 0..$#array-1 ];
or
#array[1..$#array, 0] = #array;
All of these produce the change you seem to want, but I don't feel confident you've explained clearly enough what that change is, given how complicated your attempt is compared to what would be needed to do what your example shows.
First, use round brackets for creating arrays in Perl:
#array = ('a', 'b', 'c');
If you want to move the last element to the beginning of the array, you can use:
unshift #array, pop #array;
I think you might want to take a look at List::Util, it's got a shuffle method that literally shuffles list contents.