C # - CardChooser
Резюме
Это приложение использует метод грубой силы, чтобы попытаться решить каждый список. Сначала я создаю список потенциальных карт для выбора, затем я определяю, какая из них подходит лучше всего (удаляет большинство символов + сокращает большинство длинных слов), добавляю это в список результатов и продолжаю этот процесс, пока не выберу достаточно потенциальных карт. чтобы удалить каждое слово в списке, затем я сопоставляю эти карточки с каждым словом и печатаю вывод.
Если вы хотите увидеть более ограниченную версию этого кода, не загружая и не создавая прилагаемое приложение Windows Form, вы можете использовать предоставленную ссылку для запуска моей программы на меньших наборах данных, обратите внимание, что это версия консольного приложения, поэтому карты НЕ вращаются:
http://ideone.com/fork/VD1gJF
лист регистраций изменений
Current - добавлена улучшенная оптимизация результатов, предложенная @Zgarb
Обновление 3 - больше очистки кода, исправлено больше ошибок, улучшены результаты
Обновление 2 - Windows Forms, более подробный вывод
Обновление 1 - новая / улучшенная поддержка симметрии символов
Оригинал - Консольное приложение
Примеры
acr, aft, ain, sll, win, скажем, сказал, быстрый, эпический
он, будет, не будет, будет, не хотел бы, пока, ты, ты, ты
аааа, 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
?