Sort 2 linked arrays in Swift? [duplicate] - arrays

This question already has answers here:
In Swift how can I sort one array based on another array?
(4 answers)
Closed 4 years ago.
I want to sort one array and "mirror" order changes to another array of the same dimension.
The question is similar to this one but for Swift language:
Better way to sort 2 "linked" arrays?
Example:
let arr1 = ["a", "b", "c", "d", "e"]
let arr2 = [1, 5, 9, 2, 3]
... //sort
//result == ["a", "d", "e", "b", c"]
Approximate solution I know:
for i in stride(from 0, to: arr2.count - 1, by: 1) {
for j in stride(from i + 1, to: arr2.count, by: 1) {
if arr2[i] > arr2[j] {
...//swap arr2[i] and arr2[j]
...//swap arr1[i] and arr1[j]
}
}
}
But they added a lot of additional possibilities for advanced working with arrays in Swift. So is it possible to simplify this solution with inner Swift features?
Note: arr1 and arr2 are given as separate arrays.
EDITED
Yes. You found the similar questions but their titles are awful and don't reflect the answers their authors need. In other words if you delete/close my question the people may continue to ask it again because it is impossible to find something with existing titles!

zip the arrays together
sort the new array by array 2
map the array to array 1
let arr1 = ["a", "b", "c", "d", "e"]
let arr2 = [1, 5, 9, 2, 3]
let result = zip(arr1, arr2) // [("a", 1), ("b", 5), ("c", 9), ("d", 2), ("e", 3)]
.sorted(by: {$0.1 < $1.1}) // [("a", 1), ("d", 2), ("e", 3), ("b", 5), ("c", 9)]
.map{ $0.0 } // ["a", "d", "e", "b", "c"]

Related

Array of strings and ints in swift

I am trying to make an array of arrays where each nested array has a string and an integer.
I have seen you can use structs but all I want to do is make them a constant, and I want to know if there is a way to do it without having to type loads of extra stuff
let items: [[String, Int]] = [["A", 1], ["B", 2], ["C", 3]]
I think what you want is an array of tuples, not an array of arrays. That implementation would look like this:
let items: [(String, Int)] = [("A", 1), ("B", 2), ("C", 3)]
You could access these properties like this:
let itemOneString = items[0].0 // "A"
let itemOneInt = items[0].1 // 1
It will work for you:
let items: [[(String, Int)]] = [[("A", 1)], [("B", 2)], [("C", 3)]]
Array is collection of similar data types. It can not contain heterogeneous types data.
But if you still want to do it. There are other workarounds like create array of dictionary like this.
let items: [[String: Any]] = [["String" : "A", "Int" : 1], ["String" : "B", "Int" : 2]]
or create an array of Tuples.
let items: [(String, Int)] = [("A", 1), ("B", 2), ("C", 3)]
You can add any number of items in Tuple or Dictionary.

Map Swift array with specific format

Given an array that contains elements like this:
let array = [[a], [b, c], [d, e, f]]
Is there an elegant way to convert this array to an array which returns a tuple with the index of the outer array:
let result = [(a, 0), (b, 1), (c, 1), (d, 2), (e, 2), (f, 2)]
let array = [["a"], ["b", "c"], ["d", "e", "f"]]
let result = zip(array, array.indices).flatMap { subarray, index in
subarray.map { ($0, index) }
}
result is:
[("a", 0), ("b", 1), ("c", 1), ("d", 2), ("e", 2), ("f", 2)]
I used zip(array, array.indices) instead of array.enumerated() because you specifically asked for a tuple with the array index — enumerated() produces tuples with zero-based integer offsets. If your source collection is an array, it doesn't make a difference, but other collections (like ArraySlice) would behave differently.

Most efficient way to count duplicated elements between two arrays

As part of a very basic program I am writing in Ruby, I am trying to find the total number of shared elements between two arrays of equal length, but
I need to include repeats.
My current example code for this situation is as follows:
array_a = ["B","A","A","A","B"]
array_b = ["A","B","A","B","B"]
counter = 0
array_a.each_index do |i|
array_a.sort[i] == array_b.sort[i]
counter += 1
end
end
puts counter
I want the return value of this comparison in this instance to be 4, and not 2, as the two arrays share 2 duplicate characters ("A" twice, and "B" twice). This seems to work, but I am wondering if there are any more efficient solutions for this issue. Specifically whether there are any methods you would suggest looking into. I spoke with someone who suggested a different method, inject, but I really don't understand how that applies and would like to understand. I did quite a bit of reading on uses for it, and it still isn't clear to me how it is appropriate. Thank you.
Looking at my code, I have realized that it doesn't seem to work for the situation that I am describing.
Allow me to reiterate and explain what I think the OP's original intent was:
Given arrays of equal size
array_a = ["B","A","A","A","B"]
array_b = ["A","B","A","B","B"]
We need to show the total number of matching pairs of elements between the two arrays. In other words, each B in array_a will "use up" a B in array_b, and the same will be true for each A. As there are two B's in array_a and three in array_b, this leaves us with a count of 2 for B, and following the same logic, 2 for A, for a sum of 4.
(array_a & array_b).map { |e| [array_a.count(e), array_b.count(e)].min }.reduce(:+)
If we get the intersection of the arrays with &, the result is a list of values that exist in both arrays. We then iterate over each match, and select the minimum number of times the element exists in either array --- this is the most number of times the element that can be "used". All that is left is to total the number of paired elements, with reduce(:+)
Changing array_a to ["B", "A", "A", "B", "B"] results in a total of 5, as there are now enough of B to exhaust the supply of B in array_b.
If I understand the question correctly, you could do the following.
Code
def count_shared(arr1, arr2)
arr1.group_by(&:itself).
merge(arr2.group_by(&:itself)) { |_,ov,nv| [ov.size, nv.size].min }.
values.
reduce(0) { |t,o| (o.is_a? Array) ? t : t + o }
end
Examples
arr1 = ["B","A","A","A","B"]
arr2 = ["A","B","A","B","B"]
count_shared(arr1, arr2)
#=> 4 (2 A's + 2 B's)
arr1 = ["B", "A", "C", "C", "A", "A", "B", "D", "E", "A"]
arr2 = ["C", "D", "F", "F", "A", "B", "A", "B", "B", "G"]
count_shared(arr1, arr2)
#=> 6 (2 A's + 2 B's + 1 C + 1 D + 0 E's + 0 F's + 0 G's)
Explanation
The steps are as follows for a slightly modified version of the first example.
arr1 = ["B","A","A","A","B","C","C"]
arr2 = ["A","B","A","B","B","D"]
First apply Enumerable#group_by to both arr1 and arr2:
h0 = arr1.group_by(&:itself)
#=> {"B"=>["B", "B"], "A"=>["A", "A", "A"], "C"=>["C", "C"]}
h1 = arr2.group_by(&:itself)
#=> {"A"=>["A", "A"], "B"=>["B", "B", "B"], "D"=>["D"]}
Prior to Ruby v.2.2, when Object#itself was introduced, you would have to write:
arr.group_by { |e| e }
Continuing,
h2 = h0.merge(h1) { |_,ov,nv| [ov.size, nv.size].min }
#=> {"B"=>2, "A"=>2, "C"=>["C", "C"], "D"=>["D"]}
I will return shortly to explain the above calculation.
a = h2.values
#=> [2, 2, ["C", "C"], ["D"]]
a.reduce(0) { |t,o| (o.is_a? Array) ? t : t + o }
#=> 4
Here Enumerable#reduce (aka inject) merely sums the values of a that are not arrays. The arrays correspond to elements of arr1 that do not appear in arr2 or vise-versa.
As promised, I will now explain how h2 is computed. I've used the form of Hash#merge that employs a block (here { |k,ov,nv| [ov.size, nv.size].min }) to compute the values of keys that are present in both hashes being merged. For example, when the first key-value pair of h1 ("A"=>["A", "A"]) is being merged into h0, since h0 also has a key "A", the array
["A", ["A", "A", "A"], ["A", "A"]]
is passed to the block and the three block variables are assigned values (using "parallel assignment", which is sometimes called "multiple assignment"):
k, ov, nv = ["A", ["A", "A", "A"], ["A", "A"]]
so we have
k #=> "A"
ov #=> ["A", "A", "A"]
nv #=> ["A", "A"]
k is the key, ov ("old value") is the value of "A" in h0 and nv ("new value") is the value of "A" in h1. The block calculation is
[ov.size, nv.size].min
#=> [3,2].min = 2
so the value of "A" is now 2.
Notice that the key, k, is not used in the block calculation (which is very common when using this form of merge). For that reason I've changed the block variable from k to _ (a legitimate local variable), both to reduce the chance of introducing a bug and to signal to the reader that the key is not used in the block. The other elements of h2 that use this block are computed similarly.
Another way
It would be quite simple if we had available an Array method I've proposed be added to the Ruby core:
array_a = ["B","A","A","A","B"]
array_b = ["A","B","A","B","B"]
array_a.size - (array_a.difference(array_b)).size
#=> 4
or
array_a.size - (array_b.difference(array_a)).size
#=> 4
I've cited other applications in my answer here.
This is a perfect job for Enumerable#zip and Enumerable#count:
array_a.zip(array_b).count do |a, b|
a == b
end
# => 2
The zip method pairs up elements, "zippering" them together, and the count method can take a block as to if the element should be counted.
The inject method is very powerful, but it's also the most low-level. Pretty much every other Enumerable method can be created with inject if you work at it, so it's quite flexible, but usually a more special-purpose method is better suited. It's still a useful tool if applied correctly.
In this case zip and count do a much better job and if you know what these methods do, this code is self explanatory.
Update:
If you need to count all overlapping letters regardless of order you need to do some grouping on them. Ruby on Rails provides the handy group_by method in ActiveSupport, but in pure Ruby you need to make your own.
Here's an approach that counts up all the unique letters, grouping them using chunk:
# Convert each array into a map like { "A" => 2, "B" => 3 }
# with a default count of 0.
counts = [ array_a, array_b ].collect do |a|
Hash.new(0).merge(
Hash[a.sort.chunk { |v| v }.collect { |k, a| [ k, a.length ] }]
)
end
# Iterate over one of the maps key by key and count the minimum
# overlap between the two.
counts[0].keys.inject(0) do |sum, key|
sum + [ counts[0][key], counts[1][key] ].min
end

Delete array element if index position is greater than a specific value

I am trying to delete elements from an array if its index is greater than a certain value. I am looking to do something like this:
a = ["a", "b", "c"]
b = a.delete_if {|x| x.index > 1 }
I took a look at drop, delete_if, etc. I tried completing this using each_with_index like this:
new_arr = []
a.each_with_index do |obj, index|
if index > 1
obj.delete
end
new_arry << obj
end
How can I delete an array element if it's array position is greater than a certain value?
Here are some other ways to return a sans elements at indices >= index, which is probably better expressed as "returning the first index elements". All below return ["a", "b"]).
a = ["a", "b", "c", "d", "e"]
index = 2
Non-destructive (i.e., a is not altered)
a[0,index]
index.times.map { |i| a[i] }
Destructive (a is modified or "mutated")
a.object_id #=> 70109376954280
a = a[0,index]
a.object_id #=> 70109377839640
a.object_id #=> 70109377699700
a.replace(a.first(index))
a.object_id #=> 70109377699700
You can use slice! and give it a range. It is a destructive method as indicated by the !, so it will mutate your array.
a = [1, 2, 3, 4]
a.slice!(2..-1)
a = [1, 2]
Array#first gives you the first n elements.
b = a.first(1)
# => ["a"]
If you want to do it in a destructive way, then this will do:
a.pop(a.length - 1)
a # => ["a"]
You can append with_index:
a = ["a", "b", "c"]
a.delete_if.with_index { |x, i| i > 1 }
a #=> ["a", "b"]
Another example:
a = ("a".."z").to_a
a.delete_if.with_index { |x, i| i.odd? }
#=> ["a", "c", "e", "g", "i", "k", "m", "o", "q", "s", "u", "w", "y"]
Going by your question, "How can I delete an array element if it's array position is greater than a certain value?".
I assume what you want is that the final array you have should contain only elements before the specified index.
You can just do this:
your_array.select { |element| your_array.index(element) < max_index }
E.g
figures = [1,2,3,4,5,6]
figures.select{ |fig| figures.index(fig) < 3 }
# => [1, 2, 3]

Is this a bug in the Array.fill method in Ruby? [duplicate]

This question already has an answer here:
Arrays misbehaving
(1 answer)
Closed 6 years ago.
Should this be the case i.e. I am misunderstanding, or is it a bug?
a = Array.new(3, Array.new(3))
a[1].fill('g')
=> [["g", "g", "g"], ["g", "g", "g"], ["g", "g", "g"]]
should it not result in:
=> [[nil, nil, nil], ["g", "g", "g"], [nil, nil, nil]]
Array.new(3, Array.new(3)) returns an array which contains the same array three times (in other words: the expression Array.new(3) is evaluated exactly once and no copies are made).
What you probably want is Array.new(3) { Array.new(3) }, which evaluates Array.new(3) three times and thus gives you an array of three independent arrays.
It is correct, Array.new(array) returns a new array created with size copies of obj (that is, size references to the same obj)

Resources