Data.Counter: Implement "Data.Counter" #348

Merged
merged 12 commits into from Dec 28, 2015

Projects

None yet

4 participants

@haya14busa
Member

Data.Counter is similar to python's collections.Counter

I'll add document later but the implementation is done and I assume you can understand most of behavior from comments, tests and python's documentation of collections.Counter.
Please take a look when you have time.

@haya14busa haya14busa changed the title from Data.Counter: Implement "Data.Counter" [WIP for document] to Data.Counter: Implement "Data.Counter" Dec 9, 2015
@haya14busa
Member

ドキュメント足しました (めんどいので日本語)

@crazymaster crazymaster and 1 other commented on an outdated diff Dec 9, 2015
doc/vital-data-counter.txt
+>
+ let s:c = s:Counter.new('vim')
+ call s:c.clear()
+ echo s:c.get('v')
+ " => 0
+<
+Counter.elements() *Vital.Data.Counter-Counter.elements()*
+ Returns a list of elements repeating each as many times as its count.
+ Elements are returned in arbitrary order. If an element's count is
+ less than one, elements() will ignore it.
+>
+ let s:c = s:Counter.new('ABCABC')
+ echo sort(s:c.elements())
+ " => ['A', 'A', 'B', 'B', 'C', 'C']
+<
+Counter.most_common({number}) *Vital.Data.Counter-Counter.elements()*
@crazymaster
crazymaster Dec 9, 2015 Member

s/elements()/most_common()/

@haya14busa
Member

正直微妙なところ

pythonのcollections.Counterの+-はnegative valueを無視します.
現状のPRのVim script 実装では+, -に対応するメソッドは実装せず,
negative valueを無視しない.update(), .subtract() を実装しています.

これは返ってきたCounterにたいしてpythonでは+c, このVim
script版では.keep_positive()すれば十分じゃないか?というのと,
-を実装しようとするとsubtract(sub)はすでにあって使えないので名前がムズイ...という理由です.
+.add()でokなんですが...

.update()の仕様はpythonの仕様と同じになってるけど.add()のほうがわかりやすそう.
しかし.add()だとpythonにならってnegativeを無視したほうがいいのでは...?となるので実装してない.

@crazymaster crazymaster and 1 other commented on an outdated diff Dec 9, 2015
autoload/vital/__latest__/Data/Counter.vim
+ let tuples += [[dict.value, dict.count]]
+ endfor
+ return s:L.sort_by(tuples, '-v:val[1]')[:n]
+endfunction
+
+" .to_dict() returns count dictionary.
+" @return {{string: number}}
+function! s:Counter.to_dict() abort
+ let result = {}
+ for [k, dict] in items(self._dict)
+ let result[k] = dict.count
+ endfor
+ return result
+endfunction
+
+" .to_list() returns list of element in the counter. The order is arbitary.
@crazymaster
crazymaster Dec 9, 2015 Member

s/arbitary/arbitrary/

@haya14busa
haya14busa Dec 9, 2015 Member

ありがとうございます.修正ed

@crazymaster crazymaster commented on an outdated diff Dec 9, 2015
autoload/vital/__latest__/Data/Counter.vim
+" @return {{string: number}}
+function! s:Counter.to_dict() abort
+ let result = {}
+ for [k, dict] in items(self._dict)
+ let result[k] = dict.count
+ endfor
+ return result
+endfunction
+
+" .to_list() returns list of element in the counter. The order is arbitary.
+" @return {list<any>}
+function! s:Counter.to_list() abort
+ return map(values(self._dict), 'v:val.value')
+endfunction
+
+" .values() returns list of count in the counter. The order is arbitary.
@crazymaster
crazymaster Dec 9, 2015 Member

s/arbitary/arbitrary/

@crazymaster crazymaster commented on an outdated diff Dec 9, 2015
doc/vital-data-counter.txt
+ returns all elements in the counter. Elements with equal counts are
+ ordered arbitrarily.
+>
+ echo s:Counter.new('abcdeabcdabcaba').most_common(3)
+ " => [['a', 5], ['b', 4], ['c', 3]]
+<
+Counter.to_dict() *Vital.Data.Counter-Counter.to_dict()*
+ Returns {count-dict} |Vital.Data.Counter-term-count-dict| from Counter
+ object.
+>
+ let s:c = s:Counter.new('ABCABC')
+ echo s:c.to_dict()
+ " => {'A': 2, 'B': 2, 'C': 2}
+<
+Counter.to_list() *Vital.Data.Counter-Counter.to_list()*
+ Returns list of element in the counter. The order is arbitary.
@crazymaster
crazymaster Dec 9, 2015 Member

s/arbitary/arbitrary/

@crazymaster crazymaster commented on an outdated diff Dec 9, 2015
doc/vital-data-counter.txt
+ Returns {count-dict} |Vital.Data.Counter-term-count-dict| from Counter
+ object.
+>
+ let s:c = s:Counter.new('ABCABC')
+ echo s:c.to_dict()
+ " => {'A': 2, 'B': 2, 'C': 2}
+<
+Counter.to_list() *Vital.Data.Counter-Counter.to_list()*
+ Returns list of element in the counter. The order is arbitary.
+>
+ let s:c = s:Counter.new('ABCABC')
+ echo sort(s:c.to_list())
+ " => ['A', 'B', 'C']
+<
+Counter.values() *Vital.Data.Counter-Counter.values()*
+ Returns list of count in the counter. The order is arbitary.
@crazymaster
crazymaster Dec 9, 2015 Member

s/arbitary/arbitrary/

@crazymaster crazymaster commented on an outdated diff Dec 9, 2015
autoload/vital/__latest__/Data/Counter.vim
+ if !self.in(a:x)
+ let self._dict[self._hash(a:x)] = {'count': 0, 'value': a:x}
+ endif
+endfunction
+
+" ._to_iter_countable() converts countable to easy-to-count-and-iterable type.
+" It converts `string` to `list` and `Counter` to `dict`.
+" @param {list|string|dict|Counter} countable
+" @return {list|dict}
+function! s:Counter._to_iter_countable(countable) abort
+ return type(a:countable) is# type('') ? split(a:countable, '\zs')
+ \ : s:_is_counter(a:countable) ? a:countable.to_dict()
+ \ : a:countable
+endfunction
+
+" ._hash() returns hasy key for given value.
@crazymaster
crazymaster Dec 9, 2015 Member

s/hasy/hash/

@thinca thinca and 1 other commented on an outdated diff Dec 9, 2015
autoload/vital/__latest__/Data/Counter.vim
+ let s:L = s:V.import('Data.List')
+endfunction
+
+function! s:_vital_depends() abort
+ return ['Data.List']
+endfunction
+
+let s:Counter = {
+\ '__type__': 'Counter',
+\ '_dict': {}
+\ }
+
+" s:new() creates a new instance of Counter object.
+" @param {list|string|dict?} countable (optional)
+function! s:new(...) abort
+ let c = deepcopy(s:Counter)
@thinca
thinca Dec 9, 2015 Member

引数チェックの後で良さそう。

@haya14busa
haya14busa Dec 19, 2015 Member

たしかにっ

@thinca thinca and 1 other commented on an outdated diff Dec 9, 2015
doc/vital-data-counter.txt
+ - {count-dict} |Vital.Data.Counter-term-count-dict|
+ - {Counter} |Vital.Data.Counter-term-Counter|
+
+{count-dict} *Vital.Data.Counter-term-count-dict*
+ A {count-dict} is |Dictionary| whose value is |Number|.
+ e.g. {'a': 1, 'b': 2, 'c': 3}
+
+{Counter} *Vital.Data.Counter-term-Counter*
+ A {Counter} is Counter Object |Vital.Data.Counter-Counter|.
+
+==============================================================================
+INTERFACE *Vital.Data.Counter-interface*
+------------------------------------------------------------------------------
+FUNCTIONS *Vital.Data.Counter-functions*
+
+new({countable}) *Vital.Data.Counter.new()*
@thinca
thinca Dec 9, 2015 Member

引数省略可の場合は [{countable}] で。

@thinca
Member
thinca commented Dec 9, 2015

.update() は上書きされる印象がありますね。個人的には .add() の方がわかりやすいかなと思います。(Python のことは忘れる…)

@lambdalisue lambdalisue and 1 other commented on an outdated diff Dec 11, 2015
doc/vital-data-counter.txt
+Counter.values() *Vital.Data.Counter-Counter.values()*
+ Returns list of count in the counter. The order is arbitrary.
+>
+ let s:c = s:Counter.new('ABCABC')
+ echo sort(s:c.values())
+ " => [2, 2, 2]
+<
+Counter.in({expr}) *Vital.Data.Counter-Counter.in()*
+ Returns the Number 1 if the given element is in the counter, zero
+ otherwise.
+>
+ let s:c = s:Counter.new('ABCABC')
+ echo s:c.in('A')
+ " => 1
+ echo s:c.in('D')
+ " => 1
@haya14busa
haya14busa Dec 19, 2015 Member

thanks for the catch!

@lambdalisue
Member

Python のことを忘れて add に一票

@lambdalisue
Member

-c.keep_negative() にする(非破壊・反転)でいかが?
あと

add --- subtract     : マイナス値を考慮
increase -- decrease : マイナス値を排除

などは?

@thinca
Member
thinca commented Dec 19, 2015

ping

@haya14busa
Member

ちょっと放置しててすいませんでした 🙇

  • s/update/add/g しました
  • -c の存在完全にわすれてましたが .keep_positiveは破壊的なのでやるとしても破壊的に実装するかkeep_positiveごと変更ですかね.非破壊の場合なんとなくkeepはわかりずらい感もある
  • increase/decreaseはなんとなく英語的に違和感ある気がするけどよくわからない力. 引数が増えるのではなくself(主語的な?)が増えるわけだし
@haya14busa
Member

-cの件は符号を反転するのが面倒なのが問題だと思ったので.reverse()足しました.
0以下を削除したい場合は.keep_positive()と組み合わせればok

@thinca
Member
thinca commented Dec 19, 2015

Assert Equals() の引数の順序が軒並み逆と言う衝撃の事実。

@haya14busa
Member

ハッ...バレタ...というか思い出した...!!!!

書いてる途中で逆なことに気づいたんですが最後に機械的に反転させようとおもったんでした.そして忘れてた 💦

原因としては Data.List のテストの最初の方が実は逆になってるっぽくて参考にしてしまってミスったので別PRかなんかでなおそうかと思います

@haya14busa
Member

引数の順序修正ed

@thinca
Member
thinca commented Dec 20, 2015

ちょー細かいんですが、各テストのタイトルの最後に . があったりなかったりするのが気になりました (統一するならない方に一票)

@haya14busa
Member

ふぃっくすed & ついでにrebased

@thinca thinca commented on an outdated diff Dec 20, 2015
test/Data/Counter.vimspec
+ End
+
+ It returns negative count from counter
+ let c = Counter.new({'a': -1})
+ Assert Equals(c.get('a'), -1)
+ End
+ End
+
+ Describe .set()
+ It set count
+ let c = Counter.new()
+ call c.set('l', 1)
+ Assert Equals(c.get('l'), 1)
+ End
+
+ It override count
@thinca
thinca Dec 20, 2015 Member

overwrites かな?

@thinca thinca commented on an outdated diff Dec 20, 2015
test/Data/Counter.vimspec
+ Assert Equals(c.to_dict(), {'1': 1, '2': 2, '3': 3})
+ End
+
+ It adds a new counter from a string list
+ let c = Counter.new()
+ call c.add(['a', 'b', 'b'])
+ Assert Equals(c.to_dict(), {'a': 1, 'b': 2})
+ End
+
+ It adds a new counter from a funcref list
+ let c = Counter.new()
+ call c.add([function('function'), function('tr'), function('tr')])
+ Assert Equals(c.to_dict(), {'function(''tr'')': 2, 'function(''function'')': 1})
+ End
+
+ It add a new counter from a dictionary
@thinca thinca commented on an outdated diff Dec 20, 2015
test/Data/Counter.vimspec
+ Assert Equals(c.to_dict(), {'a': 1, 'b': 2})
+ End
+
+ It adds a new counter from a funcref list
+ let c = Counter.new()
+ call c.add([function('function'), function('tr'), function('tr')])
+ Assert Equals(c.to_dict(), {'function(''tr'')': 2, 'function(''function'')': 1})
+ End
+
+ It add a new counter from a dictionary
+ let c = Counter.new()
+ call c.add({'A': 2, 'B': -1, 'C': 0})
+ Assert Equals(c.to_dict(), {'A': 2, 'B': -1, 'C': 0})
+ End
+
+ It add a new counter from a counter
@thinca thinca commented on an outdated diff Dec 20, 2015
test/Data/Counter.vimspec
+ End
+
+ Describe .union()
+ It returns a counter of the maximum of value in either of the input
+ let c1 = Counter.new('abbb')
+ let c2 = Counter.new('bcc')
+ Assert Equals(c1.union(c2).to_dict(), {'b': 3, 'c': 2, 'a': 1})
+ End
+
+ It keeps only positive count
+ let c1 = Counter.new({'a': 1, 'b': 0})
+ let c2 = Counter.new({'c': -1})
+ Assert Equals(c1.union(c2).to_dict(), {'a': 1})
+ End
+
+ It should handle dictionary as an argument
@thinca
thinca Dec 20, 2015 Member

should いらなそう。

@thinca thinca commented on an outdated diff Dec 20, 2015
test/Data/Counter.vimspec
+ It should handle dictionary as an argument
+ let c1 = Counter.new({'a': 1, 'b': 0})
+ let c2 = {'a': 2, 'c': -1}
+ Assert Equals(c1.intersection(c2).to_dict(), {'a': 1})
+ End
+ End
+
+ Describe .clear()
+ It resets all counts
+ let c = Counter.new('abbb')
+ call c.clear()
+ Assert Equals(c.to_dict(), {})
+ End
+ End
+
+ Describe elements()
@thinca
thinca Dec 20, 2015 Member

.elements()

@haya14busa haya14busa Data.Counter: Fix English
3f9551f
@thinca
Member
thinca commented Dec 20, 2015

操作系の機能のほとんどは破壊的っぽいけど、.union().intersection() だけは非破壊かな? だとしたらその説明とテストがあるとベンリそう。

@haya14busa
Member

たしかにっ! 修正ed

@thinca
Member
thinca commented Dec 20, 2015

完全に LGTM 👍 🍣

@haya14busa
Member

アッ最後のcommitいらんto_dictあるので消します

@haya14busa
Member

fixed

@haya14busa haya14busa Data.Counter: .union() and .intersection() are non-destructive
ac7f3e5
@thinca thinca merged commit b9b7f8e into vim-jp:master Dec 28, 2015

2 checks passed

continuous-integration/appveyor/pr AppVeyor build succeeded
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
@haya14busa haya14busa deleted the haya14busa:data-counter branch Dec 28, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment