Merge values in a single List - c#

I have a simple List<string> with colon delimited values.
Some values, however, may be the same before the colon, but different after. I need to merge these two values based on the last most value.
Example
name:john
surname:michael
gender:boy
name:pete
title:captain
would become
surname:michael
gender:boy
name:pete
title:captain

list = list.GroupBy(s => s.Split(':')[0].ToLower())
.Select(g => g.Last())
.ToList();

In general: Use a Dictionary --> you're using a List AS a dictionary.
I prefer LazyDictionarys that you can update without checking key existance but for a standard .Net Dict in pseudocode
dict = new Dictionary()
for each item in your_LIST
{
tmp = item.Split(':')
if dict.ContainsKey(tmp[0])
{
dict[tmp[0]] = tmp[1]
}
else
{
dict.Add(tmp[0],tmp[1])
}
}
ANd you have a dictionary, which is what you want, if you really want to then convet it back to a list then fine, but really you probably want this to be a dictionary 'all the way down'

It can be cone using linq
private string[] inputs = new string[]
{
"name:john",
"surname:michael",
"gender:boy",
"name:pete",
"title:captain",
};
private string[] expected = new string[]
{
"surname:michael",
"gender:boy",
"name:pete",
"title:captain",
};
private static List<string> FilterList(IEnumerable<string> src) {
return src.Select(s =>
{
var pieces = s.Split(':');
return new {Name = pieces[0], Value = pieces[1]};
}).GroupBy(m => m.Name)
.Select(g => String.Format("{0}:{1}", g.Key, g.Last().Value)).ToList();;
}
[TestMethod]
public void TestFilter() {
var actual = FilterList(inputs);
CollectionAssert.AreEquivalent(expected, actual);
}

Related

C# sorting multiple numbers in a list. Sort by second string

i am trying to make a list of things and trying to so
var l = new List<string[]>() {
new string[] { "10945730333", "5"},
new string[] { "298543022", "234"},
new string[] { "382958320", "35"},
};
var result = l.OrderBy(f => f[1]).ToList();
MessageBox.Show($"{result}");
i want it so it sorts the second number by lowest to highest. so that the 234 is on the bottom. When i do use it it comes up with "System.Collections.Generic.List`1[System.String[]]"
First, your sorting is incorrect, you should parse the string to an int otherwise you'll be performing a string comparison which is not what you want.
var result = l.OrderBy(f => int.Parse(f[1])).ToList();
Then you can get the result like so:
MessageBox.Show(string.Join("\n",result.SelectMany(e => e)));

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.

Alternative implementation for creating a dictionary using Linq

I have a snippet as follow. Basically I would like to create a dictionary from the series I currently have. If I did it the long way, there was no problem, the code run fine and I got the expected results.
class Program
{
static void Main(string[] args)
{
List<int> series = new List<int>() { 1,2,3,4 };
Dictionary<int, List<int>> D = new Dictionary<int,List<int>>();
foreach (var item in series)
{
D.Add(item, seriesWithinSeries(item));
}
}
public static List<int> seriesWithinSeries(int seed)
{
return Enumerable.Range(0, seed).Where(x => x < seed).ToList();
}
}
How do I convert that into Linq? I have tried this:
D = series.Select(x=> { return new (x,seriesWithinSeries(x));}).ToDictionary<int,List<int>>();
But the compiler complaints I need a type for the new, which makes sense. However, I do not really know how to fix it. Please help.
ToDictionary doesn't have a parameterless version. You probably want this:
var result = series.ToDictionary(x => x, x => seriesWithingSeries(x));
So anonymous type is not needed here. But for the sake of a complete explanation here is a correct syntax with an anonymous type:
var result = series
.Select(x => new { Key = x, Value = seriesWithinSeries(x) })
.ToDictionary(pair => pair.Key, pair => pair.Value);

How to add multiple strings to List<string[]>?

I want to add two strings to a string[] list without using a string array. I used a string array named str but I want to add d.Name and d.AvailableFreeSpace directly to list. Is there a way to do this?
public static List<string[]> GetReadyDrives()
{
DriveInfo[] drives = DriveInfo.GetDrives();
List<DriveInfo> readyDrives = new List<DriveInfo>();
List<string[]> parsedReadyDrives = new List<string[]>();
for (int i = 0; i < drives.Length; i++)
{
if (drives[i].IsReady)
{
readyDrives.Add(drives[i]);
}
}
foreach (DriveInfo d in readyDrives)
{
string[] str=new string[2];
str[0] = d.Name;
str[1] = d.AvailableFreeSpace.ToString();
parsedReadyDrives.Add(str);
}
return parsedReadyDrives;
}
public static List<string[]> GetReadyDrives()
{
return DriveInfo.GetDrives()
.Where(d => d.IsReady)
.Select(d => new[] { d.Name, d.AvailableFreeSpace.ToString() })
.ToList();
}
...but, to be honest, you'd be better off doing this:
class ReadyDriveInfo
{
public string Name { get; set; }
public string AvailableFreeSpace { get; set; }
}
public static List<ReadyDriveInfo> GetReadyDrives()
{
return DriveInfo.GetDrives()
.Where(d => d.IsReady)
.Select(d => new ReadyDriveInfo
{
Name = d.Name,
AvailableFreeSpace = d.AvailableFreeSpace.ToString()
})
.ToList();
}
... but, even there, why do you want the free space as a string?
Every element of a List<string[]> is an instance of string[]. So if you want to add strings individually, you can't. But you can add them as the single element in a single-element instance of string[]. Thus:
parsedReadyDrives.Add(new[] { d.Name });
parsedReadyDrives.Add(new[] { d.AvailableFreeSpace.ToString());
If you want them as the two elements of a two-element instance of string[], you'd say:
parsedReadyDrives.Add(new[] { d.Name, d.AvailableFreeSpace.ToString() });
Frankly, I think passing around a List<string[]> is really nasty. One major concern is that you're placing a very heavy burden on your callers to intimately know the structure of the List<string[]> and what each element of each element means. Additionally, it's not robust to change (you have a maintenance nightmare if you want to change the meaning of any single one element of any element in the List<string[]> or if you want to add additional elements. You might want to consider a more formal data structure that encapsulates your concerns more appropriately.
Can you not just do this?
parsedReadyDrives.Add(new []{d.Name, d.AvailableFreeSpace.ToString()});
It's just syntactic sugar, though.
Your list is composed of string arrays, so no, you can't add something to the list that is not a string array.
You can create an object composed of two strings, if that makes more sense for what you're trying to do, but you'd still have to initialize that object before adding it.
Yes you can do:
parsedReadyDrives.Add(new string[]{d.Name, d.AvailableFreeSpace.ToString()});
So try to use some LINQ. Instead of your code try this to return what you want:
return DriveInfo.GetDrives().Where(x => x.IsReady).Select(x => new string[]{x.Name, x.AvailableFreeSpace.ToString()}.ToList();
You can do it with single LINQ query:
public static List<string[]> GetReadyDrives()
{
return DriveInfo.GetDrives()
.Where(d => d.IsReady)
.Select(d => new string[] { d.Name, d.AvailableFreeSpace.ToString() })
.ToList();
}
UPDATE:
I'd split code which finds ready drives and code which prepares what to write to file. In this case I don't need to look inside method to understand what contained in string array:
public static IEnumerable<DriveInfo> GetReadyDrives()
{
return DriveInfo.GetDrives()
.Where(d => d.IsReady);
}
Then just write what you need:
foreach(var drive in GetReadyDrives())
WriteToFile(drive.Name, drive.AvailableFreeSpace);
Or even this way (but I like more option with descriptive method name):
foreach(var drive in DriveInfo.GetDrives().Where(d => d.IsReady))
WriteToFile(drive.Name, drive.AvailableFreeSpace);

Remove duplicates in list

I got a list of objects(ip, domainname). and want to find the duplicates in them and remove the ones that has not got www in front of the domainname.
So if it is in the list
192.168.0.0 www.stackoverflow.com
192.168.0.1 stackoverflow.com
I want to remove stackoverflow.com.
So far this is my code I am passing my list of objects to this function:
static List<ServerBindings> removeDuplicates(List<ServerBindings> inputList)
{
Dictionary<string, string> uniqueStore = new Dictionary<string, string>();
List<ServerBindings> finalList = new List<ServerBindings>();
foreach (var currValue in inputList)
{
if (!uniqueStore.ContainsKey(currValue.DomainName))
{
uniqueStore.Add(currValue.DomainName, currValue.IPAddress);
finalList.Add(new ServerBindings { DomainName = uniqueStore.Keys.ToString(), IPAddress = uniqueStore.Values.ToString() });
}
}
return finalList;
}
I have tried linq but as I'm new to it I tried to groupby but don't know how to say "select ones where it has www in front of domain name".
EDIT:
Tested this again and seems not to work...I mean the linq query selects only the ones that have www in front and ignores the ones without....to clarify if in list we have www.test.com, test.com and test3.com the end result should be www.test.com and test3.com
var result=inputList.Where(x=>x.DomainName.StartsWith("www.")).Distinct();
if distinct doesn't do the job because the bindings are different objects you could do
var result=from x in list
where x.DomainName.StartsWith("www.")
group x by x.DomainName into domain
select new ServerBindings {
DomainName=domain.Key,
IPAddress=domain.Select (d =>d.IPAddress ).First ()
};
Something like this should do the whole thing:
serverBindings
.Select(sb => new { Normalized = sb.DomainName.StartsWith("www.") ? sb.DomainName.Substring(4) : sb.DomainName, HasLeadingWWW = sb.DomainName.StartsWith("www."), Binding = sb })
.GroupBy(sbn => sbn.Normalized)
.Select(g => g.OrderBy(sbn => sbn.HasLeadingWWW).First.Binding);
NOTE: I haven't tested it, might need some tweaking.
return inputList
.GroupBy(key=>key.IpAddress)
.Select(group => {
var domain = group.Any(g=>g.StartsWith("http://www"))
? group.First(g=>g.StartsWith("http://www"))
: group.First();
return new ServerBindings
{
DomainName = group.First
IpAddress = group.Key
};)
.ToList();

Categories