-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #72 from kkebo/add-list-comparer
- Loading branch information
Showing
10 changed files
with
322 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import OrderedCollections | ||
|
||
private struct CaseInsensitiveString<S: StringProtocol> { | ||
var value: S | ||
} | ||
|
||
extension CaseInsensitiveString: Hashable {} | ||
|
||
extension CaseInsensitiveString: Equatable { | ||
static func == (lhs: Self, rhs: Self) -> Bool { | ||
lhs.value.caseInsensitiveCompare(rhs.value) == .orderedSame | ||
} | ||
} | ||
|
||
struct ListComparer { | ||
static func compare(a: String, b: String, caseSensitive: Bool, mode: ListComparisonMode) -> String { | ||
switch mode { | ||
case .intersection: Self.intersection(a: a, b: b, caseSensitive: caseSensitive) | ||
case .union: Self.union(a: a, b: b, caseSensitive: caseSensitive) | ||
case .aOnly: Self.difference(a: a, b: b, caseSensitive: caseSensitive) | ||
case .bOnly: Self.difference(a: b, b: a, caseSensitive: caseSensitive) | ||
} | ||
} | ||
|
||
private static func intersection(a: String, b: String, caseSensitive: Bool) -> String { | ||
if caseSensitive { | ||
OrderedSet(a.split(whereSeparator: \.isNewline)) | ||
.intersection(b.split(whereSeparator: \.isNewline)) | ||
.joined(separator: "\n") | ||
} else { | ||
OrderedSet(a.split(whereSeparator: \.isNewline).map(CaseInsensitiveString.init)) | ||
.intersection(b.split(whereSeparator: \.isNewline).map(CaseInsensitiveString.init)) | ||
.lazy | ||
.map(\.value) | ||
.joined(separator: "\n") | ||
} | ||
} | ||
|
||
private static func union(a: String, b: String, caseSensitive: Bool) -> String { | ||
if caseSensitive { | ||
OrderedSet(a.split(whereSeparator: \.isNewline)) | ||
.union(b.split(whereSeparator: \.isNewline)) | ||
.joined(separator: "\n") | ||
} else { | ||
OrderedSet(a.split(whereSeparator: \.isNewline).map(CaseInsensitiveString.init)) | ||
.union(b.split(whereSeparator: \.isNewline).map(CaseInsensitiveString.init)) | ||
.lazy | ||
.map(\.value) | ||
.joined(separator: "\n") | ||
} | ||
} | ||
|
||
private static func difference(a: String, b: String, caseSensitive: Bool) -> String { | ||
if caseSensitive { | ||
OrderedSet(a.split(whereSeparator: \.isNewline)) | ||
.subtracting(b.split(whereSeparator: \.isNewline)) | ||
.joined(separator: "\n") | ||
} else { | ||
OrderedSet(a.split(whereSeparator: \.isNewline).map(CaseInsensitiveString.init)) | ||
.subtracting(b.split(whereSeparator: \.isNewline).map(CaseInsensitiveString.init)) | ||
.lazy | ||
.map(\.value) | ||
.joined(separator: "\n") | ||
} | ||
} | ||
} | ||
|
||
#if TESTING_ENABLED | ||
import Foundation | ||
import PlaygroundTester | ||
|
||
@objcMembers | ||
final class ListComparerTests: TestCase { | ||
func testIntersectionCaseInsensitive() { | ||
func compare(a: String, b: String) -> String { | ||
ListComparer.compare(a: a, b: b, caseSensitive: false, mode: .intersection) | ||
} | ||
AssertEqual("", other: compare(a: "a\nB\nc", b: "d\nE\nf")) | ||
AssertEqual("c", other: compare(a: "a\nB\nc", b: "c\nE\nf")) | ||
AssertEqual("B", other: compare(a: "a\nB\nc", b: "b\nE\nf")) | ||
AssertEqual("", other: compare(a: "a\nB\nc\nb", b: "d\nE\nf")) | ||
AssertEqual("", other: compare(a: "a\nB\nc", b: "")) | ||
AssertEqual("", other: compare(a: "", b: "d\nE\nf")) | ||
} | ||
|
||
func testIntersectionCaseSensitive() { | ||
func compare(a: String, b: String) -> String { | ||
ListComparer.compare(a: a, b: b, caseSensitive: true, mode: .intersection) | ||
} | ||
AssertEqual("", other: compare(a: "a\nB\nc", b: "d\nE\nf")) | ||
AssertEqual("c", other: compare(a: "a\nB\nc", b: "c\nE\nf")) | ||
AssertEqual("", other: compare(a: "a\nB\nc", b: "b\nE\nf")) | ||
AssertEqual("", other: compare(a: "a\nB\nc\nb", b: "d\nE\nf")) | ||
AssertEqual("", other: compare(a: "a\nB\nc", b: "")) | ||
AssertEqual("", other: compare(a: "", b: "d\nE\nf")) | ||
} | ||
|
||
func testUnionCaseInsensitive() { | ||
func compare(a: String, b: String) -> String { | ||
ListComparer.compare(a: a, b: b, caseSensitive: false, mode: .union) | ||
} | ||
AssertEqual("a\nB\nc\nd\nE\nf", other: compare(a: "a\nB\nc", b: "d\nE\nf")) | ||
AssertEqual("a\nB\nc\nE\nf", other: compare(a: "a\nB\nc", b: "c\nE\nf")) | ||
AssertEqual("a\nB\nc\nE\nf", other: compare(a: "a\nB\nc", b: "b\nE\nf")) | ||
AssertEqual("a\nB\nc\nd\nE\nf", other: compare(a: "a\nB\nc\nb", b: "d\nE\nf")) | ||
AssertEqual("a\nB\nc", other: compare(a: "a\nB\nc", b: "")) | ||
AssertEqual("d\nE\nf", other: compare(a: "", b: "d\nE\nf")) | ||
} | ||
|
||
func testUnionCaseSensitive() { | ||
func compare(a: String, b: String) -> String { | ||
ListComparer.compare(a: a, b: b, caseSensitive: true, mode: .union) | ||
} | ||
AssertEqual("a\nB\nc\nd\nE\nf", other: compare(a: "a\nB\nc", b: "d\nE\nf")) | ||
AssertEqual("a\nB\nc\nE\nf", other: compare(a: "a\nB\nc", b: "c\nE\nf")) | ||
AssertEqual("a\nB\nc\nb\nE\nf", other: compare(a: "a\nB\nc", b: "b\nE\nf")) | ||
AssertEqual("a\nB\nc\nb\nd\nE\nf", other: compare(a: "a\nB\nc\nb", b: "d\nE\nf")) | ||
AssertEqual("a\nB\nc", other: compare(a: "a\nB\nc", b: "")) | ||
AssertEqual("d\nE\nf", other: compare(a: "", b: "d\nE\nf")) | ||
} | ||
|
||
func testAOnlyCaseInsensitive() { | ||
func compare(a: String, b: String) -> String { | ||
ListComparer.compare(a: a, b: b, caseSensitive: false, mode: .aOnly) | ||
} | ||
AssertEqual("a\nB\nc", other: compare(a: "a\nB\nc", b: "d\nE\nf")) | ||
AssertEqual("a\nB", other: compare(a: "a\nB\nc", b: "c\nE\nf")) | ||
AssertEqual("a\nc", other: compare(a: "a\nB\nc", b: "b\nE\nf")) | ||
AssertEqual("a\nB\nc", other: compare(a: "a\nB\nc\nb", b: "d\nE\nf")) | ||
AssertEqual("a\nB\nc", other: compare(a: "a\nB\nc", b: "")) | ||
AssertEqual("", other: compare(a: "", b: "d\nE\nf")) | ||
} | ||
|
||
func testAOnlyCaseSensitive() { | ||
func compare(a: String, b: String) -> String { | ||
ListComparer.compare(a: a, b: b, caseSensitive: true, mode: .aOnly) | ||
} | ||
AssertEqual("a\nB\nc", other: compare(a: "a\nB\nc", b: "d\nE\nf")) | ||
AssertEqual("a\nB", other: compare(a: "a\nB\nc", b: "c\nE\nf")) | ||
AssertEqual("a\nB\nc", other: compare(a: "a\nB\nc", b: "b\nE\nf")) | ||
AssertEqual("a\nB\nc\nb", other: compare(a: "a\nB\nc\nb", b: "d\nE\nf")) | ||
AssertEqual("a\nB\nc", other: compare(a: "a\nB\nc", b: "")) | ||
AssertEqual("", other: compare(a: "", b: "d\nE\nf")) | ||
} | ||
|
||
func testBOnlyCaseInsensitive() { | ||
func compare(a: String, b: String) -> String { | ||
ListComparer.compare(a: a, b: b, caseSensitive: false, mode: .bOnly) | ||
} | ||
AssertEqual("d\nE\nf", other: compare(a: "a\nB\nc", b: "d\nE\nf")) | ||
AssertEqual("E\nf", other: compare(a: "a\nB\nc", b: "c\nE\nf")) | ||
AssertEqual("E\nf", other: compare(a: "a\nB\nc", b: "b\nE\nf")) | ||
AssertEqual("d\nE\nf", other: compare(a: "a\nB\nc\nb", b: "d\nE\nf")) | ||
AssertEqual("", other: compare(a: "a\nB\nc", b: "")) | ||
AssertEqual("d\nE\nf", other: compare(a: "", b: "d\nE\nf")) | ||
} | ||
|
||
func testBOnlyCaseSensitive() { | ||
func compare(a: String, b: String) -> String { | ||
ListComparer.compare(a: a, b: b, caseSensitive: true, mode: .bOnly) | ||
} | ||
AssertEqual("d\nE\nf", other: compare(a: "a\nB\nc", b: "d\nE\nf")) | ||
AssertEqual("E\nf", other: compare(a: "a\nB\nc", b: "c\nE\nf")) | ||
AssertEqual("b\nE\nf", other: compare(a: "a\nB\nc", b: "b\nE\nf")) | ||
AssertEqual("d\nE\nf", other: compare(a: "a\nB\nc\nb", b: "d\nE\nf")) | ||
AssertEqual("", other: compare(a: "a\nB\nc", b: "")) | ||
AssertEqual("d\nE\nf", other: compare(a: "", b: "d\nE\nf")) | ||
} | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
enum ListComparisonMode { | ||
case intersection | ||
case union | ||
case aOnly | ||
case bOnly | ||
} | ||
|
||
extension ListComparisonMode: CaseIterable {} | ||
|
||
extension ListComparisonMode: CustomStringConvertible { | ||
var description: String { | ||
switch self { | ||
case .intersection: "A ∩ B" | ||
case .union: "A ∪ B" | ||
case .aOnly: "A Only" | ||
case .bOnly: "B Only" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import SwiftUI | ||
|
||
struct ListComparerView { | ||
@Environment(\.horizontalSizeClass) private var hSizeClass | ||
@Bindable var state: ListComparerViewState | ||
|
||
init(state: AppState) { | ||
self.state = state.listComparerViewState | ||
} | ||
} | ||
|
||
extension ListComparerView: View { | ||
var body: some View { | ||
ToyPage { | ||
ToySection("Configuration") { | ||
ConfigurationRow("Case sensitive comparison", systemImage: "textformat") { | ||
Toggle("", isOn: self.$state.isCaseSensitive).labelsHidden() | ||
} | ||
ConfigurationRow("Comparison mode", systemImage: "brain") { | ||
Picker("", selection: self.$state.comparisonMode) { | ||
ForEach(ListComparisonMode.allCases, id: \.self) { | ||
Text(LocalizedStringKey($0.description)) | ||
} | ||
} | ||
.labelsHidden() | ||
} | ||
} | ||
|
||
if self.hSizeClass == .compact { | ||
self.sectionA | ||
self.sectionB | ||
} else { | ||
HStack { | ||
self.sectionA | ||
Divider() | ||
self.sectionB | ||
} | ||
} | ||
|
||
ToySection(LocalizedStringKey(self.state.comparisonMode.description)) { | ||
CopyButton(text: self.state.output) | ||
} content: { | ||
CodeEditor(text: .constant(self.state.output)) | ||
} | ||
} | ||
.navigationTitle(Tool.listComparer.strings.localizedLongTitle) | ||
} | ||
|
||
private var sectionA: some View { | ||
ToySection("A") { | ||
PasteButton(text: self.$state.a) | ||
OpenFileButton(text: self.$state.a) | ||
ClearButton(text: self.$state.a) | ||
} content: { | ||
CodeEditor(text: self.$state.a) | ||
} | ||
} | ||
|
||
private var sectionB: some View { | ||
ToySection("B") { | ||
PasteButton(text: self.$state.b) | ||
OpenFileButton(text: self.$state.b) | ||
ClearButton(text: self.$state.b) | ||
} content: { | ||
CodeEditor(text: self.$state.b) | ||
} | ||
} | ||
} | ||
|
||
struct ListComparerView_Previews: PreviewProvider { | ||
static var previews: some View { | ||
NavigationStack { | ||
ListComparerView(state: .init()) | ||
} | ||
.previewPresets() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import Observation | ||
|
||
@Observable | ||
final class ListComparerViewState { | ||
var comparisonMode = ListComparisonMode.intersection { | ||
didSet { self.didUpdate() } | ||
} | ||
var isCaseSensitive = false { | ||
didSet { self.didUpdate() } | ||
} | ||
var a = "" { | ||
didSet { self.didUpdate() } | ||
} | ||
var b = "" { | ||
didSet { self.didUpdate() } | ||
} | ||
private(set) var output = "" | ||
|
||
private func didUpdate() { | ||
self.output = ListComparer.compare( | ||
a: self.a, | ||
b: self.b, | ||
caseSensitive: self.isCaseSensitive, | ||
mode: self.comparisonMode | ||
) | ||
} | ||
} |
Oops, something went wrong.