trackler 2.0.5.8 → 2.0.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/common/exercises/sum-of-multiples/description.md +2 -2
- data/lib/trackler/version.rb +1 -1
- data/tracks/csharp/config.json +24 -0
- data/tracks/csharp/exercises/book-store/BookStoreTest.cs +95 -0
- data/tracks/csharp/exercises/book-store/Example.cs +70 -0
- data/tracks/csharp/exercises/connect/ConnectTest.cs +131 -0
- data/tracks/csharp/exercises/connect/Example.cs +121 -0
- data/tracks/csharp/exercises/exercises.csproj +1 -1
- data/tracks/csharp/exercises/markdown/Example.cs +108 -0
- data/tracks/csharp/exercises/markdown/HINTS.md +3 -0
- data/tracks/csharp/exercises/markdown/Markdown.cs +151 -0
- data/tracks/csharp/exercises/markdown/MarkdownTest.cs +76 -0
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1f9e78737566c55c139024513a73f38029f2a0c
|
4
|
+
data.tar.gz: 853360aad86762df2a9279f3ad209e4497c739ad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1517522938b3420f81dd931fb53458a99858c61e4673ec0413c120fc9aff827306e257fb5a316a21a2b771c12ae8a859ea943b53967ade8471031c9761e139e6
|
7
|
+
data.tar.gz: 527684182f9afe7b3e1e4a8b9fbc0a07e1a60dc0272d1188efb311678f065d4eb49118f5c059a87bb6d8a6daf0158a20e06dea9d45c69c746b01effb5541c3ed
|
@@ -3,5 +3,5 @@ multiples of either 3 or 5, we get 3, 5, 6 and 9, 10, 12, 15, and 18.
|
|
3
3
|
|
4
4
|
The sum of these multiples is 78.
|
5
5
|
|
6
|
-
Write a program that can find the sum of the multiples
|
7
|
-
numbers.
|
6
|
+
Write a program that, given a number, can find the sum of the multiples
|
7
|
+
of a given set of numbers, up to but not including that number.
|
data/lib/trackler/version.rb
CHANGED
data/tracks/csharp/config.json
CHANGED
@@ -560,6 +560,15 @@
|
|
560
560
|
"Transforming"
|
561
561
|
]
|
562
562
|
},
|
563
|
+
{
|
564
|
+
"slug": "markdown",
|
565
|
+
"difficulty": 5,
|
566
|
+
"topics": [
|
567
|
+
"Parsing",
|
568
|
+
"Transforming",
|
569
|
+
"Refactoring"
|
570
|
+
]
|
571
|
+
},
|
563
572
|
{
|
564
573
|
"slug": "run-length-encoding",
|
565
574
|
"difficulty": 5,
|
@@ -568,6 +577,13 @@
|
|
568
577
|
"Transforming"
|
569
578
|
]
|
570
579
|
},
|
580
|
+
{
|
581
|
+
"slug": "book-store",
|
582
|
+
"difficulty": 5,
|
583
|
+
"topics": [
|
584
|
+
"Recursion"
|
585
|
+
]
|
586
|
+
},
|
571
587
|
{
|
572
588
|
"slug": "tournament",
|
573
589
|
"difficulty": 6,
|
@@ -706,6 +722,14 @@
|
|
706
722
|
"Logic"
|
707
723
|
]
|
708
724
|
},
|
725
|
+
{
|
726
|
+
"slug": "connect",
|
727
|
+
"difficulty": 8,
|
728
|
+
"topics": [
|
729
|
+
"Parsing",
|
730
|
+
"Transforming"
|
731
|
+
]
|
732
|
+
},
|
709
733
|
{
|
710
734
|
"slug": "say",
|
711
735
|
"difficulty": 8,
|
@@ -0,0 +1,95 @@
|
|
1
|
+
using System.Collections.Generic;
|
2
|
+
using System.Linq;
|
3
|
+
using NUnit.Framework;
|
4
|
+
|
5
|
+
[TestFixture]
|
6
|
+
public class BookStoreTest
|
7
|
+
{
|
8
|
+
[Test]
|
9
|
+
public void Basket_with_single_book()
|
10
|
+
{
|
11
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(1)), Is.EqualTo(8));
|
12
|
+
}
|
13
|
+
|
14
|
+
[Ignore("Remove to run test")]
|
15
|
+
[Test]
|
16
|
+
public void Basket_with_two_of_same_book()
|
17
|
+
{
|
18
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(2, 2)), Is.EqualTo(16));
|
19
|
+
}
|
20
|
+
|
21
|
+
[Ignore("Remove to run test")]
|
22
|
+
[Test]
|
23
|
+
public void Empty_basket()
|
24
|
+
{
|
25
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList()), Is.EqualTo(0));
|
26
|
+
}
|
27
|
+
|
28
|
+
[Ignore("Remove to run test")]
|
29
|
+
[Test]
|
30
|
+
public void Basket_with_two_different_books()
|
31
|
+
{
|
32
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(1, 2)), Is.EqualTo(15.2));
|
33
|
+
}
|
34
|
+
|
35
|
+
[Ignore("Remove to run test")]
|
36
|
+
[Test]
|
37
|
+
public void Basket_with_three_different_books()
|
38
|
+
{
|
39
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(1, 2, 3)), Is.EqualTo(21.6));
|
40
|
+
}
|
41
|
+
|
42
|
+
[Ignore("Remove to run test")]
|
43
|
+
[Test]
|
44
|
+
public void Basket_with_four_different_books()
|
45
|
+
{
|
46
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(1, 2, 3, 4)), Is.EqualTo(25.6));
|
47
|
+
}
|
48
|
+
|
49
|
+
[Ignore("Remove to run test")]
|
50
|
+
[Test]
|
51
|
+
public void Basket_with_five_different_books()
|
52
|
+
{
|
53
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(1, 2, 3, 4, 5)), Is.EqualTo(30));
|
54
|
+
}
|
55
|
+
|
56
|
+
[Ignore("Remove to run test")]
|
57
|
+
[Test]
|
58
|
+
public void Basket_with_eight_books()
|
59
|
+
{
|
60
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(1, 1, 2, 2, 3, 3, 4, 5)), Is.EqualTo(51.20));
|
61
|
+
}
|
62
|
+
|
63
|
+
[Ignore("Remove to run test")]
|
64
|
+
[Test]
|
65
|
+
public void Basket_with_nine_books()
|
66
|
+
{
|
67
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(1, 1, 2, 2, 3, 3, 4, 4, 5)), Is.EqualTo(55.60));
|
68
|
+
}
|
69
|
+
|
70
|
+
[Ignore("Remove to run test")]
|
71
|
+
[Test]
|
72
|
+
public void Basket_with_ten_books()
|
73
|
+
{
|
74
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(1, 1, 2, 2, 3, 3, 4, 4, 5, 5)), Is.EqualTo(60));
|
75
|
+
}
|
76
|
+
|
77
|
+
[Ignore("Remove to run test")]
|
78
|
+
[Test]
|
79
|
+
public void Basket_with_eleven_books()
|
80
|
+
{
|
81
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1)), Is.EqualTo(68));
|
82
|
+
}
|
83
|
+
|
84
|
+
[Ignore("Remove to run test")]
|
85
|
+
[Test]
|
86
|
+
public void Basket_with_twelve_books()
|
87
|
+
{
|
88
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1, 2)), Is.EqualTo(75.20));
|
89
|
+
}
|
90
|
+
|
91
|
+
private static List<int> MakeList(params int[] values)
|
92
|
+
{
|
93
|
+
return values.ToList();
|
94
|
+
}
|
95
|
+
}
|
@@ -0,0 +1,70 @@
|
|
1
|
+
using System;
|
2
|
+
using System.Collections.Generic;
|
3
|
+
using System.Linq;
|
4
|
+
|
5
|
+
public class BookStore
|
6
|
+
{
|
7
|
+
public static double CalculateTotalCost(List<int> books)
|
8
|
+
{
|
9
|
+
return CalculateTotalCost(books, 0);
|
10
|
+
}
|
11
|
+
|
12
|
+
private static double CalculateTotalCost(List<int> books, double priceSoFar)
|
13
|
+
{
|
14
|
+
if (books.Count == 0)
|
15
|
+
{
|
16
|
+
return priceSoFar;
|
17
|
+
}
|
18
|
+
|
19
|
+
var groups = books
|
20
|
+
.GroupBy(b => b)
|
21
|
+
.Select(g => g.Key)
|
22
|
+
.ToList();
|
23
|
+
|
24
|
+
var minPrice = double.MaxValue;
|
25
|
+
|
26
|
+
for (int i = groups.Count; i >= 1; i--)
|
27
|
+
{
|
28
|
+
var itemsToRemove = groups.Take(i).ToList();
|
29
|
+
var remaining = books.ToList();
|
30
|
+
|
31
|
+
foreach (var item in itemsToRemove)
|
32
|
+
{
|
33
|
+
remaining.Remove(item);
|
34
|
+
}
|
35
|
+
|
36
|
+
var price = CalculateTotalCost(remaining.ToList(), priceSoFar + CostPerGroup(i));
|
37
|
+
minPrice = Math.Min(minPrice, price);
|
38
|
+
}
|
39
|
+
|
40
|
+
return minPrice;
|
41
|
+
}
|
42
|
+
|
43
|
+
private static double CostPerGroup(int groupSize)
|
44
|
+
{
|
45
|
+
double discountPercentage;
|
46
|
+
|
47
|
+
switch (groupSize)
|
48
|
+
{
|
49
|
+
case 1:
|
50
|
+
discountPercentage = 0;
|
51
|
+
break;
|
52
|
+
case 2:
|
53
|
+
discountPercentage = 5;
|
54
|
+
break;
|
55
|
+
case 3:
|
56
|
+
discountPercentage = 10;
|
57
|
+
break;
|
58
|
+
case 4:
|
59
|
+
discountPercentage = 20;
|
60
|
+
break;
|
61
|
+
case 5:
|
62
|
+
discountPercentage = 25;
|
63
|
+
break;
|
64
|
+
default:
|
65
|
+
throw new InvalidOperationException($"Invalid group size: {groupSize}");
|
66
|
+
}
|
67
|
+
|
68
|
+
return 8 * groupSize * (100 - discountPercentage) / 100;
|
69
|
+
}
|
70
|
+
}
|
@@ -0,0 +1,131 @@
|
|
1
|
+
using NUnit.Framework;
|
2
|
+
using System.Linq;
|
3
|
+
|
4
|
+
public class ConnectTest
|
5
|
+
{
|
6
|
+
private static string MakeBoard(string[] board)
|
7
|
+
{
|
8
|
+
return string.Join("\n", board.Select(x => x.Replace(" ", "")));
|
9
|
+
}
|
10
|
+
|
11
|
+
[Test]
|
12
|
+
public void Empty_board_has_no_winner()
|
13
|
+
{
|
14
|
+
var lines = new[]
|
15
|
+
{
|
16
|
+
". . . . . ",
|
17
|
+
" . . . . . ",
|
18
|
+
" . . . . . ",
|
19
|
+
" . . . . . ",
|
20
|
+
" . . . . ."
|
21
|
+
};
|
22
|
+
var board = new Connect(MakeBoard(lines));
|
23
|
+
Assert.That(board.Result(), Is.EqualTo(Connect.Winner.None));
|
24
|
+
}
|
25
|
+
|
26
|
+
[Ignore("Remove to run test")]
|
27
|
+
[Test]
|
28
|
+
public void One_by_one_board_with_black_stone()
|
29
|
+
{
|
30
|
+
var lines = new[] { "X" };
|
31
|
+
var board = new Connect(MakeBoard(lines));
|
32
|
+
Assert.That(board.Result(), Is.EqualTo(Connect.Winner.Black));
|
33
|
+
}
|
34
|
+
|
35
|
+
[Ignore("Remove to run test")]
|
36
|
+
[Test]
|
37
|
+
public void One_by_one_board_with_white_stone()
|
38
|
+
{
|
39
|
+
var lines = new[] { "O" };
|
40
|
+
var board = new Connect(MakeBoard(lines));
|
41
|
+
Assert.That(board.Result(), Is.EqualTo(Connect.Winner.White));
|
42
|
+
}
|
43
|
+
|
44
|
+
[Ignore("Remove to run test")]
|
45
|
+
[Test]
|
46
|
+
public void Convoluted_path()
|
47
|
+
{
|
48
|
+
var lines = new[]
|
49
|
+
{
|
50
|
+
". X X . . ",
|
51
|
+
" X . X . X ",
|
52
|
+
" . X . X . ",
|
53
|
+
" . X X . . ",
|
54
|
+
" O O O O O"
|
55
|
+
};
|
56
|
+
var board = new Connect(MakeBoard(lines));
|
57
|
+
Assert.That(board.Result(), Is.EqualTo(Connect.Winner.Black));
|
58
|
+
}
|
59
|
+
|
60
|
+
[Ignore("Remove to run test")]
|
61
|
+
[Test]
|
62
|
+
public void Rectangle_black_wins()
|
63
|
+
{
|
64
|
+
var lines = new[]
|
65
|
+
{
|
66
|
+
". O . . ",
|
67
|
+
" O X X X ",
|
68
|
+
" O X O . ",
|
69
|
+
" X X O X ",
|
70
|
+
" . O X ."
|
71
|
+
};
|
72
|
+
var board = new Connect(MakeBoard(lines));
|
73
|
+
Assert.That(board.Result(), Is.EqualTo(Connect.Winner.Black));
|
74
|
+
}
|
75
|
+
|
76
|
+
[Ignore("Remove to run test")]
|
77
|
+
[Test]
|
78
|
+
public void Rectangle_white_wins()
|
79
|
+
{
|
80
|
+
var lines = new[]
|
81
|
+
{
|
82
|
+
". O . . ",
|
83
|
+
" O X X X ",
|
84
|
+
" O O O . ",
|
85
|
+
" X X O X ",
|
86
|
+
" . O X ."
|
87
|
+
};
|
88
|
+
var board = new Connect(MakeBoard(lines));
|
89
|
+
Assert.That(board.Result(), Is.EqualTo(Connect.Winner.White));
|
90
|
+
}
|
91
|
+
|
92
|
+
[Ignore("Remove to run test")]
|
93
|
+
[Test]
|
94
|
+
public void Spiral_black_wins()
|
95
|
+
{
|
96
|
+
var lines = new[]
|
97
|
+
{
|
98
|
+
"OXXXXXXXX",
|
99
|
+
"OXOOOOOOO",
|
100
|
+
"OXOXXXXXO",
|
101
|
+
"OXOXOOOXO",
|
102
|
+
"OXOXXXOXO",
|
103
|
+
"OXOOOXOXO",
|
104
|
+
"OXXXXXOXO",
|
105
|
+
"OOOOOOOXO",
|
106
|
+
"XXXXXXXXO"
|
107
|
+
};
|
108
|
+
var board = new Connect(MakeBoard(lines));
|
109
|
+
Assert.That(board.Result(), Is.EqualTo(Connect.Winner.Black));
|
110
|
+
}
|
111
|
+
|
112
|
+
[Ignore("Remove to run test")]
|
113
|
+
[Test]
|
114
|
+
public void Spiral_nobody_wins()
|
115
|
+
{
|
116
|
+
var lines = new[]
|
117
|
+
{
|
118
|
+
"OXXXXXXXX",
|
119
|
+
"OXOOOOOOO",
|
120
|
+
"OXOXXXXXO",
|
121
|
+
"OXOXOOOXO",
|
122
|
+
"OXOX.XOXO",
|
123
|
+
"OXOOOXOXO",
|
124
|
+
"OXXXXXOXO",
|
125
|
+
"OOOOOOOXO",
|
126
|
+
"XXXXXXXXO"
|
127
|
+
};
|
128
|
+
var board = new Connect(MakeBoard(lines));
|
129
|
+
Assert.That(board.Result(), Is.EqualTo(Connect.Winner.None));
|
130
|
+
}
|
131
|
+
}
|
@@ -0,0 +1,121 @@
|
|
1
|
+
using System;
|
2
|
+
using System.Collections.Generic;
|
3
|
+
using System.Drawing;
|
4
|
+
using System.Linq;
|
5
|
+
|
6
|
+
public class Connect
|
7
|
+
{
|
8
|
+
public enum Winner
|
9
|
+
{
|
10
|
+
White,
|
11
|
+
Black,
|
12
|
+
None
|
13
|
+
}
|
14
|
+
|
15
|
+
public enum Cell
|
16
|
+
{
|
17
|
+
Empty,
|
18
|
+
White,
|
19
|
+
Black
|
20
|
+
}
|
21
|
+
|
22
|
+
private readonly Cell[][] board;
|
23
|
+
|
24
|
+
public Connect(string input)
|
25
|
+
{
|
26
|
+
board = ParseBoard(input);
|
27
|
+
}
|
28
|
+
|
29
|
+
private static Cell CharToCell(char c)
|
30
|
+
{
|
31
|
+
switch (c)
|
32
|
+
{
|
33
|
+
case 'O': return Cell.White;
|
34
|
+
case 'X': return Cell.Black;
|
35
|
+
default: return Cell.Empty;
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
private static Cell[][] ParseBoard(string input)
|
40
|
+
{
|
41
|
+
var split = input.Split('\n');
|
42
|
+
var rows = split.Length;
|
43
|
+
var cols = split[0].Length;
|
44
|
+
|
45
|
+
return split.Select(row => row.Select(CharToCell).ToArray()).ToArray();
|
46
|
+
}
|
47
|
+
|
48
|
+
private int Cols => board[0].Length;
|
49
|
+
private int Rows => board.Length;
|
50
|
+
|
51
|
+
private bool IsValidCoordinate(Point coordinate) =>
|
52
|
+
coordinate.Y >= 0 && coordinate.Y < Rows &&
|
53
|
+
coordinate.X >= 0 && coordinate.X < Cols;
|
54
|
+
|
55
|
+
private bool CellAtCoordinateEquals(Cell cell, Point coordinate) => board[coordinate.Y][coordinate.X] == cell;
|
56
|
+
|
57
|
+
private HashSet<Point> Adjacent(Cell cell, Point coordinate)
|
58
|
+
{
|
59
|
+
var row = coordinate.Y;
|
60
|
+
var col = coordinate.X;
|
61
|
+
|
62
|
+
var coords = new[]
|
63
|
+
{
|
64
|
+
new Point(col + 1, row - 1),
|
65
|
+
new Point(col, row - 1),
|
66
|
+
new Point(col - 1, row ),
|
67
|
+
new Point(col + 1, row ),
|
68
|
+
new Point(col - 1, row + 1),
|
69
|
+
new Point(col, row + 1)
|
70
|
+
};
|
71
|
+
|
72
|
+
return new HashSet<Point>(coords.Where(coord => IsValidCoordinate(coord) && CellAtCoordinateEquals(cell, coord)));
|
73
|
+
}
|
74
|
+
|
75
|
+
private bool ValidPath(Cell cell, Func<Cell[][], Point, bool> stop, HashSet<Point> processed, Point coordinate)
|
76
|
+
{
|
77
|
+
if (stop(board, coordinate))
|
78
|
+
return true;
|
79
|
+
|
80
|
+
var next = Adjacent(cell, coordinate);
|
81
|
+
next.ExceptWith(processed);
|
82
|
+
|
83
|
+
if (!next.Any())
|
84
|
+
return false;
|
85
|
+
|
86
|
+
return next.Any(nextCoord => {
|
87
|
+
var updatedProcessed = new HashSet<Point>(processed);
|
88
|
+
updatedProcessed.Add(nextCoord);
|
89
|
+
|
90
|
+
return ValidPath(cell, stop, updatedProcessed, nextCoord);
|
91
|
+
});
|
92
|
+
}
|
93
|
+
|
94
|
+
private bool IsWhiteStop(Cell[][] board, Point coordinate) => coordinate.Y == Rows - 1;
|
95
|
+
private bool IsBlackStop(Cell[][] board, Point coordinate) => coordinate.X == Cols - 1;
|
96
|
+
|
97
|
+
private HashSet<Point> WhiteStart() =>
|
98
|
+
new HashSet<Point>(Enumerable.Range(0, Cols).Select(col => new Point(col, 0)).Where(coord => CellAtCoordinateEquals(Cell.White, coord)));
|
99
|
+
|
100
|
+
private HashSet<Point> BlackStart() =>
|
101
|
+
new HashSet<Point>(Enumerable.Range(0, Rows).Select(row => new Point(0, row)).Where(coord => CellAtCoordinateEquals(Cell.Black, coord)));
|
102
|
+
|
103
|
+
private bool ColorWins(Cell cell, Func<Cell[][], Point, bool> stop, Func<HashSet<Point>> start)
|
104
|
+
{
|
105
|
+
return start().Any(coordinate => ValidPath(cell, stop, new HashSet<Point>(), coordinate));
|
106
|
+
}
|
107
|
+
|
108
|
+
private bool WhiteWins() => ColorWins(Cell.White, IsWhiteStop, WhiteStart);
|
109
|
+
private bool BlackWins() => ColorWins(Cell.Black, IsBlackStop, BlackStart);
|
110
|
+
|
111
|
+
public Winner Result()
|
112
|
+
{
|
113
|
+
if (WhiteWins())
|
114
|
+
return Winner.White;
|
115
|
+
|
116
|
+
if (BlackWins())
|
117
|
+
return Winner.Black;
|
118
|
+
|
119
|
+
return Winner.None;
|
120
|
+
}
|
121
|
+
}
|
@@ -38,7 +38,7 @@
|
|
38
38
|
<Reference Include="Microsoft.CSharp" />
|
39
39
|
</ItemGroup>
|
40
40
|
<ItemGroup>
|
41
|
-
<Compile Include="**\*.cs" Exclude="sgf-parsing\SgfParsing.cs" />
|
41
|
+
<Compile Include="**\*.cs" Exclude="sgf-parsing\SgfParsing.cs;markdown\Markdown.cs" />
|
42
42
|
</ItemGroup>
|
43
43
|
<ItemGroup>
|
44
44
|
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
@@ -0,0 +1,108 @@
|
|
1
|
+
using System;
|
2
|
+
using System.Linq;
|
3
|
+
using System.Text.RegularExpressions;
|
4
|
+
|
5
|
+
public static class Markdown
|
6
|
+
{
|
7
|
+
private static string OpeningTag(string tag) => $"<{tag}>";
|
8
|
+
private static string ClosingTag(string tag) => $"</{tag}>";
|
9
|
+
private static string WrapInTag(this string text, string tag) => $"{OpeningTag(tag)}{text}{ClosingTag(tag)}";
|
10
|
+
private static bool StartsWithTag(this string text, string tag) => text.StartsWith(OpeningTag(tag));
|
11
|
+
|
12
|
+
private const string HeaderMarkdown = "#";
|
13
|
+
private const string BoldMarkdown = "__";
|
14
|
+
private const string ItalicMarkdown = "_";
|
15
|
+
private const string ListItemMarkdown = "*";
|
16
|
+
|
17
|
+
private const string BoldTag = "em";
|
18
|
+
private const string ItalicTag = "i";
|
19
|
+
private const string ParagraphTag = "p";
|
20
|
+
private const string ListTag = "ul";
|
21
|
+
private const string ListItemTag = "li";
|
22
|
+
|
23
|
+
private static string ParseDelimited(this string markdown, string delimiter, string tag)
|
24
|
+
{
|
25
|
+
var pattern = $"{delimiter}(.+){delimiter}";
|
26
|
+
var replacement = "$1".WrapInTag(tag);
|
27
|
+
return Regex.Replace(markdown, pattern, replacement);
|
28
|
+
}
|
29
|
+
|
30
|
+
private static string ParseBold(this string markdown) => markdown.ParseDelimited(BoldMarkdown, BoldTag);
|
31
|
+
private static string ParseItalic(this string markdown) => markdown.ParseDelimited(ItalicMarkdown, ItalicTag);
|
32
|
+
|
33
|
+
private static string ParseText(this string markdown, bool list)
|
34
|
+
{
|
35
|
+
var textHtml = markdown
|
36
|
+
.ParseBold()
|
37
|
+
.ParseItalic();
|
38
|
+
|
39
|
+
return list ? textHtml : textHtml.WrapInTag(ParagraphTag);
|
40
|
+
}
|
41
|
+
|
42
|
+
private static Tuple<bool, string> ParseHeader(this string markdown, bool list)
|
43
|
+
{
|
44
|
+
var headerNumber =
|
45
|
+
markdown
|
46
|
+
.TakeWhile(c => c == HeaderMarkdown[0])
|
47
|
+
.Count();
|
48
|
+
|
49
|
+
if (headerNumber == 0)
|
50
|
+
return null;
|
51
|
+
|
52
|
+
var headerTag = $"h{headerNumber}";
|
53
|
+
var headerHtml = markdown.Substring(headerNumber + 1).WrapInTag(headerTag);
|
54
|
+
var html = list ? ClosingTag(ListTag) + headerHtml : headerHtml;
|
55
|
+
|
56
|
+
return Tuple.Create(false, html);
|
57
|
+
}
|
58
|
+
|
59
|
+
private static Tuple<bool, string> ParseLineItem(this string markdown, bool list)
|
60
|
+
{
|
61
|
+
if (!markdown.StartsWith(ListItemMarkdown))
|
62
|
+
return null;
|
63
|
+
|
64
|
+
var innerHtml =
|
65
|
+
markdown
|
66
|
+
.Substring(2)
|
67
|
+
.ParseText(true)
|
68
|
+
.WrapInTag(ListItemTag);
|
69
|
+
|
70
|
+
var html = list ? innerHtml : OpeningTag(ListTag) + innerHtml;
|
71
|
+
return Tuple.Create(true, html);
|
72
|
+
}
|
73
|
+
|
74
|
+
private static Tuple<bool, string> ParseParagraph(this string markdown, bool list)
|
75
|
+
{
|
76
|
+
if (list)
|
77
|
+
return Tuple.Create(false, ClosingTag(ListTag) + markdown.ParseText(list));
|
78
|
+
|
79
|
+
return Tuple.Create(false, markdown.ParseText(list));
|
80
|
+
}
|
81
|
+
|
82
|
+
private static Tuple<bool, string> ParseLine(Tuple<bool, string> accumulator, string markdown)
|
83
|
+
{
|
84
|
+
var list = accumulator.Item1;
|
85
|
+
var html = accumulator.Item2;
|
86
|
+
|
87
|
+
var result =
|
88
|
+
markdown.ParseHeader(list) ??
|
89
|
+
markdown.ParseLineItem(list) ??
|
90
|
+
markdown.ParseParagraph(list);
|
91
|
+
|
92
|
+
if (result == null)
|
93
|
+
throw new ArgumentException("Invalid markdown");
|
94
|
+
|
95
|
+
return Tuple.Create(result.Item1, html + result.Item2);
|
96
|
+
}
|
97
|
+
|
98
|
+
public static string Parse(string markdown)
|
99
|
+
{
|
100
|
+
var lines = markdown.Split('\n');
|
101
|
+
var result = lines.Aggregate(Tuple.Create(false, ""), ParseLine);
|
102
|
+
|
103
|
+
var list = result.Item1;
|
104
|
+
var html = result.Item2;
|
105
|
+
|
106
|
+
return list ? html + ClosingTag(ListTag) : html;
|
107
|
+
}
|
108
|
+
}
|
@@ -0,0 +1,151 @@
|
|
1
|
+
using System;
|
2
|
+
using System.Text.RegularExpressions;
|
3
|
+
|
4
|
+
public static class Markdown
|
5
|
+
{
|
6
|
+
private static string Wrap(string text, string tag) => "<" + tag + ">" + text + "</" + tag + ">";
|
7
|
+
|
8
|
+
private static bool IsTag(string text, string tag) => text.StartsWith("<" + tag + ">");
|
9
|
+
|
10
|
+
private static string Parse(string markdown, string delimiter, string tag)
|
11
|
+
{
|
12
|
+
var pattern = delimiter + "(.+)" + delimiter;
|
13
|
+
var replacement = "<" + tag + ">$1</" + tag + ">";
|
14
|
+
return Regex.Replace(markdown, pattern, replacement);
|
15
|
+
}
|
16
|
+
|
17
|
+
private static string Parse__(string markdown) => Parse(markdown, "__", "em");
|
18
|
+
|
19
|
+
private static string Parse_(string markdown) => Parse(markdown, "_", "i");
|
20
|
+
|
21
|
+
private static string ParseText(string markdown, bool list)
|
22
|
+
{
|
23
|
+
var parsedText = Parse_(Parse__((markdown)));
|
24
|
+
|
25
|
+
if (list)
|
26
|
+
{
|
27
|
+
return parsedText;
|
28
|
+
}
|
29
|
+
else
|
30
|
+
{
|
31
|
+
return Wrap(parsedText, "p");
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
private static string ParseHeader(string markdown, bool list, out bool inListAfter)
|
36
|
+
{
|
37
|
+
var count = 0;
|
38
|
+
|
39
|
+
for (int i = 0; i < markdown.Length; i++)
|
40
|
+
{
|
41
|
+
if (markdown[i] == '#')
|
42
|
+
{
|
43
|
+
count += 1;
|
44
|
+
}
|
45
|
+
else
|
46
|
+
{
|
47
|
+
break;
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
if (count == 0)
|
52
|
+
{
|
53
|
+
inListAfter = list;
|
54
|
+
return null;
|
55
|
+
}
|
56
|
+
|
57
|
+
var headerTag = "h" + count;
|
58
|
+
var headerHtml = Wrap(markdown.Substring(count + 1), headerTag);
|
59
|
+
|
60
|
+
if (list)
|
61
|
+
{
|
62
|
+
inListAfter = false;
|
63
|
+
return "</ul>" + headerHtml;
|
64
|
+
}
|
65
|
+
else
|
66
|
+
{
|
67
|
+
inListAfter = false;
|
68
|
+
return headerHtml;
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
private static string ParseLineItem(string markdown, bool list, out bool inListAfter)
|
73
|
+
{
|
74
|
+
if (markdown.StartsWith("*"))
|
75
|
+
{
|
76
|
+
var innerHtml = Wrap(ParseText(markdown.Substring(2), true), "li");
|
77
|
+
|
78
|
+
if (list)
|
79
|
+
{
|
80
|
+
inListAfter = true;
|
81
|
+
return innerHtml;
|
82
|
+
}
|
83
|
+
else
|
84
|
+
{
|
85
|
+
inListAfter = true;
|
86
|
+
return "<ul>" + innerHtml;
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
inListAfter = list;
|
91
|
+
return null;
|
92
|
+
}
|
93
|
+
|
94
|
+
private static string ParseParagraph(string markdown, bool list, out bool inListAfter)
|
95
|
+
{
|
96
|
+
if (!list)
|
97
|
+
{
|
98
|
+
inListAfter = false;
|
99
|
+
return ParseText(markdown, list);
|
100
|
+
}
|
101
|
+
else
|
102
|
+
{
|
103
|
+
inListAfter = false;
|
104
|
+
return "</ul>" + ParseText(markdown, list);
|
105
|
+
}
|
106
|
+
}
|
107
|
+
|
108
|
+
private static string ParseLine(string markdown, bool list, out bool inListAfter)
|
109
|
+
{
|
110
|
+
var result = ParseHeader(markdown, list, out inListAfter);
|
111
|
+
|
112
|
+
if (result == null)
|
113
|
+
{
|
114
|
+
result = ParseLineItem(markdown, list, out inListAfter);
|
115
|
+
}
|
116
|
+
|
117
|
+
if (result == null)
|
118
|
+
{
|
119
|
+
result = ParseParagraph(markdown, list, out inListAfter);
|
120
|
+
}
|
121
|
+
|
122
|
+
if (result == null)
|
123
|
+
{
|
124
|
+
throw new ArgumentException("Invalid markdown");
|
125
|
+
}
|
126
|
+
|
127
|
+
return result;
|
128
|
+
}
|
129
|
+
|
130
|
+
public static string Parse(string markdown)
|
131
|
+
{
|
132
|
+
var lines = markdown.Split('\n');
|
133
|
+
var result = "";
|
134
|
+
var list = false;
|
135
|
+
|
136
|
+
for (int i = 0; i < lines.Length; i++)
|
137
|
+
{
|
138
|
+
var lineResult = ParseLine(lines[i], list, out list);
|
139
|
+
result += lineResult;
|
140
|
+
}
|
141
|
+
|
142
|
+
if (list)
|
143
|
+
{
|
144
|
+
return result + "</ul>";
|
145
|
+
}
|
146
|
+
else
|
147
|
+
{
|
148
|
+
return result;
|
149
|
+
}
|
150
|
+
}
|
151
|
+
}
|
@@ -0,0 +1,76 @@
|
|
1
|
+
using NUnit.Framework;
|
2
|
+
|
3
|
+
public class MarkdownTest
|
4
|
+
{
|
5
|
+
[Test]
|
6
|
+
public void Parses_normal_text_as_a_paragraph()
|
7
|
+
{
|
8
|
+
var input = "This will be a paragraph";
|
9
|
+
var expected = "<p>This will be a paragraph</p>";
|
10
|
+
Assert.That(Markdown.Parse(input), Is.EqualTo(expected));
|
11
|
+
}
|
12
|
+
|
13
|
+
[Test]
|
14
|
+
public void Parsing_italics()
|
15
|
+
{
|
16
|
+
var input = "_This will be italic_";
|
17
|
+
var expected = "<p><i>This will be italic</i></p>";
|
18
|
+
Assert.That(Markdown.Parse(input), Is.EqualTo(expected));
|
19
|
+
}
|
20
|
+
|
21
|
+
[Test]
|
22
|
+
public void Parsing_bold_text()
|
23
|
+
{
|
24
|
+
var input = "__This will be bold__";
|
25
|
+
var expected = "<p><em>This will be bold</em></p>";
|
26
|
+
Assert.That(Markdown.Parse(input), Is.EqualTo(expected));
|
27
|
+
}
|
28
|
+
|
29
|
+
[Test]
|
30
|
+
public void Mixed_normal_italics_and_bold_text()
|
31
|
+
{
|
32
|
+
var input = "This will _be_ __mixed__";
|
33
|
+
var expected = "<p>This will <i>be</i> <em>mixed</em></p>";
|
34
|
+
Assert.That(Markdown.Parse(input), Is.EqualTo(expected));
|
35
|
+
}
|
36
|
+
|
37
|
+
[Test]
|
38
|
+
public void With_h1_header_level()
|
39
|
+
{
|
40
|
+
var input = "# This will be an h1";
|
41
|
+
var expected = "<h1>This will be an h1</h1>";
|
42
|
+
Assert.That(Markdown.Parse(input), Is.EqualTo(expected));
|
43
|
+
}
|
44
|
+
|
45
|
+
[Test]
|
46
|
+
public void With_h2_header_level()
|
47
|
+
{
|
48
|
+
var input = "## This will be an h2";
|
49
|
+
var expected = "<h2>This will be an h2</h2>";
|
50
|
+
Assert.That(Markdown.Parse(input), Is.EqualTo(expected));
|
51
|
+
}
|
52
|
+
|
53
|
+
[Test]
|
54
|
+
public void With_h6_header_level()
|
55
|
+
{
|
56
|
+
var input = "###### This will be an h6";
|
57
|
+
var expected = "<h6>This will be an h6</h6>";
|
58
|
+
Assert.That(Markdown.Parse(input), Is.EqualTo(expected));
|
59
|
+
}
|
60
|
+
|
61
|
+
[Test]
|
62
|
+
public void Unordered_lists()
|
63
|
+
{
|
64
|
+
var input = "* Item 1\n* Item 2";
|
65
|
+
var expected = "<ul><li>Item 1</li><li>Item 2</li></ul>";
|
66
|
+
Assert.That(Markdown.Parse(input), Is.EqualTo(expected));
|
67
|
+
}
|
68
|
+
|
69
|
+
[Test]
|
70
|
+
public void With_a_little_bit_of_everything()
|
71
|
+
{
|
72
|
+
var input = "# Header!\n* __Bold Item__\n* _Italic Item_";
|
73
|
+
var expected = "<h1>Header!</h1><ul><li><em>Bold Item</em></li><li><i>Italic Item</i></li></ul>";
|
74
|
+
Assert.That(Markdown.Parse(input), Is.EqualTo(expected));
|
75
|
+
}
|
76
|
+
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trackler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.5.
|
4
|
+
version: 2.0.5.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Katrina Owen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-12-
|
11
|
+
date: 2016-12-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubyzip
|
@@ -1298,6 +1298,8 @@ files:
|
|
1298
1298
|
- tracks/csharp/exercises/binary-search/Example.cs
|
1299
1299
|
- tracks/csharp/exercises/bob/BobTest.cs
|
1300
1300
|
- tracks/csharp/exercises/bob/Example.cs
|
1301
|
+
- tracks/csharp/exercises/book-store/BookStoreTest.cs
|
1302
|
+
- tracks/csharp/exercises/book-store/Example.cs
|
1301
1303
|
- tracks/csharp/exercises/bowling/BowlingTest.cs
|
1302
1304
|
- tracks/csharp/exercises/bowling/Example.cs
|
1303
1305
|
- tracks/csharp/exercises/bracket-push/BracketPushTest.cs
|
@@ -1308,6 +1310,8 @@ files:
|
|
1308
1310
|
- tracks/csharp/exercises/circular-buffer/Example.cs
|
1309
1311
|
- tracks/csharp/exercises/clock/ClockTest.cs
|
1310
1312
|
- tracks/csharp/exercises/clock/Example.cs
|
1313
|
+
- tracks/csharp/exercises/connect/ConnectTest.cs
|
1314
|
+
- tracks/csharp/exercises/connect/Example.cs
|
1311
1315
|
- tracks/csharp/exercises/crypto-square/CryptoSquareTest.cs
|
1312
1316
|
- tracks/csharp/exercises/crypto-square/Example.cs
|
1313
1317
|
- tracks/csharp/exercises/custom-set/CustomSetTest.cs
|
@@ -1356,6 +1360,10 @@ files:
|
|
1356
1360
|
- tracks/csharp/exercises/list-ops/ListOpsTest.cs
|
1357
1361
|
- tracks/csharp/exercises/luhn/Example.cs
|
1358
1362
|
- tracks/csharp/exercises/luhn/LuhnTest.cs
|
1363
|
+
- tracks/csharp/exercises/markdown/Example.cs
|
1364
|
+
- tracks/csharp/exercises/markdown/HINTS.md
|
1365
|
+
- tracks/csharp/exercises/markdown/Markdown.cs
|
1366
|
+
- tracks/csharp/exercises/markdown/MarkdownTest.cs
|
1359
1367
|
- tracks/csharp/exercises/matrix/Example.cs
|
1360
1368
|
- tracks/csharp/exercises/matrix/MatrixTest.cs
|
1361
1369
|
- tracks/csharp/exercises/meetup/Example.cs
|