拼写单词的最佳字母卡


15

假设您有一个单词列表,并且希望能够使用字母卡拼写每个单词。例如,拼写 cat,您将使用三张标记为C,A,T的卡片。

假设每张卡是 双面的,请提交程序以定义可用于拼写整个单词列表的最小卡数。

输入是单词列表,可以是基于文件的,硬编码的,命令行的。输出是卡片列表,您可以按照自己的意愿格式化和排序卡片,前提是可以清楚地标记卡片的方式。

情况并不重要:高尔夫,高尔夫和高尔夫是等效的。

一些提示:

  • 卡数不能少于最长单词的长度
  • 一张卡的两边都有相同的字母是没有意义的
  • 当大小写不重要时,建议使用小写以利用某些对称性

例如,这些利用了某些对称性

输入: ben,bog,bug,den,do,doe,dog,due,dug,Ed,end,gob,上帝,Ned,ode,笔,Poe,pug

输出: b / d,e / g,o / n

输入: 一个和一个,猿,是,床,芽,bur,bur,Dan,Deb,dub,耳朵,Ed,时代,午睡,泛,豌豆,酒吧,Rae,跑,擦

输出: a / b,d / r,e / n

使其成为一场流行竞赛,因此代码的优美性,运行时性能和机敏性(包括规则弯曲和漏洞)非常重要!

加成:一些人询问“允许的”对称性,是否可以使用特殊字体以及是否可以折叠卡。

允许的对称性是在0、90、180或270度旋转后看起来彼此相似的任何字母。这包括b / q,d / p和n / u。我还会说M / W,Z / N,当然还有I / l(大写i,小写L)。我可能正在抓挠表面,因此如果您不确定其他任何问题,请询问。

为了简单起见,请限制为标准的sans-serif字体,例如SE中使用的字体。

就折叠而言,虽然您可以做一些令人惊奇的替换,例如B可以是D,E,F,I,P或R,如果真的进行创造性的折叠,则可以是C或L,但我认为这实际上是弯曲的!

当我和孩子们玩一些类似的纸牌时,我想到了这个问题。我注意到单面卡的制作非常容易,而双面卡的制作则非常困难。

另外:提供了赏金,以奖励最受欢迎的答案。如果有平局,将奖励给第一个提交的人。

另一个提示:

  • 解决单面问题将使您了解所需的最小卡数(例如,20张单面卡转换为至少10张所需的双面卡)

另外:哦,麻烦了,我很忙,忘了赏金即将到期。最终没有结果,因为唯一的答案是在赏金开始之前提交的!对于那个很抱歉。


3
只是为了澄清,允许什么?是唯一的对称对n/ud/p?怎么样b/qm/w?如果我将P卡片对折成两半D怎么办,那么上半部分就变成了怎么办?
Sp3000

3
1.是他们的认可“对称符号”列表,我认为它可能会根据字体而有所不同,这是一个潜在的漏洞(使用字符全部相同的字体,即,卡始终等于/或类似的东西)2.“大小写无关紧要”,因此“ N”可以用“ u”表示吗?
David Rogers 2014年

我认为您正在通过使它成为人气竞赛来为您的问题辩护。告诉人们要有创造力,您就无法获得创造力;给他们一个艰巨的挑战,让他们竭尽所能,您就无法获得创造力。
xnor

@ sp3000-当然是b / q。关于您的其他问题,我将澄清规则。

1
将其作为一场人气竞赛(更不用说赏金了)并不完全正确。有什么保证是最佳的答案?如果一个答案给出了次优的结果,但由于某些原因却获得最高的选票怎么办?
Optimizer

Answers:


5

C#-CardChooser

摘要

此应用程序使用蛮力方法来尝试解决每个列表。首先,我创建一个可供选择的潜在卡片列表,然后确定哪个是最合适的(删除最多字符+最缩短长单词),将其添加到结果列表,然后继续此过程,直到我选择了足够的潜在卡片删除列表中的每个单词,然后将这些卡片与每个单词重新匹配并输出输出。

如果您想查看此代码的更有限版本而不下载并构建提供的Windows窗体应用程序,则可以使用提供的链接对较小的数据集运行我的程序,请注意,这是控制台应用程序版本,因此卡未旋转:http//ideone.com/fork/VD1gJF

修订记录

当前-添加了@Zgarb建议的更好的结果优化

更新3-更多代码清理,更多错误修复,更好的结果

更新2-Windows窗体,更详细的输出

更新1-新增/更好地支持字符对称

原始-控制台应用程序

例子

acr,aft,ain,sll,win,比如说,快速,史诗 输出0

他,会,不会,会,会,不会,但是,你,你,你会 输出1

aaaa,bbbb,cccc
输出2

我仍然需要将其合并到一个更大的项目中,所有的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;
    }
}

1
没有i卡片,您如何拼写“力量” ?
Claudiu 2014年

“我当然/ L(大写字母i,小写字母L)的”这样的小写字母l应站在为资本I.
大卫·罗杰斯

哦,看来您应该在输出中指定该信息
Claudiu 2014年

@Claudiu是的,我想了一会儿,这实际上是我问了宜民荣的第二个问题,我想他正确地阐明了这一点,如果我在卡片中输出“ l”,就应该像示例中那样进行推断可以同时用于大写的I和小写的l,这将导致输出与大小写不完全匹配,但是我认为这是“ OK”,因为它仍然满足问题的条件,但是我还是开放的为了澄清是否有必要,也许在以后的版本中,我可以输出生成的带有旋转字符或类似字符的字符串...
David Rogers

我认为有一个错误。said的最后一封信不是W还是p
骄傲的haskeller 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.