Subtle List<string> Manipulation - c#

I have two List<string> (listOfFullPaths containg full database paths for e.g. "C:\MyDir\SomeDir\SomeDatabase.mdf") and some other List<string> containing some of the names of the databases (only) ('listOfDatabases'). So each might include
List<string> listOfFullPaths = new List<string>()
{
"C:\MyDir\SomeDir\SomeDatabase1.mdf",
"C:\MyDir\SomeDir\SomeDatabase2.mdf",
"C:\MyDir\SomeDir\SomeDatabase3.mdf"
};
the other just
List<string> listOfFullPaths = new List<string>()
{
"SomeDatabase1",
"SomeDatabase2"
};
My question is what is the most efficent way of returning the full paths contined in listOfFullPaths that have their corresponding database in listOfDatabases?
Note: The answer is not something like
List<string> tmpList = new List<string>();
foreach (string database in listOfDatabases)
if (listOfFullPaths.Contains(database))
tmpList.Add(database);
listOfFullPaths.Clear();
listOfFullPaths = tmpList;
although this does what I want.

It sounds like you're rejecting the nested loop due to performance concern? If so then another way to approach this problem is the following.
var set = new Set<string>(listOfDatabases);
var list = new List<string>();
foreach (string fullPath in listOfFullPaths) {
var name = Path.GetFileNameWithoutExtension(fullPath);
if (set.Contains(name)) {
list.Add(fullPath);
}
}
listOfFullPaths = list;

I think if you want maximal efficiency you create a dictionary or hashed set with the database name as a key off the bat.
var listOfFullPaths = new List<string>()
{
"C:\MyDir\SomeDir\SomeDatabase1.mdf",
"C:\MyDir\SomeDir\SomeDatabase2.mdf",
"C:\MyDir\SomeDir\SomeDatabase3.mdf"
}.ToDictionary(k => Path.GetFilenameWithoutExtension(k));
Then you can loop through listOfDBs and look for the hash
listOfDbs.Where(w => listOfFullPaths.Contains(w));
In this case, you do two loops and take advantage of the built in indexing of the dictionary.

var fileNames = listOfFullpaths.Select(x => Path.GetFileNameWithoutExtension(x));
listOfFullPaths = listofDatabases.Where(x => fileNames.Contains(x)).ToList();
Note that if you know in advance that the listOfFullPaths is sorted by the database name, you can make it much more efficient.

Assuming both lists are populated, and you don't have access to the file on disk, then this linq will help you.
using System.Linq;
List<string> listOfFullPaths = new List<string>()
{
#"C:\MyDir\SomeDir\SomeDatabase1.mdf",
#"C:\MyDir\SomeDir\SomeDatabase2.mdf",
#"C:\MyDir\SomeDir\SomeDatabase3.mdf"
};
List<string> listOfDatabases = new List<string>()
{
"SomeDatabase1",
"SomeDatabase2"
};
var dbs = from path in listOfFullPaths
from db in listOfDatabases
where System.IO.Path.GetFileNameWithoutExtension(path) == db
select path;

If you want to do it efficiently in terms of speed (i.e. in O(n)) then JaredPar's answer is correct.
Here is a more idiomatic version:
var set = new HashSet<string>(listOfDbs);
List<string> result = listOfFullPaths
.Where(p => set.Contains(Path.GetFileNameWithoutExtension(p)))
.ToList();

Related

How can I only store the second part of a string in a list?

I have a list Textlist1 that contains only strings. Each item in the list begins with the same text: "sentence.text.", but I only want to store the second part of the string in another list, I don't want to store the first part "sentence.text." in the list Textlist2.
For example:
List<string> Textlist1 = new List<string>();
List<string> Textlist2 = new List<string>();
The full strings are stored in Textlist1.
Textlist1[0] = "sentence.text.My name is Fred"
Textlist1[1] = "sentence.text.Jenny is my sister"
How can I only add "My name is Fred" and "Jenny is my sister" to Textlist2?
The result should be like this:
Textlist2[0] = "My name is Fred"
Textlist2[1] = "Jenny is my sister"
See online result: https://dotnetfiddle.net/MpswLZ
List<string> Textlist1 = new List<string>() {
"sentence.text.My name is Fred",
"sentence.text.Jenny is my sister"
};
List<string> Textlist2 = new List<string>();
Textlist2 = Textlist1.Select(item => item.Split('.')[2]).ToList();
You can use Linq. (You need do add using System.Linq)
Textlist2= Textlist1
.Select(i=> i.Substring("sentence.text.".Length))
.ToList();
Split the input strings by the periods, limiting the split to 3. Then take the last entry from the array that split produces.
Textlist2[0] = Textlist1[0].Split('.', 3)[2];
Textlist2[1] = Textlist1[1].Split('.', 3)[2];
You could use Regex.Replace the text.
var regex = new Regex("^sentence.text.",RegexOptions.Compiled);
Textlist2.AddRange(Textlist1.Select(x=>regex.Replace(x,string.Empty)));
The "^" in Regex ensure the required text ("sentence.text") is matched only at the beginning of string and not else where.
Sample Input
Sample Output
List<string> Textlist1 = new List<string>
{
"sentence.text.a",
"sentence.text.b"
};
string prefix = "sentence.text.";
List<string> Textlist2 = Textlist1.Select(x => x.Substring(prefix.Length)).ToList();
https://dotnetfiddle.net/oFhvfs
There are ultimately many ways of doing this, this is just one solution. Which approach you choose depends on how complex the pattern replacement is. I've gone with a simplistic approach as your example is a very simple case.

C# scan list against master list for missing items

I have a master list that has the values for tables of a database I know to be correct:
masterList: List<string>(){ "business", "customer", "location", "employee", etc}
And I've queried a new database that is supposed to be identical. My test will tell me if I have any errors in the scripts my team has made to make this new DB. tablesList is supposed to the be the return of my query:
tablesList: List<string>(){ "business", "customer", "location", "employee", etc}
So in practice they are supposed to be the same, but to test errors, I want to compare the tablesList against the masterList to make sure all needed tables are there. As a copy of this process, I'm also reversing the search, in case there are any extra tables that are not there on the masterList.
Question: How do I compare a list against a master list, and return items that don't match up?
I am using Visual Studio 2017 with c# .net Core 2.0.
Here is what I've been trying so far:
var errorsList = new List<string>();
tablesList = QuerySchemaForTables();
masterList = GrabMasterTableList();
foreach(var item in masterList)
errorsList.Add(tablesList.Where(x => x.Contains(item)));
But with this, I'm getting the error:
cannot convert from IEnumerable to string
You can get the two directions of errors using LINQ. No need for the loop:
var missingInMasterList = tableList.Where(x => !masterList.Contains(x)).ToList();
var missingInTableList = masterList.Where(x => !tableList.Contains(x)).ToList();
Are you looking for something like that;
var errorList = tableList.Where(x => !masterList.Contains(x));
You can capture the differences using .Except(), which is one of the IEnumerable set operations:
var missingTables = masterList.Except(tablesList);
var extraTables = tablesList.Except(masterList);
Then, to create your error message, you can join the items in these IEnumerables with a comma into a single string using string.Join():
var errorMessage = new StringBuilder();
if (missingTables.Any())
{
errorMessage.AppendLine("Missing Tables: " + string.Join(", ", missingTables));
}
if (extraTables.Any())
{
errorMessage.AppendLine("Extra Tables: " + string.Join(", ", extraTables));
}
Then you can output your results by checking the length of errorMessage to determine if any errors were encountered:
if (errorMessage.Length > 0)
{
Console.WriteLine(errorMessage.ToString());
}
else
{
Console.WriteLine("No extra or missing tables detected");
}
I think better to use is Except() as follows
var MasterList = new List<string> { "business", "customer", "location", "employee"};
var ChildList = new List<String> { "customer", "location", "employee" };
var filter = MasterList.Except(ChildList);
This will values those are not in ChildList.You can also do vice versa.
To find all items that are in the tablesList but not in the masterList use .Contains:
var errorsList = tableList.Where(x => !masterList.Contains(x));
But I recommend you use a HashSet<String> for masterList so search for an item in it will be in O(1) instead of O(n):
var masterCollection = new HashSet<String>(GrabMasterTableList());
var errorsList = tableList.Where(x => !masterCollection.Contains(x));
As for the problem with your code as you posted:
foreach(var item in masterList)
errorsList.Add(tablesList.Where(x => x.Contains(item))); // <-- error
As the error points out, Linq's .Where returns an IEnumerable<T> whereas .Add expects a single item of the type of the collection, which in this case is a single string. You could use .AddRange instead but I think a better use all together is what I wrote above.
Your code is presently trying to add an IEnumerable to a List.
If you want to add all the matches you should AddRange instead.
https://msdn.microsoft.com/en-us/library/z883w3dc(v=vs.110).aspx
var errorsList = new List<string>();
tablesList = QuerySchemaForTables();
masterList = GrabMasterTableList();
foreach(var item in masterList)
errorsList.AddRange(tablesList.Where(x => x.Contains(item)));

Combining Lists using Concat

I have some code that's main purpose is to combine multiple like lists into one master list to return with the View.
ActivityAuditDetails searchParams = new ActivityAuditDetails();
ActivityAuditDetails finalResults = new ActivityAuditDetails();
List<string> finalChangedColumns = new List<string>();
List<string> finalOldValues = new List<string>();
List<string> finalNewValues = new List<string>();
string finalAuditAction = string.Empty;
List<int> auditKeys = AuditIdentityId.Split(',').Select(int.Parse).ToList();
string url = "/Audit/GetActivityAuditDetails";
try
{
foreach (int auditKey in auditKeys)
{
searchParams.AuditIdentityId = auditKey;
ActivityAuditDetails result = // SOME METHOD THAT RETURNS RESULTS AS IT SHOULD;
finalChangedColumns.Concat(result.ChangedColumns);
finalAuditAction = result.AuditAction;
finalOldValues.Concat(result.OldValues);
finalNewValues.Concat(result.NewValues);
}
finalResults.ChangedColumns = finalChangedColumns;
finalResults.AuditAction = finalAuditAction;
finalResults.OldValues = finalOldValues;
finalResults.NewValues = finalNewValues;
}
catch (Exception e)
{
e.ToLog();
}
return View(finalResults);
I can see that the result object is populated as it should be in the debugger. I thought the Concat method would work to combine the lists, but my final values in the foreach loop never get update\incremented ( the list count remains zero ).
Is there another way to accomplish this, or am I having a morning brain fart? My question was not about the differences, as I was aware of them. I just had a momentary lapse of reason.
You likely want to use AddRange rather than Concat.
The former adds the data directly to the List. The latter concatenates data into a new IEnumerable.
But because you are not assigning the result of Concat to anything (i.e. var g = finalChangedColumns.Concat(result.ChangedColumns);) your Concat calls do effectively nothing.
List<T>.AddRange(IEnumerable<T> collection) (link to info) probably does what you're looking for, right?
Adds the elements of the specified collection to the end of the List.
From documentation:
string[] input = { "Brachiosaurus",
"Amargasaurus",
"Mamenchisaurus" };
List<string> dinosaurs = new List<string>();
dinosaurs.AddRange(input);
//The list now contains all dinosaurs from the input string array

How to append a string in front of all elements in any List<string> in C#

Is there any way I can insert a prefix string "/directory/" in front of all elements in the list ["file1.json", "file2.json"]?
Result I'm looking for would be ["/directory/file1.json", "/directory/file2.json"].
You could use the linq extension method: Select()
List<string> myList = new List<string> { "file1.JSON", "file2.JSON" };
var directory = "/directory";
myList = myList.Select(filename => Path.Combine(directory, filename)).ToList();
This will execute the Path.Combine(directory, filename) foreach item in the list. I'm using the Path.Combine method, because thats the best way to concat directories/filenames, because it should be platform independend.
Try this :
List<string> yourlist = new List<string> { "file1.JSON", "file2.JSON" };
var directory = "/directory/";
yourlist = yourlist.Select(f => string.Concat(directory,f)).ToList();
All the answers using Select are actually creating a new list if you really want to change the values in the existing list then just use a for loop
for(int i = 0; i < fileList.Count; i++)
fileList[i] = #"/directory/" + fileList[i];
You can use the extension method Enumerable.Select. There are two ways of calling Select:
Method syntax
var originalList = new List<string> { "file1.json", "file2.json" };
var list = originalList.Select(e => "/directory/" + e);
Query syntax
var originalList = new List<string> { "file1.json", "file2.json" };
var list = from e in originalList select "/directory/" + e;
Both are the same.
You can use like the following, Which will give you a List as per your requirement:
string stringToappend=#"/Directory/";
List<string> JsonList= new List<string> (){"file1.JSON","file2.JSON"};
var AppendedLis=JsonList.Select(x=> Path.Combine(stringToappend, x)).ToList();
Note :- The LINQ Extension method .Select help you to do this operation, Which will internally iterate through each element in the collection and append the required string with each item and Gives you the List as per your requirements

Remove duplicate items from a List<String[]> in C#

I have an issue here a bit complex than I'm trying to resolve since some days ago. I'm using the PetaPoco ORM and didn't found any other way to do a complex query like this:
var data = new List<string[]>();
var db = new Database(connectionString);
var memberCapabilities = db.Fetch<dynamic>(Sql.Builder
.Select(#"c.column_name
,CASE WHEN c.is_only_view = 1
THEN c.is_only_view
ELSE mc.is_only_view end as is_only_view")
.From("capabilities c")
.Append("JOIN members_capabilities mc ON c.capability_id = mc.capability_id")
.Where("mc.member_id = #0", memberID)
.Where("c.table_id = #0", tableID));
var roleCapabilities = db.Fetch<dynamic>(Sql.Builder
.Select(#"c.column_name
,CASE WHEN c.is_only_view = 1
THEN c.is_only_view
ELSE rc.is_only_view end as is_only_view")
.From("capabilities c")
.Append("JOIN roles_capabilities rc ON c.capability_id = rc.capability_id")
.Append("JOIN members_roles mr ON rc.role_id = mr.role_id")
.Where("mr.member_id = #0", memberID)
.Where("c.table_id = #0", tableID));
I'm trying to get the user capabilities, but my system have actually to ways to assign an user a capability, or direct to that user or attaching the user to a role. I wanted to get this merged list using a stored procedure but I needed cursors and I thought maybe should be easier and faster doing this on the web application. So I get that two dynamics and the members capabilities have priority to the roles capabilities, so I need to check if that using loops. And I did like this:
for (int i = 0; i < roleCapabilities.Count; i++)
{
bool added = false;
for (int j = 0; j < memberCapabilities.Count; j++)
if (roleCapabilities[i].column_name == memberCapabilities[j].column_name)
{
data.Add(new string[2] { memberCapabilities[j].column_name, Convert.ToString(memberCapabilities[j].is_only_view) });
added = true;
break;
}
if (!added)
data.Add(new string[2] { roleCapabilities[i].column_name, Convert.ToString(roleCapabilities[i].is_only_view) });
}
So now the plan is delete the duplicate entries. I have try using the following methods with no results:
data = data.Distinct();
Any help? Thanks
Make sure that your object either implements System.IEquatable or overrides Object.Equals and Object.GetHashCode. In this case, it looks like you're storing the data as string[2], which won't give you the desired behavior. Create a custom object to hold the data, and do one of the 2 options listed above.
If I understand your question correctly you want to get a distinct set of arrays of strings, so if the same array exists twice, you only want one of them? The following code will return arrays one and three while two is removed as it is the same as one.
var one = new[] {"One", "Two"};
var two = new[] {"One", "Two"};
var three = new[] {"One", "Three"};
List<string[]> list = new List<string[]>(){one, two, three};
var i = list.Select(l => new {Key = String.Join("|", l), Values = l})
.GroupBy(l => l.Key)
.Select(l => l.First().Values)
.ToArray();
You might have to use ToList() after Distinct():
List<string[]> distinct = data.Distinct().ToList();

Categories

Resources