说明

像Swype和SwiftKey这样的软件,使智能手机用户可以通过在屏幕键盘上拖动手指来输入文本,而不是轻敲每个字母。



您将获得一个字符串,代表用户将其手指拖过的字母。

例如,如果用户想要“休息”,则字符串输入字符可以是“ resdft”或“ resert”。

输入

给定以下输入字符串,找到所有可能的输出单词5个字符或更长。


qwertyuytresdftyuioknn
gijakjthoijerjidsdfnokg

输出

您的程序应该找到可以从提供的字符串中得出的所有可能的单词(5个以上的字符)

使用http://norvig.com/ngrams/enable1.txt作为搜索字典。

输出单词的顺序无关紧要。


问问题
gaeing garring收集选通geeing吉灵通行证

注释/提示

输入字符串的假设:


QWERTY键盘
小写仅az,没有空格或标点符号
输入字符串的第一个和最后一个字符将始终与所需输出单词的第一个和最后一个字符匹配
不要假设用户采用字母之间最有效的路径
输出单词的每个字母将出现在输入字符串中



问题284

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;

namespace swipe
{
    class Program
    {
        static string path = "C:\Users\px06\Documents\Visual Studio 2015\Projects\swipe\swipe\words.txt";
        static void Main(string[] args)
        {
            string[] input = { "cghhjkkllooiuytrrdfdftgyuiuytrfdsaazzseertyuioppoiuhgfcxxcfvghujiiuytrfddeews" };

            string[] words = File.ReadAllLines(path);

            var watch = System.Diagnostics.Stopwatch.StartNew();

            foreach (string inp in input)
            {
                var acceptable = reduce(words, inp);
                foreach (int i in acceptable)
                {
                    if (check(words[i], inp))
                    {
                        Debug.WriteLine(words[i]);
                    }
                }
            }
            watch.Stop();
            Console.WriteLine("Run time(ms): " + watch.ElapsedMilliseconds);
            Console.ReadLine();
        }

        static List<int> reduce(string[] list, string input)
        {
            List<int> acceptableList = new List<int>();
            int count = 0;
            foreach(string word in list)
            {
                if (word.Length >= 5 && input[0] == word[0] && input[input.Length - 1] == word[word.Length - 1])
                {
                    acceptableList.Add(count);
                }
                count++;
            }
            return acceptableList;
        }

        static bool check(string word, string input)
        {
            int charcheck = 0;

            foreach (char c in word)
            {
                charcheck = input.IndexOf(c, charcheck);
                if (charcheck == -1)
                {
                    return false;
                }
            }
            return true;
        }
    }
}


qwertyuytresdftyuioknn-> queenquestion->运行时间:2ms

gijakjthoijerjidsdfnokg
gaeing
garring
gathering
gating
geeing
gieing
going
goring
Run time(ms): 2


尝试一个非常复杂的字符串以查看是否可以找到这个词:

chlordiazepoxides


输入:

cghhjkkllooiuytrrdfdftgyuiuytrfdsaazzseertyuioppoiuhgfcxxcfvghujiiuytrfddeews


输出:

caches
cades
cafes
caffs
cages
caids
cairds
capes
caphs
capos
capotes
capouches
capuches
cards
cares
caress
carices
caries
carious
caroches
carps
carpus
carries
carroches
carrots
carrs
carryouts
cartes
cartouches
carts
carves
casefies
caseous
cases
casettes
cashes
cashews
cassettes
cassis
castes
castoffs
castors
casts
casus
catches
cates
catguts
catties
caucus
caves
cavies
cedes
cepes
cercis
cercus
ceres
ceriphs
cerites
ceros
cerous
certes
certifies
cetes
chads
chafes
chaffs
chairs
chaos
chapes
chaps
chards
chares
charges
chariots
charpoys
charros
charrs
chars
charts
chasers
chases
chassepots
chasses
chasseurs
chassis
chats
chaws
chayotes
chays
cheeps
cheerios
cheeros
cheers
chefs
cheroots
cherries
cherts
chess
cheths
chevies
chevres
chews
chias
chiaus
chichis
chics
chiders
chides
chiefs
chiggers
chigoes
chippies
chips
chiros
chirps
chirres
chirrs
chirrups
chits
chitters
chitties
chives
chivies
chivvies
chlorates
chlordiazepoxides
chlorides
chlorids
chlorites
chlorous
choices
choirs
choosers
chooses
chops
choragus
chordates
chords
choregus
chores
chorioids
chorizos
choroids
chorus
choruses
chorusses
choses
chotts
choughs
chousers
chouses
choushes
chows
christies
chrysophytes
chuddahs
chuddars
chudders
chufas
chuffs
chuggers
chugs
churches
churrs
chutes
chutists
ciders
cigars
circuits
circus
cires
cirrous
cirrus
cissies
cissoids
cists
cistus
citators
citers
cites
cities
citifies
citrates
citreous
citrous
citrus
citruses
civies
civvies
clachs
clades
clads
clags
claps
claries
clarifies
claroes
claros
clashes
clasps
class
classers
classes
classics
classifies
classis
clastics
clasts
claughts
claves
clavus
claws
clays
clefs
clefts
clepes
clergies
clerics
clerids
clerihews
clevis
clews
cliches
cliffs
clifts
clips
clitics
cloches
clods
cloggers
clogs
cloistress
cloots
clops
closeouts
closers
closes
closets
closures
clothes
cloths
clots
clotures
clouds
cloughs
clours
clouters
clouts
cloves
cloys
clozes
clues
clutches
clutters
coaches
coacts
coapts
coasts
coatees
coatis
coats
coaxes
coccids
coccus
codas
codders
codecs
coderives
coders
codes
codfishes
codgers
codices
codifies
codirects
codrives
coeds
coerces
coffeepots
coffees
coffers
coffrets
coffs
cogitates
cogitos
coiffes
coiffeurs
coiffures
coifs
coirs
coitus
coituses
cooches
cooees
cooers
cooeys
coofs
coops
coopts
cooters
cooties
coots
copes
copies
copihues
coppices
cordages
corders
cordgrass
cordgrasses
cordierites
cordites
cords
corduroys
corers
cores
corgis
corps
corpus
corrades
corrects
corridas
corridors
corries
corrodes
corrupts
corsacs
corsages
corsairs
corses
corsets
corteges
cortexes
cortices
corvees
corves
coryphees
cosecs
coses
cosets
coseys
coshes
cosies
cossets
costs
coteries
cotes
cotrustees
cottages
cottars
cottas
cotters
cottiers
cotypes
couches
cougars
coughs
coupes
coups
courages
courgettes
couriers
coursers
courses
courteous
courters
courtiers
courts
courtsides
courtyards
couters
couths
coutures
coves
coxes
coydogs
coyotes
coypous
coypus
cozes
cozeys
cozies
cozzes
crafts
crags
crapes
crappies
craps
crases
crashes
crasis
crass
cratches
crates
craves
craws
crazes
crazies
creches
creeds
creepies
creeps
crepes
cress
cretics
crews
criers
cries
cripes
crises
crisis
crisps
critics
critters
critturs
crocs
crocus
crofts
croppies
crops
crores
cross
crouches
crows
cruces
crudes
crudites
cruds
cruets
cruisers
cruises
cruors
crusades
cruses
crusets
crushes
crusts
crutches
cruxes
crypts
cuddies
cuffs
cuifs
cuirass
cuirasses
cuishes
cuisses
cupids
curacies
curaghs
curares
curaris
curassows
curates
curatives
curators
curches
curds
curers
cures
curets
curettes
curfews
curfs
curies
curios
curious
curites
currachs
curraghs
currieries
curriers
curries
currs
cursers
curses
cursives
cursors
curtseys
curtsies
curves
cuscus
cusecs
cuspides
cuspids
cuspis
cusps
cussers
cusses
cussos
custodes
custos
cutches
cutes
cuteys
cutgrass
cutgrasses
cuties
cutis
cutises
cutoffs
cutouts
cuttages
cutters
cutties
cutups
cyders
cypres
cypress
cystoids
cysts
Run time(ms): 5


我只是想看看我可以在代码上做些什么。我是C#的新手,所以我想就如何提高效率或总体上提高效率提出一些建议。在大多数情况下,它看起来运作良好,但我可以进一步改善吗?我还有什么可以增加它以使其更快的方法吗?

有人会知道该程序的效率吗?我感觉它是\ $ O(\ log n)\ $,但显然不应该通过查看结构来实现。

我尝试输入1,000,000个随机生成的字符,这是输出:

zaikai
zamindari
zastrugi
zecchini
zingani
zingari
zombi
zucchini
Run time(ms): 2


我知道,通过减少单词word[0] == dict[word[0]]的第一个索引然后减少最后一个元素word[last] == dict[word[last]]的字典,可显着减少运行时间这里会决定速度吗?我唯一想到的是:字典的大小和第一个字符的位置(即以z开头)。即使输入大小也不能确定运行时间,所以即使我试图在check()中找到匹配项,复杂度也不会增加。

话虽如此,Visual Studio在计算1M长字符串时会占用多达27 MB的RAM ...但是谁真正关心空间复杂性?

评论

这是一个很好的问题,其中包括工作代码和示例-只是应有的方式;-)

我想您可以使用正则表达式来简化代码,该正则表达式将所有中间字符作为可选字符cg?h?h?j?k?k?l?l?o?o?i?u?y?t? ..?s并将该模式​​与单词列表匹配。我怀疑它的性能是否比您现在做的要好,所以我只在这里发表评论。

@ I'lladdcommentstomorrow我主要避免使用正则表达式,因为我不太了解如何使用它的更复杂的方面,例如组和条件。但是我也避免了它,因为我觉得它使代码过于复杂,并且与使用语言提供的普通类和接口相比,并没有带来太多好处。我认为正则表达式方法可能由于迭代而变慢,除非我可以将其与仅搜索单词的最后一步结合起来。我将尝试一下,看看速度如何! 〜我明天再加评论

@ I'lladdcommentstomorrow这将使丑陋变得非常快。

@ px06您的感觉是正确的。您说您是C#的新手,我的评论更多是仅供参考:有正则表达式,它可以解决问题,但是如果您想提高性能,甚至可能不想尝试。

#1 楼

我将首先检查您编写的代码,然后再讨论其他问题。

C#中的惯例是将PascalCase中的所有方法命名为
reduce应该为Reduce

您已经是using System.Diagnostics了,所以您不需要限定Stopwatch.StartNew()的调用。应该是input

words。不要缩写变量名,这只会使代码更难阅读,没有任何好处(至少在C#中)。

foreach(var input in inputs)
{


在这里我还使用了inputs来演示其用法。当分配的类型很明显或确切类型不重要时,可以使用它。如果我说实话,我倾向于在各处使用它。

这段代码令人困惑:

不知道这段代码是关于什么的。考虑使用更具描述性的名称:

var acceptable = reduce(words, inp);
foreach (int i in acceptable)
{


现在,我们正在交流我们的代码在做什么,而无需查看实现。长而描述性的名称总是比短而含糊的名称更可取。

for循环可以更好地解决此问题: :

foreach(var wordIndex in GetCandidateWordIndicies(words, input))
{


我还重构了foreach (string inp in input)以使用varreduce(当集合实现ICollection时是优化的实现):

int count = 0;
foreach(string word in list)
{
    // stuff
    count++;
}


浏览起来比较容易。

在您的if方法中,应将First()称为Last()。最初,我对您的操作感到困惑,但实际上认为输入字母的顺序可能存在错误。


复杂度

您的解决方案很明显取决于输入字典的大小(n)和输入字典的长度(m)。

您遍历字典中的所有单词,因此您的解决方案不会比O(n )。

您也致电check。我不确定100%C#中charcheck的复杂性是什么,但是它会以某种方式与字符串的长度有关-天真的实现将是O(n * m)(currentPosition,当您搜索1时一次char)最坏的情况。更新:https://stackoverflow.com/a/2584204/1402923证实了猜测。 >

评论


\ $ \ begingroup \ $
感谢您的答复!我看到C#约定与Java完全不同,这是我习惯的Java,我将进一步阅读它们,以使我的代码适应.NET标准的一致格式。关于您指出的GetCandidateWordIndicies。如果我正在做foreach(Get(something)中的var x)之类的操作,那么每次迭代都会调用Get()方法吗?还是一次性的?另外,像您所解释的那样,使用forfor foreach有什么好处吗?我首先认为复杂度将为O(n * m),但由于其他因素而略有偏离。
\ $ \ endgroup \ $
–px06
16-9-29在13:16



\ $ \ begingroup \ $
@ px06-我没有给出太多的想法,但我同意最坏的情况是O(n * m)。要回答有关foreach的问题,该方法将仅被调用一次。如果您需要索引,我认为最好使用for而不是foreach并跟踪索引,但这是个人喜好afaik。
\ $ \ endgroup \ $
– RobH
16-9-29在13:18



#2 楼

只是一点提示,没有完整的复习。 br />
成为

string path = "C:\Users\px06\...";


为简洁起见,其余两行都省略了。

评论


\ $ \ begingroup \ $
仅供参考,这称为Verbatim字符串文字。请参阅msdn上的字符串文档
\ $ \ endgroup \ $
– RobH
16-9-29在11:31

\ $ \ begingroup \ $
还不需要关闭逐字字符串文字吗?
\ $ \ endgroup \ $
– MTCoster
16-9-29在15:21



\ $ \ begingroup \ $
@MTCoster我从未听说过我们看到过类似的东西。看起来怎么样?您是说在收盘后也应该有@吗?
\ $ \ endgroup \ $
–明天我将添加评论
16-9-29在16:17

\ $ \ begingroup \ $
@ I'lladdcommentstomorrow我的错误,我认为那是整个行都缺少结束符“,而不仅仅是第一部分
\ $ \ endgroup \ $
– MTCoster
16-9-29在16:19

\ $ \ begingroup \ $
@MTCoster的答案已澄清。
\ $ \ endgroup \ $
–明天我将添加评论
16-9-29在16:24

#3 楼


总是指定访问修饰符,
遵循.Net命名准则,方法和名称空间都是PascalCase。
RobH击败了我其余的普通化妆品。

何时在性能方面,从[FirstCharacter,LastCharacter]-> CandidateWord构建地图可能会带来很多好处。构造这样的映射不是免费的,但是如果您解析多个输入将是值得的。

您可以使用Linq来构建地图:

var words = File.ReadAllLines(path);
var wordLookup = words.Where(word => word.Length >= 5)
                      .GroupBy(word => word[0])
                      .ToDictionary(group => group.Key, group => group.ToLookup(w => w[w.Length - 1]));


DictionaryLookup是基于哈希的映射,可提供恒定的时间查找(可能适用条款和条件)。

然后您可以使用帮助程序来查找候选对象:

private static IEnumerable<string> GetCandidateWords(string input, Dictionary<char, ILookup<char, string>> candidateLookup)
{
    var firstChar = input[0];
    var lastChar = input[input.Length - 1];
    var maxLength = input.Length;
    return candidateLookup[firstChar][lastChar].Where(w => w.Length <= maxLength);
}


然后您过滤掉有效词:

public static IEnumerable<string> GetValidWords(this string input, Dictionary<char, ILookup<char, string>> candidateLookup)
{
    var candidateWords = GetCandidateWords(input, candidateLookup);
    return candidateWords.Where(candidate => ValidateCandidateWord(input, candidate));
}


您的check现在已重命名以更明确地说明其功能:启用字符,这意味着queen不再是qwertyuytresdftyuioknn的有效选项,因为第一个e之后只有一个u。我无法从规范中确定哪种方法是正确的,因此这是基于观点的。

如果将所有方法放在静态类中,则最终会在字符串上使用扩展方法:

private static bool ValidateCandidateWord(string input, string word)
{
    int searchFrom = 0;
    foreach(var c in word)
    {
        var currentIndex = input.IndexOf(c, searchFrom);
        if(currentIndex == -1)
        {
            return false;
        }
        searchFrom = currentIndex + 1;
    }

    return true;
}


您在使用它:

public static class StringSwipeExtentions
{
    private static IEnumerable<string> GetCandidateWords(string input, Dictionary<char, ILookup<char, string>> candidateLookup)
    {
        var firstChar = input[0];
        var lastChar = input[input.Length - 1];
        var maxLength = input.Length;
        return candidateLookup[firstChar][lastChar].Where(w => w.Length <= maxLength);
    }

    public static IEnumerable<string> GetValidWords(this string input, Dictionary<char, ILookup<char, string>> candidateLookup)
    {
        var candidateWords = GetCandidateWords(input, candidateLookup);
        return candidateWords.Where(candidate => ValidateCandidateWord(input, candidate));
    }

    private static bool ValidateCandidateWord(string input, string word)
    {
        int searchFrom = 0;
        foreach(var c in word)
        {
            var currentIndex = input.IndexOf(c, searchFrom);
            if(currentIndex == -1)
            {
                return false;
            }
            searchFrom = currentIndex + 1;
        }

        return true;
    }
}


评论


\ $ \ begingroup \ $
仍在阅读答案,但值得一提的是,在输入字符串qwertyuytresdftyuioknn中,queen是可以接受的,因为在实用的Swype键盘上,您可以将手指悬停在想要的字母上,如果需要,可以将其保留在该位置重复字符,即允许双打。循环,箍,女王等。为什么我们要准确地使用Dictionary?我对您的GetCandidateWords()方法的使用感到有些困惑,介意解释一下吗?
\ $ \ endgroup \ $
–px06
16-9-29在13:58

\ $ \ begingroup \ $
这几乎是我要做的,除了我要使用Dictionary ,其中键的首个和最后一个char串联在一起使它更简单。
\ $ \ endgroup \ $
– RobH
16-9-29在14:05



\ $ \ begingroup \ $
您可以使用w.First()和w.Last()代替w [0]或w [w.Length-1]和5(如果它是const int minWordLength),那么它将是一个完美的选择答案-无论如何+1为字典+查找方法;-)
\ $ \ endgroup \ $
–t3chb0t
16-9-29在14:05



\ $ \ begingroup \ $
@RobH使用复合键,您必须使用TryGetValue,因为不能保证每个组合都有一个值。查找将为缺少的键返回空序列。也许有一种语言不是在每个单词的开头都使用每个字符,然后无论如何都应该使用TryGetValue。
\ $ \ endgroup \ $
– Johnbot
16-9-29在14:12

\ $ \ begingroup \ $
我添加了关于为什么选择基于地图的方法的注释。字典具有恒定的时间查找功能,如果您将其重复用于多个输入,则可以节省n倍。
\ $ \ endgroup \ $
– Johnbot
16-9-29在14:19

#4 楼

由于您的程序只能找到5个或更多字符的单词,因此您可以过滤掉字符较少的单词。这将减少加载的单词数量。

对于此目的,您可以使用带有Linq语句的File.ReadLines。

int minimumWordLength = 5;

HashSet<string> words = new HashSet<string>();
foreach (string line in File.ReadLines(path).Where(p=>p.Length >= minimumWordLength))
{
    words.Add(line);
}


使用ReadLines和Linq可以进一步减少单词的数量,因为您只对输入字符串的第一个和最后一个字符始终与第一个和最后一个字符匹配的单词感兴趣


所需输出单词的数量


string input = "cghhjkkllooiuytrrdfdftgyuiuytrfdsaazzseertyuioppoiuhgfcxxcfvghujiiuytrfddeews";
int minimumWordLength = 5;
string firstChar = input[0].ToString();
string lastChar = input[input.Lenght-1].ToString();

foreach (string line in File.ReadLines(path)
                            .Where(p=>p.Length >= minimumWordLength)
                            .Where(p=>p.StartsWith(firstChar) && p.EndsWith(lastChar)))
{
    words.Add(line);
}


此更改将使您的reduce方法过时,您可以使用方法check直接检查单词。

#5 楼

我认为您的算法不错,但是您可以通过Filter函数在一个操作中检查候选对象,如下所示。

我用以下注释修改了您的解决方案:

  // Changed to static because it implements an extension to IEnumerable<string>
  static class Program
  {
    // use @ to get rid of the \ as another commentor explained
    static string path = @"C:\Users\px06\Documents\Visual Studio 2015\Projects\swipe\swipe\words.txt";

    static void Main(string[] args)
    {
      string[] input = { "cghhjkkllooiuytrrdfdftgyuiuytrfdsaazzseertyuioppoiuhgfcxxcfvghujiiuytrfddeews" };

      string[] words = File.ReadAllLines(path);

      var watch = System.Diagnostics.Stopwatch.StartNew();

      foreach (string inp in input)
      {
        foreach (var validWord in words.Filter(inp))
        {
          Console.WriteLine(validWord);
        }
      }

      watch.Stop();
      Console.WriteLine("Run time(ms): " + watch.ElapsedMilliseconds);
      Console.ReadLine();
    }

    // This is combining reduce() and check() in one iteration
    static IEnumerable<string> Filter(this IEnumerable<string> words, string input)
    {
      foreach (string word in words)
      {
        // calling check() at the same time
        if (word.Length >= 5 && input[0] == word[0] && input[input.Length - 1] == word[word.Length - 1] && check(word, input))
        {
          yield return word;
        }
      }
    }

    static bool check(string word, string input)
    {
      int charcheck = 0;

      foreach (char c in word)
      {
        charcheck = input.IndexOf(c, charcheck);
        if (charcheck == -1)
        {
          return false;
        }
      }
      return true;
    }
  }


您甚至可以跳过Filter函数,并使用Where()获得相同的功能,例如:

  // Changed to static because it implements an extension to IEnumerable<string>
  static class Program
  {
    // use @ to get rid of the \ as another commentor explained
    static string path = @"C:\Users\px06\Documents\Visual Studio 2015\Projects\swipe\swipe\words.txt";

    static void Main(string[] args)
    {
      string[] input = { "cghhjkkllooiuytrrdfdftgyuiuytrfdsaazzseertyuioppoiuhgfcxxcfvghujiiuytrfddeews" };

      string[] words = File.ReadAllLines(path);

      var watch = System.Diagnostics.Stopwatch.StartNew();

      foreach (string inp in input)
      {
        foreach (var validWord in words.Where(word => word.Length >= 5 && inp[0] == word[0] && inp[inp.Length - 1] == word[word.Length - 1] && check(word, inp)))
        {
          Console.WriteLine(validWord);
        }
      }

      watch.Stop();
      Console.WriteLine("Run time(ms): " + watch.ElapsedMilliseconds);
      Console.ReadLine();
    }

    static bool check(string word, string input)
    {
      int charcheck = 0;

      foreach (char c in word)
      {
        charcheck = input.IndexOf(c, charcheck);
        if (charcheck == -1)
        {
          return false;
        }
      }
      return true;
    }
  }


如果存储字典中的单词可以使性能提高几毫秒,而O(n * m)中的n会减少一点。字典接近O(1):

  // Changed to static because it implements an extension to IEnumerable<string>
  static class Program
  {
    // use @ to get rid of the \ as another commentor explained
    static string path = @"C:\Users\px06\Documents\Visual Studio 2015\Projects\swipe\swipe\words.txt";

    static void Main(string[] args)
    {
      string[] input = { "cghhjkkllooiuytrrdfdftgyuiuytrfdsaazzseertyuioppoiuhgfcxxcfvghujiiuytrfddeews", "ardidtrwekfsisuklms" };
      var words = File.ReadAllLines(path).GroupBy(str => str[0]).ToDictionary(gr => gr.Key);

      var watch = System.Diagnostics.Stopwatch.StartNew();

      foreach (string inp in input)
      {
        foreach (var validWord in words[inp[0]].Where(word => word.Length >= 5 && inp[0] == word[0] && inp[inp.Length - 1] == word[word.Length - 1] && check(word, inp)))
        {
          Console.WriteLine(validWord);
        }

        Console.WriteLine();
      }

      watch.Stop();
      Console.WriteLine("Run time(ms): " + watch.ElapsedMilliseconds);
      Console.ReadLine();
    }

    static bool check(string word, string input)
    {
      int charcheck = 0;

      foreach (char c in word)
      {
        charcheck = input.IndexOf(c, charcheck);
        if (charcheck == -1)
        {
          return false;
        }
      }
      return true;
    }
  }


评论


\ $ \ begingroup \ $
我明白您在这里说的是什么,因为我是C#.NET语言和框架的整体新手,我并不完全知道yeild的工作原理,但我猜它只是状态机的概念可以存储指向最后查看位置的指针。但是,似乎我们需要每次循环遍历IEnumerable吗?我是说这是在创建一个谓词,然后进一步逐步发展吗?看起来这似乎以某种二次方的方式运行。
\ $ \ endgroup \ $
–px06
16-09-29在11:17

\ $ \ begingroup \ $
我使用问题中给出的输入来运行程序,运行时显着增加到大约250+ ms,这在试图不仅提高计算效率而且匹配并找到单词的效率时似乎有点违反直觉。
\ $ \ endgroup \ $
–px06
16-09-29在11:18

\ $ \ begingroup \ $
您无法访问norvig.com/ngrams/enable1.txt吗?那就是我正在使用的字典文件,以及挑战本身需要使用的文件。
\ $ \ endgroup \ $
–px06
16-9-29在11:40

\ $ \ begingroup \ $
我已将其上传到gist,因此您可以从以下位置检索它:gist.githubusercontent.com/redrails/…
\ $ \ endgroup \ $
–px06
16-09-29在12:12

\ $ \ begingroup \ $
谢谢。当我在计算机上以发布模式运行您的代码时,其持续时间范围为234到301。两个版本的持续时间相同。那么如上所示,5ms是怎么产生的呢?您正在使用Debug.WriteLine()而不是在发布模式下停用的Console.WriteLine()打印的可能答案?
\ $ \ endgroup \ $
–user73941
16-9-29在12:29



#6 楼

我开始重构原始代码,但是使用一个类才有意义。 MajicWord的类使扩展更加容易。获取中间字符会更加困难。使用GetHashCode()作为Dictionary Key是有意义的。

这大约是您的解决方案速度的40倍(运行时间为5毫秒)
使用带有首字母和末尾字母键的Dictionary
因此,查找候选者(匹配第一个和最后一个)是\ $ O(1)\ $
关于候选者,它在中间(相当次要)上进行迭代
依赖于称为MajicWord的类来进行繁重的工作

{
    static void Main(string[] args)
    {
        string wordToMatch = "cghhjkkllooiuytrrdfdftgyuiuytrfdsaazzseertyuioppoiuhgfcxxcfvghujiiuytrfddeews";
        Stopwatch sw = new Stopwatch();
        sw.Start();
        List<string> wordMatches = WordMatches(wordToMatch); /*quen*/
        sw.Stop();
        //foreach (string s in wordMatches)
        //    Console.WriteLine(s);
        Console.WriteLine(string.Join(", ", wordMatches));
        Console.WriteLine("milliseconds first pass is high as has to build dictionary");
        Console.WriteLine("milliseconds  me " + sw.ElapsedMilliseconds.ToString() + Environment.NewLine);
        Console.ReadLine();
    }
    private static Dictionary<Int32, List<MajicWord>> wordSet;
    public static List<string> WordMatches(string input)
    {
        if (wordSet == null)
        {
            wordSet = new Dictionary<Int32, List<MajicWord>>();
            try
            {   // Open the text file using a stream reader.
                string line;
                using (StreamReader sr = new StreamReader(@"words.txt"))
                {
                    while ((line = sr.ReadLine()) != null)
                    {
                        if (line.Length < 5)
                            continue;
                        //Debug.WriteLine(line);
                        MajicWord mw = new MajicWord(line);
                        if (!wordSet.ContainsKey(mw.GetHashCode()))
                            wordSet.Add(mw.GetHashCode(), new List<MajicWord>());
                        wordSet[mw.GetHashCode()].Add(mw);
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("The file could not be read:");
                Console.WriteLine(e.Message);
            }
            //foreach (List<MajicWord> lmw in wordSet.Values)
            //    Debug.WriteLine(lmw.Count.ToString());
            Debug.WriteLine("end ctro");
        }
        Stopwatch sw = new Stopwatch();
        sw.Start();
        List<string> wordMatches = new List<string>();
        MajicWord wtm = new MajicWord(input);
        List<MajicWord> candidates = wordSet[wtm.GetHashCode()];
        IEnumerable<MajicWord> matches = candidates.Where(x => x.IsMatchOrder(wtm));
        foreach (MajicWord mw in matches.OrderBy(x => x.Word))
            wordMatches.Add(mw.Word);
        sw.Stop();
        Console.WriteLine("milliseconds WordMatches " + sw.ElapsedMilliseconds.ToString() + Environment.NewLine);
        return wordMatches;
    }
}

public class MajicWord
{
    public override bool Equals(Object obj)
    {
        if (!(obj is MajicWord)) return false;

        MajicWord mw = (MajicWord)obj;
        return (string.Compare(mw.Word, this.Word, true) == 0);
    }
    public override int GetHashCode()
    {
        return (int)CharFirst * 17 ^ (int)CharLast;
        //((int)CharFirst - 97)*26 + (int)CharLast - 97; this hashes better for a-z
    }
    public bool IsMatch(MajicWord mwm)
    {
        if (mwm.GetHashCode() != this.GetHashCode())
            return false;
        return (this.MiddleHash).IsSubsetOf(mwm.MiddleHash);
    }
    public bool IsMatchOrder(MajicWord mwm)
    {
        //if (!IsMatch(mwm))  // this slowed it down
        //    return false;

        if (mwm.GetHashCode() != this.GetHashCode())
            return false;

        if (this.Word.Length > mwm.Word.Length)
            return false;

        int charcheck = 0;
        foreach (char c in this.WordMiddle)
        {
            charcheck = mwm.WordMiddle.IndexOf(c, charcheck);
            if (charcheck == -1)
            {
                return false;
            }
        }
        return true;
    }
    public string Word { get; private set; }
    private string wordMiddle = null;
    public string WordMiddle
    {
        get
        {
            if(wordMiddle == null)
            {
                StringBuilder sb = new StringBuilder();
                for (int i = 1; i < Word.Length - 1; i++)
                    sb.Append(Word[i]);
                wordMiddle = sb.ToString();
            }
            return wordMiddle;
        }
    }
    public char CharFirst { get; private set; }
    public char CharLast { get; private set; }
    HashSet<char> middleHash = null;
    public HashSet<char> MiddleHash
    {   
        get
        {
            if(middleHash == null)
            {
                middleHash = new HashSet<char>();
                for (int i = 1; i < Word.Length - 1; i++)
                    middleHash.Add(Word[i]);
            }
            return middleHash;
        }
    }
    public MajicWord(string word)
    {
        if (word.Length < 2)
            throw new IndexOutOfRangeException();
        Word = word.ToLower();
        CharFirst = Word[0];
        CharLast = Word[Word.Length-1];
    }
}


评论


\ $ \ begingroup \ $
@janos我曾经解释过,我觉得MajicWord的类可以使扩展变得更容易。获取中间字符会更加困难。对字典密钥使用GetHashCode()很有意义。我开始重构OP代码,但是使用一个类才有意义。如果要删除,我可以。它抽烟快。
\ $ \ endgroup \ $
–狗仔队
16-10-9在20:49

\ $ \ begingroup \ $
我整理了一下,在您的评论中添加了更多解释,并删除了对任何人都没有真正用处的内容。随时进行进一步改进。我也邀请您重新阅读有关如何解答的文章。
\ $ \ endgroup \ $
– janos
16-10-9在21:11