Parse style attribute collection using linq - c#

I want to parse an SVG style attribute, which is just a delimited string, e.g.
"fill:#e2b126;stroke:#010101;stroke-width:0.3177;stroke-miterlimit:10"
into a Dictionary<string,string> so that I can perform some processing on it.
Here's what I have, which does the job, but I'd like to make it neater using a linq projection, just can't seem to get the syntax. I tried using .Select().ToDictionary etc, but no joy. Thanks:
string attributes = "fill:#e2b126;stroke:#010101;stroke-width:0.3177;stroke-miterlimit:10";
var pairs = attributes.Split(';').ToList();
var dic = new Dictionary<string, string>();
pairs.ForEach(p =>
{
var pair = p.Split(':');
dic.Add(pair[0], pair[1]);
});
foreach (var k in dic.Keys)
{
Console.WriteLine(k + " " + dic[k]);
}
Expected output:
fill #e2b126
stroke #010101
stroke-width 0.3177
stroke-miterlimit 10

Try the following
string attributes = "fill:#e2b126;stroke:#010101;stroke-width:0.3177;stroke-miterlimit:10";
var map = attributes
.Split(new []{';'}, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Split(new []{':'}, StringSplitOptions.RemoveEmptyEntries))
.ToDictionary(p => p[0], p => p[1]);
Breakdown
The first Split call will return an array of String values where every entry is in the key:value format. The following Select call will convert every one of those entries into a string[] where the first element is the key and the second is the value. The ToDictionary call just expressly performs this mapping

Related

Order of groups with dynamic linq

Somewhat similar to this question:
Where do I put the "orderby group.key" in this LINQ statement?
Except I'm using Dynamic.Linq which makes this a bit harder. I have a bunch of data coming from a database and then I'm grouping by some field and then outputing the result. The problem is that the ordering of the groups seems to randomly jump around which isn't very convenient for the end-user. So taking inspiration from the linked question, if I had this:
string[] words = { "boy","car", "apple", "bill", "crow", "brown" };
// note the first non-dynamic select here was just because I don't think dynamic linq
// will support indexing a string like that and it's not an important detail anyway
var wordList = words.Select(w => new {FirstLetter = w[0], Word = w})
.GroupBy("new (FirstLetter)","Word");
foreach(IGrouping<object, dynamic> g in wordList)
{
Console.WriteLine("Words that being with {0}:",
g.Key.ToString().ToUpper());
foreach (var word in g)
Console.WriteLine(" " + word);
}
Console.ReadLine();
How would I get it to order the keys? At least part of the problem is that the dynamic GroupBy returns an IEnumerable. I thought it might be as easy as:
var wordList = words.Select(w => new {FirstLetter = w[0], Word = w})
.GroupBy("new (FirstLetter)","Word")
.OrderBy("Key");
But that gives me a System.ArgumentException (At least one object must implement IComparable.) when it hits the foreach loop.
My actual code in my project is a little more complicated and looks something like this:
var colGroup = row.GroupBy(string.Format("new({0})",
string.Join(",", c)), string.Format("new({0})",
string.Join(",", v)));
Where c is a list of strings that I need to group by and v is a list of strings that I need to select in each group.
Ok - this is one way to do it, but it might be a little to static to be useful. The problem is that I had this part:
.GroupBy("new (FirstLetter)","Word");
Using new because I can't use a value type as a key (I had another question about that: https://stackoverflow.com/a/26022002/1250301). When with the OrderBy("Key") part, the problem is that it doesn't have a way to compare those dynamic types. I could solve it like this:
var wordList = words.Select(w => new {FirstLetter = w[0].ToString(), Word = w})
.GroupBy("FirstLetter","Word")
.OrderBy("Key");
Making the key a string. Or like this:
var wordList = words.Select(w => new {FirstLetter = w[0], Word = w})
.GroupBy("new (FirstLetter as k)","Word")
.OrderBy("Key.k");
Making it order by something (a char) that is comparable.
I can make it work with my actual problem like this (but it's kind of ugly):
var colGroup = row.GroupBy(string.Format("new({0})", string.Join(",", c)),
string.Format("new({0})", string.Join(",", v)))
.OrderBy(string.Join(",", c.Select(ob => string.Format("Key.{0}", ob))));
I am not sure what you are trying to do, but is that syntax even compiling?
try:
string[] words = { "boy","car", "apple", "bill", "crow", "brown" };
var wordList = words.Select(w => new {FirstLetter = w[0], Word = w})
.GroupBy(x => x.FirstLetter, x => x.Word)
.OrderBy(x => x.Key);

TextBox display closest match string

How can I get the string from a list that best match with a base string using the Levenshtein Distance.
This is my code:
{
string basestring = "Coke 600ml";
List<string> liststr = new List<string>
{
"ccoca cola",
"cola",
"coca cola 1L",
"coca cola 600",
"Coke 600ml",
"coca cola 600ml",
};
Dictionary<string, int> resultset = new Dictionary<string, int>();
foreach(string test in liststr)
{
resultset.Add(test, Ldis.Compute(basestring, test));
}
int minimun = resultset.Min(c => c.Value);
var closest = resultset.Where(c => c.Value == minimun);
Textbox1.Text = closest.ToString();
}
In this example if I run the code I get 0 changes in string number 5 from the list, so how can I display in the TextBox the string itself?
for exemple : "Coke 600ml" Right now my TextBox just returns:
System.Linq.Enumerable+WhereEnumerableIterator`1
[System.Collections.Generic.KeyValuePair`2[System.String,System.Int32]]
Thanks.
Try this
var closest = resultset.First(c => c.Value == minimun);
Your existing code is trying to display a list of items in the textbox. I looks like it should just grab a single item where Value == min
resultset.Where() returns a list, you should use
var closest = resultset.First(c => c.Value == minimun);
to select a single result.
Then the closest is a KeyValuePair<string, int>, so you should use
Textbox1.Text = closest.Key;
to get the string. (You added the string as Key and changes count as Value to resultset earilier)
There is a good solution in code project
http://www.codeproject.com/Articles/36869/Fuzzy-Search
It can be very much simplified like so:
var res = liststr.Select(x => new {Str = x, Dist = Ldis.Compute(basestring, x)})
.OrderBy(x => x.Dist)
.Select(x => x.Str)
.ToArray();
This will order the list of strings from most similar to least similar.
To only get the most similar one, simply replace ToArray() with First().
Short explanation:
For every string in the list, it creates an anonymous type which contains the original string and it's distance, computed using the Ldis class. Then, it orders the collection by the distance and maps back to the original string, so as to lose the "extra" information calculated for the ordering.

append string in List<String> contained in a dictionary using LINQ

I have list which contains objects of type Field i.e. List<Field> and my field class is defined as follows:
Public Class Field
{
public string FieldName { get; set; }
public string FieldValue { get; set; }
}
This list is then converted to a dictionary of type Dictionary<string, List<string>>
Dictionary<string, List<string>> myResult =
myFieldList.Select(m => m)
.Select((c, i) => new { Key = c.FieldName, value = c.FieldValue })
.GroupBy(o => o.Key, o => o.value)
.ToDictionary(grp => grp.Key, grp => grp.ToList());
I would like to use Linq to append the string values contained in the list as a single string, so technically the dictionary defined above should be defined as Dictionary<string, string> but I need a couple of extra steps when appending.
I need to add the \r\n in front of each values being appended and I need to make sure that these values including the new line do not get appended if the value is empty i.e.
value += (string.IsNullOrEmpty(newval) ? string.Empty : '\r\n' + newVal);
Thanks.
T.
Maybe this is what you want:
var myResult = myFieldList.GroupBy(o => o.FieldName, o => o.FieldValue)
.ToDictionary(grp => grp.Key, grp => string.Join("\r\n",
grp.Where(x=>!string.IsNullOrEmpty(x))));
Replace grp.ToList() with the logic that takes a sequence of strings and puts it together in the way you want.
The key methods you need are .Where to ignore items (i.e. the nulls), and string.Join to concatenate the strings together with a custom joiner (i.e. newlines).
Incidentally, you should use Environment.NewLine instead of '\r\n' to keep your code more portable.
Instead of grp.ToList() in the elements selector of ToDictionnary, aggregate everything to a single string there (only do this if you have a reasonable amount of strings in there, not a very high one as it would kill performance)
// Replace this
grp.ToList()
// With this
grp
.Where(s=>!string.IsNullOrEmtpy(s)) // drop the empty lines
.Aggregate((a,b)=>a+'\r\n'+b) // Aggregate all elements to a single string, adding your separator between each

Add keyvaluepair from file to Dictionary?

I am trying to import values from a .txt file into my dictionary. The .txt file is formatted like this:
Donald Duck, 2010-04-03
And so on... there is 1 entry like that on each line. My problem comes when I try to add the split strings into the dictionary.
I am trying it like this: scoreList.Add(values[0], values[1]); But it says that names doesn't exist in the context. I hope someone can point me in the correct direction about this...
Thank you!
private void Form1_Load(object sender, EventArgs e)
{
Dictionary<string, DateTime> scoreList = new Dictionary<string, DateTime>();
string path = #"list.txt";
var query = (from line in File.ReadAllLines(path)
let values = line.Split(',')
select new { Key = values[0], Value = values[1] });
foreach (KeyValuePair<string, DateTime> pair in scoreList)
{
scoreList.Add(values[0], values[1]);
}
textBox1.Text = scoreList.Keys.ToString();
}
Your values variable are only in scope within the LINQ query. You need to enumerate the query result, and add the values to the dictionary:
foreach (var pair in query)
{
scoreList.Add(pair.Key, pair.Value);
}
That being said, LINQ features a ToDictionary extension method that can help you here. You could replace your loop with:
scoreList = query.ToDictionary(x => x.Key, x => x.Value);
Finally, for the types to be correct, you need to convert the Value to DateTimeusing, for instance, DateTime.Parse.
First you are doing it wrong, you should add item from list not values[0] and values[1] used in LINQ..
Dictionary<string, DateTime> scoreList = new Dictionary<string, DateTime>();
string path = #"list.txt";
var query = (from line in File.ReadAllLines(path)
let values = line.Split(',')
select new { Key = values[0], Value = values[1] });
foreach (var item in query) /*changed thing*/
{
scoreList.Add(item.Key, DateTime.Parse(item.Value)); /*changed thing*/
}
textBox1.Text = scoreList.Keys.ToString();
The immediate problem with the code is that values only exists in the query expression... your sequence has an element type which is an anonymous type with Key and Value properties.
The next problem is that you're then iterating over scoreList, which will be empty to start with... and there's also no indication of where you plan to convert from string to DateTime. Oh, and I'm not sure whether Dictionary<,>.Keys.ToString() will give you anything useful.
You can build the dictionary simply enough though:
var scoreList = File.ReadLines(path)
.Select(line => line.Split(','))
.ToDictionary(bits => bits[0], // name
bits => DateTime.ParseExact(bits[1], // date
"yyyy-MM-dd",
CultureInfo.InvariantCulture));
Note the use of DateTime.ParseExact instead of just DateTime.Parse - if you know the format of the data, you should use that information.

Convert a delimted string to a dictionary<string,string> in C#

I have a string of the format
"key1=value1;key2=value2;key3=value3;"
I need to convert it to a dictionary for the above mentioned key value pairs.
What would be the best way to go about this?
Thanks.
Something like this?
var dict = text.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries)
.Select(part => part.Split('='))
.ToDictionary(split => split[0], split => split[1]);
Of course, this will fail if the assumptions aren't met. For example, an IndexOutOfRangeException could be thrown if the text isn't in the right format and an ArgumentException will be thrown if there are duplicate keys. Each of these scenarios will require different modifications. If redundant white-space could be present, you may need some string.Trim calls as necessary.
Updated Ani's to take in account the semi colon at the end. The where clause will ensure that you have a key and value before creating and entry.
var dictionary = "key1=value1;key2=value2;key3=value3;"
.Split(';')
.Select (part => part.Split('='))
.Where (part => part.Length == 2)
.ToDictionary (sp => sp[0], sp => sp[1]);
You could do this using JSON string, for example:
var dic = JsonConvert.DeserializeObject<Dictionary<int, string>>("{'1':'One','2':'Two','3':'Three'}");
Behold the awesome whitespace ignoring, correcting for last value having or not having a semicolon power of regular expressions:
var dict = Regex.Matches("key1 = value1; key2 = value2 ; key3 = value3", #"\s*(.*?)\s*=\s*(.*?)\s*(;|$)")
.OfType<Match>()
.ToDictionary(m => m.Groups[1].Value, m => m.Groups[2].Value);
But seriously though, Ani deserves props for the .ToDictionary(). I would never have thought of that.
You could write it like this or loop over it to do it yourself. Either way. Ultimately, you're splitting on ; to get the item pairs, then on = to get the key and value.
string input = "key1=value1;key2=value2;key3=value3;";
Dictionary<string, string> dictionary =
input.TrimEnd(';').Split(';').ToDictionary(item => item.Split('=')[0], item => item.Split('=')[1]);
Loop version:
Dictionary<string, string> dictionary = new Dictionary<string, string>();
string[] items = input.TrimEnd(';').Split(';');
foreach (string item in items)
{
string[] keyValue = item.Split('=');
dictionary.Add(keyValue[0], keyValue[1]);
}

Categories