vChewing-macOS/Packages/vChewing_SwiftExtension/Sources/SwiftExtension/SwiftExtension.swift

287 lines
7.9 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
// MARK: - Bool Operators
public func |= (lhs: inout Bool, rhs: Bool) {
lhs = lhs || rhs
}
public func &= (lhs: inout Bool, rhs: Bool) {
lhs = lhs && rhs
}
public func ^= (lhs: inout Bool, rhs: Bool) {
lhs = lhs != rhs
}
// MARK: - Root Extensions (deduplicated)
// Extend the RangeReplaceableCollection to allow it clean duplicated characters.
// Ref: https://stackoverflow.com/questions/25738817/
public extension RangeReplaceableCollection where Element: Hashable {
///
/// - Remark: class class Identifiable
var deduplicated: Self {
var set = Set<Element>()
return filter { set.insert($0).inserted }
}
}
// MARK: - Ensuring trailing slash of a string
public extension String {
mutating func ensureTrailingSlash() {
if !hasSuffix("/") {
self += "/"
}
}
}
// MARK: - CharCode printability check
public extension Unicode.Scalar {
var isPrintableASCII: Bool {
(32 ... 126).contains(value)
}
}
// MARK: - Stable Sort Extension
// Ref: https://stackoverflow.com/a/50545761/4162914
public extension Sequence {
/// Return a stable-sorted collection.
///
/// - Parameter areInIncreasingOrder: Return nil when two element are equal.
/// - Returns: The sorted collection.
func stableSort(
by areInIncreasingOrder: (Element, Element) throws -> Bool
)
rethrows -> [Element]
{
try enumerated()
.sorted { a, b -> Bool in
try areInIncreasingOrder(a.element, b.element)
|| (a.offset < b.offset && !areInIncreasingOrder(b.element, a.element))
}
.map(\.element)
}
}
// MARK: - Return toggled value.
public extension Bool {
mutating func toggled() -> Bool {
toggle()
return self
}
static func from(integer: Int) -> Bool {
integer > 0 ? true : false
}
}
// MARK: -
// Ref: https://stackoverflow.com/a/32581409/4162914
public extension Double {
func rounded(toPlaces places: Int) -> Double {
let divisor = 10.0.mathPowered(by: places)
return (self * divisor).rounded() / divisor
}
}
public extension Double {
func mathPowered(by operand: Int) -> Double {
var target = self
for _ in 0 ..< operand {
target = target * target
}
return target
}
}
// MARK: - String CharName and CodePoint Extension
public extension String {
var charDescriptions: [String] {
flatMap(\.unicodeScalars).compactMap {
let theName: String = $0.properties.name ?? ""
return String(format: "U+%02X %@", $0.value, theName)
}
}
var codePoints: [String] {
map(\.codePoint)
}
var describedAsCodePoints: [String] {
map {
"\($0) (\($0.codePoint))"
}
}
}
// MARK: - Character Codepoint
public extension Character {
var codePoint: String {
guard let value = unicodeScalars.first?.value else { return "U+NULL" }
return String(format: "U+%02X", value)
}
}
// MARK: - String Ellipsis Extension
public extension String {
var withEllipsis: String { self + "" }
}
// MARK: - Index Revolver (only for Array)
// Further discussion: https://forums.swift.org/t/62847
public extension Array {
func revolvedIndex(_ id: Int, clockwise: Bool = true, steps: Int = 1) -> Int {
if id < 0 || steps < 1 { return id }
var result = id
func revolvedIndexByOneStep(_ id: Int, clockwise: Bool = true) -> Int {
let newID = clockwise ? id + 1 : id - 1
if (0 ..< count).contains(newID) { return newID }
return clockwise ? 0 : count - 1
}
for _ in 0 ..< steps {
result = revolvedIndexByOneStep(result, clockwise: clockwise)
}
return result
}
}
public extension Int {
mutating func revolveAsIndex(with target: [Any], clockwise: Bool = true, steps: Int = 1) {
if self < 0 || steps < 1 { return }
self = target.revolvedIndex(self, clockwise: clockwise, steps: steps)
}
}
// MARK: - Overlap Checker (for two sets)
public extension Set where Element: Hashable {
func isOverlapped(with target: Set<Element>) -> Bool {
guard !target.isEmpty, !isEmpty else { return false }
var container: (Set<Element>, Set<Element>)
if target.count <= count {
container = (target, self)
} else {
container = (self, target)
}
for neta in container.0 {
guard !container.1.contains(neta) else { return true }
}
return false
}
func isOverlapped(with target: [Element]) -> Bool {
isOverlapped(with: Set(target))
}
}
public extension Array where Element: Hashable {
func isOverlapped(with target: [Element]) -> Bool {
Set(self).isOverlapped(with: Set(target))
}
func isOverlapped(with target: Set<Element>) -> Bool {
Set(self).isOverlapped(with: target)
}
}
// MARK: - Array Builder.
@resultBuilder
public enum ArrayBuilder<OutputModel> {
public static func buildEither(first component: [OutputModel]) -> [OutputModel] {
component
}
public static func buildEither(second component: [OutputModel]) -> [OutputModel] {
component
}
public static func buildOptional(_ component: [OutputModel]?) -> [OutputModel] {
component ?? []
}
public static func buildExpression(_ expression: OutputModel) -> [OutputModel] {
[expression]
}
public static func buildExpression(_: ()) -> [OutputModel] {
[]
}
public static func buildBlock(_ components: [OutputModel]...) -> [OutputModel] {
components.flatMap { $0 }
}
public static func buildArray(_ components: [[OutputModel]]) -> [OutputModel] {
Array(components.joined())
}
}
// MARK: - Extending Comparable to let it able to find its neighbor values in any collection.
public extension Comparable {
func findNeighborValue(from givenSeq: any Collection<Self>, greater isGreater: Bool) -> Self? {
let givenArray: [Self] = isGreater ? Array(givenSeq.sorted()) : Array(givenSeq.sorted().reversed())
let givenMap: [Int: Self] = .init(uniqueKeysWithValues: Array(givenArray.enumerated()))
var (startID, endID, returnableID) = (0, givenArray.count - 1, -1)
func internalCompare(_ lhs: Self, _ rhs: Self) -> Bool { isGreater ? lhs <= rhs : lhs >= rhs }
while let startObj = givenMap[startID], let endObj = givenMap[endID], internalCompare(startObj, endObj) {
let midID = (startID + endID) / 2
if let midObj = givenMap[midID], internalCompare(midObj, self) {
startID = midID + 1
} else {
returnableID = midID
endID = midID - 1
}
}
return givenMap[returnableID]
}
}
// MARK: - String.applyingTransform
public extension String {
/// This only works with ASCII chars for now.
func applyingTransformFW2HW(reverse: Bool) -> String {
var arr: [Character] = map { $0 }
for i in 0 ..< arr.count {
let oldChar = arr[i]
guard oldChar.unicodeScalars.count == 1 else { continue }
guard let oldCodePoint = oldChar.unicodeScalars.first?.value else { continue }
if reverse {
guard oldChar.isASCII else { continue }
} else {
guard oldCodePoint > 0xFEE0 || oldCodePoint == 0x3000 else { continue }
}
var newCodePoint: Int32 = reverse ? (Int32(oldCodePoint) + 0xFEE0) : (Int32(oldCodePoint) - 0xFEE0)
checkSpace: switch (oldCodePoint, reverse) {
case (0x3000, false): newCodePoint = 0x20
case (0x20, true): newCodePoint = 0x3000
default: break checkSpace
}
guard newCodePoint > 0 else { continue }
guard let newScalar = Unicode.Scalar(UInt16(newCodePoint)) else { continue }
let newChar = Character(newScalar)
arr[i] = newChar
}
return String(arr)
}
}