Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06577a2c4c | ||
| b299034a4e | |||
|
|
24f7840b9b | ||
|
|
2217c71ad8 | ||
| 12c67788c0 | |||
| 165376757d | |||
| 706806433f |
@ -25,7 +25,7 @@ jobs:
|
||||
echo "<script>window.location.href += \"/documentation/levenshteintransformations\"</script><p>Please enable JavaScript to view the documentation <a href='/documentation/levenshteintransformations'>here</a>.</p>" > docs/index.html;
|
||||
sed -i '' 's/,2px/,10px/g' docs/css/index.*.css
|
||||
- name: Upload
|
||||
uses: wangyucode/sftp-upload-action@v2.0.2
|
||||
uses: wangyucode/sftp-upload-action@v2.0.4
|
||||
with:
|
||||
host: 'volans.uberspace.de'
|
||||
username: 'akforum'
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,3 +11,4 @@ DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
||||
/.vscode
|
||||
|
||||
16
CMakeLists.txt
Normal file
16
CMakeLists.txt
Normal file
@ -0,0 +1,16 @@
|
||||
cmake_minimum_required(VERSION 3.29)
|
||||
cmake_policy(SET CMP0157 NEW)
|
||||
|
||||
project(LevenshteinTransformations
|
||||
LANGUAGES Swift)
|
||||
|
||||
set(CMAKE_Swift_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/swift)
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
|
||||
add_subdirectory(Sources)
|
||||
|
||||
if(CMAKE_PROJECT_NAME STREQUAL "LevenshteinTransformations")
|
||||
add_subdirectory(Tests)
|
||||
endif()
|
||||
@ -23,12 +23,18 @@ let package = Package(
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "LevenshteinTransformations"
|
||||
name: "LevenshteinTransformations",
|
||||
exclude: [
|
||||
"CMakeLists.txt"
|
||||
]
|
||||
),
|
||||
.executableTarget(
|
||||
name: "LevenshteinTransformationsTests",
|
||||
dependencies: ["LevenshteinTransformations"],
|
||||
path: "Tests"
|
||||
path: "Tests",
|
||||
exclude: [
|
||||
"CMakeLists.txt"
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
1
Sources/CMakeLists.txt
Normal file
1
Sources/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
add_subdirectory(LevenshteinTransformations)
|
||||
@ -30,4 +30,20 @@ public struct AsyncFunctions<Element> {
|
||||
self.insert = insert
|
||||
}
|
||||
|
||||
/// Initialize a functions value.
|
||||
/// - Parameters:
|
||||
/// - delete: Delete the element at a certain index.
|
||||
/// - insert: Insert a certain element at a certain index.
|
||||
public init(
|
||||
delete: @escaping (Int) async -> Void,
|
||||
insert: @escaping (Int, Element) async -> Void
|
||||
) {
|
||||
self.replace = { index, element in
|
||||
await delete(index)
|
||||
await insert(index, element)
|
||||
}
|
||||
self.delete = delete
|
||||
self.insert = insert
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
15
Sources/LevenshteinTransformations/CMakeLists.txt
Normal file
15
Sources/LevenshteinTransformations/CMakeLists.txt
Normal file
@ -0,0 +1,15 @@
|
||||
add_library(LevenshteinTransformations
|
||||
Extensions/Array.swift
|
||||
Extensions/String.swift
|
||||
AsyncFunctions.swift
|
||||
EditOperation.swift
|
||||
Functions.swift
|
||||
LevenshteinTransformations.swift
|
||||
Transformation.swift
|
||||
)
|
||||
|
||||
install(TARGETS LevenshteinTransformations
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
RUNTIME DESTINATION bin
|
||||
)
|
||||
@ -11,14 +11,14 @@ extension Array where Element: Equatable {
|
||||
/// - Parameter target: The target array.
|
||||
/// - Returns: The Levenshtein distance.
|
||||
public func levenshteinDistance(to target: [Element]) -> Int {
|
||||
getTransformations(to: target).count
|
||||
levenshteinDistance(to: target, id: \.self)
|
||||
}
|
||||
|
||||
/// Get the transformations needed to transform the array into the target array.
|
||||
/// - Parameter target: The target array.
|
||||
/// - Returns: The transformations.
|
||||
public func getTransformations(to target: [Element]) -> [Transformation<Element>] {
|
||||
LevenshteinTransformations.levenshteinTransformations(from: self, to: target)
|
||||
getTransformations(to: target, id: \.self)
|
||||
}
|
||||
|
||||
/// Call every transformation step needed to transform the array into the target array.
|
||||
@ -26,12 +26,7 @@ extension Array where Element: Equatable {
|
||||
/// - target: The target array.
|
||||
/// - functions: The transformation functions.
|
||||
public func transform(to target: [Element], functions: Functions<Element>) {
|
||||
var transformations = getTransformations(to: target)
|
||||
|
||||
while !transformations.isEmpty {
|
||||
let transformation = transformations.removeFirst()
|
||||
transformation.transform(functions: functions, nextTransformations: &transformations)
|
||||
}
|
||||
transform(to: target, id: \.self, functions: functions)
|
||||
}
|
||||
|
||||
/// Call every transformation step needed to transform the array into the target array.
|
||||
@ -39,12 +34,7 @@ extension Array where Element: Equatable {
|
||||
/// - target: The target array.
|
||||
/// - functions: The transformation functions.
|
||||
public func transform(to target: [Element], functions: AsyncFunctions<Element>) async {
|
||||
var transformations = getTransformations(to: target)
|
||||
|
||||
while !transformations.isEmpty {
|
||||
let transformation = transformations.removeFirst()
|
||||
await transformation.transform(functions: functions, nextTransformations: &transformations)
|
||||
}
|
||||
await transform(to: target, id: \.self, functions: functions)
|
||||
}
|
||||
|
||||
}
|
||||
@ -70,7 +60,71 @@ extension Array where Element: Identifiable {
|
||||
/// - target: The target array.
|
||||
/// - functions: The transformation functions.
|
||||
public func identifiableTransform(to target: [Element], functions: Functions<Element>) {
|
||||
var transformations = identifiableGetTransformations(to: target)
|
||||
transform(to: target, id: \.id, functions: functions)
|
||||
}
|
||||
|
||||
/// Call every transformation step needed to transform the array into the target array.
|
||||
/// - Parameters:
|
||||
/// - target: The target array.
|
||||
/// - functions: The transformation functions.
|
||||
public func identifiableTransform(to target: [Element], functions: AsyncFunctions<Element>) async {
|
||||
await transform(to: target, id: \.id, functions: functions)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Array {
|
||||
|
||||
/// Calculate the Levenshtein distance to another array.
|
||||
/// - Parameters:
|
||||
/// - target: The target array.
|
||||
/// - id: The identifier.
|
||||
/// - Returns: The Levenshtein distance.
|
||||
public func levenshteinDistance<Identifier>(
|
||||
to target: [Element],
|
||||
id: KeyPath<Element, Identifier>
|
||||
) -> Int where Identifier: Equatable {
|
||||
getRawTransformations(to: target, id: id).count
|
||||
}
|
||||
|
||||
/// Get the transformations needed to transform the array into the target array with the raw identifier data.
|
||||
/// - Parameters:
|
||||
/// - target: The target array.
|
||||
/// - id: The identifier.
|
||||
/// - Returns: The transformations applied to the identifier.
|
||||
public func getRawTransformations<Identifier>(
|
||||
to target: [Element],
|
||||
id: KeyPath<Element, Identifier>
|
||||
) -> [Transformation<Identifier>] where Identifier: Equatable {
|
||||
let equatableSource = map { $0[keyPath: id] }
|
||||
let equatableTarget = target.map { $0[keyPath: id] }
|
||||
return LevenshteinTransformations.levenshteinTransformations(from: equatableSource, to: equatableTarget)
|
||||
}
|
||||
|
||||
/// Get the transformations needed to transform the array into the target array.
|
||||
/// - Parameters:
|
||||
/// - target: The target array.
|
||||
/// - id: The identifier.
|
||||
/// - Returns: The transformations.
|
||||
public func getTransformations<Identifier>(
|
||||
to target: [Element],
|
||||
id: KeyPath<Element, Identifier>
|
||||
) -> [Transformation<Element>] where Identifier: Equatable {
|
||||
getRawTransformations(to: target, id: id)
|
||||
.compactMap { $0.convert { targetID in target.first { $0[keyPath: id] == targetID } } }
|
||||
}
|
||||
|
||||
/// Call every transformation step needed to transform the array into the target array.
|
||||
/// - Parameters:
|
||||
/// - target: The target array.
|
||||
/// - id: The identifier key path.
|
||||
/// - functions: The transformation functions.
|
||||
public func transform<Identifier>(
|
||||
to target: [Element],
|
||||
id: KeyPath<Element, Identifier>,
|
||||
functions: Functions<Element>
|
||||
) where Identifier: Equatable {
|
||||
var transformations = getTransformations(to: target, id: id)
|
||||
|
||||
while !transformations.isEmpty {
|
||||
let transformation = transformations.removeFirst()
|
||||
@ -81,9 +135,14 @@ extension Array where Element: Identifiable {
|
||||
/// Call every transformation step needed to transform the array into the target array.
|
||||
/// - Parameters:
|
||||
/// - target: The target array.
|
||||
/// - id: The identifier key path.
|
||||
/// - functions: The transformation functions.
|
||||
public func identifiableTransform(to target: [Element], functions: AsyncFunctions<Element>) async {
|
||||
var transformations = identifiableGetTransformations(to: target)
|
||||
public func transform<Identifier>(
|
||||
to target: [Element],
|
||||
id: KeyPath<Element, Identifier>,
|
||||
functions: AsyncFunctions<Element>
|
||||
) async where Identifier: Equatable {
|
||||
var transformations = getTransformations(to: target, id: id)
|
||||
|
||||
while !transformations.isEmpty {
|
||||
let transformation = transformations.removeFirst()
|
||||
|
||||
@ -30,4 +30,20 @@ public struct Functions<Element> {
|
||||
self.insert = insert
|
||||
}
|
||||
|
||||
/// Initialize a functions value.
|
||||
/// - Parameters:
|
||||
/// - delete: Delete the element at a certain index.
|
||||
/// - insert: Insert a certain element at a certain index.
|
||||
public init(
|
||||
delete: @escaping (Int) -> Void,
|
||||
insert: @escaping (Int, Element) -> Void
|
||||
) {
|
||||
self.replace = { index, element in
|
||||
delete(index)
|
||||
insert(index, element)
|
||||
}
|
||||
self.delete = delete
|
||||
self.insert = insert
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -20,17 +20,17 @@ let target = [0, 1, 5, 6, 10]
|
||||
|
||||
There are three functions available for arrays containing elements conforming to `Equatable` and strings.
|
||||
|
||||
### Arrays with Equatable Elements and Strings
|
||||
### Arrays and Strings
|
||||
|
||||
#### Levenshtein Distance
|
||||
Get the Levenshtein distance with ``Swift/Array/levenshteinDistance(to:)`` (for strings: ``Swift/String/levenshteinDistance(to:)``).
|
||||
Get the Levenshtein distance with ``Swift/Array/levenshteinDistance(to:id:)`` (for strings: ``Swift/String/levenshteinDistance(to:)``).
|
||||
```swift
|
||||
print(source.levenshteinDistance(to: target))
|
||||
```
|
||||
This example outputs `3`.
|
||||
|
||||
#### Transformation Steps
|
||||
Get the individual transformation steps with ``Swift/Array/getTransformations(to:)`` (for strings: ``Swift/String/getTransformations(to:)``).
|
||||
Get the individual transformation steps with ``Swift/Array/getTransformations(to:id:)`` (for strings: ``Swift/String/getTransformations(to:)``).
|
||||
```swift
|
||||
print(source.getTransformations(to: target).map { $0.description(source: source) }.joined(separator: "\n"))
|
||||
```
|
||||
@ -42,7 +42,7 @@ Insert '10' at position 4
|
||||
```
|
||||
|
||||
#### Run Functions for the Steps
|
||||
Directly run functions for the `replace`, `delete` and `insert` transformations using the ``Swift/Array/transform(to:functions:)-2wzm8`` (for strings: ``Swift/String/transform(to:functions:)-3b6nc``) function.
|
||||
Directly run functions for the `replace`, `delete` and `insert` transformations using the ``Swift/Array/transform(to:id:functions:)-5sa4j`` (for strings: ``Swift/String/transform(to:id:functions:)-3b6nc``) function.
|
||||
```swift
|
||||
var modified = source
|
||||
|
||||
@ -61,7 +61,7 @@ print(modified == target)
|
||||
```
|
||||
This example outputs `true`.
|
||||
|
||||
The transform functions are available in an asynchronous version: ``Swift/Array/transform(to:functions:)-756uw`` for arrays and ``Swift/String/transform(to:functions:)-46is`` for strings.
|
||||
The transform functions are available in an asynchronous version: ``Swift/Array/transform(to:id:functions:)-1gw5x`` for arrays and ``Swift/String/transform(to:functions:)-46is`` for strings.
|
||||
|
||||
### Arrays with Identifiable Elements
|
||||
|
||||
|
||||
@ -61,6 +61,29 @@ public enum Transformation<Element> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the replace transformation for an element, or nil.
|
||||
/// - Parameters:
|
||||
/// - index: The index to replace.
|
||||
/// - element: The new element.
|
||||
public static func replace(at index: Int, with element: Element?) -> Self? {
|
||||
guard let element else {
|
||||
return nil
|
||||
}
|
||||
return .replace(at: index, with: element)
|
||||
}
|
||||
|
||||
/// Get the insert transformation for an element, or nil.
|
||||
/// - Parameters:
|
||||
/// - index: The index to insert at.
|
||||
/// - element. The new element.
|
||||
/// - Returns: The transformation.
|
||||
public static func insert(at index: Int, element: Element?) -> Self? {
|
||||
guard let element else {
|
||||
return nil
|
||||
}
|
||||
return .insert(at: index, element: element)
|
||||
}
|
||||
|
||||
/// A description of the transformation.
|
||||
/// - Parameter source: The initial array.
|
||||
/// - Returns: The description.
|
||||
@ -126,6 +149,21 @@ public enum Transformation<Element> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a series of transformations in order to be able
|
||||
/// to apply the same transformation on different Swift types.
|
||||
/// - Parameter convert: The conversion function.
|
||||
/// - Returns: The transformations.
|
||||
public func convert<NewType>(convert: (Element) -> NewType?) -> Transformation<NewType>? {
|
||||
switch self {
|
||||
case let .replace(index, element):
|
||||
return .replace(at: index, with: convert(element))
|
||||
case let .delete(index):
|
||||
return .delete(at: index)
|
||||
case let .insert(index, element):
|
||||
return .insert(at: index, element: convert(element))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// swiftlint:enable identifier_name
|
||||
|
||||
11
Tests/CMakeLists.txt
Normal file
11
Tests/CMakeLists.txt
Normal file
@ -0,0 +1,11 @@
|
||||
add_executable(Tests
|
||||
LevenshteinTransformationsTests.swift
|
||||
)
|
||||
|
||||
target_compile_options(Tests PUBLIC
|
||||
-parse-as-library
|
||||
)
|
||||
|
||||
target_link_libraries(Tests PRIVATE
|
||||
LevenshteinTransformations
|
||||
)
|
||||
@ -21,6 +21,30 @@ enum LevenshteinTransformationsTests {
|
||||
|
||||
}
|
||||
|
||||
/// Test the transform function with a custom path.
|
||||
static func testCustom() {
|
||||
let source = [1, 2, 5, 6].map { TestType(id: $0) }
|
||||
let target = [0, 1, 5, 6, 10].map { TestType(id: $0) }
|
||||
|
||||
var modified = source
|
||||
|
||||
source.transform(
|
||||
to: target,
|
||||
id: \.id,
|
||||
functions: .init { index, element in
|
||||
modified[index] = element
|
||||
} delete: { index in
|
||||
modified.remove(at: index)
|
||||
} insert: { index, element in
|
||||
modified.insert(element, at: index)
|
||||
}
|
||||
)
|
||||
|
||||
print(source.getRawTransformations(to: target, id: \.id))
|
||||
|
||||
print(modified == target)
|
||||
}
|
||||
|
||||
/// Test the `transform` function on an equatable array.
|
||||
static func testEquatable() {
|
||||
let source = [1, 2, 5, 6]
|
||||
@ -86,6 +110,7 @@ enum LevenshteinTransformationsTests {
|
||||
|
||||
/// Run the tests.
|
||||
static func main() {
|
||||
testCustom()
|
||||
testEquatable()
|
||||
testIdentifiable()
|
||||
testString()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user