Let's say we have an array:
var scrambledAlphabet = ["B", "C", "D", "E", "H", "F", "G", "A"]
and we want sort it by moving two items: item 7 ("A") to location 0, and item 4 ("H") to 7, described with an array of tuples:
var moves = [(7, 0), (4, 7)]
So, the desired result is:
["A", "B", "C", "D", "E", "F", "G", "H"]
Spontaneous that would be easy solved with this:
var scrambledAlphabet = ["B", "C", "D", "E", "H", "F", "G", "A"]
var moves = [(7, 0), (4, 7)]
for (i, j) in moves {
scrambledAlphabet.insert(scrambledAlphabet.remove(at: i), at: j)
}
print(scrambledAlphabet)
But it doesn't work. The output is:
["A", "B", "C", "D", "H", "F", "G", "E"]
The problem is that once the first item is moved, the index of the next item is altered. So, what would be the best way to tackle this? I find this surprisingly hard to crack. Any help is appreciated.
The restriction is that the two variables, scrambledAlphabet and moves, must be able to grow to any number.
And also one important note, the move-to-index (second number in the tuple) is referring to the old array, not the newly created. So, the inputs ["B", "C", "E", "D", "H", "F", "G", "A"] and [(7, 0), (4, 7), (3, 2)] should result in:
["A", "B", "C", "D", "E", "F", "G", "H"]
Sorry about the confusion about this last bit.
I thought of this (IMO a little clumsy) solution:
var newArray = Array(repeating: "", count: scrambledLetters.count)
for (start, end) in moves {
newArray[end] = scrambledLetters[start]
scrambledLetters[start] = ""
}
var scrambledLetterIndex = -1
func setScrambledLetterIndexToNextNonEmptyString() {
scrambledLetterIndex += 1
while scrambledLetterIndex < scrambledLetters.count - 1 && scrambledLetters[scrambledLetterIndex].isEmpty {
scrambledLetterIndex += 1
}
}
for i in newArray.indices {
if newArray[i] == "" {
setScrambledLetterIndexToNextNonEmptyString()
newArray[i] = scrambledLetters[scrambledLetterIndex]
}
}
scrambledLetters = newArray
Essentially, I first created a new array, and "took out" the strings that needs to be moved, and placed them in the correct position in the new array. That's what the first for loop did.
After the first for loop, the two arrays will look like this:
scrambledLetters: ["B", "C", "D", "E", "", "F", "G", ""]
newArray: ["A", "", "", "", "", "", "" , "H"]
Then, I slowly copied each non-empty item in the scrambled letters array into the new array's empty places.
Because this solution makes use of the empty string, it won't work if the input domain contained empty strings. If that's the case, you'd have to use something like a [String?].
A possible approach is to use an array of all indices which are not the source of any move. Then we can fill the destination sequentially, either from one of the moves, or from one of the “other indices”:
func scramble<T>(array: [T], moves: [(Int, Int)]) -> [T] {
// Moves sorted by increasing destination index:
var moves = moves.sorted(by: { $0.1 < $1.1 })
// All indices which are not the source of a move:
let sourceIndices = Set(moves.map { $0.0 })
var otherIndices = array.indices.filter { !sourceIndices.contains($0)}
// Fill each position with an element of a move source,
// or one of the "other" array elements:
return array.indices.map {
if let (from, to) = moves.first, $0 == to {
moves.removeFirst()
return array[from]
} else {
return array[otherIndices.removeFirst()]
}
}
}
Example 1:
print(scramble(array: ["B", "C", "D", "E", "H", "F", "G", "A"],
moves: [(7, 0), (4, 7)]))
// ["A", "B", "C", "D", "E", "F", "G", "H"]
Example 2:
print(scramble(array: [0, 1, 2, 3, 4, 5, 6, 7],
moves: [(1, 6), (7, 2), (3, 5), (5, 3), (4, 7)]))
// [0, 2, 7, 5, 6, 3, 1, 4]
I solved it by couple the items in the array with their original index, and sorted the moves so that the inserts doesn't scramble the order. Also moved all code into an array extension:
extension Array where Element : Equatable {
mutating func moveItem(from fromIndex: Int, to toIndex: Int) {
self.insert(self.remove(at: fromIndex), at: toIndex)
}
func performIteratedMoves(_ moves: [(Int, Int)]) -> [Element] {
var array = self
let enumeratedArray = array.enumerated().map{ (index: $0, item: $1) }
let sortedMoves = moves.sorted { $0.1 > $1.1 }
for (i, j) in sortedMoves {
guard let (_, item) = enumeratedArray.first(where:{ $0.index == i }), let correctIndex = array.firstIndex(where:{ $0 == item }) else { continue }
array.moveItem(from: correctIndex, to: j)
}
return array
}
}
Usage:
var scrambledAlphabet = ["B", "C", "E", "D", "H", "F", "G", "A"]
var moves = [(7, 0), (4, 7), (3, 2)]
print(scrambledAlphabet.performIteratedMoves(moves))
// ["A", "B", "C", "D", "E", "F", "G", "H"]
Related
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"]
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.
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)
I have an Array of String and I want to find all the possible combinations of its element
For Example :
Array = [A,B,C,D]
should produce result as :
[A,AB,AC,AD,ABC,ABD,ACD,ABCD,B,BC,BD,BCD,C,CD,D]
Here is my Logic :
var array = ["A", "B", "C","D"]
var list = [String]()
for i in 0..<array.count{
let c = array[i]
list.append(c)
var d = c
for count in 1..<array.count{
if i+count < array.count{
for j in i+count..<array.count{
var a = d
a.appendContentsOf(array[j])
print("a : \(a)")
list.append(a)
}
}
d = c
d.appendContentsOf(array[count])
print("d : \(d)")
}
}
print(list.description)
Its Output is :
["A", "AB", "AC", "AD", "ABC", "ABD", "ACD", "B", "BC", "BD", "BBD",
"C", "CD", "D"]
This output is missing ABCD and wrongly printed BCD as BBD
Anyone Please Help me in this by Enhancing my code or suggesting your own logic for this.
#yannick's answer is very close.
By computing a Power Set of your set, you obtain all the possible subsets (including your original set and the empty set).
Once you have obtained the Power Set, all you have to do is join the subsets into a single string in order to obtain the result that you're looking for.
Here's the complete solution (along with updated code and plenty of comments):
extension Array {
var powerset: [[Element]] {
guard count > 0 else {
return [[]]
}
// tail contains the whole array BUT the first element
let tail = Array(self[1..<endIndex])
// head contains only the first element
let head = self[0]
// computing the tail's powerset
let withoutHead = tail.powerset
// mergin the head with the tail's powerset
let withHead = withoutHead.map { $0 + [head] }
// returning the tail's powerset and the just computed withHead array
return withHead + withoutHead
}
}
let myArray = ["A", "B", "C", "D"]
print(myArray.powerset) // -> [["D", "C", "B", "A"], ["C", "B", "A"], ["D", "B", "A"], ["B", "A"], ["D", "C", "A"], ["C", "A"], ["D", "A"], ["A"], ["D", "C", "B"], ["C", "B"], ["D", "B"], ["B"], ["D", "C"], ["C"], ["D"], []]
// joining the subsets
let myResult = myArray.powerset.map { $0.sort().joinWithSeparator("") }
print(myResult) // -> ["A", "AB", "ABC", "ABCD", "ABD", "AC", "ACD", "AD", "B", "BC", "BCD", "BD", "C", "CD", "D", ""]
PS
Note that this solution uses a recursive approach, while yours was using an iterative approach.
PPS
If you don't want the empty string "" in your solution, you can just filter it away:
let myResult = myArray.powerset.map({ $0.sort().joinWithSeparator("") }).filter({ $0 != "" })
print(myResult) // -> ["A", "AB", "ABC", "ABCD", "ABD", "AC", "ACD", "AD", "B", "BC", "BCD", "BD", "C", "CD", "D"]
It looks like you want to have the Power set of your array.
In mathematics, the power set (or powerset) of any set S is the set of
all subsets of S, including the empty set and S itself.
I found this Code on GitHub.
extension Array {
var powerset: [[Element]] {
if count == 0 {
return [self]
}
else {
let tail = Array(self[1..<endIndex])
let head = self[0]
let withoutHead = tail.powerset
let withHead = withoutHead.map { $0 + [head] }
return withHead + withoutHead
}
}
}
println([1,2,3,4].powerset) -> [[4, 3, 2, 1], [3, 2, 1], [4, 2, 1], [2, 1], [4, 3, 1], [3, 1], [4, 1], [1], [4, 3, 2], [3, 2], [4, 2], [2], [4, 3], [3], [4], []]
I find a neater answer for it.Power set of Collection.
The principle is using induction on the size of a collection, as showed on that link.
Here is the copy of code from that link. And all credits to its author.
extension Collection {
public var powerSet: [[Element]] {
guard let fisrt = self.first else {return [[]]}
return self.dropFirst().powerSet.flatMap{[$0, [fisrt] + $0]}
}
}
let s: Set<Int> = [1,2,3]
s.powerSet //[[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
let a: Array<Int> = [1,2,3]
a.powerSet //[[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
I know some good answers have been given already, but coming from a Java background, I just wanted to drop some insights using bitwise operators (which surprisingly still work in Swift).
You can try this out:
let len = stringArr.count
for i in 0 ..< (1<<len){
print("{", terminator: "")
for j in 0 ..< len {
if ((i & (1<<j)) > 0) {
print(stringArr[j], terminator: "")
}
}
print("}")
}
You can find more information on bitwise operators here
I will take a shot also using this logic as reference:
extension RangeReplaceableCollection {
var subSets : [SubSequence] {
guard !isEmpty else { return [] }
let count = self.count
let n = 1 << count - 1
var subSequences: [SubSequence] = .init(repeating: SubSequence(), count: n-1)
(0 ..< n).forEach { x in
autoreleasepool {
var counter = 0
for element in self {
if x & 1 << counter > 0 {
subSequences[x-1].append(element)
}
counter += 1
}
}
}
return subSequences + [self[...]]
}
}
Playground Testing:
["A", "B", "C","D"].subSets // [["A"], ["B"], ["A", "B"], ["C"], ["A", "C"], ["B", "C"], ["A", "B", "C"], ["D"], ["A", "D"], ["B", "D"], ["A", "B", "D"], ["C", "D"], ["A", "C", "D"], ["B", "C", "D"], ["A", "B", "C", "D"]]
"ABCD".subSets // ["A", "B", "AB", "C", "AC", "BC", "ABC", "D", "AD", "BD", "ABD", "CD", "ACD", "BCD", "ABCD"]
I am wondering is there any build-in function I can sort a array according to another array. For example: sort testStringArray according to testIntArray
var testStringArray = ["a", "b", "c", "d", "e"]
var testIntArray = [21, 3, 43, 5, 1]
After the function, testStringArray will be
testIntArray.sort // [1, 3, 5, 21, 43]
testStringArray // ["e", "b", "d", "a", "c"]
var array1 = ["a", "b", "c", "d", "e"]
var array2 = [21, 3, 43, 5, 1]
let sorted = zip(array1, array2).sort { $0.1 < $1.1 }
array1 = sorted.map { $0.0 }
array2 = sorted.map { $0.1 }
print(array1) // ["e", "b", "d", "a", "c"]
print(array2) // [1, 3, 5, 21, 43]
Something like this? I feel like it can be done better...
EDIT:
This doesn't feel like it's much better...
zip(array1, array2).sort { $0.1 < $1.1 }.forEach {
array1.removeAtIndex(array1.indexOf($0.0)!)
array1.insert($0.0, atIndex: array1.count)
array2.removeAtIndex(array2.indexOf($0.1)!)
array2.insert($0.1, atIndex: array2.count)
}
print(array1) // ["e", "b", "d", "a", "c"]
print(array2) // [1, 3, 5, 21, 43]