How to improve readability of a TestCase with lots of parameters - c#

It's a mostly conceptual question. I have some class representing algorithm removing text from a (custom) TextArea control. I'd like to test it - the algorithm of course ;). I'm concerned with the lack of readability of my test method:
[TestCase(new[] { "some text asdf" }, 5, 0, 9, 0, "some asdf", new int[0])]
[TestCase(new[] { "some text", "", "totally unimportant texttext that stays" }, 0, 0, 24, 2, "text that stays", new[] { 0, 1, 2 })]
public void ShouldRemoveSelectedText(string[] lines, int colStart, int lineStart, int colEnd, int lineEnd, string notRemovedText, int[] expectedRemovedLines) {
var newLines = algorithm.RemoveLines(lines, new TextPositionsPair {
StartPosition = new TextPosition(column: colStart, line: lineStart),
EndPosition = new TextPosition(column: colEnd, line: lineEnd)
});
Assert.That(newLines.LinesToChange.First().Value, Is.EqualTo(notRemovedText));
CollectionAssert.AreEqual(expectedRemovedLines, newLines.LinesToRemove.OrderBy(key => key));
}
As you can see it's a pretty simple test. I provide the algorithm with IEnumerable of string and selection area, however it's difficult to see - at first glance - which TestCase parameter goes where. I was wondering - is there a "cleaner" way of doing this?
Sidenote: I have tests that are as simple as this one, but must be provided with even more parameters...

A simple approach is to just use more lines per case...
[TestCase(new [] { "some text asdf" },
5, 0, 9, 0,
"some asdf",
new int[0])]
[TestCase(new [] { "some text", "", "totally unimportant texttext that stays" },
0, 0, 24, 2,
"text that stays",
new [] {0, 1, 2})]
public void ShouldRemoveSelectedText(...
Alternatively, you might use TestCaseSource, referring to a static array in your fixture class...
TestCaseData[] MySource = {
new TestCaseData(new [] { "some text asdf" },
5, 0, 9, 0,
"some asdf",
new int[0]),
new TestCaseData(new [] { "some text", "", "totally unimportant texttext that stays" },
0, 0, 24, 2
"text that stays",
new [] { 0, 1, 2})};
[TestCaseSource("MySource")]
public void ShouldRemoveSelectedText(..
Those are the best options I can see without changing the arguments to your test, which is what I would actually do if it were my code.
I'd create an object that encapsulates a text buffer and another for a selection. I'm showing classes here, but they might be structs...
class TextBuffer
{
public string[] Lines;
public Selection Selection;
...
}
class Selection
{
public int FromLine;
public int FromCol;
public int ToLine;
public int ToCol;
...
}
Then I'd rewrite the test in terms of those test primitives, making it all a lot easier to read.

Related

Error: cannot convert int to string while printing array values in Console.WriteLine

Code given below is giving error that at b[3] cannot convert from int to string
static void Main(string[] args)
{
int[] b = new int[5] { 1, 7, 8, 9, 2 };
Console.WriteLine(b[3],b[4]);
}
whereas code given below is working properly without any issue
int[] b = new int[5] { 1, 7, 8, 9, 2 };
Console.WriteLine($"{b[3]} {b[4]}");
Console.WriteLine is not well suited to just print out passed multiple arguments (as print in python does, for example). Console.WriteLine overloads with 2 parameters (1, 2) require first parameter to be a string containing a composite format string and the second parameter will be used to fill placheholders in this format string.
If you want to print out several items you need either combine them into string manually. For example:
via string inteerpolation as in your question
Console.WriteLine($"{b[3]} {b[4]}");
using string.Join (more convenient with collections):
Console.WriteLine(string.Join(" ", new []{ b[3], b[4]}));
or use the format string:
Console.WriteLine("{0} {1}", b[3], b[4]);
or
Console.WriteLine("{0} {1}", new object[]{b[3], b[4]}); // better used with reference types
Console.WriteLine expects one parameter to print. You are passing 2 parameters.
You can change to:
static void Main(string[] args)
{
int[] b = new int[5] { 1, 7, 8, 9, 2 };
Console.WriteLine(b[3]);
Console.WriteLine(b[4]);
}
Or better:
static void Main(string[] args)
{
int[] b = new int[5] { 1, 7, 8, 9, 2 };
Console.WriteLine($"{b[3]} {b[4]}");
}
In this case I pass one parameter with string interpolation.

Checking source files (text files) for a certain structure in C#

Below I have an example of some text (in this case it's C source code, could be any structured text honestly). I am trying to read a couple files, with variable length and different structures and figure out if for example, after every #define an #include "test.h" is present.
The same case can apply to both, within and out of the preprocessor directives (#if, #ifdef, #ifndef, #endif). It is also allowed that a #include "test.h" appears after a #endif, if the proper #define was within the if-directive prior to this.
A few side notes:
I extract the text from the file, do some actions such as removing comments, irrelevant lines, empty lines and soon and return the file as a string[]. This makes it fairly easy to iterate and jump back and forth between the lines SourceCode[i]
I have attempted to implement a solution with a few different approaches, and never managed to fully hit the nail.
My first attempt consisted of an endless amount of if-else-statements and while-loops, for every imaginable path in the logic. This ended up being so hard to maintain, confusing and to hard keep control upon.
Next, I've tried implementing a state machine, to keep track of where I am in the text file, jumping to different states as needed. I couldn't find a perfect solution.
Another attempt I've tried was using a stack, pushing a #define to the stack, checking what it was, pushing the next line to the stack, checking if it is #include, if not then return an error. This also, of course, is a bit more complicated as soon as I have directives, as one #include is sufficient for multiple #defines.
I've looked into parsers (mainly Antlr), realizing that this is possibly way too overkill for such a problem, also considering I have absolutely no clue about parsers and would need to make my own grammar.
Source code example
// directives
#if (TEST == true)
#define START_TEST_1
#include "test.h"
#else
#define START_TEST_2
#include "test.h"
#endif
#if (TEST == true)
#define STOP_TEST_1
#else
#define STOP_TEST_2
#endif
#include "test.h"
// no directives
#define START_TEST_3
#include "test.h"
#define STOP_TEST_3
#include "test.h"
Does anyone have some general tips and can maybe point me in a specific direction. What would be a suitable solution to this problem?
Edit: #jdweng
dt.Columns.Add("Next #elif State", typeof(int));
dt.Rows.Add(new object[] { 12, DEFINE_STATE.FOUND_ELIF, 13, 0, 2, 7, 12, 10, ERROR.NO_ERROR, ACTION.NONE });
dt.Rows.Add(new object[] { 13, DEFINE_STATE.FOUND_DEFINE_IN_ELIF, -1, 14, 2, 7, 12, 10, ERROR.DEFINE_FOLLOWED_BY_DEFINE, ACTION.SET_DEFINE_ELIF_LINE_NUMBER });
dt.Rows.Add(new object[] { 14, DEFINE_STATE.FOUND_INCLUDE_IN_ELIF, 13, 0, 2, 7, 12, 10, ERROR.NO_ERROR, ACTION.RESET_DEFINE_ELIF_LINE_NUMBER });
I added a check to see if elif_level is == 0, if so, then proceed as usual, removing the level of if-nest. Otherwise I remove elif_level and then the if-nest level.
Do the above rows look correct? I am thinking of either adding a bool variable to states that is set to true if elif is found, and later when I find an #endif I can pop all states that have elif set to true.
I've been parsing text files like this for over 40 years. This is a complicate logic issue so with any complicated logic issue I would use a State Machine. First I drew a state diagram
Then I wrote code to implement the state table
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using System.Data;
namespace ConsoleApplication1
{
public enum DEFINE_STATE
{
SPECIAL = -4, //define followed by IF
NONE = -3,
INVALID = -2, //Compile will give error, cannot occur
ERROR = -1,
DO_NOT_CARE = 0,
START = 1,
FOUND_IF = 2,
FOUND_DEFINE_IN_IF = 3,
FOUND_DEFINE_NOT_IN_IF = 4,
FOUND_INCLUDE_IN_IF = 5,
FOUND_ELSE = 6,
FOUND_DEFINE_IN_ELSE = 7,
FOUND_INCLUDE_IN_ELSE = 8,
FOUND_INCLUDE_NOT_IN_IF = 9,
FOUND_END_IF = 10,
RETURN = 11,
FOUND_ELIF = 12,
FOUND_DEFINE_IN_ELIF = 13,
FOUND_INCLUDE_IN_ELIF = 14,
}
public enum ERROR
{
NO_ERROR,
DEFINE_FOLLOWED_BY_DEFINE,
DEFINE_FOLLOWED_BY_DEFINE_OR_IF
}
public enum TABLE_COLUMN
{
STATE = 0,
DESCRIPTION = 1,
DEFINE,
INCLUDE,
IF,
ELSE,
ELIF,
END_IF,
ERROR,
ACTION
}
public enum ACTION
{
NONE,
RESET_DEFINE_LINE_NUMBER,
RESET_DEFINE_IF_LINE_NUMBER,
RESET_DEFINE_ELSE_LINE_NUMBER,
RESET_DEFINE_ELIF_LINE_NUMBER,
SET_DEFINE_LINE_NUMBER,
SET_DEFINE_IF_LINE_NUMBER,
SET_DEFINE_ELSE_LINE_NUMBER,
SET_DEFINE_ELIF_LINE_NUMBER,
}
public class State_Variables
{
public int define_Line_Number = 0;
public int define_If_Line_Number = 0;
public int define_Else_Line_Number = 0;
public int define_Elif_Line_Number = 0;
public int return_end_level = 0;
public DEFINE_STATE state = DEFINE_STATE.START;
public DataRow row { get; set; }
}
class Program
{
const string FILENAME = #"c:\temp\test.txt";
static void Main(string[] args)
{
string pattern = "#include\\s+\"test.h\"";
StreamReader reader = new StreamReader(FILENAME);
string input = "";
DataTable dt = new DataTable();
dt.Columns.Add("State", typeof(int));
dt.Columns.Add("Description", typeof(DEFINE_STATE));
dt.Columns.Add("Next Define State", typeof(int));
dt.Columns.Add("Next Include State", typeof(int));
dt.Columns.Add("Next IF State", typeof(int));
dt.Columns.Add("Next Else State", typeof(int));
dt.Columns.Add("Next ELIF State", typeof(int));
dt.Columns.Add("Next ENDIF State", typeof(int));
dt.Columns.Add("Error Number", typeof(ERROR));
dt.Columns.Add("Action", typeof(ACTION));
//0 do not care
//-1 error
//-2 invalid
dt.Rows.Add(new object[] { 1, DEFINE_STATE.START, 4, 0, 2, -2, -2, -2, ERROR.NO_ERROR, ACTION.NONE });
dt.Rows.Add(new object[] { 2, DEFINE_STATE.FOUND_IF, 3, 0, 2, 6, 12, 10, ERROR.NO_ERROR, ACTION.NONE });
dt.Rows.Add(new object[] { 3, DEFINE_STATE.FOUND_DEFINE_IN_IF, -1, 5, 2, 6, 12, 10, ERROR.DEFINE_FOLLOWED_BY_DEFINE, ACTION.SET_DEFINE_IF_LINE_NUMBER });
dt.Rows.Add(new object[] { 4, DEFINE_STATE.FOUND_DEFINE_NOT_IN_IF, -1, 9, -4, -2, -2, -2, ERROR.DEFINE_FOLLOWED_BY_DEFINE, ACTION.SET_DEFINE_LINE_NUMBER });
dt.Rows.Add(new object[] { 5, DEFINE_STATE.FOUND_INCLUDE_IN_IF, 3, 0, 2, 6, 12, 10, ERROR.NO_ERROR, ACTION.RESET_DEFINE_IF_LINE_NUMBER });
dt.Rows.Add(new object[] { 6, DEFINE_STATE.FOUND_ELSE, 7, 0, 2, -2, -2, 10, ERROR.NO_ERROR, ACTION.NONE });
dt.Rows.Add(new object[] { 7, DEFINE_STATE.FOUND_DEFINE_IN_ELSE, -1, 8, 2, -2, -2, 10, ERROR.DEFINE_FOLLOWED_BY_DEFINE, ACTION.SET_DEFINE_ELSE_LINE_NUMBER });
dt.Rows.Add(new object[] { 8, DEFINE_STATE.FOUND_INCLUDE_IN_ELSE, 7, 0, 2, -2, -2, 10, ERROR.NO_ERROR, ACTION.RESET_DEFINE_ELSE_LINE_NUMBER });
dt.Rows.Add(new object[] { 9, DEFINE_STATE.FOUND_INCLUDE_NOT_IN_IF, 4, 0, 2, -2, -2, -2, ERROR.NO_ERROR, ACTION.RESET_DEFINE_LINE_NUMBER });
dt.Rows.Add(new object[] { 10, DEFINE_STATE.FOUND_END_IF, 11, 1, 2, -2, -2, -2, ERROR.NO_ERROR, ACTION.NONE });
dt.Rows.Add(new object[] { 11, DEFINE_STATE.RETURN, -2, -2, 2, -2, -2, -2, ERROR.NO_ERROR, ACTION.NONE });
dt.Rows.Add(new object[] { 12, DEFINE_STATE.FOUND_ELIF, 13, 0, 2, -2, 12, 10, ERROR.NO_ERROR, ACTION.NONE });
dt.Rows.Add(new object[] { 13, DEFINE_STATE.FOUND_DEFINE_IN_ELIF, -1, 14, 2, -2, 12, 10, ERROR.DEFINE_FOLLOWED_BY_DEFINE, ACTION.SET_DEFINE_ELIF_LINE_NUMBER });
dt.Rows.Add(new object[] { 14, DEFINE_STATE.FOUND_INCLUDE_IN_ELIF, 13, 0, 2, 7, 12, 10, ERROR.NO_ERROR, ACTION.RESET_DEFINE_ELIF_LINE_NUMBER });
int level = 0;
List<State_Variables> states = new List<State_Variables>();
State_Variables newState = new State_Variables();
states.Add(newState);
DEFINE_STATE nextState = DEFINE_STATE.START;
ACTION action = ACTION.NONE;
int line_number = 0;
while ((input = reader.ReadLine()) != null)
{
line_number++;
input = input.Trim();
if (input.StartsWith("//")) continue; //ignore comments
if (input.Length == 0) continue;
Boolean returnFromIF = false;
Match match = Regex.Match(input, pattern);
//test if end if is followed by include
if (states[level].state == DEFINE_STATE.FOUND_END_IF)
{
int return_end_level = 0;
do
{
if (!match.Success)
{
int define_If_Line_Number = states[level].define_If_Line_Number;
int define_Else_Line_Number = states[level].define_Else_Line_Number;
int define_Elif_Line_Number = states[level].define_Elif_Line_Number;
if (define_If_Line_Number != 0)
{
Console.WriteLine("Define in IF at line {0} does not have and include", define_If_Line_Number.ToString());
}
if (define_Else_Line_Number != 0)
{
Console.WriteLine("Define in ELSE at line {0} does not have and include", define_Else_Line_Number.ToString());
}
if (define_Elif_Line_Number != 0)
{
Console.WriteLine("Define in ELSE at line {0} does not have and include", define_Else_Line_Number.ToString());
}
}
return_end_level = states[level].return_end_level;
states.RemoveAt(level--);
} while (level > return_end_level);
returnFromIF = true;
}
else
{
states[level].row = dt.AsEnumerable().Where(x => x.Field<int>((int)TABLE_COLUMN.STATE) == (int)states[level].state).FirstOrDefault();
}
nextState = DEFINE_STATE.NONE;
//check if defines are terminated with include
if (input.Contains("#define"))
{
nextState = (DEFINE_STATE)states[level].row.Field<int>((int)TABLE_COLUMN.DEFINE);
}
if (match.Success)
{
if (returnFromIF)
{
nextState = states[level].state;
}
else
{
nextState = (DEFINE_STATE)states[level].row.Field<int>((int)TABLE_COLUMN.INCLUDE);
}
}
if (input.Contains("#if"))
{
nextState = (DEFINE_STATE)states[level].row.Field<int>((int)TABLE_COLUMN.IF);
states.Add(new State_Variables());
level++;
states[level].return_end_level = level - 1;
}
if (input.Contains("#else"))
{
nextState = (DEFINE_STATE)states[level].row.Field<int>((int)TABLE_COLUMN.ELSE);
}
if (input.Contains("#elif"))
{
nextState = (DEFINE_STATE)states[level].row.Field<int>((int)TABLE_COLUMN.ELIF);
states.Add(new State_Variables());
level++;
states[level].return_end_level = states[level - 1].return_end_level;
}
if (input.Contains("#endif"))
{
nextState = (DEFINE_STATE)states[level].row.Field<int>((int)TABLE_COLUMN.END_IF);
}
if ((nextState != DEFINE_STATE.ERROR) && (nextState != DEFINE_STATE.DO_NOT_CARE) && (nextState != DEFINE_STATE.NONE))
{
states[level].state = nextState;
}
switch (nextState)
{
case DEFINE_STATE.DO_NOT_CARE:
//stay at current state
break;
case DEFINE_STATE.NONE: //stay at current state
Console.WriteLine("Did not find state at line {0}", line_number);
break;
case DEFINE_STATE.INVALID:
Console.WriteLine("Invalid IF/ELSE/END_IF at line {0}", line_number);
break;
case DEFINE_STATE.ERROR:
action = states[level].row.Field<ACTION>((int)TABLE_COLUMN.ACTION);
switch (action)
{
case ACTION.SET_DEFINE_LINE_NUMBER:
Console.WriteLine("Define followed by Define at line {0}", states[level].define_Line_Number.ToString());
states[level].define_Line_Number = line_number;
break;
case ACTION.SET_DEFINE_IF_LINE_NUMBER:
Console.WriteLine("Define in IF followed by Define by at line {0}", states[level].define_If_Line_Number.ToString());
states[level].define_If_Line_Number = line_number;
break;
case ACTION.SET_DEFINE_ELSE_LINE_NUMBER:
Console.WriteLine("Define in ELSE followed by Define at line {0}", states[level].define_Else_Line_Number.ToString());
states[level].define_Else_Line_Number = line_number;
break;
case ACTION.SET_DEFINE_ELIF_LINE_NUMBER :
Console.WriteLine("Define in ELIF followed by Define at line {0}", states[level].define_Elif_Line_Number.ToString());
states[level].define_Elif_Line_Number = line_number;
break;
}
break;
case DEFINE_STATE.SPECIAL:
Console.WriteLine("Define followed IF at line {0}", states[level].define_Line_Number.ToString());
states[level - 1].state = DEFINE_STATE.START;
states[level].state = DEFINE_STATE.FOUND_IF;
nextState = DEFINE_STATE.FOUND_IF;
states[level].row = dt.AsEnumerable().Where(x => x.Field<DEFINE_STATE>((int)TABLE_COLUMN.STATE) == nextState).FirstOrDefault();
break;
default:
states[level].row = dt.AsEnumerable().Where(x => x.Field<DEFINE_STATE>((int)TABLE_COLUMN.STATE) == nextState).FirstOrDefault();
action = states[level].row.Field<ACTION>((int)TABLE_COLUMN.ACTION);
switch (action)
{
case ACTION.RESET_DEFINE_LINE_NUMBER:
states[level].define_Line_Number = 0;
break;
case ACTION.RESET_DEFINE_IF_LINE_NUMBER:
states[level].define_If_Line_Number = 0;
break;
case ACTION.RESET_DEFINE_ELSE_LINE_NUMBER:
states[level].define_Else_Line_Number = 0;
break;
case ACTION.RESET_DEFINE_ELIF_LINE_NUMBER:
states[level].define_Elif_Line_Number = 0;
break;
case ACTION.SET_DEFINE_LINE_NUMBER:
states[level].define_Line_Number = line_number;
break;
case ACTION.SET_DEFINE_IF_LINE_NUMBER:
states[level].define_If_Line_Number = line_number;
break;
case ACTION.SET_DEFINE_ELSE_LINE_NUMBER:
states[level].define_Else_Line_Number = line_number;
break;
case ACTION.SET_DEFINE_ELIF_LINE_NUMBER:
states[level].define_Elif_Line_Number = line_number;
break;
}
states[level].state = nextState;
break;
}
}
//final checks
int define_Line_Number = states[level].define_Line_Number;
if (define_Line_Number != 0)
{
Console.WriteLine("Define at line {0} does not have and include", define_Line_Number.ToString());
}
if (level != 0)
{
Console.WriteLine("Did not close all IFs with End_If");
}
Console.WriteLine("Done");
Console.ReadLine();
}
}
}
If the common denominator of all text you want to analyze is a hierarchically structured document, maybe you should start by converting it to that and then do the rest of the analysis on the parsed document and not do both at the same time. Perhaps converting it to an XML-document could be sufficient for your case and then do the analysis using XSLT/XPath (Or LINQ for XDocument if you prefer that). This is basically how other code analysis is performed as well (Roslyn Code Analyzers with Syntax Trees except much more fancy of course).

Escape c# class property from JSON serialization (to remove quotes)

I EDITED THE SOLUTION BELOW THE QUESTION !!!
I have read like 30 similar articles on this, but nothing quite hits the spot.
My scenario: in order to feed a charting library (highcharts/highstock) with data in ASP.NET I manually created the basic classes for such a graph. This is due to the fact that all the wrapper projects work for highcharts but NOT for highstock. However, highstock requires a HTML script block like this (working):
<script>
$('#container2').highcharts('StockChart',
{
"title": {
"text": "Graph_Title_string"
},
"yAxis": {
"title": {
"text": "YAxis_Title_string"
},
"height": 200,
"lineWidth": 2,
"top": 80
},
"series": [
{
"type": "line",
"name": "Testdata",
"data": [[Date.UTC(2014, 3, 8, 0, 0), 3], [Date.UTC(2014, 3, 9, 0, 0), 2], [Date.UTC(2014, 3, 10, 0, 0), 4], [Date.UTC(2014, 3, 11, 0, 0), 4], [Date.UTC(2014, 3, 12, 0, 0), 3], [Date.UTC(2014, 3, 13, 0, 0), 4], [Date.UTC(2014, 3, 14, 0, 0), 2], [Date.UTC(2014, 3, 15, 0, 0), 1], [Date.UTC(2014, 3, 16, 0, 0), 4], [Date.UTC(2014, 3, 17, 0, 0), 0]]
}]
});
I created the c# classes and their properties, as for example:
public class Series
{
public string type { get; set; }
public string name { get; set; }
public object data { get; set; }
}
Later on I use JsonConvert.SerializeObject to serialize my chart-object (with instanced classes title, yAxis, series etc within), which results in following output:
{
"title": {
"text": "Graph_Title_string"
},
"yAxis": {
"title": {
"text": "YAxis_Title_string"
},
"height": 200,
"lineWidth": 2,
"top": 0
},
"series": [
{
"type": "line",
"name": "Testdata",
"data": "[[Date.UTC(2014, 3, 8, 0, 0), 3],[Date.UTC(2014, 3, 9, 0, 0), 2],[Date.UTC(2014, 3, 10, 0, 0), 0],[Date.UTC(2014, 3, 11, 0, 0), 4],[Date.UTC(2014, 3, 12, 0, 0), 4],[Date.UTC(2014, 3, 13, 0, 0), 2],[Date.UTC(2014, 3, 14, 0, 0), 1],[Date.UTC(2014, 3, 15, 0, 0), 1],[Date.UTC(2014, 3, 16, 0, 0), 0],[Date.UTC(2014, 3, 17, 0, 0), 3]]"
}
]
}
So the problem is: the value of series->data is enclosed in quotes. As highstock obviously requires an object array as data ([[DateTime, value],[DateTime, value],...etc]), the chart is not rendered unless I remove these quotes around the array.
When the array was "pure" integer there would be no quotes (I guess), but as my points need to be DateTime/value I need an array of objects.
Therefore the question: how can I force my JSON serializer to NOT enclose my value of an object array in quotes?
Maybe this is trivial and simple, and I was looking to far. As said, I've read lots of articles on similar problems, but nothing worked for me. Any help highly appreciated!
SOLUTION:
the data-array in my series-node is int/double - DATE.UTC(2014, 3, 8, 0, 0) doesn't return a DateTime but a NUMBER (reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/UTC)
Therefore defining a double array in my behind-class will result in the desired output format (as seen in the great suggestion below by craig and also the marked answer) without the quotation marks.
Code (reference: http://forums.asp.net/post/1463013.aspx ... however, slightly modified) for converting c# DateTime to the required milliseconds-timestamp:
public double MilliTimeStamp(DateTime d2)
{
DateTime d1 = new DateTime(1970, 1, 1);
TimeSpan ts = new TimeSpan(d2.Ticks - d1.Ticks);
return ts.TotalMilliseconds;
}
I created a class defined as follows:
public class Series
{
public string type { get; set; }
public string name { get; set; }
public List<object> data { get; set; }
}
Then used the following code to populate it and convert it to JSON.
Series series = new Series();
series.type = "foo";
series.name = "bar";
series.data = new List<object>();
for( int i = 0; i < 5; i++ )
{
series.data.Add( DateTime.Now );
series.data.Add( i );
}
var json = JsonConvert.SerializeObject( series );
The resulting JSON was:
{"type":"foo","name":"bar","data":["2014-04-17T07:47:16.3620755-07:00",0,"2014-0
4-17T07:47:16.3620755-07:00",1,"2014-04-17T07:47:16.3620755-07:00",2,"2014-04-17
T07:47:16.3620755-07:00",3,"2014-04-17T07:47:16.3620755-07:00",4]}
P.S. Declare data as object[] also generated the above JSON.
Answer from #CraigW. is very close. To make it closer to what you are after, try to declare data as collection of collection of object :
public class Series
{
public string type { get; set; }
public string name { get; set; }
public List<List<object>> data { get; set; }
}
Test code to populate the model :
Series series = new Series();
series.type = "foo";
series.name = "bar";
series.data = new List<List<object>>();
for (int i = 0; i < 5; i++)
{
var data = new List<object>();
data.Add(DateTime.Now);
data.Add(i);
series.data.Add(data);
}
var json = JsonConvert.SerializeObject(series, Formatting.Indented);
Test result :
{
"type": "foo",
"name": "bar",
"data": [
[
"2014-04-17T22:15:06.8812404+07:00",
0
],
[
"2014-04-17T22:15:06.8812404+07:00",
1
],
[
"2014-04-17T22:15:06.8812404+07:00",
2
],
[
"2014-04-17T22:15:06.8812404+07:00",
3
],
[
"2014-04-17T22:15:06.8812404+07:00",
4
]
]
}

Mapping Vectors

Is there a good way to map vectors? Here's an example of what I mean:
vec0 = [0,0,0,0,0,0,0,0,0,0,0]
vec1 = [1,4,2,7,3,2]
vec2 = [0,0,0,0,0,0,0,0,0]
vec2 = [7,2,7,9,9,6,1,0,4]
vec4 = [0,0,0,0,0,0]
mainvec =
[0,0,0,0,0,0,0,0,0,0,0,1,4,2,7,3,2,0,0,0,0,0,0,0,0,0,7,2,7,9,9,6,1,0,4,0,0,0,0,0,0]
Lets say mainvec doesn't exist (I'm just showing it to you so you can see the general data structure in mind.
Now say I want mainvec(12) which would be 4. Is there a good way to map the call of these vectors without just stitching them together into a mainvec? I realize I could make a bunch of if statements that test the index of mainvec and I can then offset each call depending on where the call is within one of the vectors, so for instance:
mainvec(12) = vec1(1)
which I could do by:
mainvec(index)
if (index >=13)
vect1(index-11);
I wonder if there's a concise way of doing this without if statements. Any Ideas?
Are you looking for something like this?
using System.Collections.Generic;
namespace Test
{
class Program
{
static void Main(string[] args)
{
int[] vec0 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
int[] vec1 = { 1, 4, 2, 7, 3, 2 };
int[] vec2 = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
int[] vec3 = { 7, 2, 7, 9, 9, 6, 1, 0, 4 };
int[] vec4 = { 0, 0, 0, 0, 0, 0 };
List<int> temp = new List<int>();
temp.AddRange(vec0);
temp.AddRange(vec1);
temp.AddRange(vec2);
temp.AddRange(vec3);
temp.AddRange(vec4);
int[] mainvec = temp.ToArray();
}
}
}
I would create a class that would receive array of lengths, and have a method to give you Array number and Index inside the array for a given index in the combined list.
It would be wrapped by a class that will get references to the actual arrays and an indexer to bring you to the right element.
It looks like your doing basic list concatenation, in which case the Concat function would seem to be the most straight forward way of doing things. In real-live code terms, somethng like:
var vec0 = new[] {0,0,0,0,0,0,0,0,0,0,0};
var vec1 = new[] {1,4,2,7,3,2};
var vec2 = new[] {0,0,0,0,0,0,0,0,0};
var vec3 = new[] {7,2,7,9,9,6,1,0,4};
var vec4 = new[] { 0, 0, 0, 0, 0, 0 };
var mainvec = vec0.Concat(vec1).Concat(vec2).Concat(vec3).Concat(vec4).ToList();
mainvec[12] == 1;
I'm not really sure of the context behind what you are wanting to do, so there may be a more direct way of doing things, but based on what you've got, this seems the simplest to me.
I would use a jagged array.
You still have to have a loop, but you can keep the separate vectors without redundancy:
var mainvec = new int[][]{vec0, vec1, vec2, vec3, vec4};
int desiredInd = 12, totalInd = 0, rowInd = 0, result;
while(rowInd < mainvec.Length && (totalInd + mainvec[rowInd].Length) <= desiredInd)
{
totalInd += mainvec[rowInd++].Length;
}
if(rowInd < mainvec.Length && (desiredInd - totalInd) < mainvec[rowInd].Length)
{
result = mainvec[rowInd][desiredInd - totalInd];
}

A dictionary object that uses ranges of values for keys

I have need of a sort of specialized dictionary. My use case is this: The user wants to specify ranges of values (the range could be a single point as well) and assign a value to a particular range. We then want to perform a lookup using a single value as a key. If this single value occurs within one of the ranges then we will return the value associated to the range.
For example:
// represents the keyed value
struct Interval
{
public int Min;
public int Max;
}
// some code elsewhere in the program
var dictionary = new Dictionary<Interval, double>();
dictionary.Add(new Interval { Min = 0, Max = 10 }, 9.0);
var result = dictionary[1];
if (result == 9.0) JumpForJoy();
This is obviously just some code to illustrate what I'm looking for. Does anyone know of an algorithm to implement such a thing? If so could they point me towards it, please?
I have already tried implementing a custom IEqualityComparer object and overloading Equals() and GetHashCode() on Interval but to no avail so far. It may be that I'm doing something wrong though.
A dictionary is not the appropriate data structure for the operations you are describing.
If the intervals are required to never overlap then you can just build a sorted list of intervals and binary search it.
If the intervals can overlap then you have a more difficult problem to solve. To solve that problem efficiently you'll want to build an interval tree:
http://en.wikipedia.org/wiki/Interval_tree
This is a well-known data structure. See "Introduction To Algorithms" or any other decent undergraduate text on data structures.
This is only going to work when the intervals don't overlap. And your main problem seems to be converting from a single (key) value to an interval.
I would write a wrapper around SortedList. The SortedList.Keys.IndexOf() would find you an index that can be used to verify if the interval is valid and then use it.
This isn't exactly what you want but I think it may be the closest you can expect.
You can of course do better than this (Was I drinking earlier?). But you have to admit it is nice and simple.
var map = new Dictionary<Func<double, bool>, double>()
{
{ d => d >= 0.0 && d <= 10.0, 9.0 }
};
var key = map.Keys.Single(test => test(1.0))
var value = map[key];
I have solved a similar problem by ensuring that the collection is contiguous where the intervals never overlap and never have gaps between them. Each interval is defined as a lower boundary and any value lies in that interval if it is equal to or greater than that boundary and less than the lower boundary of the next interval. Anything below the lowest boundary is a special case bin.
This simplifies the problem somewhat. We also then optimized key searches by implementing a binary chop. I can't share the code, unfortunately.
I would make a little Interval class, which would something like that:
public class Interval
{
public int Start {get; set;}
public int End {get; set;}
public int Step {get; set;}
public double Value {get; set;}
public WriteToDictionary(Dictionary<int, double> dict)
{
for(int i = Start; i < End; i += Step)
{
dict.Add(i, Value);
}
}
}
So you still can a normal lookup within your dictionary. Maybe you should also perform some checks before calling Add() or implement some kind of rollback if any value is already within the dictionary.
You can find a Java flavored C# implementation of an interval tree in the Open Geospatial Library. It needs some minor tweaks to solve your problem and it could also really use some C#-ification.
It's Open Source but I don't know under what license.
i adapted some ideas for Dictionary and func, like "ChaosPandion" gave me the idea in his earlier post here above.
i still solved the coding, but if i try to refactor
i have a amazing problem/bug/lack of understanding:
Dictionary<Func<string, double, bool>, double> map = new Dictionary<Func<string, double, bool>, double>()
{
{ (a, b) => a == "2018" && b == 4, 815.72},
{ (a, b) => a == "2018" && b == 6, 715.72}
};
What is does is, that i call the map with a search like "2018"(year) and 4(month), which the result is double value 815,72.
When i check the unique map entries they look like this:
map working unique keys
so thats the orginal behaviour, anything fine so far.
Then i try to refactor it, to this:
Dictionary<Func<string, double, bool>, double> map =
new Dictionary<Func<string, double, bool>, double>();
WS22(map, values2018, "2018");
private void WS22(Dictionary<Func<string, double, bool>, double> map, double[] valuesByYear, string strYear)
{
int iMonth = 1;
// step by step this works:
map.Add((a, b) => (a == strYear) && (b == 1), dValue);
map.Add((a, b) => (a == strYear) && (b == 2), dValue);
// do it more elegant...
foreach (double dValue in valuesByYear)
{
//this does not work: exception after second iteration of foreach run
map.Add((a, b) => (a == strYear) && (b == iMonth), dValue );
iMonth+=1;
}
}
this works: (i use b==1 and b==2)
this does not work (map not working exception on add item on second iteration)
so i think the problem is, that the map does not have a unique key while adding to map dictionary. The thing is, i dont see my error, why b==1 is working and b==iMonth not.
Thx for any help, that open my eyes :)
Using Binary Search, I created an MSTest v2 test case that approaches the solution. It assumes that the index is the actual number you are looking for, which does not (might not?) suit the description given by the OP.
Note that the ranges do not overlap. And that the ranges are
[negative infinity, 0)
[0, 5]
(5, 15]
(15, 30]
(30, 100]
(100, 500]
(500, positive infinity]
This values passed as minimumValues are sorted, since they are constants in my domain. If these values can change, the minimumValues list should be sorted again.
Finally, there is a test that uses if statements to get to the same result (which is probably more flexible if you need something else than the index).
[TestClass]
public class RangeUnitTests
{
[DataTestMethod]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, -1, 0)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 0, 1)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 1, 1)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 5, 1)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 7, 2)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 15, 2)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 16, 3)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 30, 3)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 31, 4)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 100, 4)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 101, 5)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 500, 5)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 501, 6)]
public void Use_BinarySearch_To_Determine_Range(int[] minimumValues, int inputValue, int expectedRange)
{
var list = minimumValues.ToList();
var index = list.BinarySearch(inputValue);
if (index < 0)
{
index = ~index;
}
Assert.AreEqual(expectedRange, index);
}
[DataTestMethod]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, -1, 0)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 0, 1)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 1, 1)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 5, 1)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 7, 2)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 15, 2)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 16, 3)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 30, 3)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 31, 4)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 100, 4)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 101, 5)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 500, 5)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 501, 6)]
public void Use_Ifs_To_Determine_Range(int[] _, int inputValue, int expectedRange)
{
int actualRange = 6;
if (inputValue < 0)
{
actualRange = 0;
}
else if (inputValue <= 5)
{
actualRange = 1;
}
else if (inputValue <= 15)
{
actualRange = 2;
}
else if (inputValue <= 30)
{
actualRange = 3;
}
else if (inputValue <= 100)
{
actualRange = 4;
}
else if (inputValue <= 500)
{
actualRange = 5;
}
Assert.AreEqual(expectedRange, actualRange);
}
}
I did a little perfomance testing by duplicating the initial set [DataRow] several times (up to 260 testcases for each method). I did not see a significant difference in performance with these parameteres. Note that I ran each [DataTestMethod] in a seperate session. Hopefully this balances out any start-up costs that the test framework might add to first test that is executed.
You could check out the powercollections here found on codeplex that has a collection that can do what you are looking for.
Hope this helps,
Best regards,
Tom.

Categories

Resources