search.scm 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. (library (search)
  2. (export search
  3. searches
  4. string-contains-whole-word)
  5. (import (except (rnrs base) vector-map)
  6. (only (guile)
  7. lambda* λ
  8. ;; string
  9. string-downcase
  10. string-suffix?
  11. string-prefix?
  12. string-contains
  13. display
  14. simple-format)
  15. ;; lists
  16. (srfi srfi-1)
  17. ;; vectors
  18. (srfi srfi-43)
  19. ;; custom
  20. (alist-utils)
  21. (vector-utils)
  22. (model)
  23. (contract))
  24. ;; idea: search should return a vocabulary
  25. ;; if anything contains the string
  26. ;; if anything contains the string as a whole word
  27. ;; ignored attributes for search
  28. ;; string edit distance
  29. (define-with-contract string-contains-whole-word
  30. (require (string? str)
  31. (string? seeked))
  32. (ensure (boolean? <?>))
  33. (lambda* (str seeked #:key (ignore-case #t))
  34. (let ([str (if ignore-case (string-downcase str) str)]
  35. [seeked (if ignore-case (string-downcase seeked) seeked)])
  36. (or (string=? str seeked)
  37. (string-suffix? (string-append " " seeked) str)
  38. (string-prefix? (string-append seeked " ") str)
  39. ;; string-contains returns an index in case of the string
  40. ;; containing the searched string or false otherwise. `or`
  41. ;; will take it as a truthy value, but we really want a
  42. ;; boolean.
  43. (number?
  44. (string-contains str (string-append " " seeked " ")))))))
  45. (define default-search-attr-refs
  46. '(("metadata" "description")
  47. ("metadata" "tags")
  48. ("metadata" "usage-examples")
  49. ("translation-data" "native")
  50. ("translation-data" "phonetic-script")
  51. ("translation-data" "simplified")
  52. ("translation-data" "traditional")))
  53. (define-with-contract in-vector-attribute?
  54. (require (vector? attr-val)
  55. (procedure? contains?))
  56. (ensure (boolean? <?>))
  57. (λ (attr-val search-term contains?)
  58. (vector-fold (λ (ind cur acc) (or acc cur))
  59. #f
  60. (vector-map (λ (index element)
  61. (contains? element search-term))
  62. attr-val))))
  63. (define-with-contract search
  64. (require (vocabulary? voc)
  65. (string? term))
  66. (ensure (vocabulary? <?>))
  67. (lambda* (voc term
  68. #:key
  69. (whole-word #f)
  70. (ignore-case #t)
  71. (attr-refs default-search-attr-refs))
  72. "Search for words in the vocabulary."
  73. (let ([string-compare
  74. (if whole-word
  75. (λ (str seeked)
  76. (string-contains-whole-word str seeked #:ignore-case ignore-case))
  77. ;; string-contains returns an index in case of the
  78. ;; string containing the searched string or false
  79. ;; otherwise. `or` will take it as a truthy value,
  80. ;; but we really want a boolean.
  81. (λ (string sought)
  82. (number?
  83. (string-contains string sought))))])
  84. (vocabulary-filter voc
  85. (λ (words)
  86. (vector-filter
  87. (λ (_index word)
  88. (reduce (λ (cur acc) (or acc cur))
  89. #f
  90. (map (λ (attr-ref)
  91. (let ([attr-val (alist-refs word attr-ref)])
  92. (cond
  93. [(vector? attr-val)
  94. (in-vector-attribute? attr-val term string-compare)]
  95. [else
  96. (string-compare attr-val term)])))
  97. attr-refs)))
  98. words))))))
  99. (define searches
  100. (lambda* (voc
  101. #:key
  102. (whole-word #f)
  103. (ignore-case #t)
  104. (attr-refs default-search-attr-refs)
  105. .
  106. terms)
  107. (cond
  108. [(null? terms) voc]
  109. [else
  110. (apply searches
  111. (search voc
  112. (first terms)
  113. #:whole-word whole-word
  114. #:ignore-case ignore-case
  115. #:attr-refs attr-refs)
  116. (drop terms 1))]))))