【问题标题】:Generate the shortest alphanumeric save code生成最短的字母数字保存代码
【发布时间】:2018-12-11 15:41:01
【问题描述】:

出于游戏的目的,我需要生成一个保存代码,用户可以在某处记录并使用它稍后重新加载他的游戏状态(不可能有持久数据)。 保存代码需要像 6DZF1D3 一样短,(base 36 或 base 62 字符串)。

游戏关卡的分数可以简化为string,例如 1232312321321321321,其中每个字符都是“星”(1、2 或 3 星)中的关卡分数。 将有大约 30 个游戏关卡。

我想为用户生成尽可能短的代码,所以我的第一个想法是在一个数组中生成所有可能性。然后生成用户所在密钥的base 62码。但是有 3^30 种可能性,这会生成一个包含 2e+14 个键/值的数组,这对内存和 CPU 不利。

第二个想法是使用基数 4 到 62 的转换器,但我发现的大多数代码都使用 intlong,它们的大小有限且少于 30 个字符。

您知道如何生成由字母数字字符组成的最短保存代码吗?

【问题讨论】:

    标签: c# powerset


    【解决方案1】:

    将二进制数据转换为文本表示的最常用方法是Base64。每个字符代表 6 位信息。你只有不到 48 位的信息,这很好地让你得到了 8 个 Base64 数字。

    所以策略是:
    1. 使用this algorithm 将您的基数 3(星形)数组转换为基数 2。
    2.将位转换为字节数组using Convert.ToByte();
    3.使用Convert.ToBase64String()创建Base64字符串。

    编辑:我知道你想把它放在 Base36 中,there are some code examples that can do it. 此代码需要一个字符串作为输入,但将其转换为 char[],所以你可以只提供 ByteArray而是。

    编辑2: 证据在吃,刚刚为任何base36(但可以扩展)的base创建了一个来回转换器。对于您的星星,您只需提供一个字符串,其中星星的值是数字(1 到 3)。

        private static string ConvertToOtherBase(string toConvert, int fromBase, int toBase)
        {
            const string characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    
            long value = 0;
            string result = "";
    
            foreach (char digit in toConvert.ToCharArray())
                value = (value * fromBase) + characters.IndexOf(digit);
    
            while (value > 0)
            {
                result = characters[(int)(value % toBase)] + result;
                value /= toBase;
            }
    
            return result;
        }
    

    你可以这样称呼它(来回):

            var stars = "112131121311213112131121311213";
    
            string base36Result = ConvertToOtherBase(stars, 4, 36);
            // 32NSB7MBR9T3
    
            string base4Result = ConvertToOtherBase(base36Result, 36, 4);
            // 112131121311213112131121311213
    

    【讨论】:

    • 那个 fromDigits 函数应该生成一个 intlong 在我的情况下还不够大?
    • Base36.Encode() 也是一样的,它采用 long 输入,而不是 string,所以我限制为 22 个字符,所以如果我是之前生成 2e+14 键,而不是传递我的分数字符串.. I
    • @DrNootNoot 当然,提供的链接仅供参考,不会给您直接的解决方案。但因为我真的很喜欢以一种干净的方式完成这项工作的精神挑战,所以我只提供了我自己的版本,可以在第二次编辑中找到。
    【解决方案2】:

    这是我用@Yosh 的想法编写的代码,它的功能是:https://www.pvladov.com/2012/07/arbitrary-to-decimal-numeral-system.html

    string code = "";
    string[] scoreArray = new string[100];
    foreach (KeyValuePair<string, LevelScore> l in scores)
    {
        scoreArray[l.Value.levelNum - 1] = Convert.ToString(l.Value.stars, 2).PadLeft(2, '0');
    }
    for (int s = 0; s < scoreArray.Length; s++)
    {
        code = scoreArray[s] + code;
    }
    string b2 = code ;// like "111111111111111111111111111111111111111111111111111111111111";
    print("b2 " + b2);
    
    long b10 = ScoreUtils.ArbitraryToDecimalSystem(b2, 2);
    print("b10 " + b10);
    
    string b36 = ScoreUtils.DecimalToArbitrarySystem(b10, 36);
    print("b36 " + b36);
    

    【讨论】:

      【解决方案3】:

      当然,这个问题是基于意见的,但这是一种简单的保存方法

      创建对象

      public class Memento
      {
           public int Id {get; set;}
           public int Level {get; set;}
           public int Score {get; set;}
      }
      

      然后只需使用Newtonsoft.Json 库来序列化它。最重要的是,您可以加密序列化的 JSON,这样用户就无法看到已保存数据的内部,并将其写入磁盘。但是,当然,有很多方法可以保持分数。顺便说一句,我的班级名称应该指向一个专门解决这个问题的编程模式

      更新

      阅读您的评论 - 这是您要查找的内容吗?

          int x = 5, y = 10;
          byte[]xb  = BitConverter.GetBytes(x);
          var enumer  = xb.Concat(BitConverter.GetBytes(y));
          string outStr = Convert.ToBase64String(enumer.ToArray());
      
          Console.WriteLine(outStr);
          // your code: BQAAAAoAAAA=
      

      顺便说一句,如果你使用 int16,你的代码会更短:BQAKAA==

          byte[] back = Convert.FromBase64String(outStr);
          short a = BitConverter.ToInt16(back, 0);
          short b = BitConverter.ToInt16(back, 2);
          Console.WriteLine(a + "_" + b); 
      

      【讨论】:

      • 抱歉,澄清一下,我希望生成一个简短的 base 36 代码,用户可以写在纸上,并在下次使用它(不可能有持久数据)
      • @DrNootNoot 你的意思是,像这样的字符串7G6?你的意思是,你想手动生成用户代码,以后会使用哪个用户?
      • 是的,就是这样,我会按照你的解释编辑我的帖子;)
      • 根据您的更新,我执行了此功能,但我的 27 分数字符串已转换为 144 个字符长度的 base64 字符串: public static string Test(string s) { var lst = new List( ); foreach (char c in s) { lst.AddRange(BitConverter.GetBytes((int)Char.GetNumericValue(c))); } 字符串 outStr = Convert.ToBase64String(lst.ToArray());返回输出Str; }
      • @DrNootNoot 我只是缩短了一点
      【解决方案4】:

      如果用户应该能够将其写下来,我会更喜欢 Base58 编码。因此,对于每个级别 1-3 个可能的星,我们需要 2 位来编码每个级别。

      00 => 0 star (would mean last unplayed level reached)
      01 => 1 star
      10 => 2 stars
      11 => 3 stars
      

      30 个关卡需要 60 位,所有带 3 颗星的关卡都是十进制的 1152921504606846975。这个,base58 编码,应该是 3gDmDv6tjHG,不会太长吧?!

      更新:

      @DrNootNoot 很高兴您找到了解决问题的方法!但我很高兴为我提到的 base58 版本破解一小段代码。我改编了你使用的 Pavel Vladov 的两个函数。

      也许有一天其他人也会遇到类似的问题:

      using System;
      using System.Collections.Generic;
      
      namespace ConsoleApplication1
      {
          class Program
          {
              static void Main(string[] args)
              {
                  string[] scoreArray = new string[30] { "1", "2", "3", "3", "1", "2", "2", "2", "3", "1", "1", "1", "2", "3", "2", "1", "2", "3", "1", "1", "1", "2", "2", "2", "1", "1", "2", "1", "2","3" };
      
                  ulong numScore = ScoreToDecimal(scoreArray);
      
                  string saveScore = UDecimalToBase58String(numScore);
      
                  Console.WriteLine("Score array: " + String.Join("-",scoreArray));
                  Console.WriteLine("Numeric score: " + Convert.ToString(numScore));
                  Console.WriteLine("Base58 score: " + saveScore);
      
                  ulong numScoreRestored = Base58StringToUDecimal(saveScore);
                  string[] scoreArrayRestored = DecimalToScore(numScoreRestored);
      
                  Console.WriteLine("From Base58 converted numeric score: " + Convert.ToString(numScoreRestored));
                  Console.WriteLine("From Base58 converted score array: " + String.Join("-", scoreArray));
                  Console.Read();
              }
      
              /// <summary>
              /// Converts the stars-per-level array to a decimal value for the saved game.
              /// </summary>
              /// <param name="score">score array to convert. Max. 32 entries/levels.</param>
              /// <returns></returns>
              public static ulong ScoreToDecimal(string[] score)
              {
                  int arrLength = score.Length;
      
                  if (arrLength > 32)
                      throw new ArgumentException("The score array must not be larger than 32 entries");
      
                  ulong result = 0;
      
                  for (int i = arrLength - 1; i >= 0; i--)
                  {
                      ulong singleScore = Convert.ToUInt64(score[i]);
      
                      if (singleScore > 3)
                          throw new ArgumentException(String.Format("Invalid score value. Max. allowed value is 3, but {0} was given at index {1}", singleScore, i), "score");
      
                      result += (singleScore << ((arrLength - 1 - i) * 2));
                  }
      
                  return result;
              }
      
              /// <summary>
              /// Converts the decimal value of the saved game back to a stars-per-level array.
              /// </summary>
              /// <param name="decimalScore">Maximal 64-bit unsigned saved game number to convert.</param>
              /// <returns></returns>
              public static string[] DecimalToScore(ulong decimalScore)
              {
                  List<string> result = new List<string>();
                  while(decimalScore > 0)
                  {
                      result.Add(Convert.ToString(decimalScore % 4));
                      decimalScore /= 4;
                  }
      
                  result.Reverse();
                  return result.ToArray();
              }
      
              /// <summary>
              /// Adapted Unsigned-Base58-Version of Pavel Vladovs DecimalToArbitrarySystem function.
              /// See: https://www.pvladov.com/2012/05/decimal-to-arbitrary-numeral-system.html
              /// </summary>
              /// <param name="decimalNumber"></param>
              /// <returns></returns>
              public static string UDecimalToBase58String(ulong decimalNumber)
              {
                  const int BitsInLong = 64;
                  const int FixedRadix = 58;
                  const string Digits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
      
                  if (decimalNumber == 0)
                      return "0";
      
                  int index = BitsInLong - 1;
                  ulong currentNumber = decimalNumber;
                  char[] charArray = new char[BitsInLong];
      
                  while (currentNumber != 0)
                  {
                      int remainder = (int)(currentNumber % FixedRadix);
                      charArray[index--] = Digits[remainder];
                      currentNumber = currentNumber / FixedRadix;
                  }
      
                  string result = new String(charArray, index + 1, BitsInLong - index - 1);
      
                  return result;
              }
      
              /// <summary>
              /// Adapted Unsigned-Base58-Version of Pavel Vladovs ArbitraryToDecimalSystem function.
              /// See: https://www.pvladov.com/2012/07/arbitrary-to-decimal-numeral-system.html
              /// </summary>
              /// <param name="base58String"></param>
              /// <returns></returns>
              public static ulong Base58StringToUDecimal(string base58String)
              {
                  const int FixedRadix = 58;
                  const string Digits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
      
                  if (String.IsNullOrEmpty(base58String))
                      return 0;
      
                  ulong result = 0;
                  ulong multiplier = 1;
                  for (int i = base58String.Length - 1; i >= 0; i--)
                  {
                      char c = base58String[i];
                      int digit = Digits.IndexOf(c);
                      if (digit == -1)
                          throw new ArgumentException(
                              "Invalid character in the arbitrary numeral system number",
                              "number");
      
                      result += (uint)digit * multiplier;
                      multiplier *= FixedRadix;
                  }
      
                  return result;
              }
          }
      }
      

      问候

      【讨论】:

      • 谢谢!我会尝试在我这边重现这个!
      • 我不确定如何处理字节,我想我不应该只将 3e+30 转换为二进制,而是使用二进制操作?
      • base 36 只给了我 1 个字符,所以我想我会坚持下去:8RC4KBDVSS1R
      • 60 位太多了。每个级别只需要 1.5 位,因为 0 星不是一个明确的选项。与十进制相同,我们不将数字 42 记为 0000042,而只是 42。
      • @AutomatedChaos 我知道 0 不是明确需要的,但是你将如何对 1/2 位进行编码?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-10
      • 1970-01-01
      • 1970-01-01
      • 2013-12-13
      • 2016-07-05
      • 1970-01-01
      相关资源
      最近更新 更多