65.9K
CodeProject 正在变化。 阅读更多。
Home

Scrabble 单词查找器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.51/5 (9投票s)

2017年10月5日

CPOL

2分钟阅读

viewsIcon

16985

downloadIcon

277

用匿名类型、分组、空值延续和合并运算符测试你的 Linq 水平

引言

我最近知道我有一个有效的 8 个字母的单词(我的 7 个字母加上棋盘上的一个字母)——“Word With Friends”单词查找器告诉我我可以写出这个 8 个字母的单词——但我不知道这个单词是什么。

我手头有这些字母,包括棋盘上的 's'

List<char> myLetters = new List<char>() { 't', 'r', 'o', 'i', 'l', 'b', 's', 's' };

使用 More Words 网站,搜索“以 s 开头的 8 个字母的单词”,我得到了 3480 个符合该标准的单词。但是哪个与我的字母匹配?

第一次尝试

鉴于我知道这个单词以 's' 开头和结尾,我做了以下操作

string[] words = File.ReadAllLines("words.txt");
var f1 = words.Where(w => w[0] == 's' && w[7] == 's');

var f2 = (from word in f1
where myLetters.All(c => word.Any(wc => wc == c))
select word).ToList();

f2.ForEach(w => Console.WriteLine(w));

瞧,这就是我的单词:“strobils”——

1. 裸子植物或无种子维管植物(如木贼或石松)的球果。
2. 各种类似结构,例如啤酒花的雌花序,由被重叠的绿色苞片遮盖的小花组成。

但是,我是在知道首尾字母的情况下预先过滤了整个单词列表。如果我不使用该过滤器

var f2 = (from word in <font color="#FF0000">words</font>
where myLetters.All(c => word.Any(wc => wc == c))
select word).ToList();

我得到了这个列表

sorbitol
strobila
strobile
strobili
strobils

请注意,有些单词包含重复的字母,有些单词包含我没有的其他字母。

第二次尝试

那么,如何精确匹配可用的字母呢?嗯,统计它们,并确保可能单词中每个字母的计数与我拥有的字母的计数相匹配。这给了我我的字母计数

var occurences = myLetters.Select(c => new { letter = c, count = myLetters.Count(o => o == c) });

请注意,我们有两个 's' 的条目。让我们清理一下

var occurences = myLetters.Distinct().
  Select(c => new { letter = c, count = myLetters.Count(o => o == c) });

同样,我们对每个可能的单词执行相同的操作(ToList() 使调试器更易读)

 var wordLetterCounts = words.Select(word => 
  new 
  { 
    word = word, 
    counts = 
      word.GroupBy(c => c).Select((a, b) => 
      new 
      {
        letter = a.Key, 
        count = word.Count(c => c == a.Key) 
      }).ToList()
  });

列表中的前两个单词

现在我们可以匹配测试单词中字母的计数与我可用的字母

var matches = wordLetterCounts.Where(wlc => 
  wlc.counts.All(letterCount => 
    occurences.Single(o => o.letter == letterCount.letter).count == letterCount.count));

var listOfMatches = matches.ToList();

糟糕

这里的问题是,我每个字母的出现次数列表(elephant,我在代码中错误地拼写了“occurrences”)可能不包含测试单词中的字母。

第三次尝试

空值延续和空值合并运算符来拯救(也修正了拼写错误)

var matches = wordLetterCounts.Where(wlc => 
  wlc.counts.All(letterCount => 
    (occurrences.SingleOrDefault(o => 
       o.letter == letterCount.letter)?.count ?? 0) == letterCount.count));

成功!

替代实现

一个“更简单”的替代 Linq 语句将各个部分组合在一起,而无需创建临时匿名类型 wordLetterCounts

var matches = words.Select(word =>
{
  return new { word = word, matches =
  word.GroupBy(c => c).Select((a, b) => new { letter = a.Key, count = word.Count(c => c == a.Key) })
  .All(q => (occurrences.FirstOrDefault(o => o.letter == q.letter)?.count ?? 0) == q.count) };
}).Where(word => word.matches);

一个简单的 UI

using System;
using System.Data;
using System.Linq;
using System.Windows.Forms;

namespace WordFinderUI
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
      tbWordList.MaxLength = Int32.MaxValue;
    }

  private void btnFind_Click(object sender, EventArgs e)
  {
    var myLetters = tbMyLetters.Text.ToList();
    var occurrences = myLetters.
       Distinct().
       Select(c => new { letter = c, count = myLetters.Count(o => o == c) });
    var words = tbWordList.Text.Split(new char[] { '\r' }).Select(w=>w.Trim());

    var matches = words.Select(word =>
    {
      return new
      {
        word = word,
        matches =
        word.GroupBy(c => c).
          Select((a, b) => new { letter = a.Key, count = word.Count(c => c == a.Key) })
        .All(q => (occurrences.FirstOrDefault(o => o.letter == q.letter)?.count ?? 0) == q.count)
      };
    }).Where(word => word.matches);

    tbMatches.Text = matches.Count() == 0 ? 
      "No Matches!" : String.Join("\r\n", matches.Select(m => m.word));  }
  }
}

这里唯一值得注意的是

tbWordList.MaxLength = Int32.MaxValue;

TextBox 的默认长度为 32767 个字符,而其中一些单词列表要长得多。这给你 2GB 的字符!

结论

现在,当我试图找到 7 个字母的单词时,我可以“作弊”了!

© . All rights reserved.