Finding filenames containing version numbers in C# - c#

I have file names with version numbers embedded, similar to NuGet's naming scheme. Examples:
A.B.C.1.2.3.4.zip
A.B.C.1.2.3.5.zip
A.B.C.3.4.5.dll
A.B.C.1.2.3.6.zip
A.B.C.1.2.3.dll
X.Y.Z.7.8.9.0.zip
X.Y.Z.7.8.9.1.zip
Given a pattern "A.B.C.1.2.3", how do I find all those files and directories that match, regardless of version number? I support both major.minor.build.revision and major.minor.build schemes.
That is, given "A.B.C.1.2.3", return the following list:
A.B.C.1.2.3.4.zip
A.B.C.1.2.3.5.zip
A.B.C.1.2.3.6.zip
A.B.C.1.2.3.dll
A.B.C.3.4.5.dll
Bonus points for determining which file name has the highest version.

If you know the filenames end with the version, you could Split the filename string on .. Then iterate backwards from the end (skipping the extension) and stop on the first non-numeric string. (TryParse is probably good for this.) Then you can string.Join the remaining parts and you have the package name.
Do this for the search term to find the package name, then each file in the directory, and you can compare just the package names.

Credits to jdwweng for his answer as well as 31eee384 for his thoughts. This answer basically combines both ideas.
First, you can create a custom class like so:
class CustomFile
{
public string FileName { get; private set; }
public Version FileVersion { get; private set; }
public CustomFile(string file)
{
var split = file.Split(".".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
int versionIndex;
int temp;
for (int i = split.Length - 2; i >= 0; i--)
{
if (!Int32.TryParse(split[i], out temp))
{
versionIndex = i+1;
break;
}
}
FileName = string.Join(".", split, 0, versionIndex);
FileVersion = Version.Parse(string.Join(".", split, versionIndex, split.Length - versionIndex - 1));
}
}
Using it to parse the filename, you can then filter based on it.
string[] input = new string[] {
"A.B.C.D.1.2.3.4.zip",
"A.B.C.1.2.3.5.zip",
"A.B.C.3.4.5.dll",
"A.B.C.1.2.3.6.zip",
"A.B.C.1.2.3.dll",
"X.Y.Z.7.8.9.0.zip",
"X.Y.Z.7.8.9.1.zip"
};
var parsed = input.Select(x => new CustomFile(x));
var results = parsed
.Where(cf => cf.FileName == "A.B.C")
.OrderByDescending(cf=>cf.FileVersion)
.ToList();
In this example, the first element would have the highest version.

Try this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string[] input = new string[] {
"A.B.C.1.2.3.4.zip",
"A.B.C.1.2.3.5.zip",
"A.B.C.3.4.5.dll",
"A.B.C.1.2.3.6.zip",
"A.B.C.1.2.3.dll",
"X.Y.Z.7.8.9.0.zip",
"X.Y.Z.7.8.9.1.zip"
};
var parsed = input.Select(x => x.Split(new char[] { '.' }))
.Select(y => new
{
name = string.Join(".", new string[] { y[0], y[1], y[2] }),
ext = y[y.Count() - 1],
major = int.Parse(y[3]),
minor = int.Parse(y[4]),
build = int.Parse(y[5]),
revision = y.Count() == 7 ? (int?)null : int.Parse(y[6])
}).ToList();
var results = parsed.Where(x => (x.major >= 1) && (x.major <= 3)).ToList();
var dict = parsed.GroupBy(x => x.name, y => y)
.ToDictionary(x => x.Key, y => y.ToList());
var abc = dict["A.B.C"];
}
}
}
​
​

you can use new Version() to compare versions like this:
List<string> fileNames = new List<string>();
fileNnames.AddRange(new[] {
"A.B.C.1.2.3.4.zip",
"A.B.C.1.2.3.5.zip",
"A.B.C.3.4.5.dll",
"A.B.C.1.2.3.6.zip",
"A.B.C.1.2.3.dll",
"X.Y.Z.7.8.9.0.zip",
"X.Y.Z.7.8.9.1.zip" });
string filter = "a.b.c";
var files = fileNames
//Filter the filenames that start with your filter
.Where(f => f
.StartsWith(filter, StringComparison.InvariantCultureIgnoreCase)
)
//retrieve the version number and create a new version element to order by
.OrderBy(f =>
new Version(
f.Substring(filter.Length + 1, f.Length - filter.Length - 5)
)
);

Try to use regular expression like in example below
var firstPart = Console.ReadLine();
var names = new List<string>
{
"A.B.C.1.2.3.4.zip",
"A.B.C.1.2.3.5.zip",
"A.B.C.1.2.3.6.zip",
"A.B.C.1.2.3.dll",
"X.Y.Z.7.8.9.0.zip",
"X.Y.Z.7.8.9.1.zip"
};
var versionRegexp = new Regex("^" + firstPart + "\\.([\\d]+\\.){1}([\\d]+\\.){1}([\\d]+\\.){1}([\\d]+\\.)?[\\w\\d]+$");
foreach (var name in names)
{
if (versionRegexp.IsMatch(name))
{
Console.WriteLine(name);
foreach (Group group in versionRegexp.Match(name).Groups)
{
Console.WriteLine("Index {0}: {1}", group.Index, group.Value);
}
}
}
Console.ReadKey();

This works using only LINQ, assuming the file name itself doesn't end with a digit:
List<string> names = new List<string> { "A.B.C.1.2.3.4.zip",
"A.B.C.1.2.3.5.zip",
"A.B.C.3.4.5.dll",
"A.B.C.1.2.3.6.zip" ,
"A.B.C.1.2.3.dll",
"X.Y.Z.7.8.9.0.zip",
"X.Y.Z.7.8.9.1.zip" };
var groupedFileNames = names.GroupBy(file => new string(Path.GetFileNameWithoutExtension(file)
.Reverse()
.SkipWhile(c => Char.IsDigit(c) || c == '.')
.Reverse().ToArray()));
foreach (var g in groupedFileNames)
{
Console.WriteLine(g.Key);
foreach (var file in g)
Console.WriteLine(" " + file);
}

First of all I think you can use Version class for comparison.
I believe function below can get you versions starting with certain name.
It matches the starting name then performs a non greedy search until a dot and digit followed by 2 or 3 dot digit pair and any character after.
public static List<Version> GetLibraryVersions(List<string> files, string Name)
{
string regexPattern = String.Format(#"\A{0}(?:.*?)(?:\.)(\d+(?:\.\d+){{2,3}})(?:\.)(?:.*)\Z", Regex.Escape(Name));
Regex regex = new Regex(regexPattern);
return files.Where(f => regex.Match(f).Success).
Select(f => new Version(regex.Match(f).Groups[1].Value)).ToList();
}

Related

C#: How to split a string with a changing prefix

Hello I looked at several post about this topics but no answer could help me.
I extract data about various machines which look like this:
"time, M1.A, M1.B, M1.C, M2.A, M2.B, M2.C, M3.A, M3.B, M3.C"
M1 is the prefix which specifies which machine. A,B,C are attributes of this machine like temperature, pressure, etc.
The output should then look like this:
{{"time", "M1.A", "M1.B", "M1.C"}, {"time", "M2.A",....}}
I know that I could possibly split at "," and then create the list but I was wondering if there is another way to detect if the prefix changed.
Regex.Matches(myList, #"M(?<digit>\d+)\..") //find all M1.A etc
.Cast<Match>() //convert the resulting list to an enumerable of Match
.GroupBy(m => m.Groups["digit"].Value) //find the groups with the same digits
.Select(g => new[] { "time" }.Union(g.Select(m => m.Value)).ToArray());
//combine the groups into arrays beginning with "time"
You mention "the output should then look like this...", but then you mention a list, so I'm going to assume that you mean to make the original string into a list of lists of strings.
List<string> split = new List<string>(s.Split(','));
string first = split[0];
split.RemoveAt(0);
List<List<string>> result = new List<List<string>>();
foreach (var dist in split.Select(o => o.Split('.')[0]).Distinct())
{
List<string> temp = new List<string> {first};
temp.AddRange(split.Where(o => o.StartsWith(dist)));
result.Add(temp);
}
This does the original split, removes the first value (you didn't really specify that, I assumed), then loops around each machine. The machines are created by splitting each value further by '.' and making a distinct list. It then selects all values in the list that start with the machine and adds them with the first value to the resulting list.
Using Regex I created a dictionary :
string input = "time, M1.A, M1.B, M1.C, M2.A, M2.B, M2.C, M3.A, M3.B, M3.C";
string pattern1 = #"^(?'name'[^,]*),(?'machines'.*)";
Match match1 = Regex.Match(input, pattern1);
string name = match1.Groups["name"].Value;
string machines = match1.Groups["machines"].Value.Trim();
string pattern2 = #"\s*(?'machine'[^.]*).(?'attribute'\w+)(,|$)";
MatchCollection matches = Regex.Matches(machines, pattern2);
Dictionary<string, List<string>> dict = matches.Cast<Match>()
.GroupBy(x => x.Groups["machine"].Value, y => y.Groups["attribute"].Value)
.ToDictionary(x => x.Key, y => y.ToList());
Some quick example for you. I think is better to parse it by you own way and have string structure of your Machine-Attribute pair.
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp4 {
class Program {
static void Main(string[] args) {
string inputString = "time, M1.A, M1.B, M1.C, M2.A, M2.B, M2.C, M3.A, M3.B, M3.C";
string[] attrList = inputString.Split(',');
// 1. Get all machines with attributes
List<MachineAttribute> MachineAttributeList = new List<MachineAttribute>();
for (int i = 1; i < attrList.Length; i++) {
MachineAttributeList.Add(new MachineAttribute(attrList[i]));
}
// 2. For each machine create
foreach (var machine in MachineAttributeList.Select(x=>x.Machine).Distinct()) {
Console.Write(attrList[0]);
foreach (var attribute in MachineAttributeList.Where(x=>x.Machine == machine)) {
Console.Write(attribute + ",");
}
Console.WriteLine();
}
Console.ReadLine();
}
}
public class MachineAttribute {
public string Machine { get; }
public string Attribute { get; }
public MachineAttribute(string inputData) {
var array = inputData.Split('.');
if (array.Length > 0) Machine = array[0];
if (array.Length > 1) Attribute = array[1];
}
public override string ToString() {
return Machine + "." + Attribute;
}
}
}

Make group from list string

I have one List as below:
var paths = new List<string> {
#"rootuploaded\samplefolder\1232_234234_1.jpg",
#"rootuploaded\samplefolder\1232_2342.jpg",
#"rootuploaded\samplefolder\subfolder\1232_234234_1.jpg",
#"rootuploaded\samplefolder\subfolder\1232_2342.jpg",
#"rootuploaded\file-­5.txt",
#"rootuploaded\file­-67.txt",
#"rootuploaded\file­-a.txt",
#"rootuploaded\file1.txt",
#"rootuploaded\file5.txt",
#"rootuploaded\filea.txt",
#"rootuploaded\text.txt",
#"rootuploaded\file_sample_a.txt",
#"rootuploaded\file2.txt",
#"rootuploaded\file_sample.txt",
#"rootuploaded\samplefolder\1232_234234_2.bmp",
};
How to print output like this:
○ Group 1
rootuploaded\samplefolder\1232_234234_1.jpg,
rootuploaded\samplefolder\1232_234234_2.bmp
○ Group 2
rootuploaded\file1.txt
rootuploaded\file2.txt
rootuploaded\file5.txt
○ Group 3
rootuploaded\file-5.txt
rootuploaded\file-67.txt
○ Group 4
rootuploaded\file_sample.txt
rootuploaded\file_sample_a.txt
○ Cannot grouped
rootuploaded\samplefolder\1232_2342.jpg
rootuploaded\file-a.txt
rootuploaded\filea.txt
rootuploaded\text.txt
Grouping files based on 6 naming conventions (with top¬down priority):
FileName.ext, FileName_anything.ext, FileName_anythingelse.ext, ...
FileName.ext, FileName-anything.ext, FileName-anythingelse.ext, ...
FileName_1.ext, FileName_2.ext, ..., FileName_N.ext (maybe not continuous)
FileName-1.ext, FileName-2.ext, ..., FileName-N.ext (maybe not continuous)
FileName 1.ext, FileName 2.ext, ..., FileName N.ext (maybe not continuous)
FileName1.ext, FileName2.ext, ..., FileNameN.ext (maybe not continuous)
I used Linq to separate:
var groups1 = paths.GroupBy(GetFileName, (key, g) => new
{
key = key,
count = g.Count(),
path = g.ToList()
}).Where(x => x.count < 5 && x.count >= 2).ToList();
public string GetFileName(string fileName)
{
var index = 0;
if (fileName.Contains("_"))
index = fileName.IndexOf("_", StringComparison.Ordinal);
else if (fileName.Contains("-"))
index = fileName.IndexOf("-", StringComparison.Ordinal);
var result = fileName.Substring(0, index);
return result;
}
Try doing this:
var groups = new []
{
new { regex = #"rootuploaded\\samplefolder\\1232_234234_\d\..{3}", grp = 1 },
new { regex = #"rootuploaded\\file\d\.txt", grp = 2 },
new { regex = #"rootuploaded\\file-\d+\.txt", grp = 3 },
new { regex = #"rootuploaded\\file_sample.*\.txt", grp = 4 },
};
var results =
from path in paths
group path by
groups
.Where(x => Regex.IsMatch(path, x.regex))
.Select(x => x.grp)
.DefaultIfEmpty(99)
.First()
into gpaths
orderby gpaths.Key
select new
{
Group = gpaths.Key,
Files = gpaths.ToArray(),
};
That gives you this:
You would just have to jig around with the regex until you get exactly what you want.
Sadly, 1. and 2. group turn this solution difficult. Cause both contain 'FileName.ext', so it has to check whole list together :(
I try to separate groupping 1. 2. and 3 - 6:
First step:
Find and remove Group 1 and 2 candidates.
It orders the list base on file path:
var orderedFilenames = pathsDistinct().OrderBy(p => p).ToList();
Than find Group 1 and 2 candidates:
var groupped = orderedFilenames.GroupBy(s => GetStarterFileName(s, orderedFilenames));
private static string GetStarterFileName(string fileNameMatcher, List<string> orderedFilenames)
{
string fileNameMatcherWOExt = Path.GetFileNameWithoutExtension(fileNameMatcher);
return orderedFilenames.FirstOrDefault(p =>
{
if (p == fileNameMatcher) return true;
string p_directory = Path.GetDirectoryName(p);
string directory = Path.GetDirectoryName(fileNameMatcher);
if (p_directory != directory) return false;
string pure = Path.GetFileNameWithoutExtension(p);
if (!fileNameMatcherWOExt.StartsWith(pure)) return false;
if (fileNameMatcherWOExt.Length <= pure.Length) return false;
char separator = fileNameMatcherWOExt[pure.Length];
if (separator != '_' && separator != '-') return false;
return true;
});
}
Step two:
After first step, you got Group 1 and 2 candidates, but all others are separated into different groups.
Collect remaining path and separete group 1 and 2:
var mergedGroupps = groupped.Where(grp => grp.Count() == 1).SelectMany(grp => grp);
var starterFileNameGroups = groupped.Where(grp => grp.Count() > 1);
Step three
Now you could find 3-6 based on regex validation:
var endWithNumbersGroups = mergedGroupps.GroupBy(s => GetEndWithNumber(s));
private static string GetEndWithNumber(string fileNameMatcher)
{
string fileNameWithoutExtesion = Path.Combine(Path.GetDirectoryName(fileNameMatcher), Path.GetFileNameWithoutExtension(fileNameMatcher));
string filename = null;
filename = CheckWithRegex(#"_(\d+)$", fileNameWithoutExtesion, 1);
if (filename != null) return filename;
filename = CheckWithRegex(#"-(\d+)$", fileNameWithoutExtesion, 1);
if (filename != null) return filename;
filename = CheckWithRegex(#" (\d+)$", fileNameWithoutExtesion, 1);
if (filename != null) return filename;
filename = CheckWithRegex(#"(\d+)$", fileNameWithoutExtesion);
if (filename != null) return filename;
return fileNameWithoutExtesion;
}
private static string CheckWithRegex(string p, string filename, int additionalCharLength = 0)
{
Regex regex = new Regex(p, RegexOptions.Compiled | RegexOptions.CultureInvariant);
Match match = regex.Match(filename);
if (match.Success)
return filename.Substring(0, filename.Length - (match.Groups[0].Length - additionalCharLength));
return null;
}
Final Step:
Collect non groupped items and merge Group 1-2 and 3-6 candidates
var nonGroupped = endWithNumbersGroups.Where(grp => grp.Count() == 1).SelectMany(grp => grp);
endWithNumbersGroups = endWithNumbersGroups.Where(grp => grp.Count() > 1);
var result = starterFileNameGroups.Concat(endWithNumbersGroups);
You could try to solve both step in one shot, but as you see groupping mechanism are different. My solution is not so beautiful, but I think it's clear... maybe :)

How to find maximum number of repeated string in a string in a list of string in c#

If we have a list of strings, then how we can find the list of strings that have the maximum number of repeated symbol by using LINQ.
List <string> mylist=new List <string>();
mylist.Add("%1");
mylist.Add("%136%250%3"); //s0
mylist.Add("%1%5%20%1%10%50%8%3"); // s1
mylist.Add("%4%255%20%1%14%50%8%4"); // s2
string symbol="%";
List <string> List_has_MAX_num_of_symbol= mylist.OrderByDescending(s => s.Length ==max_num_of(symbol)).ToList();
//the result should be a list of s1 + s2 since they have **8** repeated '%'
I tried
var longest = mylist.Where(s => s.Length == mylist.Max(m => m.Length)) ;
this gives me only one string not both
Here's a very simple solution, but not exactly efficient. Every element has the Count operation performed twice...
List<string> mylist = new List<string>();
mylist.Add("%1");
mylist.Add("%136%250%3"); //s0
mylist.Add("%1%5%20%1%10%50%8%3"); // s1
mylist.Add("%4%255%20%1%14%50%8%4"); // s2
char symbol = '%';
var maxRepeat = mylist.Max(item => item.Count(c => c == symbol));
var longest = mylist.Where(item => item.Count(c => c == symbol) == maxRepeat);
It will return 2 strings:
"%1%5%20%1%10%50%8%3"
"%4%255%20%1%14%50%8%4"
Here is an implementation that depends upon SortedDictionary<,> to get what you're after.
var mylist = new List<string> {"%1", "%136%250%3", "%1%5%20%1%10%50%8%3", "%4%255%20%1%14%50%8%4"};
var mappedValues = new SortedDictionary<int, IList<string>>();
mylist.ForEach(str =>
{
var count = str.Count(c => c == '%');
if (mappedValues.ContainsKey(count))
{
mappedValues[count].Add(str);
}
else
{
mappedValues[count] = new List<string> { str };
}
});
// output to validate output
foreach (var str in mappedValues.Last().Value)
{
Console.WriteLine(str);
}
Here's one using LINQ that gets the result you're after.
var result = (from str in mylist
group str by str.Count(c => c == '%')
into g
let max = (from gKey in g select g.Key).Max()
select new
{
Count = max,
List = (from str2 in g select str2)
}).LastOrDefault();
OK, here's my answer:
char symbol = '%';
var recs = mylist.Select(s => new { Str = s, Count = s.Count(c => c == symbol) });
var maxCount = recs.Max(x => x.Count);
var longest = recs.Where(x => x.Count == maxCount).Select(x => x.Str).ToList();
It is complicated because it has three lines (the char symbol = '%'; line excluded), but it counts each string only once. EZI's answer has only two lines, but it is complicated because it counts each string twice. If you really want a one-liner, here it is:
var longest = mylist.Where(x => x.Count(c => c == symbol) == mylist.Max(y => y.Count(c => c == symbol))).ToList();
but it counts each string many times. You can choose whatever complexity you want.
We can't assume that the % is always going to be the most repeated character in your list. First, we have to determine what character appears the most in an individual string for each string.
Once we have the character and it maximum occurrence, we can apply Linq to the List<string> and grab the strings that contain the character equal to its max occurrence.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
List <string> mylist=new List <string>();
mylist.Add("%1");
mylist.Add("%136%250%3");
mylist.Add("%1%5%20%1%10%50%8%3");
mylist.Add("%4%255%20%1%14%50%8%4");
// Determine what character appears most in a single string in the list
char maxCharacter = ' ';
int maxCount = 0;
foreach (string item in mylist)
{
// Get the max occurrence of each character
int max = item.Max(m => item.Count(c => c == m));
if (max > maxCount)
{
maxCount = max;
// Store the character whose occurrence equals the max
maxCharacter = item.Select(c => c).Where(c => item.Count(i => i == c) == max).First();
}
}
// Print the strings containing the max character
mylist.Where(item => item.Count(c => c == maxCharacter) == maxCount)
.ToList().ForEach(Console.WriteLine);
}
}
Results:
%1%5%20%1%10%50%8%3
%4%255%20%1%14%50%8%4
Fiddle Demo
var newList = myList.maxBy(x=>x.Count(y=>y.Equals('%'))).ToList();
This should work. Please correct syntax if wrong anywhere and update here too if it works for you.

Implementing method in a functional style

I have the following method:
private List<string> CreateSegments(string virtualPath)
{
List<string> segments = new List<string>();
int i = virtualPath.IndexOf('/', 1);
while (i >= 0 && i < virtualPath.Length)
{
var segment = virtualPath.Substring(0, i);
if (!string.IsNullOrWhiteSpace(segment))
{
segments.Add(segment);
segments.Add(VirtualPathUtility.Combine(segment, "default"));
}
i = virtualPath.IndexOf('/', i + 1);
}
segments.Add(virtualPath);
segments.Add(VirtualPathUtility.Combine(virtualPath, "default"));
return segments;
}
Basically, it creates path segments which I will use to check if a file exists in any of those segments. Like this:
string[] extensions = GetRegisteredExtensions();
HttpServerUtilityBase server = HttpContext.Current.Server;
List<string> segments = CreateSegments(virtualPath);
// check if a file exists with any of the registered extensions
var match = extensions.SelectMany(x => segments.Select(s => string.Format("{0}.{1}", s, x)))
.FirstOrDefault(p => System.IO.File.Exists(server.MapPath(p)));
All the above code looks like it could use some clean up and optimization, but I'm looking for a way to use LINQ if possible to generate the segments.
Something like: var segments = virtualPath.Split('/').SelectMany(...) and get a result similar to the following:
/path
/path/default
/path/to
/path/to/default
/path/to/file
/path/to/file/default
Where virtualPath would contain the value "/path/to/file"
EDIT: Changed string.Format("{0}/{1}", ...) to VirtualPathUtility.Combine(..., ...)
Any ideas?
One way would be to incrementally select the path segments, then "join" it with an empty string and "/default" to get the two variations:
string path = #"/path/to/file";
string temp = "";
var query = path.Split('/')
.Where(s => !string.IsNullOrEmpty(s))
.Select((p) => {temp += ("/" + p); return temp;} )
.SelectMany(s => new[]{"","/default"}.Select (d => s + d) );
If you first define an extension method like this:
public static IEnumerable<int> SplitIndexes(this string subject, char search)
{
for(var i = 1; i < subject.Length; i++)
{
if(subject[i] == search)
{
yield return i;
}
}
yield return subject.Length;
}
Then you could do this:
var endings = new string[] { string.Empty, "/default" };
var virtualPath = "/path/to/file";
var results =
from i in virtualPath.SplitIndexes('/')
from e in endings
select virtualPath.Substring(0, i) + e;
Or if you prefer query syntax:
var endings = new string[] { string.Empty, "/default" };
var virtualPath = "/path/to/file";
var results = virtualPath.SplitIndexes('/')
.SelectMany(i => endings.Select(e => virtualPath.Substring(0, i) + e));
The result will be:
/path
/path/default
/path/to
/path/to/default
/path/to/file
/path/to/file/default
As others have suggested, you can this in a more platform independent way by using Path.Combine, like this:
var endings = new string[] { string.Empty, "default" }; // Note: no / before default
var results =
from i in virtualPath.SplitIndexes(Path.DirectorySeparatorChar)
from e in endings
select Path.Combine(virtualPath.Substring(0, i), e);
This might do the trick. It is not the most succinct code but it seems very readable to me. It uses string concatenation because for short strings, like paths or URLs, it is faster than any of the alternatives.
Edit: fixed and tested.
var query = path.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries)
.Aggregate(new List<string>(), (memo, segment) => {
memo.Add(memo.DefaultIfEmpty("").Last() + "/" + segment);
return memo;
}).Aggregate(new List<string>(), (memo, p) => {
memo.Add(p);
memo.Add(p + "/default");
return memo;
});
The higher-order function you're looking for is called scan. There is no such function in normal LINQ, but you can find it in MoreLinq. Using that, your code could look like this:
private List<string> CreateSegments(string virtualPath)
{
return virtualPath.Split('/')
.Scan((s1, s2) => s1 + '/' + s2)
.Skip(1)
.SelectMany(p => new[] { p, p + "/default" })
.ToList();
}
This assumes your path will be always an absolute path starting with a /. For relative paths, you will need to remove the .Skip(1) part.
If you don't want to get MoreLinq just for this one method, you can just copy its source into your project.
The provided answers so far are more succinct, but this is what I came up with:
public static IEnumerable<string> PossiblePaths(string basePath)
{
return PossiblePaths(basePath.Split(new[] { "/" },
StringSplitOptions.RemoveEmptyEntries));
}
private static IEnumerable<string> PossiblePaths(IEnumerable<string> segments,
string current = "/")
{
if (segments.Count() == 0)
{
return new string[0];
}
else
{
string next = current + segments.First();
return new[] { next, next + "/default" }
.Concat(PossiblePaths(segments.Skip(1), next + "/"));
}
}
Something like this:
public static IEnumerable<string> EnumerateSegments( this IEnumerable<string> segments )
{
StringBuilder sb = new StringBuilder() ;
foreach ( string segment in segements )
{
sb.Append( Path.DirectorySeparatorChar ).Append( segment ) ;
yield return sb.ToString() ;
int n = sb.Length ;
sb.Append( Path.DirectorySeparatorChar ).Append("default") ;
yield return sb.ToString() ;
sb.Length = n ;
}
}
ought to do you.

C# Combining lines

Hey everybody, this is what I have going on. I have two text files. Umm lets call one A.txt and B.txt.
A.txt is a config file that contains a bunch of folder names, only 1 listing per folder.
B.txt is a directory listing that contains folders names and sizes. But B contains a bunch of listing not just 1 entry.
What I need is if B, contains A. Take all lines in B that contain A and write it out as A|B|B|B ect....
So example:
A.txt:
Apple
Orange
Pear
XBSj
HEROE
B.txt:
Apple|3123123
Apple|3434
Orange|99999999
Orange|1234544
Pear|11
Pear|12
XBSJ|43949
XBSJ|43933
Result.txt:
Apple|3123123|3434
Orange|99999999|1234544
Pear|11|12
XBSJ|43949|43933
This is what I had but it's not really doing what I needed.
string[] combineconfig = File.ReadAllLines(#"C:\a.txt");
foreach (string ccline in combineconfig)
{
string[] readlines = File.ReadAllLines(#"C:\b.txt");
if (readlines.Contains(ccline))
{
foreach (string rdlines in readlines)
{
string[] pslines = rdlines.Split('|');
File.AppendAllText(#"C:\result.txt", ccline + '|' + pslines[0]);
}
}
I know realize it's not going to find the first "if" because it reads the entire line and cant find it. But i still believe my output file will not contain what I need.
Assuming you're using .NET 3.5 (so can use LINQ), try this:
string[] configLines = File.ReadAllLines("a.txt");
var dataLines = from line in File.ReadAllLines("b.txt")
let split = line.Split('|')
select new { Key = split[0], Value = split[1] };
var lookup = dataLines.ToLookup(x => x.Key, x => x.Value);
using (TextWriter writer = File.CreateText("result.txt"))
{
foreach (string key in configLines)
{
string[] values = lookup[key].ToArray();
if (values.Length > 0)
{
writer.WriteLine("{0}|{1}", key, string.Join("|", values));
}
}
}
var a = new HashSet<string>(File.ReadAllLines(#"a.txt")
.SelectMany(line => line.Split(' ')),
StringComparer.CurrentCultureIgnoreCase);
var c = File.ReadAllLines(#"b.txt")
.Select(line => line.Split('|'))
.GroupBy(item => item[0], item => item[1])
.Where(group => a.Contains(group.Key))
.Select(group => group.Key + "|" + string.Join("|", group.ToArray()))
.ToArray();
File.WriteAllLines("result.txt", c);
Output:
Apple|3123123|3434
Orange|99999999|1234544
Pear|11|12
XBSJ|43949|43933
A short one :
var a = File.ReadAllLines("A.txt");
var b = File.ReadAllLines("B.txt");
var query =
from bline in b
let parts = bline.Split('|')
group parts[1] by parts[0] into bg
join aline in a on bg.Key equals aline
select aline + "|" + string.Join("|", bg.ToArray());
File.WriteAllLines("result.txt", query.ToArray());
This should work:
using System;
using System.Linq;
using System.IO;
using System.Globalization;
namespace SO2593168
{
class Program
{
static void Main(string[] args)
{
var a = File.ReadAllLines("A.txt");
var b =
(from line in File.ReadAllLines("B.txt")
let parts = line.Split('|')
select new { key = parts[0], value = parts[1] });
var comparer = StringComparer.Create(CultureInfo.InvariantCulture, true);
var result =
from key in a
from keyvalue in b
where comparer.Compare(keyvalue.key, key) == 0
group keyvalue.value by keyvalue.key into g
select new { g.Key, values = String.Join("|", g.ToArray()) };
foreach (var entry in result)
Console.Out.WriteLine(entry.Key + "|" + entry.values);
}
}
}
This produces:
Apple|3123123|3434
Orange|99999999|1234544
Pear|11|12
XBSJ|43949|43933
Code here.

Categories

Resources