合并/丢弃重叠的单词

时间:2013-06-09 18:54:02

标签: python ruby perl bash

我想合并相似的字符串(单词)(字符串在其他字符串中)。

 word
 wor 
 words
 wormhole
 hole    

会:

words
wormhole  

wor与:word重叠时,wordswormhole - wor将被丢弃;
word重叠:words - word被丢弃;
hole重叠:wormhole - hole被丢弃;
wordswormhole不重叠 - 所以他们留下来 我怎样才能做到这一点?

修改
我的解决方案是:

while read a
do  
   grep $a FILE | 
   awk 'length > m { m = length; a = $0 } END { print a }'
done < FILE | 
sort -u

但我不知道它是否会导致大型数据集出现问题。

10 个答案:

答案 0 :(得分:3)

在Ruby中:

list = %w[word wor words wormhole]

list.uniq
.tap{|a| a.reverse_each{|e| a.delete(e) if (a - [e]).any?{|x| x.include?(e)}}}

答案 1 :(得分:3)

使用足够长的单词列表,对单词的任何嵌套循环都会非常缓慢。我就是这样做的:

use strict;
use warnings;

use File::Slurp 'read_file';
chomp( my @words = read_file('/usr/share/dict/words') );

my %overlapped;
for my $word (@words) {
    $word =~ /(.*)(?{++$overlapped{$1}})(*FAIL)/;
    --$overlapped{$word};
}

print "$_\n" for grep ! $overlapped{$_}, @words;

或许可以通过Darshan Computing建议处理最长到最短的单词来改进它。

答案 2 :(得分:2)

您可以使用哈希来计算单词列表的子字符串:

use strict;
use warnings;
use feature 'say';

my %seen;                   # seen substrings
my @words;                  # original list
while (<DATA>) {            # read a new substring
    chomp;
    push @words, $_;        # store the original
    while (length) {        # while a substring remains
            $seen{$_}++;    # increase its counter
            chop;           # shorten the substring
    }
}

# All original words with count == 1 are the merged list
my @merged = grep $seen{$_} == 1, @words;

say for @merged;

__DATA__
w
word
wor
words
wormhole
hole
holes

<强>输出:

words
wormhole
holes

当然,您需要补偿大小写,标点符号和空格,因为哈希键是精确的,而键Foo与键foo不同。

答案 3 :(得分:1)

将列表理解与any / all

一起使用
>>> lis = ['word','wor', 'words', 'wormhole']
#all
>>> [x for x in lis if all(x not in y for y in lis if y != x)]
['words', 'wormhole']
#any
>>> [x for x in lis if not any(x in y for y in lis if y != x)]
['words', 'wormhole']

您也可以在此处使用marisa_trie

>>> import marisa_trie
>>> lis = ['word','wor', 'words', 'wormhole', 'hole', 'holes']
>>> def trie(lis):
        trie = marisa_trie.Trie(lis)
        return [x for x in lis if len(trie.keys(unicode(x))) ==1 ]
... 
>>> trie(lis)
['words', 'wormhole', 'holes']

答案 4 :(得分:1)

amon建议......

  

按升序对所有单词列表进行排序。如果一个单词是   下一个字的子串,丢弃当前字;继续前进。

...需要O(n log n)进行排序,我不确定Ashwini解决方案的时间复杂度,但它看起来要超过O(n log n)。

我认为这是一个O(n)解决方案......

from collections import defaultdict

words = ['word', 'wor', 'words', 'wormhole']

infinite_defaultdict = lambda: defaultdict(infinite_defaultdict)

mydict = infinite_defaultdict()
for word in words:
    d = mydict
    for char in word:
        d = d[char]

result = []
for word in words:
    d = mydict
    for char in word:
        d = d[char]
    if not d:
        result.append(word)

print result

...打印......

['words', 'wormhole']

<强>更新

  

但我不知道它是否会导致大型数据集出现问题。

为了比较,使用来自/usr/share/dict/words的10,000个单词,这需要大约70毫秒的CPU时间,而Ashwini需要大约11秒。


更新2

好。原始问题看起来好像单词在开始时只能重叠,但如果它们可以在任何地方重叠,则此代码将不起作用。我认为任何能够做到这一点的算法都会出现O(n²)的最坏情况复杂性。

答案 5 :(得分:1)

我理解你的问题是

  

给定一个单词列表,我们想要删除所有那些是其他单词的子串的单词。

这是Perl的一般解决方案:

sub weed_out {
  my @out;
  WORD:
  while (my $current = shift) {
    for (@_) {
      # skip $current word if it's a substring of any other word
      next WORD if -1 != index $_, $current;
    }
    push @out, $current;
  }
  return @out;
}

请注意shift参数数组中的@_,因此内部循环每次都会变短。

如果我们在执行内循环时遇到一个单词$current字的子字符串,我们实际上可以通过splice删除它:

  WORD:
  while (my $current = shift) {
    for (my $i = 0; ; $i++) {
      last unless $i <= $#_; # loop condition must be here
      # remove the other word if it's a substring of $current
      splice(@_, $i, 1), redo if -1 != index $current, $_[$i];
      # skip $current word if it's a substring of any other word
      next WORD if -1 != index $_[$i], $current;
    }
    push @out, $current;
  }

但我宁愿对“优化”进行基准测试。

如果需要,可以很容易地将其嵌入到shell脚本中:

$ perl - <<'END' FILE
my @words = <>;
chomp(@words);
WORD: while (my $current = shift @words) {
  for (@words) {
    # skip $current word if it's a substring of any other word
    next WORD if -1 != index $_, $current;
  }
  print "$current\n";
}
END

答案 6 :(得分:1)

在我看来,排序最长到最短的单词,然后我们可以只通过排序列表一次,只匹配保留的单词。我在算法分析方面很差,但这对我来说很有意义,我认为表现会很好。它似乎也有效,假设保留字的顺序无关紧要:

words = ['word', 'wor', 'words', 'wormhole', 'hole']
keepers = []

words.sort_by(&:length).reverse.each do |word|
  keepers.push(word) if ! keepers.any?{|keeper| keeper.include?(word)}
end

keepers
# => ["wormhole", "words"]

如果保留的单词的顺序很重要,那么修改它就可以很容易。一种选择就是:

words & keepers
# => ["words", "wormhole"]

答案 7 :(得分:1)

使用awk

awk '
NR==FNR {
    a[$1]++
    next
} 
{
    for (x in a) { 
        if (index ($1,x) == 0) { 
            a[x] 
        } 
        else { 
            delete a[x]
            a[$1] 
        } 
    }
}
END {
    for (x in a) {
        print x 
    }
}' inputFile inputFile

测试:

inputFile of:

word
wormholes
wor
words
wormhole
hole

Returns:

words
wormholes

答案 8 :(得分:1)

bash解决方案:

#!/bin/bash
dict="word wor words wormhole hole "
uniq=()

sort_by_length() {
    for word; do
        printf "%d %s\n" ${#word} "$word"
    done | sort -n | cut -d " " -f2-
}
set -- $(sort_by_length $dict)

while [[ $# -gt 0 ]]; do
    word=$1
    shift
    found=false
    for w;  do
        if [[ $w == *"$word"* ]]; then
            found=true
            break
        fi
    done
    if ! $found; then
        uniq+=($word)
    fi
done

echo "${uniq[@]}"

答案 9 :(得分:1)

冗长的perl oneliner,

perl -nE 'chomp;($l,$p)=($_,0); @w=grep{ $p=1 if /$l/; $p|| $l!~/$_/} @w; $p or push @w,$l}{say for @w' file