MATLAB:字符串的单元格数组之间匹配的单词

时间:2011-11-08 11:36:17

标签: string algorithm matlab comparison vectorization

我正在尝试解决以下问题,我需要尽可能高效地进行此操作(即尽量避免循环)。

我有两个单元格阵列,即A和B. A和B的每个单元格都包含一串字符。这些字符串的长度是可变的。让我们说:

A={‘life is wonderful’, ‘matlab makes your dreams come true’};

B={‘life would be meaningless without wonderful matlab’, ‘what a wonderful world’, ‘the shoemaker makes shoes’, ‘rock and roll baby’};

此外,单元阵列B的元素数量比单元阵列A大三个数量级。

我的目标是找到A中每个字符串中每个字符串的字数也出现在B的每个字符串中。

对于前面的示例,合适的结果可能是:

match = [2 1 0 0
1 0 1 0]

第一行表示A的第一个字符串中有多少单词出现在B的四个字符串中。第二行表示A的第二个字符串相同。

双循环实现很简单,但非常耗时,特别是因为单元阵列B的长度(超过300万个单元)。

有什么想法吗?非常感谢你。

3 个答案:

答案 0 :(得分:2)

让我通过发布“直截了当”的解决方案(至少其他人有一个基线进行比较)来开始:

A = {'life is wonderful', 'matlab makes your dreams come true'};
B = {'life would be meaningless without wonderful matlab', 'what a wonderful world', 'the shoemaker makes shoes', 'rock and roll baby'};

count = zeros(numel(A),numel(B));

%# for each string
for i=1:numel(A)
    %# split into words
    str = textscan(A{i}, '%s', 'Delimiter',' '); str = str{1};

    %# for each word
    for j=1:numel(str)
        %# count occurences
        count(i,:) = count(i,:) + cellfun(@numel, strfind(B,str{j}));
    end
end

结果:

>> count
count =
     2     1     0     0
     1     0     1     0

更好的算法可能是构建某种索引或哈希表...

答案 1 :(得分:2)

解决方案并不复杂。

将句子分开:

a_words = regexp(A,'(\w+)','match')
b_words = regexp(B,'(\w+)','match')

然后循环比较:

match = nan(numel(a_words),numel(b_words));
for i = 1:numel(a_words)
    for j = 1:numel(b_words)
        match(i,j) = sum(ismember(a_words{i},b_words{j}));
    end
end

但为了更快 - 我不太确定。 你肯定可以把内循环放在一个应该并行化的parfor中。 如果它真的很多话可能会把它们放在数据库中。那将为你做索引。

答案 2 :(得分:1)

您可以利用Map,它为您提供基于字典的高效结构:

对于每个单词,保存显示每个字符串中出现次数的向量:

A = {'life is wonderful', 'matlab makes your dreams come true'};
B = {'life would be meaningless without wonderful matlab', 'what a wonderful world', 'the shoemaker makes shoes', 'rock and roll baby'};

mapA = containers.Map();
sizeA = size(A,2);
for i = 1:size(A,2)         % for each string
    a = regexpi(A(i),'\w+','match');
    for w = a{:}                % for each word extracted
        str = cell2mat(w);
        if(mapA.isKey(str))     % if word already indexed
            occ = mapA(str);
        else                    % new key
            occ = zeros(1,sizeA);
        end
        occ(i) = occ(i)+1;
        mapA(str) = occ;
    end
end

% same for B
mapB = containers.Map();
sizeB = size(B,2);
for i = 1:size(B,2) 
    a = regexpi(B(i),'\w+','match');
    for w = a{:}
        str = cell2mat(w);
        if(mapB.isKey(str))
            occ = mapB(str);
        else
            occ = zeros(1,sizeB);
        end
        occ(i) = occ(i)+1;
        mapB(str) = occ;
    end
end

然后,对于在A中找到的每个唯一单词,计算与B的匹配

match = zeros(size(A,2),size(B,2));
for w = mapA.keys
    str = cell2mat(w);
    if (mapB.isKey(str))
        match = match + diag(mapA(str))*ones(size(match))*diag(mapB(str));
    end
end

结果:

match =

     2     1     0     0
     1     0     1     0
这样你就有了#wordsA + #wordsB + #singleWordsA的复杂性,而不是#wordsA *#wordsB

编辑:或者,如果您不喜欢Map,则可以按字母顺序向量保存单词出现向量。然后你可以同时检查两个向量:

(假设我们使用的结构中w属性是单词字符串,而occ是出现向量)

i = 1; j = 1;
while(i<=size(wordsA,2) && i<=size(wordsB,2))
if(strcmp(wordsA(i).w, wordsB(j).w))
    % update match
else
    if(before(wordsA(i).w, wordsA(i).w)) % before: fancy function returning 1 if the first argument comes (alphabetically) before the second one (no builtin function comes to my mind)
        i = i+1;
    else
        j = j+1;
    end
end

如果你正在寻找'matlab'并且你知道在第10个位置存储'生命'是没有用的来检查之前的位置,因为矢量按字母顺序排列。所以我们有#wordsA +#wordsB迭代与嵌套循环解决方案的#wordsA * #wordsB。