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

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);

Related

How to create a list of Guid (Guid []) from a list of objects with linq?

I have a code :
{
int i = 0;
Guid[] ids = new Guid[clientCertifications.Count()];
foreach (Certification certif in clientCertifications)
{
ids[i] = certif.Id;
i++;
}
return listOffices.GroupBy(lo => lo.pk_Office)
.Select(loG => loG.First()
.MapOfficeToModel(
loG.Where(g => g.FK_Guid.In(ids)).Select(g => g.FK_Guid).ToCertifications(clientCertifications)
));
}
I would like to know if it is possible to obtain the list "ids" using a select or other word of linq? In the example I use a loop for and foreach, but I think we can do shorter no? In the line :
loG.Where(g => g.FK_Guid.In(***here something like: clientCertifications.Select(o => o.Id ... )*** ids)).Select(g => g.FK_Guid).ToCertifications(clientCertifications)`
This piece of your code:
int i = 0;
Guid[] ids = new Guid[clientCertifications.Count()];
foreach (Certification certif in clientCertifications)
{
ids[i] = certif.Id;
i++;
}
is basically the complicated version of:
var ids = clientCertifications.Select(certif => certif.Id).ToArray();
And you should be able to put clientCertifications.Select(certif => certif.Id).ToArray() wherever you would have used the variable ids if it's plain LinQ. If you have a provider for LinQ that does transformations (for example to database statements) that may not work and you may need the temporary variable. But then, if you do use such a provider, there might be an entirely different and maybe better way.

Compare two List elements and replace if id is equals

I have two lists with Classes
public class Product
{
int id;
string url;
ect.
}
I need compare in the old list (10k+ elements) a new list(10 elements) by ID
and if an id is same just replace data from new List to old list
I think it will be good using LINQ.
Can you help me how can I use LINQ or there are batter library?
Do you need to modify the collection in place or return a new collection?
If you are returning a new collection you could
var query = from x in oldItems
join y in newItems on y.Id equals x.Id into g
from z in g.DefaultIfEmpty()
select z ?? x;
var new List = query.ToList();
This method will ignore entries in newItems that do not exist in old items.
If you are going to be modifying the collection in place you would be better off working with a dictionary and referencing that everywhere.
You can create a dictionary from the list by doing
var collection = items.ToDictionary(x => x.Id, x => x);
Note modifying the dictionary doesn't alter the source collection, the idea is to replace your collection with the dictionary object.
If you are using the dictionary you can then iterate over new collection and check the key.
foreach (var item in newItems.Where(x => collection.ContainsKey(x.Id))) {
collection[item.Id] = item;
}
Dictionaries are iterable so you can loop over the Values collection if you need to. Adds and removes are fast because you can reference by key. The only problem I can think you may run into is if you rely on the ordering of the collection.
If you are stuck needing to use the original collection type then you could use the ToDictionary message on your newItems collection. This makes your update code look like this.
var converted = newItems.ToDictionary(x => x.Id, x => x);
for (var i = 0; i < oldItems.Count(); i++) {
if (converted.ContainsKey(oldItems[i].Id)) {
oldItems[i] = converted[oldItems[i].Id];
}
}
This has the advantage the you only need to loop the newitems collection once, from then on it's key lookups, so it's less cpu intensive. The downside is you've created an new collection of keys for newitems so it consumes more memory.
Send you a sample function that joins the two list by id property of both lists and then update original Product.url with the newer one
void ChangeItems(IList<Product> original, IList<Product> newer){
original.Join(newer, o => o.id, n => n.id, (o, n) => new { original = o, newer = n })
.ToList()
.ForEach(j => j.original.Url = j.newer.Url);
}
Solution :- : The LINQ solution you're look for will be something like this
oldList = oldList.Select(ele => { return (newList.Any(i => i.id == ele.id) ? newList.FirstOrDefault(newObj => newObj.id == ele.id) : ele); }).ToList();
Note :- Here we are creating the OldList based on NewList & OldList i.e we are replacing OldList object with NewList object.If you only want some of the new List properties you can create a copy Method in your class
EG for copy constructor
oldList = oldList.Select(ele => { return (newList.Any(i => i.id == ele.id) ? ele.Copy(newList.FirstOrDefault(newObj => newObj.id == ele.id)) : ele); }).ToList();
//Changes in your class
public void Copy(Product prod)
{
//use req. property of prod. to be replaced the old class
this.id = prod.id;
}
Read
It is not a good idea to iterate over 10k+ elements even using linq as such it will still affect your CPU performance*
Online sample for 1st solution
As you have class
public class Product
{
public int id;
public string url;
public string otherData;
public Product(int id, string url, string otherData)
{
this.id = id;
this.url = url;
this.otherData = otherData;
}
public Product ChangeProp(Product newProd)
{
this.url = newProd.url;
this.otherData = newProd.otherData;
return this;
}
}
Note that, now we have ChangeProp method in data class, this method will accept new class and modify old class with properties of new class and return modified new class (as you want your old class be replaced with new classes property (data). So at the end Linq will be readable and clean.
and you already have oldList with lots of entries, and have to replace data of oldList by data of newList if id is same, you can do it like below.
suppose they are having data like below,
List<Product> oldList = new List<Product>();
for (int i = 0; i < 10000; i++)
{
oldList.Add(new Product(i, "OldData" + i.ToString(), "OldData" + i.ToString() + "-other"));
}
List<Product> newList = new List<Product>();
for (int i = 0; i < 5; i++)
{
newList.Add(new Product(i, "NewData" + i.ToString(), "NewData" + i.ToString() + "-other"));
}
this Linq will do your work.
oldList.Where(x => newList.Any(y => y.id == x.id))
.Select(z => oldList[oldList.IndexOf(z)].ChangeProp(newList.Where(a => a.id == z.id).FirstOrDefault())).ToList();
foreach(var product in newList)
{
int index = oldList.FindIndex(x => x.id == product.id);
if (index != -1)
{
oldList[index].url = product.url;
}
}
This will work and i think it's a better solution too.
All the above solution are creating new object in memory and creating new list with 10k+
records is definitely a bad idea.
Please make fields in product as it won't be accessible.

c# populate list<T> from two arrays or delimited strings

I have two arrays populated from strings:
public partial class tarBuyInternet
{
public string desc { get; set; }
public string param { get; set; }
}
public List<tarBuyInternet> listTarBuyInternet;
// …
string[] descs;
string[] prms;
descs = jarrObj1["desc"].ToString().Split(GlobalClass.strDelimiter);
prms = jarrObj1["params"].ToString().Split(GlobalClass.strDelimiter);
How can I populate properties of List<TarBuyInternet> with corresponding array? Or how to populate list directly from string splitting it to corresponding property?
You can use the extension method Enumerable.Zip to walk the two arrays in parallel and create a tarBuyInternet for each pair:
var listTarBuyInternet = descs.Zip(prms, (d, p) => new tarBuyInternet { desc = d, param = p }).ToList();
You can just loop through the arrays, and add to the list (provided that the arrays have the same length):
for (var i=0; i<descs.Length; i++){
listTarBuyInternet.Add(new tarBuyInternet{
desc = descs[i];
param = prms[i];
});
}
Since there is a discussion that this cannot be done in linq, here's a monstrosity that will get the job done (even without using Zip, which is the correct answer here):
var result =
from d in descs.Select((item, index) => new {item, index})
join p in prms.Select((item, index) => new {item, index})
on d.index equals p.index
select new tarBuyInternet{desc = d.item, param = p.item};
listTarBuyInternet = result.ToList();
#Kajal Sinha
Well no, you actually can.
Edit #SWeko: You are right, my bad. THis however should work ^^
listTarBuyInternet = jarrObj1["desc"].ToString().Split(GlobalClass.strDelimiter)
.Zip(jarrObj1["params"].ToString().Split(GlobalClass.strDelimiter),
(d, p) => new tarBuyInternet { desc = d, param = p }).ToList();
Should work.
As your question seems like you are asking the C# compiler to automatically populate the list by directly injecting the splitted array somehow, you need to tell the compiler how to read from array and assign property values. You need to iterate over to create tarBuyInternet instances and then add those instances to the listTarBuyInternet.

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

Merge values in a single List

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);
}

Categories