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.
Related
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.
Let's say I have an array of strings:
var array: [String] = ["a", "a", "b", "c", "c", "c", "d", "d"]
In the array, I have 4 times a, 1 time b, 3 times c and 2 times d.
I want to update the a value from 4 to 7, and the c value from 3 to 1.
The maximum times I want a single string to be in the array is up to 10 times.
I tried to do it using this:
for _ in 0..<10 {
if array.contains("a") {
if let index = array.index(of: "a") {
array.remove(at: index)
}
}
}
for _ in 0..<7 {
array += ["a"]
}
First, in a loop which runs 10 times, I check every time if the array still contains a, and if so then I remove it. After that, I run a loop for 7 times, and this loop adds every time another a value to the array, until there are supposed to be 7 a in the array.
This isn't what's really happening. What really happening is that it replaces all objects in the array to a, and definitely runs more than 7 times.
What can I do to solve it?
I would suggest using a dictionary. This current method is not really efficient.
var dict = [
"a": 4,
"b": 8
]
This way you can update the value for each letter without having to repeat them in an array. To set a dictionary value you can use the subscript:
dict["a"] = 2
This seems more suited to what you're trying to do.
If you want to bring the occurrences of a given element to n you can write something like this.
extension Array where Element == String {
func updated(numOccurrencies: Int, ofWord word: String) -> [String] {
let currentOccurrencies = self.filter { $0 == word }.count
let delta = numOccurrencies - currentOccurrencies
if delta > 0 {
let newOccurrencies = Array<String>(repeatElement(word, count: delta))
return self + newOccurrencies
}
if delta < 0 {
var numElmsToDelete = -delta
return filter {
guard $0 == word else { return true }
guard numElmsToDelete > 0 else { return true }
numElmsToDelete -= 1
return false
}
}
return self
}
}
Examples
Now given you array
let words = ["a", "a", "b", "c", "c", "c", "d", "d"]
You can produce a new array bringing the occurrences of "a" to different values
words.updated(numOccurrencies: 0, ofWord: "a")
// ["b", "c", "c", "c", "d", "d"]
words.updated(numOccurrencies: 1, ofWord: "a")
// ["a", "b", "c", "c", "c", "d", "d"]
words.updated(numOccurrencies: 2, ofWord: "a")
// ["a", "a", "b", "c", "c", "c", "d", "d"]
words.updated(numOccurrencies: 3, ofWord: "a")
// ["a", "a", "b", "c", "c", "c", "d", "d", "a"]
words.updated(numOccurrencies: 4, ofWord: "a")
// ["a", "a", "b", "c", "c", "c", "d", "d", "a", "a"]
Sorting
As you can see the new occurrences of "a" ad added at the end of the array. If you want the array to stay sorted just append .sorted() to each invocation
words.updated(numOccurrencies: 4, ofWord: "a").sorted()
// ["a", "a", "a", "a", "b", "c", "c", "c", "d", "d"]
"The maximum times I want a single string to be in the array is up to 10 times"
I am now assuming the output array must be sorted.
I am going to use a different approach for this. I will calculate for each word, the number of occurrences we expect for that word in the output array.
Each occurrences will be the minimum between 10 and the current occurrences for that word.
Example
a: min(10, 2) = 2
b: min(10, 1) = 1
...
Once I have the number of occurrences expected for each word I can build the final sorted array from scratch.
extension Array where Element == String {
func updated(withMaximumOccurrencies max: Int) -> [String] {
let countedSet = NSCountedSet(array: self)
let uniqueWords = Set(self)
return uniqueWords
.reduce([String]()) { (res, word) -> [String] in
let occurrencies = Swift.min(max, countedSet.count(for: word))
return res + [String](repeatElement(word, count: occurrencies))
}.sorted()
}
}
Examples
let words: [String] = ["a", "a", "b", "c", "c", "c", "d", "d"]
words.updated(withMaximumOccurrencies: 1)
["a", "b", "c", "d"]
words.updated(withMaximumOccurrencies: 2)
["a", "a", "b", "c", "c", "d", "d"]
words.updated(withMaximumOccurrencies: 10)
["a", "a", "b", "c", "c", "c", "d", "d"]
How to combine 2 arrays like this
a = ["x","y","z"]
b = [["a","b"],["c","d"],["e","f"]]
expected output:
[["a","b","x" ],["c","d","y"],["e","f","z"]]
Is there any inbuilt method?
There is. You can use Array#zip in conjunction with Array#flatten:
b.zip(a).map(&:flatten)
#=> [["a", "b", "x"], ["c", "d", "y"], ["e", "f", "z"]]
another way is:
[b, a].transpose.map(&:flatten)
#=> [["a", "b", "x"], ["c", "d", "y"], ["e", "f", "z"]]
:)
Here is one more way to do this:
a = ["x","y","z"]
b = [["a","b"],["c","d"],["e","f"]]
b.map.with_index {|arr, idx| arr << a[idx]}
#=> [["a", "b", "x"], ["c", "d", "y"], ["e", "f", "z"]]
enum = a.to_enum
b.map { |arr| arr << enum.next }
#=> [["a", "b", "x"], ["c", "d", "y"], ["e", "f", "z"]]
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)
The code below currently pushes a copy of #startyear into #new. I need to convert this into 1 single array, any ideas?
Forums didn't have much
startyear = [["a", "b", "z"], ["c", "d"], ["e", "f"], ["g", "h", "i", "j"]]
new = []
startyear.each do |n| #.transpose here?
puts "looping #{n}"
new.push(n)
#n.join is needed somewhere
puts "#{startyear.length} is the length of startyear"
break if startyear.length == startyear.length[4]
end
puts "your new array is : #{new}"
You can use Array#flatten:
startyear = [["a", "b", "z"], ["c", "d"], ["e", "f"], ["g", "h", "i", "j"]]
flattened = startyear.flatten
# flattened is now ["a", "b", "z", "c", "d", "e", "f", "g", "h", "i", "j"]
Array#flatten is the obvious method to use here, but as is generally the case with Ruby, there are alternatives. Here are two.
Use Enumerable#flat_map and Object#itself
startyear.flat_map(&:itself)
#=> ["a", "b", "z", "c", "d", "e", "f", "g", "h", "i", "j"]
itself was introduced in Ruby v2.2. For earlier versions, use:
startyear.flat_map { |a| a }
Use Enumerable#reduce (aka inject)
startyear.reduce(:+)
#=> ["a", "b", "z", "c", "d", "e", "f", "g", "h", "i", "j"]