Shuffling an array with a condition - arrays
Here's an array:
scramble = [
"R", "U", "R' ", "U' ", "L", "L' ", "L2", "B", "B' ",
"B2", "F", "F' ", "F2", "R2", "U2", "D", "D' ", "D2"
]
I want to shuffle it with a condition such that "R", "R' ", and "R2" are not together while shuffled, and similar for other letters.
scramble.shuffle does shuffle it, but how can I set such conditions?
Lets derive a generic solution.
Given a 2D array of groups of elements, return an array of shuffled elements such that no two consecutive elements belong to same group.
For example:
all_groups = [
["R", "R'", "R2" ],
["L", "L'", "L2" ],
["U", "U'", "U2" ],
["D", "D'", "D2" ],
["F", "F'", "F2" ],
["B", "B'", "B2" ]
]
Expected output:
["F'", "U'", "R'", "L2", "R2", "D2", "F2", "R", "L", "B", "F", "L'", "D'", "B'", "U2", "B2", "U", "D"]
TL;DR Code:
all_groups = [
["R", "R'", "R2" ],
["L", "L'", "L2" ],
["U", "U'", "U2" ],
["D", "D'", "D2" ],
["F", "F'", "F2" ],
["B", "B'", "B2" ]
]
ans = []
total = all_groups.map(&:length).reduce(:+)
# Initial shuffling
all_groups = all_groups.each { |i| i.shuffle! }.shuffle
until all_groups.empty?
# Select and pop last group
last_group = all_groups.pop
# Insert last element to our ans, and remove from group
ans.push(last_group.pop)
total -= 1
# Shuffle remaining groups
all_groups.shuffle!
# Check if any group has reached critical state
length_of_longest_group = all_groups.reduce(0) { |len, i| [len, i.length].max }
if length_of_longest_group * 2 == total + 1
# Move that group to last
# This ensures that next element picked is from this group
longest_group = all_groups.detect { |i| i.length == length_of_longest_group }
all_groups.delete(longest_group)
all_groups.push(longest_group)
end
# Insert the last group at first place. This ensures that next element
# is not from this group.
all_groups.unshift(last_group) unless last_group.empty?
end
puts ans.inspect
Examples:
all_groups = [
["R", "R'", "R2" ],
["L", "L'", "L2" ],
["U", "U'", "U2" ],
["D", "D'", "D2" ],
["F", "F'", "F2" ],
["B", "B'", "B2" ]
]
# Output 1:
ans = ["B'", "U'", "L'", "U2", "R", "B2", "F2", "R2", "D2", "L2", "D", "R'", "U", "F'", "D'", "L", "F", "B"]
# Output 2:
ans = ["U'", "R", "D", "R'", "U", "D2", "B2", "D'", "L", "B", "L2", "B'", "U2", "F'", "L'", "F", "R2", "F2"]
# Output 3:
ans = ["B", "D", "R'", "D'", "B'", "R", "F2", "L", "D2", "B2", "F'", "R2", "U'", "F", "L'", "U2", "L2", "U"]
# Output 4:
ans = ["U'", "F'", "R2", "B2", "D", "L2", "B'", "U", "R", "B", "R'", "L'", "D'", "U2", "F", "D2", "F2", "L"]
# Output 5:
ans = ["U2", "F2", "L'", "F'", "R'", "F", "D'", "B2", "D2", "L", "B", "D", "L2", "B'", "R", "U", "R2", "U'"]
all_groups = [
['a', 'aa', 'aaa', 'A', 'AA', 'AAA'],
['b', 'bb', 'bbb', 'B', 'BB', 'BBB'],
['c', 'cc', 'ccc', 'C', 'CC', 'CCC']
]
ans = ["c", "AAA", "B", "ccc", "bbb", "C", "AA", "CC", "aa", "BB", "CCC", "bb", "cc", "A", "BBB", "a", "b", "aaa"]
all_groups = [
['r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8'],
['b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8']
]
# Output1:
ans = ["r2", "b7", "r1", "b5", "r7", "b6", "r3", "b8", "r4", "b3", "r5", "b1", "r6", "b4", "r8", "b2"]
# Output2:
ans = ["b6", "r8", "b2", "r1", "b4", "r2", "b8", "r7", "b3", "r4", "b5", "r5", "b7", "r3", "b1", "r6"]
Explanation:
To begin with, lets shuffle the input data. First we shuffle each group, then we shuffle the outer array.
all_groups = all_groups.each { |i| i.shuffle! }.shuffle
Now our array looks like this:
[["B", "B2", "B'"],
["F'", "F", "F2"],
["L", "L2", "L'"],
["R2", "R'", "R"],
["D", "D'", "D2"],
["U'", "U", "U2"]]
Now, if we traverse this array in column-major order, we get a prety decent shuffling of elements wherein no two consecutive elements belong to same group.
["B", "F'", "L", "R2", "D", "U'", "B2", "F", "L2", "R'", "D'", "U", "B'", "F2", "L'", "R", "D2", "U2"]
But only flaw in this is that all elements of a particular group are equidistant. Lets improve.
So we have a set of groups. Lets select any one of group and then select the last element from that group and add this element to our answer array. Now shuffle the 2D array and repeat until all elements are selected.
Also, we don't want any two consecutive elements to belong to same group, so we need to ensure that the next selection of element is from some other group. So how about this strategy:
Always pick the last group from our 2d array, and then once we shuffle we will ensure that this group is not the last group in 2d array.
In terms of psuedocode:
1. Select last group from 2d array.
2. Remove an element from this group.
3. Shuffle the order of other groups in 2d array
4. Insert the selected group at begining.
For example, lets start with this 2d array:
[["D", "D'", "D2"],
["U", "U'", "U2"],
["B2", "B", "B'"],
["F2", "F", "F'"],
["L'", "L", "L2"],
["R'", "R2", "R"]]
last group: ["R'", "R2", "R"]
Element removed: ("R")
Shuffle order of remaining groups of 2d array:
[["L'", "L", "L2"],
["B2", "B", "B'"],
["D", "D'", "D2"],
["U", "U'", "U2"],
["F2", "F", "F'"]]
2D array after inserting the popped group(the group from which we extracted an element)
[["R'", "R2"],
["L'", "L", "L2"],
["B2", "B", "B'"],
["D", "D'", "D2"],
["U", "U'", "U2"],
["F2", "F", "F'"]]
Now, since our logic selects the last group and then inserts it in the begining, we are insured that two elements of same group will never be picked in two consecutive turns.
A catch: There can be a possibility that there is a group which never reaches the last position, hence this group will never shrink, and its element will be forced to be picked consecutively once the number of elements is too less.
For example observe the output of 4 iterations of above algorithm on below input:
[['a', 'aa', 'aaa', 'A', 'AA', 'AAA'],
['b', 'bb', 'bbb', 'B', 'BB', 'BBB'],
['c', 'cc', 'ccc', 'C', 'CC', 'CCC']]
# Output 1: in this 'a' & 'aaa' are together
["CC", "bb", "C", "BB", "aa", "bbb", "AA", "CCC", "b", "c", "B", "cc", "A", "BBB", "AAA", "ccc", "a", "aaa"]
# Output 2: in this 'b' & 'BB' are together
["cc", "A", "B", "c", "AAA", "C", "a", "ccc", "bbb", "CCC", "aaa", "bb", "CC", "aa", "BBB", "AA", "b", "BB"]
# Output 3: this is perfect
["CCC", "BB", "a", "c", "BBB", "aa", "bbb", "A", "bb", "ccc", "B", "CC", "AAA", "b", "AA", "cc", "aaa", "C"]
# Output 4: in this 'c' & 'cc' are together
["CC", "bb", "AA", "b", "aa", "BBB", "aaa", "bbb", "CCC", "B", "A", "BB", "C", "a", "ccc", "AAA", "c", "cc"]
So, there are chances that when ratio of number of elements in a group to total no of elements increases, two elements of same group can be clubbed together. Hmm, lets improve further:
Since the group with maximum number of elements has most probability of having two elements combined consecutively, lets study such groups. i.e. group with maximum number of elements.
Lets say, there is a group with X number of elements in it. So what is the minimum number of other type of elements required that none of the X elements are consecutive? Simple right: insert a different element between each pair of element of this group.
x * x * x * x * x * x
So we realise, that if group has X elements it requires atleast (X-1) other type of elements so that no two elements of this group get consecutive.
Mathematically the condition can be represented as:
number_of_elements_in_longest_group == number_of_other_type_of_elements + 1
=> number_of_elements_in_longest_group == (total_number_of_elements - number_of_elements_in_longest_group) + 1
=> number_of_elements_in_longest_group * 2 == total_number_of_elements + 1
Going back to our algorithm, lets add a condition, that at any point of time, if number of remaining items is 1 less than the number of elements in largest group then we have to ensure that next element picked is from this largest group
Juksefantomet sent me this solution in discord, since he can't post here due to an account lock.
The below codeblock contains an alternative approach on how to tackle the problem at hand. This contains a fragmented solution to understand the steps on how you would normally approach a complex problem like the one presented above.
going through the various steps you can see how each condition has to be known in advance and specified to the point where your final array is not "illegal".
#illegal_order = ['1','2','3','4','5','6','7','8','9']
puts #illegal_order.count
puts "#{#illegal_order}"
#foo = []
# traverse the original array and append a random value from that into foo array
# this can of course and should be done in a loop where you check for duplicates
# this is done below in this example, fragmented to see the individual action
(0..#illegal_order.count).each do |add_value|
#temp = #illegal_order[rand(#illegal_order.count)]
unless #foo.count == #illegal_order.count
#foo.push(#temp)
end
end
# making sure it contains everything in the original array
#foo.each do |bar|
unless #illegal_order.include?(bar)
#approve = false
puts "Errored generation!"
end
end
# defining patterns, this can of course be extracted by the original array and be done alot cleaner
# but printing it out to better break it down
#pattern1 = [#illegal_order[0],#illegal_order[1],#illegal_order[2]]
#pattern2 = [#illegal_order[3],#illegal_order[4],#illegal_order[5]]
#pattern3 = [#illegal_order[6],#illegal_order[7],#illegal_order[8]]
# Let us step through the new array and use case to check for various conditions that would flag the new array as invalid
#foo.each do |step|
# setting a temp pattern based on current state
#temp_pattern = [#foo[step.to_i-1],#illegal_order[step.to_i],#illegal_order[step.to_i+1]]
case step
when #temp_pattern == #pattern1
#illegalpatterns = true
when #temp_pattern == #pattern2
#illegalpatterns = true
when #temp_pattern == #pattern3
#illegalpatterns = true
end
# checking the foo array if it contains duplicates, if yes, set conditional to true
#illegal_order.each do |count|
if #foo.count(count) > 1
#duplicates = true
end
end
end
# printing out feedback based on duplicates or not, this is where you rescramble the array if duplicate found
(#duplicates == true) ? (puts "dupes found") : (puts "looks clear. no duplicates")
# printing out feedback based on illegal patterns or not, this is where you rescramble the array if duplicate found
(#illegalpatterns == true) ? (puts "illegal patterns found") : (puts "looks clear, no illegal patterns")
puts "#{#foo}"
Related
Find ungrouped elements in array using Regex in Ruby
By "upgrouped" I mean elements that are uniq to its left and its right elements. Example: arr = ["a", "a", "a", "b", "a", "c", "c", "d", "b"] We can group arr like this [["a", 3], ["b, 1], ["a", 1], ["c", 2], ["d", 1], ["b", 1]]. Let's say once a letter is "grouped" we are not going to pick any other ungrouped instances of that letter. So our final answer would be arr = ["b", "d"] What could be a great option to replace the "regex" part in arr.select{|chr| chr(/\regex/)} (if there is no other ways)? Edit: added "b" in the end of array. arr.count("b") == 2, but since "b" is ungrouped, it is still part of the return array.
You could do it in two steps without regex: banned = arr.chunk(&:itself).select { |e| e.last.size > 1 }.map(&:first) #=> ["a", "c"] arr.uniq - banned #=> ["b", "d"] Basically first we get the list of elements that are grouped and need to be rejected, and then remove those elements from original array. Here is a step by step example (for banned list): arr.chunk(&:itself).to_a #=> [["a", ["a", "a", "a"]], ["b", ["b"]], ["a", ["a"]], ["c", ["c", "c"]], ["d", ["d"]], ["b", ["b"]]] .select { |e| e.last.size > 1 } #=> [["a", ["a", "a", "a"]], ["c", ["c", "c"]]] .map(&:first) #=> ["a", "c"] Notice that to_a is added only to show the content of the Enumerable that results from chunk, but it is not used in the final solution since map can be executed on any Enumerable object. Another option to get banned list could be using each_with_object: arr.chunk(&:itself).each_with_object([]) { |elem, result| result << elem[0] if elem[1].size > 1 } #=> ["a", "c"] This will only iterate arr twice (first option will iterate it 3 times).
I don't know how I'd use a regular expression for this problem, but the method Enumerable#each_cons can be used to advantage. Let grouped and ungrouped respectively be arrays of elements of a given array arr, where grouped are the grouped elements and ungrouped are the ungrouped elements. Every element is grouped or ungrouped. My understanding of the question is that we wish to construct an array (ungrouped - grouped).uniq where Array#- is array difference. It is clear that, for i=1,..., arr.size-2, arr[i] is ungrouped if arr[i-1] != arr[i] && arr[i] != arr[i+1]. It's not evident, however, how one determines whether the first and last elements of an array are ungrouped. The definition states that, to be ungrouped, the element must differ from both of it's adjacent elements. Since the first and last elements only have one adjacent element, it follows that the first and last elements are never ungrouped. The alternative interpretation is that an element is ungrouped if it differs from all of its adjacent elements (that being one for the first and last and two for all others). I will consider these two interpretations separately. For both, arr = [["a", 3], ["b", 1], ["a", 1], ["c", 2], ["d", 1], "b"] Ungrouped elements must differ from both of their adjacent elements ungrouped, grouped = [arr.first, *arr, arr.last].each_cons(3). with_object([[],[]]) do |(p,c,n), (ungrouped, grouped)| (c==p || c==n ? grouped : ungrouped) << c end (ungrouped - grouped).uniq # => ["d"] Ungrouped elements must differ from all of their adjacent elements ungrouped, grouped = [nil, *arr, nil].each_cons(3). with_object([[],[]]) do |(p,c,n), (ungrouped, grouped)| (c==p || c==n ? grouped : ungrouped) << c end (ungrouped - grouped).uniq # => ["b", "d"] This assumes that no elements of arr equal nil. The steps for this second interpretation are as follows. a = [nil, *arr, nil] #=> [nil, "a", "a", "a", "b", "a", "c", "c", "d", "b", nil] b = a.each_cons(3) #=> #<Enumerator: [nil, "a", "a",..., "d", "b", nil]:each_cons(3)> We can see the elements that will be generated by this enumerator by converting it to an array. b.to_a #=> [[nil, "a", "a"], ["a", "a", "a"], ["a", "a", "b"], ["a", "b", "a"], # ["b", "a", "c"], ["a", "c", "c"], ["c", "c", "d"], ["c", "d", "b"], # ["d", "b", nil]] Continuing, d = b.with_object([[],[]]) #=> #<Enumerator: #<Enumerator: [nil, "a", "a",..., "d", "b", nil]: # each_cons(3)>:with_object([[], []])> d.to_a #=> [[[nil, "a", "a"], [[], []]], [["a", "a", "a"], [[], []]], # [["a", "a", "b"], [[], []]], [["a", "b", "a"], [[], []]], # [["b", "a", "c"], [[], []]], [["a", "c", "c"], [[], []]], # [["c", "c", "d"], [[], []]], [["c", "d", "b"], [[], []]], # [["d", "b", nil], [[], []]]] If one examines the return value for the construction of this enumerator, you can see it can be thought of as a compound enumerator. ungrouped, grouped = d.each do |(p,c,n), (ungrouped, grouped)| (c==p || c==n ? grouped : ungrouped) << c end #=> [["b", "a", "d", "b"], ["a", "a", "a", "c", "c"]] ungrouped #=> ["b", "a", "d", "b"] grouped #=> ["a", "a", "a", "c", "c"]] e = ungrouped - grouped #=> ["b", "d", "b"] e.uniq #=> ["b", "d"] Let's take a closer look at the calculation of ungrouped and grouped. The first element of the enumerator d is passed to the block and the block variables are assigned values. (p,c,n), (ungrouped, grouped) = d.next #=> [[nil, "a", "a"], [["b", "a", "d", "b"], ["a", "a", "a", "c", "c"]]] p #=> nil c #=> "a" n #=> "a" ungrouped #=> [] grouped #=> [] c==p || c==n #=> "a"==nil || "a"=="a" #=> true Therefore, grouped << c #=> ["a"] The remaining calculations are similar, as are the calculations under the first assumption concerning the first and last elements of the array.
Sort arrays that are Hash values
I have a hash where the keys are integers and the values are arrays of strings. I need to then sort the items in the arrays alphabetically and return the sorted array as the new hash value. I thought something like hash.map{ |k,v| v.sort } would work, but, no. Just to be a little more explicit, I need to turn: hash = { 0 => ["c", "d", "b", "a"], 1 => ["e", "q", "x", "m"] } into: hash = { 0 => ["a", "b", "c", "d"], 1 => ["e", "m", "q", "x"] }
This is one way: hash.each_value { |v| v.sort! } #=> {0=>["a", "b", "c", "d"], 1=>["e", "m", "q", "x"]} or more succinctly: hash.each_value(&:sort!) However if you wish to preserve the original hash do this: hash.map { |k,v| [k,v.sort] }.to_h #=> {0=>["a", "b", "c", "d"], 1=>["e", "m", "q", "x"]}
Try this, hash.each { |k,v| v.sort! } This sorts the arrays in-place.
This does not mutate the original hash. hash.merge(hash) { |*,n| n.sort } #=> {0=>["a", "b", "c", "d"], 1=>["e", "m", "q", "x"]} If the original hash is to be modified, substitute merge! (aka update) for merge. This uses the forms of Hash#merge and Hash#merge! that employ a block to determine the values of keys that are present in both hashes being merged, which here is all keys of the original hash. See the docs for details.
Iterate through array and start a new array whenever a certain value is found
Say i have the following array: array = ["a", "b", "c", "new", "d", "e", "f", "g", "new", "h", "i", "new"] I want to create a new array every time I find the value "new". So I would end up with: array1 = ["a", "b", "c"] array2 = ["d", "e", "f", "g"] array3 = ["h", "i"] What would be the best way to do this?
As the name implies you can split arrays with the split function, however that returns ArraySlice objects. To get arrays back from the slices you need to map them (credits to Martin R) let array = ["a", "b", "c", "new", "d", "e", "f", "g", "new", "h", "i", "new"] let splittedArrays = array.split(separator: "new").map(Array.init)
How to "or" all elements in the range of an array
I am trying to say if x == consonants[0] or [1] or [2] all the way to [21] on one line. For some reason I thought consonants[0..21] would work but it doesn't: consonants = ["b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z"] new_first = ["m", "a", "t", "t", "h", "e", "w", "s"] new_first.each do |x| if x == consonants[0] puts x.next! elsif x == consonants[1] puts x.next! elsif x == consonants[2] puts x.next! elsif x == consonants[3] puts x.next! else puts "test failed" end end
There's several ways to crack this nut, but it depends on your performance concerns, and how extensible this needs to be. Normally a chain of if statements which are of the form x == y and x == z can be folded into: case (x) when y, z # ... Executed on a match end In your case you can even do this by using your array as a list of valid values: case (x) when *constants puts x.next! end For larger lists you might want to fold this up into a Set since these are optimized for include? tests: consonants_set = Set.new(consonants) if (consonants_set.include?(x)) puts x.next! end Since you're doing single letter matches you have a lot more options. For example, a regular expression: consonants_regexp = Regexp.new('[%s]' % consonants.join) if (consonants_regexp.match(x)) puts x.next! end Or you can even do a simple substring match: consonants_string = consonants.join if (consonants_string[x]) puts x.next! end Worth noting but you can iterate over the characters in strings: 'cwzbrly'.each_char do |c| puts c end That avoids the need to create and/or type in long arrays of the form [ 'a', 'b', ... ].
You can do this: if consonants.include? (x) your-code-here end This will check if there's an element equal to x inside the array.
You can use regular expression if x =~ /[aeiou]/ puts 'test failed' else puts x.next! end /[aeiou]/ is saying match anything that is a, e, i, o, or u. This will eliminate the need to create an array of consonants.
If I correctly understand and you're expecting nuuixt as a result, you can do: new_first.select { |letter| consonants.include?(letter) && letter.next! } && works here this way: if consonants.include?(letter) evaluates to true then block return letter.next! .
Consider this: consonants = ('a' .. 'z').to_a - %w[a e i o u] # => ["b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z"] shifted_consonants = consonants.zip(consonants.rotate).to_h # => {"b"=>"c", "c"=>"d", "d"=>"f", "f"=>"g", "g"=>"h", "h"=>"j", "j"=>"k", "k"=>"l", "l"=>"m", "m"=>"n", "n"=>"p", "p"=>"q", "q"=>"r", "r"=>"s", "s"=>"t", "t"=>"v", "v"=>"w", "w"=>"x", "x"=>"y", "y"=>"z", "z"=>"b"} 'matthews'.chars.map{ |c| shifted_consonants[c] || c } # => ["n", "a", "v", "v", "j", "e", "x", "t"] That took the range of 'a' to 'z' and converted it to an array, then subtracted the array of vowels, resulting in only consonants. Next, it turned the array of consonants into a hash/look-up table shifted_consonants where each key is a current consonant and the value is the next consonant. Finally, it takes each character in 'matthews' and looks to see if there is a value in shifted_consonants for that character. If not, nil is returned, which triggers || and returns the current character. If there is a hit in the hash, the next value for that consonant is returned, which short-circuits the || "or". An alternate would be to take advantage of tr: consonants = (('a' .. 'z').to_a - %w[a e i o u]).join # => "bcdfghjklmnpqrstvwxyz" shifted_consonants = consonants.chars.rotate.join # => "cdfghjklmnpqrstvwxyzb" 'matthews'.tr(consonants, shifted_consonants).chars # => ["n", "a", "v", "v", "j", "e", "x", "t"] Checking for speed: CONSONANTS = ('a' .. 'z').to_a - %w[a e i o u] # => ["b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z"] SHIFTED_CONSONANTS_HASH = CONSONANTS.zip(CONSONANTS.rotate).to_h # => {"b"=>"c", "c"=>"d", "d"=>"f", "f"=>"g", "g"=>"h", "h"=>"j", "j"=>"k", "k"=>"l", "l"=>"m", "m"=>"n", "n"=>"p", "p"=>"q", "q"=>"r", "r"=>"s", "s"=>"t", "t"=>"v", "v"=>"w", "w"=>"x", "x"=>"y", "y"=>"z", "z"=>"b"} CONSONANTS_STR = (('a' .. 'z').to_a - %w[a e i o u]).join # => "bcdfghjklmnpqrstvwxyz" SHIFTED_CONSONANTS_STR = CONSONANTS_STR.chars.rotate.join # => "cdfghjklmnpqrstvwxyzb" require 'fruity' sample_string = 'matthews' compare do use_hash { sample_string.chars.map{ |c| SHIFTED_CONSONANTS_HASH[c] || c } } use_tr { sample_string.tr(CONSONANTS_STR, SHIFTED_CONSONANTS_STR).chars } end # >> Running each test 2048 times. Test will take about 1 second. # >> use_tr is faster than use_hash by 10.000000000000009% ± 10.0% The longer the sample string is, the greater the difference. Changing to: sample_string = 'matthews' * 1000 I see a result of: # >> Running each test 4 times. Test will take about 1 second. # >> use_tr is faster than use_hash by 4x ± 0.1 Found in a comment, NOT in the question where it belongs... my goal is to change the constonant to the next consonant and the vowel to the next vowel for the arraynew_first = ["m", "a", Adjusting for that here's some changes. You can unravel the deltas: ALPHABET = ('a' .. 'z').to_a VOWELS = %w[a e i o u] CONSONANTS = ALPHABET - VOWELS # => ["b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z"] SHIFTED_CONSONANTS = CONSONANTS.rotate # => ["c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z", "b"] SHIFTED_VOWELS = VOWELS.rotate # => ["e", "i", "o", "u", "a"] SHIFTED_CONSONANTS_HASH = CONSONANTS.zip(SHIFTED_CONSONANTS).to_h # => {"b"=>"c", "c"=>"d", "d"=>"f", "f"=>"g", "g"=>"h", "h"=>"j", "j"=>"k", "k"=>"l", "l"=>"m", "m"=>"n", "n"=>"p", "p"=>"q", "q"=>"r", "r"=>"s", "s"=>"t", "t"=>"v", "v"=>"w", "w"=>"x", "x"=>"y", "y"=>"z", "z"=>"b"} SHIFTED_VOWELS_HASH = VOWELS.zip(SHIFTED_VOWELS).to_h # => {"a"=>"e", "e"=>"i", "i"=>"o", "o"=>"u", "u"=>"a"} sample_string = 'matthews' sample_string.chars.map{ |c| SHIFTED_CONSONANTS_HASH[c] || SHIFTED_VOWELS_HASH[c] } # => ["n", "e", "v", "v", "j", "i", "x", "t"] CONSONANTS_STR = CONSONANTS.join # => "bcdfghjklmnpqrstvwxyz" SHIFTED_CONSONANTS_STR = SHIFTED_CONSONANTS.join # => "cdfghjklmnpqrstvwxyzb" SHIFTED_VOWELS_STR = SHIFTED_VOWELS.join # => "eioua" CHARACTERS_STR = (CONSONANTS + VOWELS).join # => "bcdfghjklmnpqrstvwxyzaeiou" SHIFTED_CHARACTERS_STR = SHIFTED_CONSONANTS_STR + SHIFTED_VOWELS_STR # => "cdfghjklmnpqrstvwxyzbeioua" sample_string.tr(CHARACTERS_STR, SHIFTED_CHARACTERS_STR).chars # => ["n", "e", "v", "v", "j", "i", "x", "t"] The changes won't affect the speed of the actual code: tr would still outrun using the hash lookups.
How to iterate a particular item in array in ruby
I want to do different iteration to elements in single array. arr = ["1111", "2221", "7ext", "3345"] I want to run a block that would slice only the element that contains letters and elements with no letter remain the same. The result would be following arr = ["1111", "2221", "7", "e", "x", "t", "3345"] I do not know neither how many elements would be in array or order. All elements are strings.
arr.flat_map{|s| s.split(/([a-z])/i)}.reject(&:empty?) # => ["1111", "2221", "7", "e", "x", "t", "3345"]
Try this: arr = ['1111', '2221', '7ext', '3345'] arr.flat_map { |e| e =~ /\D/ ? e.split('') : e } # => ["1111", "2221", "7", "e", "x", "t", "3345"]