【问题标题】:Checking if a word is a sub-Anagram of another (Java)检查一个词是否是另一个词的子字谜(Java)
【发布时间】:2017-12-02 20:14:08
【问题描述】:

“unsold”和“silo”这两个词是“insidously”这个词的子变位词。也就是说,它们只能使用“insidously”中的字母拼写。显然还有更多,这个概念是“澳大利亚人”报纸上的文字游戏的基础。

我正在尝试编写一个程序,它接受两个参数 - 一个单词,另一个可能是这个单词的子变位词,如果是则返回 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 局部变量的情况下做到这一点?

【问题讨论】:

  • “它确实有效”不,这是不可能的,缺少 { } ; ( ) 它不能编译所以它不能工作

标签: java anagram


【解决方案1】:

如果您想让现有程序更好地考虑使用 SET 而不是 LIST,因为它会

  • 消除角色集合中的重复添加,节省空间。
  • 在下一个循环中为您节省一些迭代,从而节省时间。

编辑

但是,这种优化可能不适用于其中一个 cmets 指出的条件。

EX - 当base 只有“ab”而word 有“aab”时

【讨论】:

  • 使用集合可能没有帮助,因为重复字符与此任务相关。例如,word 'aab' 仅适用于 base,当它包含 'aab' 而不是仅包含 'ab' 时。
【解决方案2】:

我建议您使用 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();

    }

如果基本字符串中的字母数和所谓的子字谜需要相同,则选择:

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;
}

【讨论】:

  • 套装不合适。如果您的基本词使用一个字符两次或更多次,则应允许您在子字谜中使用该字符相同的次数。
  • 我不知道文字游戏。但如果是这种情况,则不应使用案例集。我会编辑这个。谢谢!
  • @Michael 您从哪里获得该信息,这些字母最多可以出现在基本单词中的确切次数? OP没有给出这样的约束。
  • @KrzysztofCichocki 因为我知道字谜是什么。 :)
【解决方案3】:

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

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 个字母。我在报纸上看到的每一个这样的游戏都避免使用带有变音符号的词:cafe、fiancée、naïve 等。

【讨论】:

    【解决方案4】:

    我相信这将是运行速度快且占用内存最少的解决方案:

    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;
    }
    

    }

    【讨论】:

    • 好吧,这很有趣,你刚刚使用了 baseCharArr[bi] = '_',而我使用了 characters.remove(x)。除了循环将遍历每个字符,即使它找到一个与基数不匹配的字符。所以如果我们纠正它,它可以变得更快。
    • 看看 ar:if (!contains) { return false; } - 如果缺少任何必需的字母,它显然会返回 false
    【解决方案5】:

    你的代码漏掉了很多{},;,(),不能清晰的编译和工作^^,我改了“if”的顺序以及如何添加所有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;
    }
    

    【讨论】:

    • X 使用时也超出范围
    • @Michael 是的,实际上是 'y' ^^ 并且在 toCharArray 和其他更正处错过了 '()'
    【解决方案6】:

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

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

    【讨论】:

    • 但是每次迭代都会产生新的String,这显然不是很有效的解决方案。
    • 保存一些代码的好主意,但不适用于 sub-anagrams。 dofood 的子变位词,但该方法将返回false。还要注意字母多次出现的情况。 replace 替换所有匹配项。
    • 你是对的。我不知道字符的 replace() 方法会替换所有出现的字符。修改上面的例子以使用字符串。
    • 感谢 Florian,这是一个有趣的方法。 @Socow
    【解决方案7】:

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

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

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

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

    由于恒速因素,所提出的解决方案也很慢。当我们使用List&lt;Character&gt;Set&lt;Character&gt; 时,String 的chars 必须装箱在Character 对象中。创建和处理这些对象比使用基本的char 类型要慢得多。

    解决方案

    多组

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

    示例

    基本词"Food" 有多重集合{f, o, o, d}
    Word "do" 具有多重集 {d, o}
    Word "dod" 有多重集合{d, d, o}

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

    存储多集

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

    示例

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

    // 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] &lt;= b[i] 对于所有i

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

    重用工作

    我们要检查很多词中的一个基本词。我们可以将多重集重新用于基本词,而不必一遍又一遍地计算它。 我们不是编写返回 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]

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-02-09
      • 2021-05-16
      • 2019-01-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-04
      • 1970-01-01
      相关资源
      最近更新 更多