TDDBC 東京 1.6 のお題を Squeak Smalltalk で


ファイルイン用の .st ファイルはこちら→ TDDBC-Tokyo16.st (Squeak4.2-ja-all-in-one で動作確認)

Object subclass: #Kvs
   instanceVariableNames: 'dict log'
   classVariableNames: ''
   poolDictionaries: ''
   category: 'TDDBC-Tokyo16'

Kvs>>initialize "initialization"
   dict := Dictionary new.
   log := Dictionary new

Kvs>>at: key "accessing"
   key ifNil: [self error: 'キーにnilは使用できない'].
   ^dict at: key

Kvs>>at: key put: value "accessing"
   | time |
   time := DateAndTime now.
   key ifNil: [self error: 'キーにnilは使用できない'].
   self removeKeyFromLog: key.
   (log at: time ifAbsentPut: [OrderedCollection new]) add: key.
   ^dict at: key put: value

Kvs>>dump "accessing"
   | sortedKeys |
   sortedKeys := (log keys sort collect: [:time | log at: time]) concatenation reversed.
   ^sortedKeys collect: [:key| key -> (self at: key)]

Kvs>>dumpAfter: period "accessing"
   | sortedTimes found |
   sortedTimes := log keys sort.
   found := sortedTimes findLast: [:time | time < period].
   found = 0 ifTrue: [^#() copy].
   ^((sortedTimes allButFirst: found) collect: [:time | log at: time]) concatenation
      collect: [:key | dict associationAt: key]

Kvs>>removeAllBeforeAgo: duration "accessing"
   | period sortedTimes index |
   period := DateAndTime now - duration.
   sortedTimes := log keys sort.
   index := sortedTimes findLast: [:time | time < period].
   index = 0 ifTrue: [^self].
   ^((sortedTimes first: index) collect: [:time | log at: time]) concatenation
      collect: [:key | self removeKey: key]

Kvs>>removeKey: key "accessing"
   key ifNil: [self error: 'キーにnilは使用できない'].
   self removeKeyFromLog: key.
   ^dict removeKey: key ifAbsent: []

Kvs>>addAll: assocs "adding"
   | time |
   time := DateAndTime now.
   (assocs anySatisfy: [:assoc | assoc key isNil]) ifTrue: [self error: 'キーにnilは使用できない'].
   assocs do: [:assoc | self at: assoc key put: assoc value time: time]

Kvs>>at: key put: value time: time "private"
   key ifNil: [self error: 'キーにnilは使用できない'].
   self removeKeyFromLog: key.
   (log at: time ifAbsentPut: [OrderedCollection new]) add: key.
   ^dict at: key put: value

Kvs>>removeKeyFromLog: key "private"
   (log associations detect: [:assoc | assoc value includes: key] ifNone: []) 
      ifNotNilDo: [:assoc | (assoc value remove: key; yourself) ifEmpty: [log removeKey: assoc key]].

Kvs>>addAll: assocs time: time "obsolete"
   (assocs anySatisfy: [:assoc | assoc key isNil]) ifTrue: [self error: 'キーにnilは使用できない'].
   assocs do: [:assoc | self at: assoc key put: assoc value time: time]
TestCase subclass: #KvsTest
   instanceVariableNames: ''
   classVariableNames: ''
   poolDictionaries: ''
   category: 'TDDBC-Tokyo16'

KvsTest>>test01xキーと値を追加して値を取り出せる "tests"
   "仕様変更につき削除" [
   | kvs key val |
   kvs := Kvs new.
   key := #key.
   val := 'val'.
   kvs at: key put: val.
   self assert: (kvs at: key) = val]

KvsTest>>test02xキー値の一覧を得られる "tests"
   "仕様変更につき削除" [
   | kvs pairs |
   kvs := Kvs new.
   pairs := {#key1->'val1'. #key2->'val2'. #key3->'val3'}.
   pairs do: [:assoc | kvs at: assoc key put: assoc value].
   self assert: kvs dump asSet = pairs asSet]

KvsTest>>test03xキーにnilを与えると例外を発生 "tests"
   "仕様変更につき削除" [
   | kvs |
   kvs := Kvs new.
   self should: [self shouldnt: [kvs at: nil] raise: KeyNotFound] raise: Error.
   self should: [kvs at: nil put: 'value'] raise: Error]

KvsTest>>test04x値にはnilも許容する "tests"
   "仕様変更につき削除" [
   | kvs |
   kvs := Kvs new.
   kvs at: #key put: nil.
   self assert: (kvs at: #key) = nil]

KvsTest>>test05x指定したキーとその値を削除できる "tests"
   "仕様変更につき削除" [
   | kvs |
   kvs := Kvs new.
   kvs at: #k1 put: 'v1'.
   kvs at: #k2 put: 'v2'.
   kvs removeKey: #k1.
   self deny: [kvs dump includes: #k1->'v2']]

KvsTest>>test06xキー値削除時にキーが存在しない場合は何もしない "tests"
   "仕様変更につき削除" [
   | kvs pairs |
   kvs := Kvs new.
   pairs := {#k1->'v1'. #k2->'v2'}.
   pairs do: [:assoc | kvs at: assoc key put: assoc value].
   kvs removeKey: #k3.
   self assert: [kvs dump asSet = pairs asSet]]

KvsTest>>test07xキー値削除時にnilを与えると例外が発生 "tests"
   "仕様変更につき削除" [
   | kvs |
   kvs := Kvs new.
   kvs at: #k1 put: 'v1'.
   kvs at: #k2 put: 'v2'.
   self should: [kvs removeKey: nil] raise: Error]

KvsTest>>test08xすでに存在するキーに値を設定すると上書きする "tests"
   "仕様変更につき削除" [
   | kvs |
   kvs := Kvs new.
   kvs at: #key put: 'val1'.
   self assert: (kvs at: #key) = 'val1'.
   kvs at: #key put: 'val2'.
   self assert: (kvs at: #key) = 'val2']

KvsTest>>test09x複数のキーと値の組を追加できる "tests"
   "使用変更につき削除" [
   | kvs pairs |
   kvs := Kvs new.
   pairs := {#k1->'v1'. #k2->'v2'}.
   kvs addAll: pairs.
   self assert: kvs dump asSet = pairs asSet]

KvsTest>>test10x複数キー値追加時には同じキーが複数ある場合には最後に指定されたものが使用される "tests"
   "仕様変更につき削除" [
   | kvs pairs |
   kvs := Kvs new.
   pairs := {#k1->'v1'. #k2->'v2'. #k1->'v3'}.
   kvs addAll: pairs.
   self assert: (kvs at: #k1) = 'v3']

KvsTest>>test11x複数キー値追加時には既に存在するキーがある場合も指定したものが優先される "tests"
   "仕様変更につき削除" [
   | kvs pairs1 pairs2 |
   kvs := Kvs new.
   pairs1 := {#k1->'v1'. #k2->'v2'}.
   kvs addAll: pairs1.
   pairs2 := {#k1->'v3'. #k2->'v4'}.
   kvs addAll: pairs2.
   self assert: kvs dump asSet = pairs2 asSet]

KvsTest>>test13xキー値追加時に時刻も渡せるように仕様変更 "tests"

KvsTest>>test14xキーと値と時刻を追加して値を取り出せる "tests"
   "仕様変更につき削除" [
   | kvs key val |
   kvs := Kvs new.
   key := #key.
   val := 'val'.
   kvs at: key put: val time: DateAndTime now.
   self assert: (kvs at: key) = val]

KvsTest>>test15xキー値の新しく追加された順に列んだ一覧を得られる "tests"
   "仕様変更につき削除" [
   | kvs pairs now |
   kvs := Kvs new.
   now := DateAndTime now.
   pairs := {#key1->'val1'. #key2->'val2'. #key3->'val3'}.
   pairs do: [:assoc | kvs at: assoc key put: assoc value time: (now := now + 1 seconds)].
   self assert: kvs dump = pairs reversed]

KvsTest>>test16xキーにnilを与えると例外を発生 "tests"
   "仕様変更につき削除" [
   | kvs |
   kvs := Kvs new.
   self should: [self shouldnt: [kvs at: nil] raise: KeyNotFound] raise: Error.
   self should: [kvs at: nil put: 'value' time: DateAndTime now] raise: Error]

KvsTest>>test17x値にはnilも許容する "tests"
   "仕様変更につき削除" [
   | kvs |
   kvs := Kvs new.
   kvs at: #key put: nil time: DateAndTime now.
   self assert: (kvs at: #key) = nil]

KvsTest>>test18x指定したキーとその値を削除できる "tests"
   "仕様変更につき削除" [
   | kvs |
   kvs := Kvs new.
   kvs at: #k1 put: 'v1' time: DateAndTime now.
   kvs at: #k2 put: 'v2' time: DateAndTime now.
   kvs removeKey: #k1.
   self deny: [kvs dump includes: #k1->'v2']]

KvsTest>>test19xキー値削除時にキーが存在しない場合は何もしない "tests"
   "仕様変更につき削除" [
   | kvs pairs |
   kvs := Kvs new.
   pairs := {#k1->'v1'. #k2->'v2'}.
   pairs do: [:assoc | kvs at: assoc key put: assoc value time: DateAndTime now].
   kvs removeKey: #k3.
   self assert: [kvs dump asSet = pairs asSet]]

KvsTest>>test20xキー値削除時にnilを与えると例外が発生 "tests"
   "仕様変更につき削除" [
   | kvs |
   kvs := Kvs new.
   kvs at: #k1 put: 'v1' time: DateAndTime now.
   kvs at: #k2 put: 'v2' time: DateAndTime now.
   self should: [kvs removeKey: nil] raise: Error]

KvsTest>>test21xすでに存在するキーに値を設定すると上書きする "tests"
   "仕様変更につき削除" [
   | kvs |
   kvs := Kvs new.
   kvs at: #key put: 'val1' time: DateAndTime now.
   self assert: (kvs at: #key) = 'val1'.
   kvs at: #key put: 'val2' time: DateAndTime now.
   self assert: (kvs at: #key) = 'val2']

KvsTest>>test22x複数のキーと値の組を追加できる "tests"
   "使用変更につき削除" [
   | kvs assocs |
   kvs := Kvs new.
   assocs := {#k1->'v1'. #k2->'v2'}.
   kvs addAll: assocs time: DateAndTime now.
   self assert: kvs dump = assocs reversed]

KvsTest>>test23x複数キー値追加時には同じキーが複数ある場合には最後に指定されたものが使用される "tests"
   "仕様変更につき削除" [
   | kvs assocs |
   kvs := Kvs new.
   assocs := {#k1->'v1'. #k2->'v2'. #k1->'v3'}.
   kvs addAll: assocs time: DateAndTime now.
   self assert: (kvs at: #k1) = 'v3']

KvsTest>>test24x複数キー値追加時には既に存在するキーがある場合も指定したものが優先される "tests"
   "仕様変更につき削除" [
   | kvs now assocs1 assocs2 |
   kvs := Kvs new.
   now := DateAndTime now.
   assocs1 := {#k1->'v1'. #k2->'v2'}.
   kvs addAll: assocs1 time: now.
   assocs2 := {#k1->'v3'. #k2->'v4'}.
   kvs addAll: assocs2 time: now.
   self assert: kvs dump = {#k2->'v4'. #k1->'v3'}]

KvsTest>>test25x複数キー値追加時には既に存在するキーがある場合も指定したものが優先される "tests"
   "仕様変更につき削除" [
   | kvs now assocs1 assocs2 |
   kvs := Kvs new.
   now := DateAndTime now.
   assocs1 := {#k1->'v1'. #k2->'v2'}.
   kvs addAll: assocs1 time: now.
   assocs2 := {#k1->'v3'. #k2->'v4'}.
   kvs addAll: assocs2 time: now.
   self assert: kvs dump = {#k2->'v4'. #k1->'v3'}]

KvsTest>>test26x指定した時刻以降のキー値一覧が得られる "tests"
   "仕様変更につき削除" [
   | kvs pairs now |
   kvs := Kvs new.
   now := DateAndTime now.
   pairs := {#key1->'val1'. #key2->'val2'. #key3->'val3'}.
   pairs doWithIndex: [:assoc :delta | kvs at: assoc key put: assoc value time: (now + delta seconds)].
   self assert: (kvs dumpAfter: now + 2 seconds) = pairs allButFirst]

KvsTest>>test27x現時刻から指定した時間の長さより前の時点より前に登録されたデータを一括削除できる "tests"
   "仕様変更につき削除" [
   | kvs pairs time |
   kvs := Kvs new.
   time := DateAndTime now - 2 minutes.
   pairs := {#key1->'val1'. #key2->'val2'. #key3->'val3'}.
   pairs doWithIndex: [:assoc :delta | kvs at: assoc key put: assoc value time: (time + (delta * 30) seconds)].
   self assert: (kvs removeAllBeforeAgo: 90 seconds; dump) = pairs allButFirst reversed]

KvsTest>>test28xキー値追加時刻に時刻の指定はせずに現時刻を使用する仕様変更 "tests"

KvsTest>>test29x複数のキーと値の組を追加できる "tests"
   | kvs assocs |
   kvs := Kvs new.
   assocs := {#k1->'v1'. #k2->'v2'}.
   kvs addAll: assocs.
   self assert: kvs dump = assocs reversed

KvsTest>>test30x複数キー値追加時には同じキーが複数ある場合には最後に指定されたものが使用される "tests"
   | kvs assocs |
   kvs := Kvs new.
   assocs := {#k1->'v1'. #k2->'v2'. #k1->'v3'}.
   kvs addAll: assocs.
   self assert: (kvs at: #k1) = 'v3'

KvsTest>>test31x複数キー値追加時には既に存在するキーがある場合も指定したものが優先される "tests"
   | kvs assocs1 assocs2 |
   kvs := Kvs new.
   assocs1 := {#k1->'v1'. #k2->'v2'}.
   kvs addAll: assocs1.
   assocs2 := {#k1->'v3'. #k2->'v4'}.
   kvs addAll: assocs2.
   self assert: kvs dump = {#k2->'v4'. #k1->'v3'}

KvsTest>>test32x複数キー値追加時には既に存在するキーがある場合も指定したものが優先される "tests"
   | kvs assocs1 assocs2 |
   kvs := Kvs new.
   assocs1 := {#k1->'v1'. #k2->'v2'}.
   kvs addAll: assocs1.
   assocs2 := {#k1->'v3'. #k2->'v4'}.
   kvs addAll: assocs2.
   self assert: kvs dump = {#k2->'v4'. #k1->'v3'}

KvsTest>>test33xキーと値と時刻を追加して値を取り出せる "tests"
   | kvs key val |
   kvs := Kvs new.
   key := #key.
   val := 'val'.
   kvs at: key put: val.
   self assert: (kvs at: key) = val

KvsTest>>test34xキー値の新しく追加された順に列んだ一覧を得られる "tests"
   | kvs pairs |
   kvs := Kvs new.
   pairs := {#key1->'val1'. #key2->'val2'. #key3->'val3'}.
   pairs do: [:assoc | kvs at: assoc key put: assoc value. 1 seconds asDelay wait].
   self assert: kvs dump = pairs reversed

KvsTest>>test35xキーにnilを与えると例外を発生 "tests"
   | kvs |
   kvs := Kvs new.
   self should: [self shouldnt: [kvs at: nil] raise: KeyNotFound] raise: Error.
   self should: [kvs at: nil put: 'value'] raise: Error

KvsTest>>test36x値にはnilも許容する "tests"
   | kvs |
   kvs := Kvs new.
   kvs at: #key put: nil.
   self assert: (kvs at: #key) = nil

KvsTest>>test37x指定したキーとその値を削除できる "tests"
   | kvs |
   kvs := Kvs new.
   kvs at: #k1 put: 'v1'.
   kvs at: #k2 put: 'v2'.
   kvs removeKey: #k1.
   self deny: [kvs dump includes: #k1->'v2']

KvsTest>>test38xキー値削除時にキーが存在しない場合は何もしない "tests"
   | kvs pairs |
   kvs := Kvs new.
   pairs := {#k1->'v1'. #k2->'v2'}.
   pairs do: [:assoc | kvs at: assoc key put: assoc value].
   kvs removeKey: #k3.
   self assert: [kvs dump asSet = pairs asSet]

KvsTest>>test39xキー値削除時にnilを与えると例外が発生 "tests"
   | kvs |
   kvs := Kvs new.
   kvs at: #k1 put: 'v1'.
   kvs at: #k2 put: 'v2'.
   self should: [kvs removeKey: nil] raise: Error

KvsTest>>test40xすでに存在するキーに値を設定すると上書きする "tests"
   | kvs |
   kvs := Kvs new.
   kvs at: #key put: 'val1'.
   self assert: (kvs at: #key) = 'val1'.
   kvs at: #key put: 'val2'.
   self assert: (kvs at: #key) = 'val2'

KvsTest>>test41x指定した時刻以降のキー値一覧が得られる "tests"
   | kvs pairs |
   kvs := Kvs new.
   pairs := {#key1->'val1'. #key2->'val2'. #key3->'val3'}.
   pairs do: [:assoc | kvs at: assoc key put: assoc value. 1 seconds asDelay wait].
   self assert: (kvs dumpAfter: DateAndTime now - 2.5 seconds) = pairs allButFirst

KvsTest>>test42x現時刻から指定した時間の長さより前の時点より前に登録されたデータを一括削除できる "tests"
   | kvs pairs |
   kvs := Kvs new.
   pairs := {#key1->'val1'. #key2->'val2'. #key3->'val3'}.
   pairs do: [:assoc | kvs at: assoc key put: assoc value. 1 seconds asDelay wait].
   self assert: (kvs removeAllBeforeAgo: 2.5 seconds; dump) = pairs allButFirst reversed