【问题标题】:Match at least 3 words in any order from some 5 words从大约 5 个单词中以任意顺序匹配至少 3 个单词
【发布时间】:2018-06-02 21:41:53
【问题描述】:

我有一组词:

"dog", "car", "house", "work", "cat"

我需要能够在一个文本中至少匹配其中的 3 个,例如:

"I always let my cat and dog at the animal nursery when I go to work by car"

这里我要匹配正则表达式,因为它至少匹配 3 个单词(这里是 4 个单词):

"cat", "dog", "car" and "work"

编辑 1

我想将它与 Oracleregexp_like 函数一起使用

编辑 2

我还需要它来处理连续的单词

【问题讨论】:

  • 我想和oracle的regexp_like函数一起使用
  • 请澄清“dog dog dog”是否应该匹配(到目前为止的所有答案都与此匹配)
  • 解决方案必须使用regexp_like吗?
  • 或任何可以完成工作的 oracle 正则表达式函数
  • 您需要完全匹配的单词吗?如果您的输入字符串包含单词“dogs”怎么办 - 是否匹配“dog”?大写怎么样:'Work is great......' - 'Work' 匹配 'work' 吗?复合词怎么样 - “山茱萸”(一种树)匹配“狗”吗? 'doghouse' 怎么样 - 这算两次(它同时匹配 'dog' 和 'house')?你有没有想过这些问题?如果你没有,在你考虑任何可能的解决方案之前,你不需要吗?

标签: regex oracle string-matching regexp-like


【解决方案1】:

由于Oracle的regexp_like不支持非捕获组和字边界,可以使用以下表达式:

^((.*? )?(dog|car|house|work|cat)( |$)){3}.*$

Try it out here.

或者,一个更大但可以说更清洁的解决方案是:

^(.*? )?(dog|car|house|work|cat) .*?(dog|car|house|work|cat) .*?(dog|car|house|work|cat)( .*)?$

Try it out here.

注意:它们都将匹配多次使用的同一个词,例如“狗狗狗”。

编辑:为了解决标点符号的问题,可以进行一些小的修改。它并不完美,但应该匹配 99% 涉及标点符号的情况(但不匹配,例如 !dog):

^((.*? )?(dog|car|house|work|cat)([ ,.!?]|$)){3}.*$

Try it out here

【讨论】:

  • @CallumWatkins 该链接实际上是针对 OP 的。但它是在 Oracle 上使用 REGEXP_LIKE 可用的正则表达式选项列表
  • @YassinHajaj 我明白了,你知道为什么我的表达式可能在 oracle 环境中自己不起作用吗?
  • @FrancisEisenhower 我正在用 4 对其进行测试,它仍然有效,您可以尝试将 {3} 更改为 {3,},但我认为您不必这样做。它适用于 3 个或更多。
  • 最后一个正则表达式似乎工作正常。 SQLFiddle here。祝你好运。
【解决方案2】:

这是一个不使用正则表达式的解决方案,将排除重复的单词,并且可以将要匹配的单词作为集合中的绑定参数传入:

SQL Fiddle

Oracle 11g R2 架构设置

创建一个集合类型来存储单词列表:

CREATE TYPE StringList IS TABLE OF VARCHAR2(50)
/

创建一个 PL/SQL 函数将分隔字符串拆分到集合中:

CREATE OR REPLACE FUNCTION split_String(
  i_str    IN  VARCHAR2,
  i_delim  IN  VARCHAR2 DEFAULT ','
) RETURN StringList DETERMINISTIC
AS
  p_result       StringList := StringList();
  p_start        NUMBER(5) := 1;
  p_end          NUMBER(5);
  c_len CONSTANT NUMBER(5) := LENGTH( i_str );
  c_ld  CONSTANT NUMBER(5) := LENGTH( i_delim );
BEGIN
  IF c_len > 0 THEN
    p_end := INSTR( i_str, i_delim, p_start );
    WHILE p_end > 0 LOOP
      p_result.EXTEND;
      p_result( p_result.COUNT ) := SUBSTR( i_str, p_start, p_end - p_start );
      p_start := p_end + c_ld;
      p_end := INSTR( i_str, i_delim, p_start );
    END LOOP;
    IF p_start <= c_len + 1 THEN
      p_result.EXTEND;
      p_result( p_result.COUNT ) := SUBSTR( i_str, p_start, c_len - p_start + 1 );
    END IF;
  END IF;
  RETURN p_result;
END;
/

创建一些测试数据:

CREATE TABLE test_data ( value ) AS
SELECT 'I always let my cat and dog at the animal nursery when I go to work by car' FROM DUAL UNION ALL
SELECT 'dog dog foo bar dog' FROM DUAL
/

查询 1

SELECT *
FROM   test_data
WHERE  CARDINALITY(
         split_string( value, ' ' )    -- Split the string into a collection
         MULTISET INTERSECT            -- Intersect it with the input words
         StringList( 'dog', 'car', 'house', 'work', 'cat' )
       ) >= 3                          -- Check that the size of the intersection
                                       -- is at least 3 items.

Results

|                                                                      VALUE |
|----------------------------------------------------------------------------|
| I always let my cat and dog at the animal nursery when I go to work by car |

【讨论】:

  • 这是一个非常好的解决方案,在功能性和可读性方面都将击败任何正则表达式。
  • 此解决方案与所有其他解决方案一样,忽略了“什么构成了精确的单词匹配”这一微妙问题。例如,它只会在短语“我的兄弟有猫、狗和汽车”中找到一个单词匹配。 - 因为相关的标记将是“cat”(带有逗号)和“car”。 (带句号)。在此示例中,要求很可能是“匹配”三个单词。再多巧妙的代码编写也无法弥补需求的不明确性。
【解决方案3】:

忽略我在原始帖子下的评论中提出的问题,这是解决问题的一种简单方法,使用连接和聚合(使用 HAVING 条件)。请注意,输入中像doghouse 这样的词将同时匹配doghouse 等。(请阅读我在原帖下的评论!)

在下面的查询中,输入短语和要匹配的单词都被硬编码在分解子查询中(WITH 子句)。在严肃的环境中,两者都应该在基表中,或者作为输入变量等提供。

我展示了如何使用标准字符串比较运算符LIKE。这可以更改为REGEXP_LIKE,但这通常是不需要的(而且确实是个坏主意)。但是如果需要区分'dog'和'dogs'(和'dogwood'),或者需要不区分大小写的比较等,可以使用REGEXP_LIKE。该解决方案的重点是您无需担心匹配三个不同的单词;如果你知道如何匹配一个(是否需要全词匹配,大小写是否重要等),那么你也可以在相同的规则下轻松匹配三个单词。

with
  inputs ( input_phrase ) as (
    select
  'I always let my cat and dog at the animal nursery when I go to work by car'
    from   dual
  ),
  words ( word_to_match) as (
    select 'dog'   from dual union all
    select 'car'   from dual union all
    select 'house' from dual union all
    select 'work'  from dual union all
    select 'cat'   from dual
  )
select   input_phrase
from     inputs inner join words 
                on input_phrase like '%' || word_to_match || '%'
group by input_phrase
having   count(*) >= 3
;

INPUT_PHRASE                                                              
--------------------------------------------------------------------------
I always let my cat and dog at the animal nursery when I go to work by car

【讨论】:

  • 又好又简单..应该比其他人表现更好。
  • I always keep my carrot houseplant at the cattery 也会被匹配。
  • @MT0 - 我在对 OP 的评论中说了同样的话 - 我用“山茱萸”作为类似于你的“胡萝卜”的例子。但是,“仅匹配整个单词”是一个不完整的说明;想要的单词的复数形式怎么样,可以吗?大写怎么写?等等。正如我在回答中明确指出的那样:如果您知道如何满足所有这些附加要求(使用 REGEXP 或任何其他方式),那么您可以使用我展示的技术从匹配一个单词扩展到匹配多达需要。因为那是我唯一的目标,所以我不担心其他的。
【解决方案4】:

以下解决方案将排除重复匹配,不使用正则表达式(尽管您可以根据需要),并且不使用 PL/SQL。

WITH match_list ( match_word ) AS (
    SELECT 'dog' AS match_word FROM dual
     UNION ALL
    SELECT 'work' FROM dual
     UNION ALL
    SELECT 'car' FROM dual
     UNION ALL
    SELECT 'house' FROM dual
     UNION ALL
    SELECT 'cat' FROM dual
)
SELECT phrase, COUNT(*) AS unique_match_cnt, SUM(match_cnt) AS total_match_cnt
     , LISTAGG(match_word, ',') WITHIN GROUP ( ORDER BY match_word ) AS unique_matches
  FROM (
    SELECT pt.phrase, ml.match_word, COUNT(*) AS match_cnt
      FROM phrase_table pt INNER JOIN match_list ml
        ON ' ' || LOWER(pt.phrase) || ' ' LIKE '%' || ml.match_word || '%'
     GROUP BY pt.phrase, ml.match_word
) GROUP BY phrase
HAVING COUNT(*) >= 3;

关键是将要匹配的单词放入表或公用表表达式/子查询中。如果您愿意,可以使用REGEXP_LIKE() 代替LIKE,尽管我认为那样会更贵。如果您未使用 Oracle 11g 或更高版本,或者您实际上不需要知道匹配了哪些单词,请跳过 LISTAGG(),如果您想要区分大小写的匹配,请跳过 LOWER()

【讨论】:

    【解决方案5】:

    如果你不需要匹配不同的单词。

    (?:\b(?:dog|car|house|work|cat)\b.*?){3}
    

    我不知道这是否适用于您的环境。

    编辑:我没有看到几乎像这个一样的另一个答案。

    【讨论】:

    • 对不起,这与 oracle 的 regexp_like 函数不匹配
    • 遗憾的是 Oracle 正则表达式不支持单词边界。
    猜你喜欢
    • 1970-01-01
    • 2022-01-18
    • 2017-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多