web-dev-qa-db-ja.com

Swiftでソートされた配列の正しい位置に要素を挿入するにはどうすればよいですか?

NSArrayには、ソートされた配列内の新しいオブジェクトの挿入位置を決定する- (NSUInteger)indexOfObject:(id)obj inSortedRange:(NSRange)r options:(NSBinarySearchingOptions)opts usingComparator:(NSComparator)cmpがあります。

純粋なSwiftでこれを行うための最良かつ高性能な方法は何ですか?

以下の線に沿った何か:

var myArray = ["b", "e", "d", "a"]
myArray.sort { $0 < $1 }

// myArray is now [a, b, d, e]

myArray.append("c")
myArray.sort { $0 < $1 }

// myArray is now [a, b, c, d, e]

新しい要素を追加して配列を並べ替えるのではなく、正しい位置を見つけて要素を挿入したいと思います。

let index = [... how to calculate this index ??? ...]
myArray.insert("c", atIndex: index)
20
Klaas

以下は、Swiftバイナリ検索を使用した実装の例です(- http://rosettacode.org/wiki/Binary_search#Swift からわずかに変更を加えて)):

extension Array {
    func insertionIndexOf(elem: Element, isOrderedBefore: (Element, Element) -> Bool) -> Int {
        var lo = 0
        var hi = self.count - 1
        while lo <= hi {
            let mid = (lo + hi)/2
            if isOrderedBefore(self[mid], elem) {
                lo = mid + 1
            } else if isOrderedBefore(elem, self[mid]) {
                hi = mid - 1
            } else {
                return mid // found at position mid
            }
        }
        return lo // not found, would be inserted at position lo
    }
}

indexOfObject:inSortedRange:options:usingComparator:と同様に、配列はコンパレーターに関してソートされていると想定されます。要素が既に配列に存在する場合は、要素の(任意の)インデックス、または順序を維持しながら挿入できるインデックスのいずれかを返します。これは、NSBinarySearchingInsertionIndexメソッドのNSArrayに対応します。

使用法:

let newElement = "c"
let index = myArray.insertionIndexOf(newElement) { $0 < $1 } // Or: myArray.indexOf(c, <)
myArray.insert(newElement, atIndex: index)
37
Martin R

Swift 3では、index(where:)を使用できます:

var myArray = ["a", "b", "d", "e"]
let newElement = "c"
if let index = myArray.index(where: { $0 > newElement }) {
    myArray.insert(newElement, at: index)
}

この場合、クロージャー内の条件を逆にする必要があることに注意してください(つまり、> の代わりに <は、配列内の要素を増やすため)。関心のあるインデックスは、述語と一致しない最初の要素であるためです。また、このメソッドは、新しく挿入された要素が配列の最後になる場合にnilを返します(newElement = "z"上記の例では。

便宜上、これはこれらすべての問題を処理する別の関数にラップできます。

extension Collection {
    func insertionIndex(of element: Self.Iterator.Element,
                        using areInIncreasingOrder: (Self.Iterator.Element, Self.Iterator.Element) -> Bool) -> Index {
        return index(where: { !areInIncreasingOrder($0, element) }) ?? endIndex
    }
}

使用法:

var myArray = ["a", "b", "d", "e"]
let newElement = "c"
let index = myArray.insertionIndex(of: newElement, using: <)
myArray.insert(newElement, at: index)
17
maxkonovalov

配列がソートされていることがわかっている場合は、このメソッドを使用できます。これは、任意のタイプの配列で機能します。毎回配列全体を走査するため、これを大きな配列で使用しないでください。より大きなニーズがある場合は、別のデータ型を使用してください。

func insertSorted<T: Comparable>(inout seq: [T], newItem item: T) {
    let index = seq.reduce(0) { $1 < item ? $0 + 1 : $0 }
    seq.insert(item, atIndex: index)
}

var arr = [2, 4, 6, 8]
insertSorted(&arr, newItem: 5)
insertSorted(&arr, newItem: 3)
insertSorted(&arr, newItem: -3)
insertSorted(&arr, newItem: 11)
// [-3, 2, 3, 4, 5, 6, 8, 11]
6
Nate Cook

WWDC 2018 Session 406:Swift Generics(Expanded) によると、バイナリ検索はslicingコレクションオブジェクト。

Slice には2つの大きなメリットがあります。

  1. スライスは常に、追加のメモリを割り当てることなく、元のオブジェクトのサブセットです。
  2. スライスのすべてのインデックスは、元のオブジェクトを参照します。
    たとえば、5つのオブジェクトの配列をスライスした場合let slice = array[2..<4]、次にslice.startIndex2ない0

RandomAccessCollection は、さまざまな構造体/クラスが準拠するプロトコル( BidirectionalCollection から継承)です

extension RandomAccessCollection where Element : Comparable {
    func insertionIndex(of value: Element) -> Index {
        var slice : SubSequence = self[...]

        while !slice.isEmpty {
            let middle = slice.index(slice.startIndex, offsetBy: slice.count / 2)
            if value < slice[middle] {
                slice = slice[..<middle]
            } else {
                slice = slice[index(after: middle)...]
            }
        }
        return slice.startIndex
    }
}

例:

let array = [1, 2, 4, 7, 8]
let index = array.insertionIndex(of: 6) // 3

関数を拡張して、単一の値の代わりに述語クロージャをチェックできます。

extension RandomAccessCollection { // the predicate version is not required to conform to Comparable
    func insertionIndex(for predicate: (Element) -> Bool) -> Index {
        var slice : SubSequence = self[...]

        while !slice.isEmpty {
            let middle = slice.index(slice.startIndex, offsetBy: slice.count / 2)
            if predicate(slice[middle]) {
                slice = slice[index(after: middle)...]
            } else {
                slice = slice[..<middle]
            }
        }
        return slice.startIndex
    }
}

例:

struct Person { let name : String }

let array = [Person(name: "Adam"), Person(name: "Cynthia"), Person(name: "Nancy"), Person(name: "Tom")]
let index = array.insertionIndex{ $0.name < "Bruce" } // 1
2
vadian

ここでの二分探索木は進むべき道です。

順序付けられた配列で、中央の要素を取り、その位置にあるオブジェクトが新しいオブジェクトより大きいかどうかを確認します。そうすれば、1回の比較で配列要素の半分を忘れることができます。

残りの半分でそのステップを繰り返します。ここでも、1回の比較で、残りのオブジェクトの半分を忘れることができます。ターゲット要素の数は、最初は2つの比較のみで、配列サイズの4分の1になりました。

新しい要素を挿入するための正しい位置が見つかるまで、これを繰り返します。

これが Swiftを使用したバイナリ検索ツリーに関する優れた記事 です。

2
zisoft