C#-CardChooser
摘要
此应用程序使用蛮力方法来尝试解决每个列表。首先,我创建一个可供选择的潜在卡片列表,然后确定哪个是最合适的(删除最多字符+最缩短长单词),将其添加到结果列表,然后继续此过程,直到我选择了足够的潜在卡片删除列表中的每个单词,然后将这些卡片与每个单词重新匹配并输出输出。
如果您想查看此代码的更有限版本而不下载并构建提供的Windows窗体应用程序,则可以使用提供的链接对较小的数据集运行我的程序,请注意,这是控制台应用程序版本,因此卡未旋转:http:
//ideone.com/fork/VD1gJF
修订记录
当前-添加了@Zgarb建议的更好的结果优化
更新3-更多代码清理,更多错误修复,更好的结果
更新2-Windows窗体,更详细的输出
更新1-新增/更好地支持字符对称
原始-控制台应用程序
例子
acr,aft,ain,sll,win,比如说,快速,史诗
他,会,不会,会,会,不会,但是,你,你,你会
aaaa,bbbb,cccc
码
我仍然需要将其合并到一个更大的项目中,所有的ConsoleApp和WindowsForms代码都共享相同的类和方法,然后在RunButton_Click方法中拆分出不同的区域,这样无论何时我有空的时候,我都可以在它们周围编写单元我会的,现在是这样:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace CardChooserForms
{
public partial class CardChooser : Form
{
private class Solution : IEquatable<Solution>
{
public List<string> Cards { get; set; }
public List<string> Remaining { get; set; }
public int RemainingScore
{
get
{
return this.Remaining.Sum(b => b.ToCharArray().Count());
}
}
public bool Equals(Solution other)
{
return new string(Cards.OrderBy(a => a).SelectMany(a => a).ToArray()) == new string(other.Cards.OrderBy(a => a).SelectMany(a => a).ToArray());
}
public override int GetHashCode()
{
return (new string(Cards.OrderBy(a => a).SelectMany(a => a).ToArray())).GetHashCode();
}
}
private class Symmetry
{
public char Value { get; set; }
public Int16 RotationDifference { get; set; }
}
/// <summary>
/// This is where Symmetries are stored, right now it only has support for pairs(two values per array)
/// </summary>
private static Symmetry[][] _rotatableCharacters = new Symmetry[][] {
new Symmetry[] { new Symmetry {Value = 'Z'}, new Symmetry {Value = 'N', RotationDifference = 90}},
new Symmetry[] { new Symmetry {Value = 'd'}, new Symmetry {Value = 'p', RotationDifference = 180 }},
new Symmetry[] { new Symmetry {Value = 'u'}, new Symmetry {Value = 'n', RotationDifference = 180 }},
new Symmetry[] { new Symmetry {Value = 'm'}, new Symmetry {Value = 'w', RotationDifference = 180 }},
new Symmetry[] { new Symmetry {Value = 'b'}, new Symmetry {Value = 'q', RotationDifference = 180 }},
new Symmetry[] { new Symmetry {Value = 'l'}, new Symmetry {Value = 'I', RotationDifference = 0}},
};
//These all control the output settings
private readonly static int _defualtSpacing = 25;
private readonly static int _defualtFontSize = 8;
private readonly static Font _defualtFont = new Font("Microsoft Sans Serif", _defualtFontSize);
private readonly static Brush _defualtBackgroundColor = Brushes.Beige;
private readonly static Brush _defualtForegroundColor = Brushes.Black;
public CardChooser()
{
InitializeComponent();
}
private void RunButton_Click(object sender, EventArgs e)
{
#region Input Parsing
//Get input
string input = InputRichTextBox.Text;
if (!input.Contains(","))
throw new ArgumentException("Input must contain more than one value and must be seprated by commas.");
//Parse input
var inputLowercasedTrimedTransformed = input.Split(',').Select(a => a.ToLowerInvariant().Trim()).ToArray();
var inputSplitTrimIndex = input.Split(',').Select(a => a.Trim()).ToArray().Select((a, index) => new { value = a, index }).ToArray();
#endregion Input Parsing
#region Card Formation
var inputCharParsed = inputLowercasedTrimedTransformed.Select(a => a.ToCharArray()).ToArray();
var possibleCards = GetAllCasesTwoLengthArrayElements(
UniqueBiDirection(
//Get unique characters
inputCharParsed
.SelectMany(a => a)
.Distinct()
.Select(a => new
{
Character = a,
PossibleCharacters = inputCharParsed.SelectMany(b => b).Where(b => b != a).ToList()
})
//Now get distinct cards(ie NB == BN, NB != NE)
.SelectMany(a => a.PossibleCharacters.Select(b => new string(new char[] { a.Character, b })).ToArray()).ToArray()
).ToArray()
).ToArray();
//Now get every possible character each card can eliminate
var possibleCharsFromCards = GetAllPossibleCharsFromACards(possibleCards).ToArray();
//Now set up some possibilities that contain only one card
var possibleCardCombinations = possibleCards.Select((a, index) => new Solution
{
Cards = new List<string> { a },
//Use the index of each card to reference the possible characters it can remove, then remove them per card to form a initial list of cards
Remaining = inputLowercasedTrimedTransformed.Select(b => b.RemoveFirstInCharArr(possibleCharsFromCards[index].ToLowerInvariant().ToCharArray())).ToList()
})
//Take the best scoring card, discard the rest
.OrderBy(a => a.RemainingScore)
.ThenBy(a => a.Remaining.Max(b => b.Length))
.Take(1).ToList();
#endregion Card Formation
#region Card Selection
//Find best combination by iteratively trying every combination + 1 more card, and choose the lowest scoring one
while (!possibleCardCombinations.Any(a => a.Remaining.Sum(b => b.ToCharArray().Count()) == 0) && possibleCardCombinations.First().Cards.Count() < possibleCards.Count())
{
//Clear the list each iteration(as you can assume the last generations didn't work
var newPossibilites = new List<Solution>();
var currentRoundCardCombinations = possibleCardCombinations.ToArray();
possibleCardCombinations.Clear();
foreach (var trySolution in currentRoundCardCombinations)
foreach (var card in possibleCards.Select((a, index) => new { value = a, index }).Where(a => !trySolution.Cards.Contains(a.value)).ToArray())
{
var newSolution = new Solution();
newSolution.Cards = trySolution.Cards.ToList();
newSolution.Cards.Add(card.value);
newSolution.Remaining = trySolution.Remaining.ToList().Select(a => a.RemoveFirstInCharArr(possibleCharsFromCards[card.index].ToLowerInvariant().ToCharArray())).ToList();
newPossibilites.Add(newSolution);
}
//Choose the highest scoring card
possibleCardCombinations = newPossibilites
.OrderBy(a => a.RemainingScore)
.ThenBy(a => a.Remaining.Max(b => b.Length))
.Distinct().Take(1).ToList();
}
var finalCardSet = possibleCardCombinations.First().Cards.ToArray();
#endregion Card Selection
#region Output
using (var image = new Bitmap(500, inputSplitTrimIndex.Count() * _defualtSpacing + finalCardSet.Count() * (_defualtFontSize / 2) + _defualtSpacing))
using (Graphics graphic = Graphics.FromImage(image))
{
//Background
graphic.FillRectangle(_defualtBackgroundColor, 0, 0, image.Width, image.Height);
//Header
graphic.DrawString("Total Number of Cards Required: " + finalCardSet.Count(), _defualtFont, _defualtForegroundColor, new PointF(0, 0));
graphic.DrawString(
"Cards: " + String.Join(", ", finalCardSet.Select(a => a[0] + "/" + a[1])),
_defualtFont,
_defualtForegroundColor,
new RectangleF(0, _defualtSpacing, image.Width - _defualtSpacing, finalCardSet.Count() * 5));
//Results
foreach (var element in inputSplitTrimIndex)
{
//Paint the word
graphic.DrawString(element.value + " -> ", _defualtFont, _defualtForegroundColor, new PointF(0, element.index * _defualtSpacing + finalCardSet.Count() * (_defualtFontSize / 2) + _defualtSpacing));
//Now go through each character, determining the matching card, and wether that card has to be flipped
foreach (var card in GetOrderedCardsRequired(inputLowercasedTrimedTransformed[element.index].ToLowerInvariant(), finalCardSet.ToArray()).ToArray().Select((a, index) => new { value = a, index }))
using (var tempGraphic = Graphics.FromImage(image))
{
//For cards that need to flip
if (Char.ToUpperInvariant(element.value[card.index]) != Char.ToUpperInvariant(card.value[0]) &&
Char.ToUpperInvariant(element.value[card.index]) != Char.ToUpperInvariant(card.value[1]))
{
//TODO this is hacky and needs to be rethought
var rotateAmount = _rotatableCharacters
.OrderByDescending(a => a.Any(b => b.Value == Char.ToLowerInvariant(element.value[card.index])))
.First(a => a.Any(b => Char.ToUpperInvariant(b.Value) == Char.ToUpperInvariant(element.value[card.index])))
[1].RotationDifference;
//Rotate
tempGraphic.TranslateTransform(
_defualtSpacing * (_defualtFontSize / 2) + card.index * _defualtSpacing + (rotateAmount == 90 ? 0 : _defualtSpacing / 2) + (rotateAmount == 180 ? -(_defualtSpacing / 4) : 0),
finalCardSet.Count() * (_defualtFontSize / 2) + _defualtSpacing + element.index * _defualtSpacing + (rotateAmount == 180 ? 0 : _defualtSpacing / 2));
tempGraphic.RotateTransform(rotateAmount);
//Print string
tempGraphic.DrawString(
String.Join("/", card.value.ToCharArray().Select(a => new string(new char[] { a })).ToArray()),
_defualtFont,
Brushes.Black,
new RectangleF(-(_defualtSpacing / 2), -(_defualtSpacing / 2), _defualtSpacing, _defualtSpacing));
}
else
tempGraphic.DrawString(
String.Join("/", card.value.ToCharArray().Select(a => new string(new char[] { a })).ToArray()),
_defualtFont,
_defualtForegroundColor,
new RectangleF(
_defualtSpacing * (_defualtFontSize / 2) + card.index * _defualtSpacing,
finalCardSet.Count() * (_defualtFontSize / 2) + _defualtSpacing + element.index * _defualtSpacing,
_defualtSpacing, _defualtSpacing));
}
}
OutputPictureBox.Image = new Bitmap(image);
}
#endregion Output
}
private IEnumerable<string> GetAllPossibleCharsFromACards(string[] cards)
{
return cards.Select(a =>
new string(a.ToCharArray().Concat(_rotatableCharacters
.Where(b => b.Select(c => c.Value).Intersect(a.ToCharArray()).Count() > 0)
.SelectMany(b => b.Select(c => c.Value))
.Distinct().ToArray()).Distinct().ToArray()));
}
private IEnumerable<string> GetOrderedCardsRequired(string word, string[] cards)
{
var solution = new List<string>();
var tempCards = GetAllPossibleCharsFromACards(cards).Select((a, index) => new { value = a, index }).ToList();
foreach (var letter in word.ToCharArray())
{
//TODO this still could theoretically fail I think
var card = tempCards
//Order by the least number of characters match
.OrderBy(a => word.ToLowerInvariant().Intersect(a.value.ToLowerInvariant()).Count())
.ThenByDescending(a => tempCards.Sum(b => b.value.ToLowerInvariant().Intersect(a.value.ToLowerInvariant()).Count()))
//Then take the least useful card for the other parts of the word
.First(a => a.value.ToLowerInvariant().Contains(Char.ToLowerInvariant(letter)));
solution.Add(cards[card.index]);
tempCards.Remove(card);
}
return solution;
}
private static IEnumerable<string> UniqueBiDirection(string[] input)
{
var results = new List<string>();
foreach (var element in input)
if (!results.Any(a => a == new string(element.ToCharArray().Reverse().ToArray()) || a == element))
results.Add(element);
return results;
}
private static IEnumerable<string> GetAllCasesTwoLengthArrayElements(string[] input)
{
if (input.Any(a => a.Length != 2))
throw new ArgumentException("This method is only for arrays with two characters");
List<string> output = input.ToList();
foreach (var element in input)
{
output.Add(new string(new char[] { Char.ToUpperInvariant(element[0]), Char.ToUpperInvariant(element[1]) }));
output.Add(new string(new char[] { element[0], Char.ToUpperInvariant(element[1]) }));
output.Add(new string(new char[] { Char.ToUpperInvariant(element[0]), element[1] }));
}
return output;
}
private void SaveButton_Click(object sender, EventArgs e)
{
using (var image = new Bitmap(OutputPictureBox.Image))
image.Save(Directory.GetCurrentDirectory() + "Output.png", ImageFormat.Png);
}
}
public static class StringExtensions
{
public static string RemoveFirstInCharArr(this string source, char[] values)
{
var tempSource = source.ToUpperInvariant();
foreach (var value in values)
{
int index = tempSource.IndexOf(Char.ToUpperInvariant(value));
if (index >= 0) return source.Remove(index, 1);
}
return source;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CardChooserForms
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new CardChooser());
}
}
}
namespace CardChooserForms
{
partial class CardChooser
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.InputRichTextBox = new System.Windows.Forms.RichTextBox();
this.EnterInputLabel = new System.Windows.Forms.Label();
this.RunButton = new System.Windows.Forms.Button();
this.OutputPictureBox = new System.Windows.Forms.PictureBox();
this.OutputPanel = new System.Windows.Forms.Panel();
this.SaveButton = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.OutputPictureBox)).BeginInit();
this.OutputPanel.SuspendLayout();
this.SuspendLayout();
//
// InputRichTextBox
//
this.InputRichTextBox.Location = new System.Drawing.Point(60, 40);
this.InputRichTextBox.Name = "InputRichTextBox";
this.InputRichTextBox.Size = new System.Drawing.Size(400, 100);
this.InputRichTextBox.TabIndex = 0;
this.InputRichTextBox.Text = "";
//
// EnterInputLabel
//
this.EnterInputLabel.AutoSize = true;
this.EnterInputLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.EnterInputLabel.Location = new System.Drawing.Point(57, 20);
this.EnterInputLabel.Name = "EnterInputLabel";
this.EnterInputLabel.Size = new System.Drawing.Size(81, 17);
this.EnterInputLabel.TabIndex = 1;
this.EnterInputLabel.Text = "Enter Input:";
//
// RunButton
//
this.RunButton.Font = new System.Drawing.Font("Microsoft Sans Serif", 20F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.RunButton.Location = new System.Drawing.Point(60, 147);
this.RunButton.Name = "RunButton";
this.RunButton.Size = new System.Drawing.Size(180, 52);
this.RunButton.TabIndex = 2;
this.RunButton.Text = "Run";
this.RunButton.UseVisualStyleBackColor = true;
this.RunButton.Click += new System.EventHandler(this.RunButton_Click);
//
// OutputPictureBox
//
this.OutputPictureBox.Location = new System.Drawing.Point(3, 3);
this.OutputPictureBox.Name = "OutputPictureBox";
this.OutputPictureBox.Size = new System.Drawing.Size(500, 500);
this.OutputPictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize;
this.OutputPictureBox.TabIndex = 3;
this.OutputPictureBox.TabStop = false;
//
// OutputPanel
//
this.OutputPanel.AutoScroll = true;
this.OutputPanel.Controls.Add(this.OutputPictureBox);
this.OutputPanel.Location = new System.Drawing.Point(4, 205);
this.OutputPanel.Name = "OutputPanel";
this.OutputPanel.Size = new System.Drawing.Size(520, 520);
this.OutputPanel.TabIndex = 4;
//
// SaveButton
//
this.SaveButton.Font = new System.Drawing.Font("Microsoft Sans Serif", 20F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.SaveButton.Location = new System.Drawing.Point(280, 147);
this.SaveButton.Name = "SaveButton";
this.SaveButton.Size = new System.Drawing.Size(180, 52);
this.SaveButton.TabIndex = 5;
this.SaveButton.Text = "Save";
this.SaveButton.UseVisualStyleBackColor = true;
this.SaveButton.Click += new System.EventHandler(this.SaveButton_Click);
//
// CardChooser
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(534, 737);
this.Controls.Add(this.SaveButton);
this.Controls.Add(this.RunButton);
this.Controls.Add(this.EnterInputLabel);
this.Controls.Add(this.InputRichTextBox);
this.Controls.Add(this.OutputPanel);
this.Name = "CardChooser";
this.Text = "Card Chooser";
((System.ComponentModel.ISupportInitialize)(this.OutputPictureBox)).EndInit();
this.OutputPanel.ResumeLayout(false);
this.OutputPanel.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.RichTextBox InputRichTextBox;
private System.Windows.Forms.Label EnterInputLabel;
private System.Windows.Forms.Button RunButton;
private System.Windows.Forms.PictureBox OutputPictureBox;
private System.Windows.Forms.Panel OutputPanel;
private System.Windows.Forms.Button SaveButton;
}
}
n/u
,d/p
?怎么样b/q
和m/w
?如果我将P
卡片对折成两半D
怎么办,那么上半部分就变成了怎么办?