检查一个单词是否是另一个单词Anagram(Java)

时间:2017-06-29 08:21:14

标签: java anagram

单词"未售出" &安培; "筒仓"是"阴险地"这个词的子字谜。也就是说,它们可以使用来自"阴险地"的字母拼写。显然还有更多,这个概念是在澳大利亚'中发现的文字游戏的基础。报纸。

我试图编写一个带有两个参数的程序 - 一个单词,另一个可能是这个单词的子字形,如果是,则返回true。到目前为止,这就是我所拥有的:

public boolean isAnswer(String word, String base)
    ArrayList<Character> characters = new ArrayList<>();
    for(char x : base.toCharArray)
    {
        characters.add(x)
    }
    for(char y : word.toCharArray)
    {
        if(characters.contains(x))
        {
            characters.remove(x)
        }
        else
        {
            return false;
        }
    return true;
    }

它确实有效,但是如果我循环浏览英语词典中的每个单词,这将对内存产生极大的负担。如何在不创建ArrayList局部变量的情况下执行此操作?

7 个答案:

答案 0 :(得分:0)

如果您想更好地考虑现有节目,请考虑使用 SET 而不是 LIST

  • 消除字符集中的重复添加,节省空间。
  • 在下一个循环中保存一些迭代,节省时间。

修改

然而,这种优化可能不适用于其中一条评论指出的条件。

EX - 当base只有&#34; ab&#34; &安培; word有&#34; aab&#34;

答案 1 :(得分:0)

您可以直接替换base。这不是很有效并创建了很多String个对象,但它很容易阅读:

public boolean isAnswer(String word, String base)
{
  for (char ch : word.toCharArray())
  {
    base = base.replaceFirst("" + ch, "");
  }
  return base.trim().length() == 0;
}

答案 2 :(得分:0)

您的代码错过了许多 {} ; (),它无法清楚地编译和工作^^,以及我改变了&#34;的顺序,如果&#34;以及如何添加所有base

public boolean isAnswer(String word, String base) {
      ArrayList<Character> characters = new ArrayList<>();
      characters.addAll(Arrays.asList(base.toCharArray()));
      for (char y : word.toCharArray()) {
          if (!characters.contains(y)) {
              return false;
          }
          characters.remove(y);
      }
      return true;
}

答案 3 :(得分:0)

我相信这将是应该快速运行并消耗最少内存的解决方案:

public class Snippet {

public static void main(String[] args) {

    System.out.println(isAnswer("unsold", "insidiously"));
    System.out.println(isAnswer("silo", "insidiously"));
    System.out.println(isAnswer("silk", "insidiously"));
}

public static boolean isAnswer(String word, String base) {
    char[] baseCharArr = base.toCharArray();
    for (int wi = 0; wi < word.length(); wi++) {
        boolean contains = false;
        char wchar = word.charAt(wi);
        for (int bi = 0; bi < baseCharArr.length; bi++) {
            if (baseCharArr[bi]==wchar) {
                baseCharArr[bi]='_'; // to not use this letter anymore we delete it using some sign that is non valid to from a word.
                contains=true;
                break;
            }
        }
        if (!contains) {
            return false;
        }
    }
    return true;
}

}

答案 4 :(得分:0)

我建议你去java.util.Set以避免不必要的迭代。请找到以下代码:

private static boolean isSubAnagram() {
        String str  = "insidiously";
        String anagram = "siloy";

        Set<Character> set = new HashSet<Character>();
        for(int i = 0 ; i < str.length() ; ++i){
            set.add(new Character(str.charAt(i)));
        }

        int count = 0;
        for(int i = 0 ; i < anagram.length() ; ++i){
            if(set.contains(anagram.charAt(i))){
                ++count;
            }
        }

        return count == anagram.length();

    }

如果基本字符串中的字母数和所谓的字anagram需要相同,那么请转到:

private static boolean isSubAnagram() {
    String str  = "insidiously";
    String anagram = "siloyl";

    List<Character> list = new ArrayList<Character>();
    for(int i = 0 ; i < str.length() ; ++i){
        list.add(new Character(str.charAt(i)));
    }               

    for(int i = 0 ; i < anagram.length() ; ++i){
        char curChar = anagram.charAt(i);
        if(list.contains(curChar)){
            list.remove(new Character(curChar));
            continue;
        }else{
            return false;
        }
    }

    return true;
}

答案 5 :(得分:0)

一个优化可能是首先确保单词不长于基数。

public boolean isAnswer(String word, String base)
{
    if (word.length() > base.length()) return false;
    //...
}

我怀疑这些单词的长度是否完全相同there may be a faster way than comparing all of the characters

public boolean isAnswer(String word, String base)
{
    if (word.length() > base.length()) {
        return false;
    }
    else if (word.length() == base.length()) {
        return isFullAnagram(); // I'll leave the implementation of this up to you
    }
    //...
}

优化此功能的下一步是确保您天真地尝试字典中的每个单词:

// Don't do this
public static void main(String... args)
{
    String base = "something";
    for (final String word : dictionary)
    {
        if (isAnswer(word, base)) // do something
    }
}
// Don't do this

你有一个很大的优势,任何值得盐的字典文本文件都会被预先排序。一个基本的优化是将你的字典分成26个文件 - 一个用于以每个字母开头的单词 - 并跳过任何不可能匹配的文件。

public static void main(String... args)
{
    String base = "something";
    Set<Characters> characters = // populate with chars from base

    for (final Section section : dictionary)
    {
        if (characters.contains(section.getChar())
        {
            for (final String word : section)
            {
                if (isAnswer(word, base)) // do something
            }
        }
    }
}

接下来我要做的是看看这个过程的并行化。一个基本的方法是在自己的线程上运行每个部分(因此,对于大多数常见的英语单词,您最多可以查看大约12个线程)。

public static void main(String... args)
{
    String base = "something";
    Set<Characters> characters = // populate with chars from base

    for (final Section section : dictionary)
    {
        if (characters.contains(section.getChar())
        {
            startMyThread(section, base);
        }
    }
}

你可以让线程返回Future,你可以在最后检查。我会把这个细节留给你。

CUDA这样的库允许您通过将计算推送到GPU来使用非常高的并发性。您可以同时运行数百个线程。在这种情况下,我不确定一个好的策略会是什么样子。

我正在假设你只需要处理罗马字母的26个字母。我在报纸上看到的每一个这样的游戏都避免了带有变音符号的词:咖啡馆,未婚妻,幼稚等。

答案 6 :(得分:0)

当前方法/其他答案的问题

有很多答案,但没有一个是非常有效的。

对于子字谜候选人中的每个字母,我们搜索列表并删除字母。一次搜索需要线性时间。由于我们必须搜索每个字母,我们最终会得到二次时间复杂度。

有人建议使用集合而不是列表。在集合中搜索需要恒定的时间,因此我们最终会得到线性时间。但是,当同一个字母多次出现时,set方法会失败。

由于速度因素恒定,所提出的解决方案也很慢。当我们使用List<Character>Set<Character>时,字符串的char必须在Character个对象中加框。创建和处理这些对象要比使用原始char类型慢得多。

解决方案

多重集

我们可以使用 multiset (也称为 bag )来表示单词中的字母。对于每个单词,我们创建一个多字节的字母,并检查该多重集是否是基本单词的字母multiset的子集。

示例

基础词"Food"具有多组{f, o, o, d} Word "do"具有多组{d, o} Word "dod"具有多组{d, d, o}

{d, o}{f, o, o, d} ==&gt;的子集dofood的子字谜 {d, o, d} {f, o, o, d} ==&gt;的子集dod不是food的子字谜。

存储多字节

由于我们知道,只有'a''z'的字符出现,我们使用int数组来表示多集。 array[0]的值是'a' s的数量; array[1]的值是'b'的数量,依此类推。 array[1]也可以写为array['b' - 'a']

示例

带有多集"Food"的单词{f, o, o, d}由数组

表示
// Entry for:     a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z
int[] multiSet = {0,0,0,1,0,1,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0};

子集检查

ab的子集,当且仅当a[i] <= b[i]适用于所有i

当我们在计算多集a时进行子集测试时,我们不必检查所有26个数组条目,而只检查设置为大于零的值的条目。

重复使用

我们要检查一个基本单词的很多单词。我们可以重复使用multiset作为基本单词,而不必一遍又一遍地计算它。 我们编写的方法不是编写返回truefalse的方法,而是返回给定基本单词和给定字典(要检查的单词列表)的所有子字符的列表。

次要优化

如果一个单词比基本单词长,则它不能是一个小圆形。在这种情况下,我们不必为该单词计算多重集。

实施

public static List<String> subAnagrams(String base, List<String> dictionary) {
    char[] usableChars = new char['z' - 'a'];
    base = base.toLowerCase();
    for (int i = 0; i < base.length(); ++i) {
        ++usableChars[base.charAt(i) - 'a'];
    }

    List<String> subAnagrams = new ArrayList<>();
    for (String candidate : dictionary) {
        boolean isSubAnagram = candidate.length() <= base.length();
        candidate = candidate.toLowerCase();
        char[] usedChars = new char['z' - 'a'];
        for (int i = 0; isSubAnagram && i < candidate.length(); ++i) {
            int charIndex = candidate.charAt(i) - 'a';
            isSubAnagram = ++usedChars[charIndex] <= usableChars[charIndex];
        }
        if (isSubAnagram) {
            subAnagrams.add(candidate);
        }
    }
    return subAnagrams;
}

使用示例

public static void main(String[] args) {
    List<String> dict = new ArrayList<>();
    dict.add("Do");
    dict.add("Odd");
    dict.add("Good");
    dict.add("World");
    dict.add("Foo");
    System.out.println(subAnagrams("Food", dict));  
}

打印[do, foo]