Skip to content

Instantly share code, notes, and snippets.

@skalinets
Created February 15, 2012 11:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save skalinets/1835067 to your computer and use it in GitHub Desktop.
Save skalinets/1835067 to your computer and use it in GitHub Desktop.
StringCalculator Kata (no regex, very close to ideal clean code -- for me)
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using FluentAssertions;
using Xunit;
namespace StringCalculatorKata
{
public class StringCalculatorTests
{
[Fact]
public void should_return_0_for_empty_string()
{
// arrange
var calculator = new StringCalculator();
// act
var result = calculator.Add("");
// assert
result
.Should()
.Be(0);
}
[Fact]
public void should_return_5_for_5()
{
// arrange
var calculator = new StringCalculator();
// act
var result = calculator.Add("5");
// assert
result
.Should()
.Be(5);
}
[Fact]
public void should_return_6_for_2_and_4()
{
// arrange
var calculator = new StringCalculator();
// act
var result = calculator.Add("2,4");
// assert
result
.Should()
.Be(6);
}
[Fact]
public void should_return_sum_for_unknown_amount_of_numbers()
{
// arrange
var calculator = new StringCalculator();
// act
var unknownNumbers = GetUnknownNumbers();
var unknownNumbersString = String.Join(",", unknownNumbers);
var result = calculator.Add(unknownNumbersString);
// assert
var expectedResult = unknownNumbers.Sum();
result
.Should()
.Be(expectedResult);
}
[Fact]
public void random_numbers_should_be_random()
{
GetUnknownNumbers().Should().NotEqual(GetUnknownNumbers());
}
[Fact]
public void should_support_new_line_as_delimiter()
{
// arrange
var calculator = new StringCalculator();
// act
var result = calculator.Add("1\n2,3");
// assert
result
.Should()
.Be(1 + 2 + 3);
}
[Fact]
public void should_allow_to_define_custom_delimiter()
{
// arrange
var calculator = new StringCalculator();
// act
var result = calculator.Add("//*\n2*5*4");
// assert
result
.Should()
.Be(2 + 5 + 4);
}
[Fact]
public void should_raise_exception_for_negative_numbers_and_include_numbers_to_message()
{
// arrange
var calculator = new StringCalculator();
// act
Action action = () => calculator.Add("-1,-5,-9,3,4");
// assert
action
.ShouldThrow<InvalidOperationException>()
.WithMessage(StringCalculator.NegativesAreNotAllowedError + "-1,-5,-9");
}
[Fact]
public void should_ignore_numbers_bigger_than_1000()
{
// arrange
var calculator = new StringCalculator();
// act
int result = calculator.Add("1001,2");
// assert
result
.Should()
.Be(2);
}
[Fact]
public void should_support_delimiters_of_any_length()
{
// arrange
var calculator = new StringCalculator();
// act
var result = calculator.Add("//[&@#]\n2&@#7&@#8&@#2");
// assert
result
.Should()
.Be(2 + 7 + 8 + 2);
}
[Fact]
public void should_support_muliple_delimiters_of_any_length()
{
// arrange
var calculator = new StringCalculator();
// act
var result = calculator.Add("//[^%][(*]\n2^%3^%7(*4(*5");
// assert
result
.Should()
.Be(2 + 3 + 7 + 4 + 5);
}
private int[] GetUnknownNumbers()
{
var random = new Random(Guid.NewGuid().GetHashCode());
var number = random.Next(100);
return Enumerable.Range(0, number)
.Select(_ => random.Next(100))
.ToArray();
}
}
public class StringCalculator
{
public const string NegativesAreNotAllowedError = "negatives are not allowed: ";
private string[] delimiters = new[] {",", "\n"};
private int[] ints;
private string numbers;
public int Add(string numbers)
{
if (numbers == "") return 0;
this.numbers = numbers;
ProcessDelimiters();
SplitNumbersToInts();
CheckIntsForNegatives();
return ints.Sum();
}
private void SplitNumbersToInts()
{
ints = numbers
.Split(delimiters, StringSplitOptions.None)
.Select(Int32.Parse)
.Where(_ => _ <= 1000)
.ToArray();
}
private void CheckIntsForNegatives()
{
var negatives = ints.Where(i => i < 0).ToArray();
if (negatives.Any())
{
throw new InvalidOperationException(NegativesAreNotAllowedError + String.Join(",", negatives));
}
}
private void ProcessDelimiters()
{
var customDelimitersSpecified = numbers.StartsWith("//");
if (customDelimitersSpecified)
{
var multiCharDelimitersSpecified = numbers[2] == '[';
delimiters = multiCharDelimitersSpecified ? GetAllMultiCharCustomDelimiters() : GetSingleCharCustomDelimiter();
RemoveDelimitersFromNumbers();
}
}
private string[] GetSingleCharCustomDelimiter()
{
return new[] {numbers[2].ToString(CultureInfo.InvariantCulture)};
}
private string[] GetAllMultiCharCustomDelimiters()
{
var result = new List<string>();
numbers.TakeWhile(c => c != '\n')
.Aggregate(new StringBuilder(), (builder, c) => DoMagic(result, c, builder));
return result.ToArray();
}
private static StringBuilder DoMagic(ICollection<string> list, char currentChar, StringBuilder stringBuilder)
{
if (currentChar == '[')
{
stringBuilder.Clear();
}
else if (currentChar == ']')
{
list.Add(stringBuilder.ToString());
}
else
{
stringBuilder.Append(currentChar);
}
return stringBuilder;
}
private void RemoveDelimitersFromNumbers()
{
numbers = String.Join("", numbers
.SkipWhile(c => c != '\n'));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment