Skip to content

Commit f7e9cd8

Browse files
authored
Merge pull request #1888 from spevans/pr_sr_5829_42
2 parents 5105e59 + 9a182d8 commit f7e9cd8

File tree

2 files changed

+36
-39
lines changed

2 files changed

+36
-39
lines changed

Foundation/NSString.swift

Lines changed: 19 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -434,49 +434,29 @@ extension NSString {
434434
}
435435

436436
public func commonPrefix(with str: String, options mask: CompareOptions = []) -> String {
437-
var currentSubstring: CFMutableString?
438-
let isLiteral = mask.contains(.literal)
439-
var lastMatch = NSRange()
440-
let selfLen = length
441-
let otherLen = str.length
442-
var low = 0
443-
var high = selfLen
444-
var probe = (low + high) / 2
445-
if (probe > otherLen) {
446-
probe = otherLen // A little heuristic to avoid some extra work
447-
}
448-
if selfLen == 0 || otherLen == 0 {
437+
438+
let receiver = self as String
439+
if receiver.isEmpty || str.isEmpty {
449440
return ""
450441
}
451-
var numCharsBuffered = 0
452-
var arrayBuffer = [unichar](repeating: 0, count: 100)
453-
let other = str._nsObject
454-
return arrayBuffer.withUnsafeMutablePointerOrAllocation(selfLen, fastpath: UnsafeMutablePointer<unichar>(mutating: _fastContents)) { (selfChars: UnsafeMutablePointer<unichar>) -> String in
455-
// Now do the binary search. Note that the probe value determines the length of the substring to check.
456-
while true {
457-
let range = NSRange(location: 0, length: isLiteral ? probe + 1 : NSMaxRange(rangeOfComposedCharacterSequence(at: probe))) // Extend the end of the composed char sequence
458-
if range.length > numCharsBuffered { // Buffer more characters if needed
459-
getCharacters(selfChars, range: NSRange(location: numCharsBuffered, length: range.length - numCharsBuffered))
460-
numCharsBuffered = range.length
461-
}
462-
if currentSubstring == nil {
463-
currentSubstring = CFStringCreateMutableWithExternalCharactersNoCopy(kCFAllocatorSystemDefault, selfChars, range.length, range.length, kCFAllocatorNull)
464-
} else {
465-
CFStringSetExternalCharactersNoCopy(currentSubstring, selfChars, range.length, range.length)
466-
}
467-
if other.range(of: currentSubstring!._swiftObject, options: mask.union(.anchored), range: NSRange(location: 0, length: otherLen)).length != 0 { // Match
468-
lastMatch = range
469-
low = probe + 1
470-
} else {
471-
high = probe
472-
}
473-
if low >= high {
474-
break
475-
}
476-
probe = (low + high) / 2
442+
let literal = mask.contains(.literal)
443+
let caseInsensitive = mask.contains(.caseInsensitive)
444+
445+
var result = ""
446+
var otherIterator = str.makeIterator()
447+
448+
for ch in receiver {
449+
guard let otherCh = otherIterator.next() else { break }
450+
451+
if ch != otherCh {
452+
guard caseInsensitive && String(ch).lowercased() == String(otherCh).lowercased() else { break }
477453
}
478-
return lastMatch.length != 0 ? substring(with: lastMatch) : ""
454+
455+
if literal && otherCh.unicodeScalars.count != ch.unicodeScalars.count { break }
456+
result.append(ch)
479457
}
458+
459+
return result
480460
}
481461

482462
public func contains(_ str: String) -> Bool {

TestFoundation/TestNSString.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ class TestNSString: LoopbackServerTest {
9191
("test_getLineStart", test_getLineStart),
9292
("test_substringWithRange", test_substringWithRange),
9393
("test_createCopy", test_createCopy),
94+
("test_commonPrefix", test_commonPrefix)
9495
]
9596
}
9697

@@ -1208,6 +1209,22 @@ class TestNSString: LoopbackServerTest {
12081209
XCTAssertEqual(string, "foobar")
12091210
XCTAssertEqual(stringCopy, "foo")
12101211
}
1212+
1213+
func test_commonPrefix() {
1214+
XCTAssertEqual("".commonPrefix(with: ""), "")
1215+
XCTAssertEqual("1234567890".commonPrefix(with: ""), "")
1216+
XCTAssertEqual("".commonPrefix(with: "1234567890"), "")
1217+
XCTAssertEqual("abcba".commonPrefix(with: "abcde"), "abc")
1218+
XCTAssertEqual("/path/to/file1".commonPrefix(with: "/path/to/file2"), "/path/to/file")
1219+
XCTAssertEqual("/a_really_long_path/to/a/file".commonPrefix(with: "/a_really_long_path/to/the/file"), "/a_really_long_path/to/")
1220+
XCTAssertEqual("this".commonPrefix(with: "THAT", options: [.caseInsensitive]), "th")
1221+
1222+
// Both forms of ä, a\u{308} decomposed and \u{E4} precomposed, should match without .literal and not match when .literal is used
1223+
XCTAssertEqual("Ma\u{308}dchen".commonPrefix(with: "M\u{E4}dchenschule"), "Ma\u{308}dchen")
1224+
XCTAssertEqual("Ma\u{308}dchen".commonPrefix(with: "M\u{E4}dchenschule", options: [.literal]), "M")
1225+
XCTAssertEqual("m\u{E4}dchen".commonPrefix(with: "M\u{E4}dchenschule", options: [.caseInsensitive, .literal]), "mädchen")
1226+
XCTAssertEqual("ma\u{308}dchen".commonPrefix(with: "M\u{E4}dchenschule", options: [.caseInsensitive, .literal]), "m")
1227+
}
12111228
}
12121229

12131230
func test_reflection() {

0 commit comments

Comments
 (0)