rime / librime

Rime Input Method Engine, the core library
https://rime.im
BSD 3-Clause "New" or "Revised" License
3.39k stars 556 forks source link

如何进行输入统计? #90

Open Axure opened 8 years ago

Axure commented 8 years ago

如果我要进行这方面的开发,应该从哪里入手呢?

boboIqiqi commented 8 years ago

顶一个,我也想了解。 @lotem @Prcuvu 求指教

现在都说什么大数据,机器学习 希望Rime可以像添雨跟打器一样,五笔,打字速度,统计平均码长,回退键次数。 还有QQ五笔,可以统计哪些词组词库里有,但是输入时单字输入

还可以看看这个 个人大数据时代的到来 - 简书

lotem commented 8 years ago

第一步是確定需求吧。有一些是可以從用戶詞典裏挖掘的,但是想要更多信息和更精細的控制,肯定要切入到輸入法引擎的內部。

Engine 大致就是所謂「輸入法引擎」。處理按鍵導致一系列內部狀態更新的事件,引擎監聽這些事件並完成相應的動作,如輸入碼變化則重新轉換,又如選詞、上屏、輸入法開關狀態變化等。 https://github.com/rime/librime/blob/develop/src/engine.cc#L71

需要寫一個用作統計的組件,在 Engine 加載時預先加載爲 processor,但他並不響應任何按鍵,只是監聽以上各種事件,完成統計。 https://github.com/rime/librime/blob/develop/src/engine.cc#L287

現在 librime 公開的 API 並沒有暴露很多內部的實現。雖然支持寫插件(以動態鏈接庫形式提供新的組件定義),但尚不能暗中把某個輸入方案中未指明的組件注入到引擎,所以要動一動源碼。

boboIqiqi commented 8 years ago

谢谢@lotem 大大。还要您现场讲解代码。 等我把环境配好了,有问题再向您请教吧。

ipcjs commented 6 years ago

看起来忘记这茬子事了😅

ddkk3000 commented 5 years ago

其实这个功能做成一个单独的软件就足够了呀。不需要集成在软件里面。不过我纳闷的是为什么没有单独的这样的软件。令人感到很奇怪。

choyri commented 1 year ago

@ddkk3000 其实这个功能做成一个单独的软件就足够了呀。不需要集成在软件里面。不过我纳闷的是为什么没有单独的这样的软件。令人感到很奇怪。

因为涉及到隐私问题,独立软件如果不开源往往不值得信赖;而输入统计,我个人认为最合适的地方就是在输入法本身这一层来做。

七年过去了,这个议题好像还是没有动静,很希望能有这个功能模块(插件) 🥲

ksqsf commented 10 months ago

抛砖引玉,试用 Lua processor 做了一个 非常粗糙的 初步的实现,只能用来感受一下粗略的打字速度。因为没处理 Backspace,实际上会高估速度。

image

遗憾的是因为 os.time() 的精度只能到秒,所以不能在每个 key event 时进行统计,不然时间可能会被统计成 0。

目前做法是在每次 commit 时,把 commit 的字符数量和时间戳一并保存到一个全局列表中:

local Queue = {}

function Queue.new()
   local ret = {first=1, last=0}
   ret.CountCharsWithinSecs = function(self, secs)
      local ret = 0
      local now = os.time()
      for _, entry in Queue.iter(self) do
         if os.difftime(now, entry[1]) <= secs then
            ret = ret + entry[2]
         end
      end
      return ret
   end
   return ret
end

function Queue.push(q, value)
   q[q.last+1] = value
   q.last = q.last + 1
end

function Queue.empty(q)
   return q.last < q.first
end

function Queue.len(q)
   return q.last - q.first + 1
end

function Queue.pop(q)
   if q.last < q.first then
      return nil
   else
      local ret = q[q.first]
      q[q.first] = nil
      q.first = q.first + 1
      return ret
   end
end

function Queue.top(q)
   if Queue.empty(q) then
      return nil
   else
      return q[q.first]
   end
end

function Queue.next(q, i)
   if i <= q.last then
      return i+1, q[i]
   else
      return nil
   end
end

function Queue.iter(q)
   return Queue.next, q, q.first
end

local Module = {}

global_stats = Queue.new()

function Module.init(env)
   global_stats = Queue.new()

   local function on_commit(ctx)
      local commit_text = ctx:get_commit_text()
      local now = os.time()

      -- prune old entries.
      while not Queue.empty(global_stats) and os.difftime(now, Queue.top(global_stats)[1]) > 3600 do
         Queue.pop(global_stats)
      end

      if Queue.empty(global_stats) then
         -- mitigate overflows.
         global_stats = Queue.new()
      end

      Queue.push(global_stats, { now, utf8.len(commit_text) })
   end

   env.stat_commit_notifier = env.engine.context.commit_notifier:connect(on_commit, 0)
end

function Module.fini(env)
   env.stat_commit_notifier:disconnect()
end

function Module.func(event, env)
   return 2  -- kNoop
end

return Module

(使用了 connect 的 group 参数,依赖于该PR: https://github.com/hchunhui/librime-lua/pull/271

这个 processor 须放在 processor 列表较开头的位置。

在 translator 中,可以利用该 processor 记录的信息计算一段时间内的打字速度:

   if global_stats and (input == "osd") then
      yield(Candidate("stat", seg.start, seg._end, tostring(global_stats:CountCharsWithinSecs(60)) .. '字/min', '1分鐘'))
      return
   end
ipcjs commented 9 months ago

@ksqsf 最新版的鼠须管(0.16.2),似乎还不支持这个group参数?

ksqsf commented 9 months ago

@ksqsf 最新版的鼠须管(0.16.2),似乎还不支持这个group参数?

是的,需要自己编译。

yjnu commented 1 week ago

24 年底了,不知道还会不会做这个功能,输入统计,我有时想看今天打了多少字,rime 累计打了多少字,打字速度等。