diff --git a/README.md b/README.md index 9553852..e43b275 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,12 @@ An implementation of KeyedTree To have an overview of the features this datastructure provide, have a look at the following code snippet (extracted from a unit test): ```st -firstLevelOneSubTree := KeyedTree new. +firstLevelOneSubTree := CTKeyedTree new. firstLevelOneSubTree at: #two put: 'One-Two'; at: #three put: 'One-Three'. -tree := KeyedTree new. +tree := CTKeyedTree new. tree at: 1 put: firstLevelOneSubTree; at: 2 put: 'Two'. diff --git a/src/BaselineOfContainersKeyedTree/BaselineOfContainersKeyedTree.class.st b/src/BaselineOfContainersKeyedTree/BaselineOfContainersKeyedTree.class.st index 05ac212..0d7bf47 100644 --- a/src/BaselineOfContainersKeyedTree/BaselineOfContainersKeyedTree.class.st +++ b/src/BaselineOfContainersKeyedTree/BaselineOfContainersKeyedTree.class.st @@ -7,9 +7,8 @@ Class { { #category : #baselines } BaselineOfContainersKeyedTree >> baseline: spec [ - spec for: #pharo do: [ spec package: 'Containers-KeyedTree'. spec package: 'Containers-KeyedTree-Tests' with: [ spec requires: #('Containers-KeyedTree') ] ] -] +] \ No newline at end of file diff --git a/src/Containers-KeyedTree-Tests/CTKeyedTreeTest.class.st b/src/Containers-KeyedTree-Tests/CTKeyedTreeTest.class.st new file mode 100644 index 0000000..ff7451c --- /dev/null +++ b/src/Containers-KeyedTree-Tests/CTKeyedTreeTest.class.st @@ -0,0 +1,281 @@ +" +SUnit tests for CTKeyedTree +" +Class { + #name : #CTKeyedTreeTest, + #superclass : #TestCase, + #instVars : [ + 'tree', + 'firstLevelOneSubTree' + ], + #category : 'Containers-KeyedTree-Tests' +} + +{ #category : #running } +CTKeyedTreeTest >> setUp [ + super setUp. + firstLevelOneSubTree := CTKeyedTree new + at: #two put: 'One-Two'; + at: #three put: 'One-Three'; + yourself. + tree := CTKeyedTree new + at: 1 put: firstLevelOneSubTree; + at: 2 put: 'Two'; + yourself +] + +{ #category : #'tests - operation' } +CTKeyedTreeTest >> t13 [ + ^ CTKeyedTree new + at: 1 put: '1-3-1'; + at: 2 put: '1-3-2'; + yourself +] + +{ #category : #'tests - operation' } +CTKeyedTreeTest >> t2 [ + ^ CTKeyedTree new + at: 1 put: '1-1'; + at: 2 put: '1-2'; + at: 3 put: (self t13); + yourself +] + +{ #category : #'tests - operation' } +CTKeyedTreeTest >> t2AB [ + ^ CTKeyedTree new + at: 1 put: '1-1'; + at: 2 put: '1-2'; + at: 3 put: (self tAB); + yourself +] + +{ #category : #'tests - operation' } +CTKeyedTreeTest >> tAB [ + ^ CTKeyedTree new + at: #A put: '1-3-1'; + at: #B put: '1-3-2'; + yourself +] + +{ #category : #'tests - operation' } +CTKeyedTreeTest >> testAllKeys [ + self assert: self t13 allKeys asArray equals: #(1 2). + self assert: self t2AB allKeys asArray equals: #(1 2 3 #A #B). +] + +{ #category : #'tests - operation' } +CTKeyedTreeTest >> testAllKeysEmptyTree [ + | emptyTree | + emptyTree := CTKeyedTree new. + self assert: emptyTree allKeys isEmpty. +] + +{ #category : #'tests - at' } +CTKeyedTreeTest >> testAtPath [ + self assert: (tree atPath: #(1)) equals: firstLevelOneSubTree. + self assert: (tree atPath: #(1 two)) equals: 'One-Two'. + self assert: (tree atPath: #(1 three)) equals: 'One-Three'. + self assert: (tree atPath: #(2)) equals: 'Two'. + self should: [ tree atPath: #(2 4) ] raise: self defaultTestError. + self should: [ tree atPath: #(1 two three) ] raise: self defaultTestError. + self should: [ tree atPath: #(3) ] raise: self defaultTestError. +] + +{ #category : #'tests - at' } +CTKeyedTreeTest >> testAtPathEmpty [ + | emptyTree | + emptyTree := CTKeyedTree new. + self should: [ emptyTree atPath: #(1) ] raise: self defaultTestError. +] + +{ #category : #tests } +CTKeyedTreeTest >> testAtPathIfAbsent [ + "Test accessing elements using a path with an ifAbsent: block." + + self assert: (tree atPath: #(1) ifAbsent: [ #missing ]) equals: firstLevelOneSubTree. + self assert: (tree atPath: #(1 two) ifAbsent: [ #missing ]) equals: 'One-Two'. + self assert: (tree atPath: #(1 three) ifAbsent: [ #missing ]) equals: 'One-Three'. + self assert: (tree atPath: #(2) ifAbsent: [ #missing ]) equals: 'Two'. + self assert: (tree atPath: #(2 4) ifAbsent: [ #missing ]) equals: #missing. + self assert: (tree atPath: #(1 two three) ifAbsent: [ #missing ]) equals: #missing. + self assert: (tree atPath: #(3) ifAbsent: [ #missing ]) equals: #missing +] + +{ #category : #'tests - at' } +CTKeyedTreeTest >> testAtPathIfAbsentPut [ + self assert: (tree atPath: #(1) ifAbsentPut: [ #new ]) equals: firstLevelOneSubTree. + self assert: (tree atPath: #(1 two) ifAbsentPut: [ #new ]) equals: 'One-Two'. + self assert: (tree atPath: #(1 three) ifAbsentPut: [ #new ]) equals: 'One-Three'. + self assert: (tree atPath: #(2) ifAbsentPut: [ #new ]) equals: 'Two'. + self assert: (tree atPath: #(1 four one) ifAbsentPut: [ #anotherNew ]) equals: #anotherNew. + self assert: (tree atPath: #(1 four one)) equals: #anotherNew. + self assert: (tree atPath: #(3) ifAbsentPut: [ #yetAnotherNew ]) equals: #yetAnotherNew. + self assert: (tree atPath: #(3)) equals: #yetAnotherNew. + self should: [ tree atPath: #(2 4) ifAbsentPut: [ #new ] ] raise: self defaultTestError. +] + +{ #category : #'tests - at' } +CTKeyedTreeTest >> testAtPathPut [ + self assert: (tree atPath: #(1 two) put: #new) equals: #new. + self assert: (tree atPath: #(1 two)) equals: #new. + self assert: (tree atPath: #(1 three) put: (firstLevelOneSubTree := CTKeyedTree new)) equals: firstLevelOneSubTree. + self assert: (tree atPath: #(1 three $1) put: #anotherNew) equals: #anotherNew. + self assert: (tree atPath: #(1 three $1)) equals: #anotherNew. + self assert: (tree atPath: #(1 four one) put: #anotherNew) equals: #anotherNew. + self assert: (tree atPath: #(1 four one)) equals: #anotherNew. + self should: [ tree atPath: #(2 4) put: [ #new ] ] raise: self defaultTestError. +] + +{ #category : #'tests - copying' } +CTKeyedTreeTest >> testCopy [ + | c t2 t3 | + tree := CTKeyedTree new + at: 1 put: (t2 := CTKeyedTree new + at: #two put: 'One-Two'; + at: #three put: 'One-Three'; + at: #four put: (t3 := CTKeyedTree new); + yourself); + at: 2 put: 'Two'; + yourself. + c := tree copy. + self assert: c = tree. + self deny: c == tree. + self assert: (c at: 1) = t2. + self deny: (c at: 1) == t2. + self assert: (c atPath: #(1 four)) = t3. + self deny: (c atPath: #(1 four)) == t3. +] + +{ #category : #'tests - operation' } +CTKeyedTreeTest >> testFormattedText [ + self assert: self t13 formattedText equals: +'1 : ''1-3-1'' +2 : ''1-3-2'' +'. + self assert: self t2AB formattedText equals: +'1 : ''1-1'' +2 : ''1-2'' +3 + #A : ''1-3-1'' + #B : ''1-3-2'' +'. +] + +{ #category : #'tests - operation' } +CTKeyedTreeTest >> testMerge [ + | t1 t2 t13 m subT1 subt11 wrapSubt11 | + t13 := self t13. + subT1 := self t2. + subt11 := CTKeyedTree new + at: 1 put: '1-1-1'; + at: 2 put: '1-1-2'; + yourself. + wrapSubt11 := CTKeyedTree new + at: 1 put: subt11; + at: 2 put: '1-2*'; + yourself. + t1 := CTKeyedTree new + at: 1 put: subT1; + at: 2 put: '2'; + yourself. + t2 := CTKeyedTree new + at: 1 put: wrapSubt11; + at: 3 put: '3'; + yourself. + m := t1 merge: t2. + self assert: (m at: 2) equals: '2'. + self assert: (m at: 3) equals: '3'. + self assert: (m atPath: #(1 2)) equals: '1-2*'. + self assert: (m atPath: #(1 1 1)) equals: '1-1-1'. + self assert: (m atPath: #(1 1 2)) equals: '1-1-2'. + self assert: (m atPath: #(1 3 1)) equals: '1-3-1'. + self assert: (m atPath: #(1 3 2)) equals: '1-3-2'. +] + +{ #category : #'tests - copying' } +CTKeyedTreeTest >> testPostCopy [ + | original copy subTree | + original := CTKeyedTree new + at: 1 put: (subTree := CTKeyedTree new at: #a put: 'A'; yourself); + at: 2 put: 'B'; + yourself. + copy := original copy. + "Modify the copy and ensure the original is unaffected" + (copy at: 1) at: #a put: 'Modified'. + self assert: (copy atPath: #(1 a)) equals: 'Modified'. + self assert: (original atPath: #(1 a)) equals: 'A'. + self deny: (copy at: 1) == subTree. +] + +{ #category : #tests } +CTKeyedTreeTest >> testPutFormattedTextOnLevelIndentString [ + "Test formatted text output with a custom indent string." + + | stream | + stream := String new writeStream. + tree putFormattedTextOn: stream level: 1 indentString: '>>'. + self + assert: stream contents + equals: + '>>1 +>> #three : ''One-Three'' +>> #two : ''One-Two'' +>>2 : ''Two'' +' +] + +{ #category : #'tests - removing' } +CTKeyedTreeTest >> testRemovePath [ + self should: [ tree removePath: #(4) ] raise: self defaultTestError. + self should: [ tree removePath: #(1 one) ] raise: self defaultTestError. + self assert: (tree removePath: #(1 two)) equals: 'One-Two'. + self assert: (tree atPath: #(1 two) ifAbsent: [ #missing ]) equals: #missing. + self assert: (tree removePath: #(2)) equals: 'Two'. + self assert: (tree atPath: #(2) ifAbsent: [ #missing ]) equals: #missing. +] + +{ #category : #'tests - removing' } +CTKeyedTreeTest >> testRemovePathIfAbsent [ + self assert: (tree removePath: #(4) ifAbsent: [ #none ]) equals: #none. + self assert: (tree removePath: #(1 2 3 4) ifAbsent: [ #none ]) equals: #none. + self assert: (tree removePath: #(1 two) ifAbsent: [ #none ]) equals: 'One-Two'. + self assert: (tree atPath: #(1 two) ifAbsent: [ #missing ]) equals: #missing. + self assert: (tree removePath: #(2) ifAbsent: [ #none ]) equals: 'Two'. + self assert: (tree atPath: #(2) ifAbsent: [ #missing ]) equals: #missing. +] + +{ #category : #tests } +CTKeyedTreeTest >> testSortBlock [ + "Test the sort block for keys." + + | treeWithMixedKeys sortedKeys | + treeWithMixedKeys := CTKeyedTree new + at: 2 put: 'Two'; + at: #a put: 'A'; + at: 1 put: 'One'; + yourself. + sortedKeys := treeWithMixedKeys keys asSortedCollection: treeWithMixedKeys sortBlock. + self assert: sortedKeys asArray equals: #(#a 1 2) +] + +{ #category : #'tests - operation' } +CTKeyedTreeTest >> testSubtrees [ + | t1 t2 t3 t4 | + t2 := self t2. + t3 := self t13. + t1 := CTKeyedTree new + at: 1 put: t2; + at: 2 put: '2'; + at: 3 put: (t4 := self t13); + yourself. + self assert: t1 subtrees equals: {t2. t4}. + self assert: (t1 at: 1) subtrees equals: {t3}. +] + +{ #category : #'tests - operation' } +CTKeyedTreeTest >> testSubtreesEmpty [ + | emptyTree | + emptyTree := CTKeyedTree new. + self assert: emptyTree subtrees isEmpty. +] \ No newline at end of file diff --git a/src/Containers-KeyedTree-Tests/KeyedTreeTest.class.st b/src/Containers-KeyedTree-Tests/KeyedTreeTest.class.st deleted file mode 100644 index 194ea65..0000000 --- a/src/Containers-KeyedTree-Tests/KeyedTreeTest.class.st +++ /dev/null @@ -1,251 +0,0 @@ -" -SUnit tests for keyed trees -" -Class { - #name : #KeyedTreeTest, - #superclass : #TestCase, - #instVars : [ - 'tree', - 'firstLevelOneSubTree' - ], - #category : 'Containers-KeyedTree-Tests' -} - -{ #category : #running } -KeyedTreeTest >> setUp [ - super setUp. - firstLevelOneSubTree := KeyedTree new - at: #two put: 'One-Two'; - at: #three put: 'One-Three'; - yourself. - tree := KeyedTree new - at: 1 put: firstLevelOneSubTree; - at: 2 put: 'Two'; - yourself -] - -{ #category : #'tests - operation' } -KeyedTreeTest >> t13 [ - ^ KeyedTree new - at: 1 put: '1-3-1'; - at: 2 put: '1-3-2'; - yourself -] - -{ #category : #'tests - operation' } -KeyedTreeTest >> t2 [ - - ^ KeyedTree new - at: 1 put: '1-1'; - at: 2 put: '1-2'; - at: 3 put: (self t13); - yourself -] - -{ #category : #'tests - operation' } -KeyedTreeTest >> t2AB [ - - ^ KeyedTree new - at: 1 put: '1-1'; - at: 2 put: '1-2'; - at: 3 put: (self tAB); - yourself -] - -{ #category : #'tests - operation' } -KeyedTreeTest >> tAB [ - ^ KeyedTree new - at: #A put: '1-3-1'; - at: #B put: '1-3-2'; - yourself -] - -{ #category : #'tests - operation' } -KeyedTreeTest >> testAllKeys [ - - self assert: self t13 allKeys asArray equals: #(1 2). - self assert: self t2AB allKeys asArray equals: #(1 2 3 #A #B). -] - -{ #category : #'tests - at' } -KeyedTreeTest >> testAtPath [ - "Test the at path method for success and failure modes." - - self assert: (tree atPath: #(1)) equals: firstLevelOneSubTree. - self assert: (tree atPath: #(1 two)) equals: 'One-Two'. - self assert: (tree atPath: #(1 three)) equals: 'One-Three'. - self assert: (tree atPath: #(2)) equals: 'Two'. - self should: [ tree atPath: #(2 4) ] raise: self defaultTestError. - self should: [ tree atPath: #(1 two three) ] raise: self defaultTestError. - self should: [ tree atPath: #(3) ] raise: self defaultTestError -] - -{ #category : #'tests - at' } -KeyedTreeTest >> testAtPathIfAbsent [ - "Test the at path if absent method for success and failure modes." - - self assert: (tree atPath: #(1) ifAbsent: [ ]) equals: firstLevelOneSubTree. - self assert: (tree atPath: #(1 two) ifAbsent: [ ]) equals: 'One-Two' . - self assert: (tree atPath: #(1 three) ifAbsent: [ ]) equals: 'One-Three'. - self assert: (tree atPath: #(2) ifAbsent: [ ]) equals: 'Two' . - self assert: (tree atPath: #(2 4) ifAbsent: [ #missing ]) equals: #missing. - self - should: [ (tree atPath: #(1 two three) ifAbsent: [ #missing ]) = #missing ] - raise: self defaultTestError. - self should: [ (tree atPath: #(3) ifAbsent: [ #missing ]) = #missing ] -] - -{ #category : #'tests - at' } -KeyedTreeTest >> testAtPathIfAbsentPut [ - "Test the at path if absent put method for success and failure modes." - - self assert: (tree atPath: #(1) ifAbsentPut: [ #new ]) equals: firstLevelOneSubTree. - self assert: (tree atPath: #(1 two) ifAbsentPut: [ #new ]) equals: 'One-Two' . - self assert: (tree atPath: #(1 three) ifAbsentPut: [ #new ]) equals: 'One-Three'. - self assert: (tree atPath: #(2) ifAbsentPut: [ #new ]) equals: 'Two'. - self - should: [ tree atPath: #(2 4) ifAbsentPut: [ #new ] ] - raise: self defaultTestError. - self - should: [ tree atPath: #(1 two three) ifAbsentPut: [ #new ] ] - raise: self defaultTestError. - self - should: [ (tree atPath: #(1 four one) ifAbsentPut: [ #anotherNew ]) = #anotherNew ]. - self should: [ (tree atPath: #(1 four one)) = #anotherNew ]. - self - should: [ (tree atPath: #(3) ifAbsentPut: [ #yetAnotherNew ]) = #yetAnotherNew ]. - self should: [ (tree atPath: #(3)) = #yetAnotherNew ] -] - -{ #category : #'tests - at' } -KeyedTreeTest >> testAtPathPut [ - "Test the at path put method for success and failure modes." - - self assert: (tree atPath: #(1 two) put: #new) equals: #new. - self assert: (tree atPath: #(1 two)) equals: #new. - self - assert: (tree atPath: #(1 three) put: (firstLevelOneSubTree := KeyedTree new)) - equals: firstLevelOneSubTree . - self assert: (tree atPath: #(1 three $1) put: #anotherNew) equals: #anotherNew. - self assert: (tree atPath: #(1 three $1)) equals: #anotherNew. - self should: [ tree atPath: #(2 4) put: [ #new ] ] raise: self defaultTestError. - self assert: (tree atPath: #(1 four one) put: #anotherNew) equals: #anotherNew. - self assert: (tree atPath: #(1 four one)) equals: #anotherNew -] - -{ #category : #'tests - at' } -KeyedTreeTest >> testCopy [ - "Test the copy method for success and failure modes." - - | c t2 t3 | - tree := KeyedTree new - at: 1 - put: - (t2 := KeyedTree new - at: #two put: 'One-Two'; - at: #three put: 'One-Three'; - at: #four put: (t3 := KeyedTree new); - yourself); - at: 2 put: 'Two'; - yourself. - c := tree copy. - self assert: c = tree . - self deny: c == tree . - self assert: (c at: 1) = t2 . - self deny: (c at: 1) == t2 . - self assert: (c atPath: #(1 four)) = t3 . - self deny: (c atPath: #(1 four)) == t3 -] - -{ #category : #'tests - operation' } -KeyedTreeTest >> testFormattedText [ - - self assert: self t13 formattedText equals: -'1 : ''1-3-1'' -2 : ''1-3-2'' -'. - self assert: self t2AB formattedText equals: -'1 : ''1-1'' -2 : ''1-2'' -3 - #A : ''1-3-1'' - #B : ''1-3-2'' -' -] - -{ #category : #'tests - operation' } -KeyedTreeTest >> testMerge [ - "Test the merge method for success and failure modes." - - | t1 t2 t13 m subT1 subt11 wrapSubt11 | - t13 := self t13. - subT1 := self t2. - subt11 := KeyedTree new - at: 1 put: '1-1-1'; - at: 2 put: '1-1-2'; - yourself. - wrapSubt11 := KeyedTree new - at: 1 put: subt11; - at: 2 put: '1-2*'; - yourself. - t1 := KeyedTree new - at: 1 put: subT1; - at: 2 put: '2'; - yourself. - t2 := KeyedTree new - at: 1 put: wrapSubt11; - at: 3 put: '3'; - yourself. - m := t1 merge: t2. - self assert: (m at: 2) equals: '2'. - self assert: (m at: 3) equals: '3'. - self assert: (m atPath: #(1 2)) equals: '1-2*'. - self assert: (m atPath: #(1 1 1)) equals: '1-1-1'. - self assert: (m atPath: #(1 1 2)) equals: '1-1-2'. - self assert: (m atPath: #(1 3 1)) equals: '1-3-1'. - self assert: (m atPath: #(1 3 2)) equals: '1-3-2' -] - -{ #category : #'tests - remove' } -KeyedTreeTest >> testRemovePath [ - "Test the remove path method for success and failure modes." - - self should: [ tree removePath: #(4) ] raise: self defaultTestError. - self should: [ tree removePath: #(1 one) ] raise: self defaultTestError. - self assert: (tree removePath: #(1 two)) equals: 'One-Two'. - self assert: (tree atPath: #(1 two) ifAbsent: [ ]) equals: nil. - self assert: (tree removePath: #(2)) equals: 'Two' . - self assert: (tree atPath: #(2) ifAbsent: [ ]) equals: nil -] - -{ #category : #'tests - remove' } -KeyedTreeTest >> testRemovePathIfAbsent [ - "Test the remove path if absent method for success and failure modes." - - self assert: (tree removePath: #(4) ifAbsent: [#none ]) equals: #none . - self assert: (tree removePath: #(1 2 3 4) ifAbsent: [#none ]) equals: #none . - self assert: (tree removePath: #(1 two) ifAbsent: [#none ]) equals: 'One-Two' . - self assert: (tree atPath: #(1 two) ifAbsent: [ ] ) equals: nil . - self assert: (tree removePath: #(2) ifAbsent: [#none ]) equals: 'Two' . - self assert: (tree atPath: #(2) ifAbsent: [ ]) equals: nil -] - -{ #category : #'tests - operation' } -KeyedTreeTest >> testSubtrees [ - "Test the subtrees method for success and failure modes." - - | t1 t2 t3 t4 | - t2 := self t2. - t3 := self t13. - t1 := KeyedTree new - at: 1 put: t2; - at: 2 put: '2'; - at: 3 put: (t4 := self t13); - yourself. - self - assert: t1 subtrees - equals: - {t2. - t4}. - self assert: (t1 at: 1) subtrees equals: {t3} -] diff --git a/src/Containers-KeyedTree/CTKeyedTree.class.st b/src/Containers-KeyedTree/CTKeyedTree.class.st new file mode 100644 index 0000000..870fcde --- /dev/null +++ b/src/Containers-KeyedTree/CTKeyedTree.class.st @@ -0,0 +1,189 @@ +" +I'm a special kind of dictionary. I provide path-based access to elements contained in the receiver and any subtrees. + +Example: + +[[[ +(CTKeyedTree new + at: 1 put: 'One'; + at: 2 put: 'Two'; + at: 'Tree' put: (CTKeyedTree new + at: $a put: 'Tree-A'; + at: $b put: 'Tree-B'; + yourself); + yourself) atPath: #('Tree' $b) +>>> 'Tree-B' +]]] +" +Class { + #name : #CTKeyedTree, + #superclass : #Dictionary, + #category : 'Containers-KeyedTree' +} + +{ #category : #samples } +CTKeyedTree class >> exampleKeyedTree1 [ + + ^ CTKeyedTree new + at: 1 put: 'One'; + at: 2 put: 'Two'; + at: 'Tree' + put: + (CTKeyedTree new + at: $a put: 'Tree-A'; + at: $b put: 'Tree-B'; + yourself); + yourself +] + +{ #category : #accessing } +CTKeyedTree >> allKeys [ + "Answer an ordered collection of the keys of the receiver and any subtrees. Please no circular references!" + | answer | + answer := OrderedCollection new. + answer addAll: self keys. + self subtrees do: [ :t | answer addAll: t allKeys ]. + ^ answer +] + +{ #category : #accessing } +CTKeyedTree >> atPath: anArray [ + "Answer the element referenced by the given key path. Signal an error if not found." + ^ self atPath: anArray ifAbsent: [ self errorKeyNotFound: anArray ] +] + +{ #category : #accessing } +CTKeyedTree >> atPath: anArray ifAbsent: aBlock [ + | element | + element := self. + anArray do: [ :key | + (element isKindOf: self class) + ifTrue: [ element := element at: key ifAbsent: [ ^aBlock value ] ] + ifFalse: [ ^aBlock value ] ]. + ^ element +] + +{ #category : #accessing } +CTKeyedTree >> atPath: anArray ifAbsentPut: aBlock [ + "Answer the element referenced by the given key path. Answer the value of aBlock if not found after creating its path." + | element | + anArray isEmpty ifTrue: [ ^self ]. + element := self. + anArray allButLastDo: [ :key | element := element at: key ifAbsentPut: [ self species new ] ]. + ^ element at: anArray last ifAbsentPut: aBlock +] + +{ #category : #accessing } +CTKeyedTree >> atPath: anArray put: aBlock [ + "Answer the value of aBlock after creating its path." + | element | + anArray isEmpty ifTrue: [ ^self ]. + element := self. + anArray allButLastDo: [ :key | element := element at: key ifAbsentPut: [ self species new ] ]. + ^ element at: anArray last put: aBlock +] + +{ #category : #printing } +CTKeyedTree >> formattedText [ + "Answer a string or text representing the receiver with indentation and, possibly, markup." + ^ String new writeStreamDo: [ :str | + self putFormattedTextOn: str level: 0 indentString: ' '. + str contents ] +] + +{ #category : #adding } +CTKeyedTree >> merge: aKeyedTree [ + "Merge the given tree into the receiver, overwriting or extending elements as needed." + aKeyedTree keysAndValuesDo: [ :k :v | + | subtree | + (v isKindOf: CTKeyedTree) + ifTrue: [ + subtree := self at: k ifAbsentPut: [ v species new ]. + (subtree isKindOf: CTKeyedTree) + ifFalse: [ subtree := self at: k put: v species new ]. + subtree merge: v ] + ifFalse: [ self at: k put: v ] ] +] + +{ #category : #copying } +CTKeyedTree >> postCopy [ + "Must copy the associations, or later store will affect both the original and the copy. Copy any subtrees too!" + array := array collect: [ :assoc | + assoc ifNil: [ nil ] + ifNotNil: [ Association + key: assoc key + value: ((assoc value isKindOf: CTKeyedTree) + ifTrue: [ assoc value copy ] + ifFalse: [ assoc value ]) ] ] +] + +{ #category : #printing } +CTKeyedTree >> putFormattedTextOn: aStream level: indentLevel indentString: aString [ + "Write a textual representation of the receiver to aStream, indenting to indentLevel using aString as the indent string." + + (self keys asSortedCollection: self sortBlock) + do: [ :k | + | v | + indentLevel = 0 + ifFalse: [ + indentLevel = 1 + ifTrue: [ aStream nextPutAll: aString ] + ifFalse: [ indentLevel - 1 timesRepeat: [ aStream nextPutAll: aString ]. aStream nextPutAll: ' ' ] ]. + aStream nextPutAll: k printString. + v := self at: k. + (v isKindOf: self class) + ifTrue: [ aStream cr. + v putFormattedTextOn: aStream level: indentLevel + 1 indentString: aString ] + ifFalse: [ aStream + nextPutAll: ' : '; + nextPutAll: v printString. + aStream cr ] ] +] + +{ #category : #removing } +CTKeyedTree >> removePath: anArray [ + "Remove and answer the element referenced by the given path. Signal an error if not found." + ^ self removePath: anArray ifAbsent: [ self errorKeyNotFound: anArray ] +] + +{ #category : #removing } +CTKeyedTree >> removePath: anArray ifAbsent: aBlock [ + "Remove and answer the element referenced by the given path. Answer the value of aBlock if not found." + | element | + anArray isEmpty ifTrue: [ ^self ]. + element := self. + anArray allButLastDo: [ :key | element := element at: key ifAbsent: [ ^aBlock value ] ]. + ^ element removeKey: anArray last ifAbsent: aBlock +] + +{ #category : #accessing } +CTKeyedTree >> sortBlock [ + "Answer a sort block that can be used to sort the keys of the receiver." + + ^ [ :a :b | + a class = b class + ifTrue: [ a <= b ] + ifFalse: [ + (a isSymbol and: [ b isNumber ]) + ifTrue: [ true ] + ifFalse: [ + (b isSymbol and: [ a isNumber ]) + ifTrue: [ false ] + ifFalse: [ a class name <= b class name ] ] ] ] +] + +{ #category : #accessing } +CTKeyedTree >> subtrees [ + "Answer the subtrees of the receiver." + ^ (self select: [ :v | v isKindOf: CTKeyedTree ]) values +] + +{ #category : #tests } +CTKeyedTreeTest >> testFormattedText [ + "Test the formatted text representation of the tree." + + self assert: self t13 formattedText equals: + '1 : ''1-3-1'' +2 : ''1-3-2'' +' +] \ No newline at end of file diff --git a/src/Containers-KeyedTree/KeyedTree.class.st b/src/Containers-KeyedTree/KeyedTree.class.st deleted file mode 100644 index cd95c1e..0000000 --- a/src/Containers-KeyedTree/KeyedTree.class.st +++ /dev/null @@ -1,189 +0,0 @@ -" -I'm special kind of dictionary. I provide path based access to elements contained in the receiver and any subtrees. - -Example: - -[[[ -(KeyedTree new - at: 1 put: 'One'; - at: 2 put: 'Two'; - at: 'Tree' put: (KeyedTree new - at: $a put: 'Tree-A'; - at: $b put: 'Tree-B'; - yourself); - yourself) atPath: #('Tree' $b) ->>> 'Tree-B' -]]] -" -Class { - #name : #KeyedTree, - #superclass : #Dictionary, - #category : 'Containers-KeyedTree' -} - -{ #category : #samples } -KeyedTree class >> exampleKeyedTree1 [ - - - ^ KeyedTree new - at: 1 put: 'One'; - at: 2 put: 'Two'; - at: 'Tree' - put: - (KeyedTree new - at: $a put: 'Tree-A'; - at: $b put: 'Tree-B'; - yourself); - yourself -] - -{ #category : #accessing } -KeyedTree >> allKeys [ - "Answer an ordered collection of the keys of the receiver and any subtrees. Please no circular references!" - - | answer | - answer := OrderedCollection new. - answer addAll: self keys. - self subtrees do: [ :t | answer addAll: t allKeys ]. - ^ answer -] - -{ #category : #accessing } -KeyedTree >> atPath: anArray [ - "Answer the element referenced by the give key path. - Signal an error if not found." - - ^self atPath: anArray ifAbsent: [self errorKeyNotFound: anArray] -] - -{ #category : #accessing } -KeyedTree >> atPath: anArray ifAbsent: aBlock [ - "Answer the element referenced by the given key path. - Answer the value of aBlock if not found." - - |element| - element := self. - anArray do: [:key | - element := element at: key ifAbsent: [^aBlock value]]. - ^element -] - -{ #category : #accessing } -KeyedTree >> atPath: anArray ifAbsentPut: aBlock [ - "Answer the element referenced by the given key path. - Answer the value of aBlock if not found after creating its path." - - |element| - anArray isEmpty - ifTrue: [^self]. - element := self. - anArray allButLastDo: [:key | - element := element at: key ifAbsentPut: [self species new]]. - ^element at: anArray last ifAbsentPut: aBlock -] - -{ #category : #accessing } -KeyedTree >> atPath: anArray put: aBlock [ - "Answer the value of aBlock after creating its path." - - |element| - anArray isEmpty - ifTrue: [^self]. - element := self. - anArray allButLastDo: [:key | - element := element at: key ifAbsentPut: [self species new]]. - ^element at: anArray last put: aBlock -] - -{ #category : #printing } -KeyedTree >> formattedText [ - "Answer a string or text representing the receiver with indentation and, possibly, markup." - - - ^ String new writeStreamDo: [ :str | - self putFormattedTextOn: str level: 0 indentString: ' '. - str contents ] -] - -{ #category : #adding } -KeyedTree >> merge: aKeyedTree [ - "Merge the given tree into the receiver, overwriting or extending elements as needed." - - aKeyedTree keysAndValuesDo: [ :k :v | - | subtree | - (v isKindOf: KeyedTree) - ifTrue: [ - subtree := self at: k ifAbsentPut: [ v species new ]. - (subtree isKindOf: KeyedTree) - ifFalse: [ subtree := self at: k put: v species new ]. - subtree merge: v ] - ifFalse: [ self at: k put: v ] ] -] - -{ #category : #copying } -KeyedTree >> postCopy [ - "Must copy the associations, or later store will affect both the original and the copy. - Copy any subtrees too!" - - array := array collect: [:assoc | - assoc ifNil: [nil] - ifNotNil: [Association - key: assoc key - value: ((assoc value isKindOf: KeyedTree) - ifTrue: [assoc value copy] - ifFalse: [assoc value])]] -] - -{ #category : #printing } -KeyedTree >> putFormattedTextOn: aStream level: indentLevel indentString: aString [ - "Place a description of the receiver on the given stream with the given indentation level." - - - (self keys asSortedCollection: self sortBlock) do: [:k | | v | - indentLevel timesRepeat: [aStream nextPutAll: aString]. - aStream nextPutAll: k printString. - v := self at: k. - (v isKindOf: self class) - ifTrue: [aStream cr. - v putFormattedTextOn: aStream level: indentLevel + 1 indentString: aString] - ifFalse: [aStream - nextPutAll: ' : '; - nextPutAll: v printString. - aStream cr]] -] - -{ #category : #removing } -KeyedTree >> removePath: anArray [ - "Remove and answer the element referenced by the given path. - Signal an error if not found." - - ^self removePath: anArray ifAbsent: [self errorKeyNotFound: anArray] -] - -{ #category : #removing } -KeyedTree >> removePath: anArray ifAbsent: aBlock [ - "Remove and answer the element referenced by the given path. - Answer the value of aBlock if not found." - - |element| - anArray isEmpty - ifTrue: [^self]. - element := self. - anArray allButLastDo: [:key | - element := element at: key ifAbsent: [^aBlock value]]. - ^element removeKey: anArray last ifAbsent: aBlock -] - -{ #category : #accessing } -KeyedTree >> sortBlock [ - "Answer the block to sort tree keys with." - - ^[:a :b | [a <= b] on: Error do: [a class name <= b class name]] -] - -{ #category : #accessing } -KeyedTree >> subtrees [ - "Answer the subtrees of the receiver." - - ^(self select: [:v | v isKindOf: KeyedTree]) values -]