content_moderation_keyword_matcher.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. package service
  2. import (
  3. "strings"
  4. )
  5. type contentModerationKeywordMatcher struct {
  6. nodes []contentModerationKeywordNode
  7. edges []contentModerationKeywordEdge
  8. rootTransitions [256]int32
  9. keywords []string
  10. }
  11. type contentModerationKeywordNode struct {
  12. failure int32
  13. bestKeyword int32
  14. edgeStart uint32
  15. edgeCount uint16
  16. }
  17. type contentModerationKeywordEdge struct {
  18. target int32
  19. label byte
  20. }
  21. type contentModerationKeywordBuildEdge struct {
  22. target int32
  23. nextSibling int32
  24. label byte
  25. }
  26. func newContentModerationKeywordMatcher(keywords []string) *contentModerationKeywordMatcher {
  27. if len(keywords) == 0 {
  28. return nil
  29. }
  30. buildNodes := []contentModerationKeywordNode{newContentModerationKeywordNode()}
  31. buildEdges := make([]contentModerationKeywordBuildEdge, 0)
  32. originalKeywords := append([]string(nil), keywords...)
  33. for keywordIndex, keyword := range keywords {
  34. if keyword == "" {
  35. continue
  36. }
  37. state := int32(0)
  38. for _, label := range []byte(strings.ToLower(keyword)) {
  39. next := contentModerationKeywordBuildTransition(buildNodes, buildEdges, state, label)
  40. if next < 0 {
  41. next = int32(len(buildNodes))
  42. buildNodes = append(buildNodes, newContentModerationKeywordNode())
  43. buildEdges = append(buildEdges, contentModerationKeywordBuildEdge{
  44. target: next,
  45. nextSibling: contentModerationKeywordBuildFirstEdge(buildNodes[state]),
  46. label: label,
  47. })
  48. buildNodes[state].edgeStart = uint32(len(buildEdges))
  49. }
  50. state = next
  51. }
  52. if current := buildNodes[state].bestKeyword; current < 0 || int32(keywordIndex) < current {
  53. buildNodes[state].bestKeyword = int32(keywordIndex)
  54. }
  55. }
  56. if len(buildNodes) == 1 {
  57. return nil
  58. }
  59. queue := make([]int32, 0, len(buildNodes)-1)
  60. var rootTransitions [256]int32
  61. for edgeIndex := contentModerationKeywordBuildFirstEdge(buildNodes[0]); edgeIndex >= 0; edgeIndex = buildEdges[edgeIndex].nextSibling {
  62. edge := buildEdges[edgeIndex]
  63. rootTransitions[edge.label] = edge.target
  64. queue = append(queue, edge.target)
  65. }
  66. for queueIndex := 0; queueIndex < len(queue); queueIndex++ {
  67. state := queue[queueIndex]
  68. for edgeIndex := contentModerationKeywordBuildFirstEdge(buildNodes[state]); edgeIndex >= 0; edgeIndex = buildEdges[edgeIndex].nextSibling {
  69. edge := buildEdges[edgeIndex]
  70. failure := buildNodes[state].failure
  71. fallback := contentModerationKeywordBuildTransition(buildNodes, buildEdges, failure, edge.label)
  72. for fallback < 0 && failure != 0 {
  73. failure = buildNodes[failure].failure
  74. fallback = contentModerationKeywordBuildTransition(buildNodes, buildEdges, failure, edge.label)
  75. }
  76. if fallback >= 0 {
  77. buildNodes[edge.target].failure = fallback
  78. }
  79. buildNodes[edge.target].bestKeyword = minKeywordIndex(
  80. buildNodes[edge.target].bestKeyword,
  81. buildNodes[buildNodes[edge.target].failure].bestKeyword,
  82. )
  83. queue = append(queue, edge.target)
  84. }
  85. }
  86. edges := make([]contentModerationKeywordEdge, 0, len(buildEdges))
  87. var outgoing [256]contentModerationKeywordEdge
  88. for nodeIndex := range buildNodes {
  89. count := 0
  90. for edgeIndex := contentModerationKeywordBuildFirstEdge(buildNodes[nodeIndex]); edgeIndex >= 0; edgeIndex = buildEdges[edgeIndex].nextSibling {
  91. edge := buildEdges[edgeIndex]
  92. outgoing[count] = contentModerationKeywordEdge{target: edge.target, label: edge.label}
  93. count++
  94. }
  95. for index := 1; index < count; index++ {
  96. current := outgoing[index]
  97. insertAt := index
  98. for insertAt > 0 && current.label < outgoing[insertAt-1].label {
  99. outgoing[insertAt] = outgoing[insertAt-1]
  100. insertAt--
  101. }
  102. outgoing[insertAt] = current
  103. }
  104. buildNodes[nodeIndex].edgeStart = uint32(len(edges))
  105. buildNodes[nodeIndex].edgeCount = uint16(count)
  106. edges = append(edges, outgoing[:count]...)
  107. }
  108. return &contentModerationKeywordMatcher{
  109. nodes: buildNodes,
  110. edges: edges,
  111. rootTransitions: rootTransitions,
  112. keywords: originalKeywords,
  113. }
  114. }
  115. func newContentModerationKeywordNode() contentModerationKeywordNode {
  116. return contentModerationKeywordNode{bestKeyword: -1}
  117. }
  118. func contentModerationKeywordBuildFirstEdge(node contentModerationKeywordNode) int32 {
  119. if node.edgeStart == 0 {
  120. return -1
  121. }
  122. return int32(node.edgeStart - 1)
  123. }
  124. func contentModerationKeywordBuildTransition(
  125. nodes []contentModerationKeywordNode,
  126. edges []contentModerationKeywordBuildEdge,
  127. state int32,
  128. label byte,
  129. ) int32 {
  130. if state < 0 || int(state) >= len(nodes) {
  131. return -1
  132. }
  133. for edgeIndex := contentModerationKeywordBuildFirstEdge(nodes[state]); edgeIndex >= 0; edgeIndex = edges[edgeIndex].nextSibling {
  134. if edges[edgeIndex].label == label {
  135. return edges[edgeIndex].target
  136. }
  137. }
  138. return -1
  139. }
  140. func minKeywordIndex(left, right int32) int32 {
  141. if left < 0 {
  142. return right
  143. }
  144. if right < 0 || left < right {
  145. return left
  146. }
  147. return right
  148. }
  149. func (m *contentModerationKeywordMatcher) Match(text string) (string, bool) {
  150. if m == nil || text == "" || len(m.nodes) == 0 || len(m.keywords) == 0 {
  151. return "", false
  152. }
  153. lower := strings.ToLower(text)
  154. state := int32(0)
  155. bestKeyword := int32(-1)
  156. for index := 0; index < len(lower); index++ {
  157. label := lower[index]
  158. for {
  159. next := m.next(state, label)
  160. if next != 0 {
  161. state = next
  162. break
  163. }
  164. if state == 0 {
  165. break
  166. }
  167. state = m.nodes[state].failure
  168. }
  169. bestKeyword = minKeywordIndex(bestKeyword, m.nodes[state].bestKeyword)
  170. if bestKeyword == 0 {
  171. return m.keywords[0], true
  172. }
  173. }
  174. if bestKeyword < 0 || int(bestKeyword) >= len(m.keywords) {
  175. return "", false
  176. }
  177. return m.keywords[bestKeyword], true
  178. }
  179. func (m *contentModerationKeywordMatcher) next(state int32, label byte) int32 {
  180. if state == 0 {
  181. return m.rootTransitions[label]
  182. }
  183. if state < 0 || int(state) >= len(m.nodes) {
  184. return 0
  185. }
  186. node := m.nodes[state]
  187. left := int(node.edgeStart)
  188. right := left + int(node.edgeCount)
  189. for left < right {
  190. middle := left + (right-left)/2
  191. edge := m.edges[middle]
  192. if edge.label < label {
  193. left = middle + 1
  194. continue
  195. }
  196. right = middle
  197. }
  198. end := int(node.edgeStart) + int(node.edgeCount)
  199. if left < end && m.edges[left].label == label {
  200. return m.edges[left].target
  201. }
  202. return 0
  203. }