So this question is a follow up to this one: swift string permutations allowing the same strings
There, I asked about all possible mutations that I can make with a defined set of strings. What I am trying to do next is to filter out all results that have the same combination, but in a different order.
So if the input is: ["AB", "AC", "CA", "CB"], the output should be ["AB", "AC", "CB"], since "AC" and "CA" have the same building blocks.
So my thought was to first sort each string alphabetically, and maybe then create a Set.
I am already stuck on the first part :(
let array = ["AB", "AC", "DC", "CA", "CB"]
print(type(of: array))
print(array)
let sortedArray = array.map{ $0.sorted() }
print(type(of: sortedArray))
print(sortedArray)
The output is:
Array<String>
["AB", "AC", "DC", "CA", "CB"]
Array<Array<Character>>
[["A", "B"], ["A", "C"], ["C", "D"], ["A", "C"], ["B", "C"]]
While I expected for the sortedArray:
["AB", "AC", "CD", "AC", "BC"]
Then I thought to join the individual strings back together:
print(array.map{ $0.joined() } )
Resulting in ambiguous reference to member 'joined()'
But how to fix this I don't understand.
I also saw this one: swift sort characters in a string where the following code is used:
var nonSortedString = "5121"
var sortedString = String(Array(nonSortedString.characters).sort())
But I don't see how to apply that using map and friends (after converting to Swift 4)
Any help appreciated.
If you want to take a string, sort its characters, and build a new string from that, in Swift 4 you can:
let string = "foobar"
let sortedString = String(string.sorted())
That results in:
"abfoor"
So, going back to your original problem, you can take you strings, which are a collection of permutations, and build a sorted array of combinations like so:
let permutations = ["AB", "AC", "DC", "CA", "CB"]
// build set of combinations where each string has, itself, been sorted alphabetically
let combinations = Set(permutations.map { String($0.sorted()) })
// convert set (which removed duplicates) back to an array and sort it
let result = Array(combinations).sorted()
That results in:
["AB", "AC", "BC", "CD"]
A different approach...
This solution uses another reduce function implementation from the Sequence protocol
let reduced = array.map({ String($0.sorted()) }).reduce(into: [String]() ){ (result, element) -> Void in
if !result.contains(element)
{
result.append(element)
}
}.sorted()
print(reduced)
The result is...
["AB", "AC", "BC", "CD"]
Related
This question already has an answer here:
Check if an array contains elements of another array in swift
(1 answer)
Closed 2 years ago.
I have 2 500,000 count arrays of strings. is there a more efficient way to check if they have elements that match then:
let array1 = ["a", "b", "c", "d", "e"]
let array2 = ["d", "e", "f", "g", "h"]
var maching = [0]
for element1 in array1 {
for element2 in array2 {
if element1 == element1 {
maching.append(element1)
}
}
}
Thanks in advance
If elements are Hashable (strings are) and if we can ignore duplicates and ordering, then using Set is the easiest solution:
let matching = Set(array1).intersection(Set(array2))
Depending on the nature of the data, we could come with an even better solution, e.g. an interval tree. The more information we have, the better solution can be designed. However, an optimal solution specific to one use case will be much more complex.
I have an array:
["a", "b", "c", "d"]
How do I figure out the index of the first element of the above array to occur within a second array:
["next", "last", "d", "hello", "a"]
The index of the first element from the first array to occur within the above array would be 2; "d" belongs to the first array and occurs at position 2.
There's a couple of ways to do this, but the naive approach might work well enough to get going:
tests = ["a", "b", "c", "d"]
in_array = ["next", "last", "d", "hello", "a"]
in_array.each_with_index.find do |e, i|
tests.include?(e)
end
# => ["d", 2]
You can speed this up by making tests a Set which avoids a lot of O(N) lookups:
tests = Set.new([ ... ])
The same code will work with include? but that's now much faster on longer lists.
This approach, wrapped in a method, returns an array containing all indexes of common elements between two arrays.
def find_positions(original_array, look_up_array)
positions_array = []
original_array.each do |x|
if look_up_array.index(x) != nil
positions_array << look_up_array.index(x)
end
end
positions_array
# positions_array.first => for the first matched element
end
If you want only the first matched element you could return positions_array.first but this way you'll not avoid the extra lookups.
PS: you could also use #collect and avoid the extra array (positions_array)
You can iterate through the array you want to be compared and use .select or .find iterator method. .find will select the first element match in the arrays while .select will match all elements in the arrays. If you want to add the index in the selection you can add .each_with_index. '.index(a)' returns the element if present else it will return nil.
alphabet = %w(a b c d)
%w(next last d hello a).each_with_index.find {|a, _index| alphabet.index(a) }
=> ["d", 2]
%w(next last d hello a).each_with_index.select {|a, _index| alphabet.index(a) }[0]
=> ["d", 2]
# if you just need the index of the first match
%w(next last d hello a).index {|a| alphabet.index(a) }
=> 2
I'm writing some code to filter out driving trips from the motion sensor. I figured out the best way to do this is to add a subarray to a nested array based on the following:
Detect the first occurrence of a confident automotive event
Add all the following motion events to the same array of the events until the first confident observation that says otherwise.
For example
automotive confidence 2 //Add
automotive confidence 2 //Add
automotive confidence 2 //Add
walking confidence 2 //Add the sub-array to the master array and start over on the next confident automotive event.
Currently i'm doing it this way:
//Remove all uncertain values.
let confidentActivities = activities!.filter{$0.confidence.rawValue == 2}
var needsNew = true
var automotiveActivities:Array<Array<CMMotionActivity>> = Array() //Master array to contain subarrays of automotiveactivity arrays
var automotiveActivitySession:Array<CMMotionActivity> = Array()
for activity in confidentActivities {
if activity.automotive && (!activity.cycling && !activity.running && !activity.walking){
if needsNew {
needsNew = false
}
automotiveActivitySession.append(activity)
} else {
if !needsNew {
//If user is no longer in car, store a cpoy of the session and reset the array
automotiveActivities.append(Array(automotiveActivitySession))
automotiveActivitySession = []
needsNew = true
}
}
}
This solution is not very elegant.
Is there any way to use Swift's Array.filter{} to make this sorting prettier?
Filter is not going to do it, but you can use reduce.
The example below shows how to collect runs of consecutive "A"s (denoting an automotive event) into arrays inside an array of arrays:
let data = ["A","A","A","B","A","A","B","A","A","A","A","B","B","B","A","B","A","A","A","A","A","A","B","A"]
var res = [[String]]()
_ = data.reduce("") { (last: String, current: String) in
if current == "A" {
if last != "A" {
res.append([String]())
}
res[res.count-1].append(current)
}
return current
}
print(res)
The prior value is passed to reduce's function as the first parameter. This makes it possible for the function to decide whether to append to the current list or to start a new list.
The result of this run is as follows:
[ ["A", "A", "A"]
, ["A", "A"]
, ["A", "A", "A", "A"]
, ["A"]
, ["A", "A", "A", "A", "A", "A"]
, ["A"]]
If you're after a pretty solution, you can use split to do this. You just have to provide it with a condition on what should be considered a separator. In your case, this will be any motion event that isn't an automotive one.
let arr = ["A","A","A","B","A","A","B","A","A","C","A","B","D","B","A","B","A","E","A","A","F","A","B","A","B"]
let split = arr.split {$0 != "A"} // insert your condition for whether the given element should be considered a 'seperator'
$0 here is the anonymous closure argument for an element in the array (as it iterates through). You can always expand the closure in order to make the namings more explicit, although it looks less elegant. For example:
let split = arr.split {element in
return element != "A"
}
This will return an array of ArraySlices like so:
[
ArraySlice(["A", "A", "A"]),
ArraySlice(["A", "A"]),
ArraySlice(["A", "A"]),
ArraySlice(["A"]),
ArraySlice(["A"]),
ArraySlice(["A"]),
ArraySlice(["A", "A"]),
ArraySlice(["A"]),
ArraySlice(["A"])
]
If you want them to be explicit Arrays, you can simply use map afterwards:
let split = arr.split {$0 != "A"}.map{Array($0)}
Returns:
[
["A", "A", "A"],
["A", "A"],
["A", "A"],
["A"], ["A"],
["A"],
["A", "A"],
["A"],
["A"]
]
Scenario:
An array of strings, many are duplicated.
Goal:
Produce a UNIQUE array of strings.
Modus Operandi:
I was thinking of converting the array to a set of strings which become unique; from which to generate a new array of unique strings.
Question: How does one convert a Swift array into a Swift Set?
let nonUniqueArray = ["A", "B", "C", "C", "B", "A"]
let uniqueArray = Array(Set(nonUniqueArray))
print(uniqueArray)
produces
["C", "B", "A"]
Swift 2.2 produces exactly the same result as well.
Have you tried let myset = Set(myarray) ?
I have an array that includes float-like strings like "4.5", and regular strings like "Hello". I want to sort the array so that regular strings come at the end and the float-like strings come before them and are sorted by their float value.
I did:
#arr.sort {|a,b| a.to_f <=> b.to_f }
arr = ["21.4", "world", "6.2", "1.1", "hello"]
arr.sort_by { |s| Float(s) rescue Float::INFINITY }
#=> ["1.1", "6.2", "21.4", "world", "hello"]
sort in ruby 1.9+
["1.2", "World", "6.7", "3.4", "Hello"].sort
will return
["1.2", "3.4", "6.7", "Hello", "World"]
You can use #cary solution for certain edge cases eg ["10.0","3.2","hey","world"]
Quick and dirty:
arry = ["1", "world", "6", "21", "hello"]
# separate "number" strings from other strings
tmp = arry.partition { |x| Float(x) rescue nil }
# sort the "numbers" by their numberic value
tmp.first.sort_by!(&:to_f)
# join them all in a single array
tmp.flatten!
Will probably suit your needs