Давайте сделаем некоторые "enciph5r47g"

Я реализовал алгоритм шифрования /расшифровки из этой задачи для гольфа в кодах .

  • Шифрование читает строку слева направо, заменяя каждый символ числом N (0-9), чтобы указать, что он совпадает с символом N + 1 перед ним.
  • Только символы до 10 позиций назад заменить.
  • Расшифровка завершает процедуру.
  • Зашифрованная строка, которая должна быть зашифрована, должна содержать символов в диапазоне 32-126 и без цифр (0-9).
  • Зашифрованная строка должна содержать только символы в диапазоне 32-126.

Класс шифрования

using System;
using System.Diagnostics;
using System.Linq;

namespace Enciph5r47g {
    public static class Cipher {
        public static string Encipher(string str) {
            CheckStringThrowException(str, IsEnciphered: false);

            string encStr = string.Empty;

            // For every character c in unciphered string...
            for (int index = 0; index<str.Length; index++) {
                char c = str[index];
                char digit = FirstDigit;
                bool digitIsUsed = false;

                // ...check its previous characters from right to left (so long as we are within available digits).
                for (int backIndex=index-1; (backIndex >= 0) && (digit <= LastDigit); backIndex--) {
                    // If a character matches c, add a digit in place of c in encoded string.
                    if (str[backIndex] == c) {
                        encStr = encStr + digit;
                        digitIsUsed = true;
                        break;
                    }
                    digit++;
                }

                // If no character matched c, add the original character in encoded string.
                if (!digitIsUsed)
                    encStr = encStr + c;
            }

            AssertCheckString(encStr, IsEnciphered: true);
            return encStr;
        }

        public static string Decipher(string str) {
            CheckStringThrowException(str, IsEnciphered: true);

            string decStr = string.Empty;

            // For every character c in enciphered string...
            for (int index = 0; index<str.Length; index++) {
                char c = str[index];

                // ...if not a digit, add it in deciphered string...
                if (!char.IsDigit(c)) {
                    decStr = decStr + c;
                    continue;
                }

                // ...else go to the character the digit is referring...
                int backIndex = index - ((int)c - (int)FirstDigit + 1);
                if (backIndex < 0)
                    throw new ArgumentException(string.Format(
                        "Invalid digit '{0}' in position {1} in enciphered string '{2}'", c, index, str));
                // ...and add this character in deciphered string.
                decStr = decStr + decStr[backIndex];
            }

            AssertCheckString(decStr, IsEnciphered: true);
            return decStr;
        }


        private static readonly char FirstDigit = '0';
        private static readonly char LastDigit = '9';

        #region "Cipherstring validation"

        private static void CheckStringThrowException(string s, bool IsEnciphered) {
            string msg = CheckString(s, IsEnciphered);
            if (msg != string.Empty)
                throw new ArgumentException(msg);
        }

        private static void AssertCheckString(string s, bool IsEnciphered) {
            string msg = CheckString(s, IsEnciphered);
            Debug.Assert(msg == string.Empty);
        }

        /// <returns>A message describing the error in s, or empty string for no error.</returns>
        private static string CheckString(string s, bool IsEnciphered) {
            string stringDescr = IsEnciphered ? "Enciphered" : "Deciphered"; 

            char ch = s.ToCharArray().FirstOrDefault((c) => !IsAllowed(c));
            if (ch != char.MinValue)
                return string.Format("{0} string '{1}' contains unallowed character '{2}'", 
                    stringDescr, s, ch.ToString());

            if (!IsEnciphered) {
                ch = s.ToCharArray().FirstOrDefault((c) => char.IsDigit(c));
                if (ch != char.MinValue)
                    return string.Format("{0} string '{1}' contains digit '{2}'", 
                        stringDescr, s, ch.ToString());
            }

            return string.Empty;
        }

        private static bool IsAllowed(char c) => 32 <= c && c <= 126;

        #endregion
    }
}

Класс CipherPair

namespace Enciph5r47g.Test {
    internal struct CipherPair {
        public CipherPair(string dec, string enc) {
            DecipheredValue = dec;
            EncipheredValue = enc;
        }

        public string DecipheredValue { get; private set; }
        public string EncipheredValue { get; private set; }
    }
}

Класс CipherTest

using Enciph5r47g;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;

namespace Enciph5r47g.Test {
    [TestClass]
    public class CipherTest {
        [ExpectedException(typeof(ArgumentException))]
        [TestMethod]
        public void EncipherInvalidString() {
            string enciphered = Cipher.Encipher(InvalidCipherString);
        }

        [ExpectedException(typeof(ArgumentException))]
        [TestMethod]
        public void EncipherInvalidStringWithDigit() {
            string enciphered = Cipher.Encipher(InvalidDecipheredStringWithDigit);
        }

        [TestMethod]
        public void Encipher() {
            foreach (CipherPair pair in TestData) {
                string enciphered = Cipher.Encipher(pair.DecipheredValue);
                Assert.AreEqual(pair.EncipheredValue, enciphered, "Enciphering('{0}').", pair.DecipheredValue);
            }
        }


        [ExpectedException(typeof(ArgumentException))]
        [TestMethod]
        public void DecipherInvalidString() {
            string deciphered = Cipher.Decipher(InvalidCipherString);
        }

        [TestMethod]
        public void DecipherInvalidEncipheredString() {
            foreach (string s in InvalidEncipheredStrings) {
                try {
                    string deciphered = Cipher.Decipher(s);
                    Assert.Fail("Deciphering invalid enciphered string '{0}' did not throw expected exception.", s);
                } catch (ArgumentException) {
                    // Expected exception thrown, no action necessary.
                }
            }
        }

        [TestMethod]
        public void DecipherTest() {
            foreach (CipherPair pair in TestData) {
                string deciphered = Cipher.Decipher(pair.EncipheredValue);
                Assert.AreEqual(pair.DecipheredValue, deciphered, "Deciphering('{0}').", pair.EncipheredValue);
            }
        }


        private static readonly string InvalidCipherString = ((char)127).ToString();
        private static readonly string InvalidDecipheredStringWithDigit = ('0').ToString();

        private string[] InvalidEncipheredStrings = new string[] {
            "0",
            "a1",
        };

        private CipherPair[] TestData = new CipherPair[] {
            new CipherPair("abcd", 
                           "abcd"),
            new CipherPair("aaaa", 
                           "a000"),
            new CipherPair("banana", 
                           "ban111"),
            new CipherPair("Hello World!", 
                           "Hel0o W2r5d!"),
            new CipherPair("this is a test", 
                           "this 222a19e52"),
            new CipherPair("golfing is good for you", 
                           "golfin5 3s24o0d4f3r3y3u"),
            new CipherPair("Programming Puzzles & Code Golf", 
                           "Prog2am0in6 Puz0les7&1Cod74G4lf"),
            new CipherPair("Replicants are like any other machine. They're either a benefit or a hazard.", 
                           "Replicants 4re3lik448ny3oth8r5mac6in8.8T64y'r371it9376a1b5n1fit7or2a1h2z17d."),
        };
    }
}

Был бы рад услышать ваши комментарии.

8 голосов | спросил Manolis 10 AMpMon, 10 Apr 2017 00:25:23 +030025Monday 2017, 00:25:23

2 ответа


6
private static void CheckStringThrowException(string s, bool IsEnciphered)
{
  string msg = CheckString(s, IsEnciphered);
  if (msg != string.Empty)
      throw new ArgumentException(msg);
}
  • Я считаю, что у вас должно быть два метода проверки строки. Один для каждого случая.
  • Имя CheckString - действительно плохое имя. Мне нужно было достаточно времени, чтобы выяснить, что здесь происходит, только чтобы обнаружить, что он не только проверяет строку, но и создает сообщение об ошибке! Эти две обязанности необходимо разделить, например.
void ValidateDecipheredString(string text) {...} 

void ValidateEncipheredString(string text) {...} 

Каждый из этих методов уже может генерировать исключение с соответствующим сообщением.


private static void AssertCheckString(string s, bool IsEnciphered)
{
  string msg = CheckString(s, IsEnciphered);
  Debug.Assert(msg == string.Empty);
}

Имя Assert в этом методе предполагает, что оно предназначено только для отладки, но оно не украшено кодом ConditionalAttribute. Разве вы на самом деле не пишете тесты для этой цели? Я считаю, что этот метод не нужен.

ответил t3chb0t 11 PMpTue, 11 Apr 2017 17:02:35 +030002Tuesday 2017, 17:02:35
6

Стиль кода:

  1. Обычно вы добавляете фигурные скобки в новую строку на C #.
  2. Параметры метода должны использовать случай верблюда: IsEnciphered => isEnciphered
  3. У вас есть несогласованный интервал между операторами: backIndex >= 0, но index<str.Length, например. Первое лучше.

Обратитесь к этим и для других общих рекомендаций.


        char ch = s.ToCharArray().FirstOrDefault((c) => !IsAllowed(c));
        if (ch != char.MinValue)

Это, вероятно, не сработает, если исходная строка содержит char.MinValue, создавая ложноположительную проверку.


private static string CheckString(string s, bool IsEnciphered)

Я бы предпочел иметь два отдельных метода: один для проверки ввода и один для проверки вывода. Наличие флага для этого выглядит грязным и плохо масштабируется.


Вы можете заменить некоторые из ваших циклов на LINQ. Например, из чего я могу это увидеть:

            char digit = FirstDigit;
            bool digitIsUsed = false;

            // ...check its previous characters from right to left (so long as we are within available digits).
            for (int backIndex=index-1; (backIndex >= 0) && (digit <= LastDigit); backIndex--) {
                // If a character matches c, add a digit in place of c in encoded string.
                if (str[backIndex] == c) {
                    encStr = encStr + digit;
                    digitIsUsed = true;
                    break;
                }
                digit++;
            }

            // If no character matched c, add the original character in encoded string.
            if (!digitIsUsed)
                encStr = encStr + c;

, вероятно, эквивалентен этому ( IndexOf ):

var digit = str.Reverse().Skip(str.Length - index).Take(10).IndexOf(c);
encStr += digit < 0 ? c.ToString() : digit.ToString(); 

У меня не было возможности проверить это, хотя ^^. Это может быть дополнительно оптимизировано, если вы измените входную строку перед входом в внешний цикл. Затем вы можете просто использовать str.Skip(index) вместо str.Reverse().Skip(str.Length - index). Вы также можете заменить внешний цикл на Enumerable.Select. Решение LINQ не будет столь же быстрым, как основано на петле, по очевидным причинам, но оно должно быть намного короче и, вероятно, лучше с точки зрения удобочитаемости.

То же самое можно сделать и для декодирования.


Наверное, не так уж важно, поскольку вы играете в кодовое гольф, но я думаю, что нет причин для Cipher быть статичным. Нестатический класс был бы более полезен. Под этим я подразумеваю, что вы не можете расширять или заменять (в LSP-смысле) статический класс. Если Cipher был частью более крупного программного обеспечения, я бы рекомендовал определить контракт, скажем:

interface ICipher 
{
    string Encipher(string str);
    string Decipher(string str);
}

и заменяя

public static class Cipher

с

public class Cipher : ICipher 

Затем вы сможете продлить или изменить реализацию стратегии кодирования при необходимости без каких-либо проблем.

ответил Nikita B 11 PMpTue, 11 Apr 2017 17:00:04 +030000Tuesday 2017, 17:00:04

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132