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;
}
}
}
Related
I have a folder with multiple files with format NameIndex.Index. I need a 2D array returned with Linq, where Name is my string and the two indices (both only 1 char) are the 2D array indices.
Example: Head4.2 will be in my returned array on the [4,2] position.
File[,] files = arrayWithAllFiles.Select(f => f.name.StartsWith(partName))
. // dont know how to continue
/*
result would look like, where the element is the File (not it's name):
| Head0.0 Head0.1 Head0.2 ... |
| Head1.0 Head1.1 Head1.2 ... |
*/
PS: could it be also done to check for indices bigger than 9?
You can use Regex to parse the file names, that way you don't have to worry about the numbers being bigger than 9. The numbers can have more than one digit.
For example,
using System.Text.RegularExpressions;
/*
Pattern below means - one or more non-digit characters, then one or more digits,
then a period, then one or more digits. Each segment inside the parentheses will be one
item in the split array.
*/
string pattern = #"^(\D+)(\d+)\.(\d+)$";
string name = "Head12.34";
var words = Regex.Split(name, pattern);
// Now words is an array of strings - ["Head", "12", "34"]
So for your example you could try:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
string pattern = #"^(\D+)(\d+)\.(\d+)$";
var indices = files.Select(f =>
Regex.Split(f.name, pattern).Skip(1).Select(w => System.Convert.ToInt32(w)).ToArray()).ToArray();
// Getting the max indices in each direction. This decides the dimensions of the array.
int iMax = indices.Select(p => p[0]).Max();
int jMax = indices.Select(p => p[1]).Max();
var pairs = Enumerable.Zip(indices, files, (index, file) => (index, file));
File[,] files = new File[iMax + 1, jMax + 1];
foreach (var pair in pairs){
files[pair.index[0], pair.index[1]] = pair.file;
}
I'm not much of a regex fan, thats why I suggest the following solution, assuming your input is valid:
private static (string name, int x, int y) ParseText(string text)
{
var splitIndex = text.IndexOf(text.First(x => x >= '0' && x <= '9'));
var name = text.Substring(0, splitIndex);
var position = text.Substring(splitIndex).Split('.').Select(int.Parse).ToArray();
return (name, position[0], position[1]);
}
private static string[,] Create2DArray((string name, int x, int y)[] items)
{
var array = new string[items.Max(i => i.x) + 1, items.Max(i => i.y) + 1];
foreach (var item in items)
array[item.x, item.y] = item.name;
return array;
}
Then you can call these functions like this:
var files = new[] { "head1.0", "head4.1", "head2.3" };
var arr = Create2DArray(files.Select(ParseText).ToArray());
Below code is used to find all indices of a string that might occur only once in an array but the code isn't very fast. Does somebody know a faster and more efficient way to find unique strings in an array?
using System;
using System.Collections.Generic;
using System.Linq;
public static class EM
{
// Extension method, using Linq to find indices.
public static int[] FindAllIndicesOf<T>(this IEnumerable<T> values, T val)
{
return values.Select((b,i) => Equals(b, val) ? i : -1).Where(i => i != -1).ToArray();
}
}
public class Program
{
public static string FindFirstUniqueName(string[] names)
{
var results = new List<string>();
for (var i = 0; i < names.Length; i++)
{
var matchedIndices = names.FindAllIndicesOf(names[i]);
if (matchedIndices.Length == 1)
{
results.Add(names[matchedIndices[0]]);
break;
}
}
return results.Count > 0 ? results[0] : null;
}
public static void Main(string[] args)
{
Console.WriteLine("Found: " + FindFirstUniqueName(new[]
{
"James",
"Bill",
"Helen",
"Bill",
"Helen",
"Giles",
"James",
}
));
}
}
Your solution has O(n^2) complexity. You can improve it to O(n) by using Hash-Map.
Consider a Hash-Map with which in each name has it number of recurrences in your original list. Now all you have to do is check all key in the dictionary (aka hash-map) and return all that equal to 1. Notice that check all key in this dictionary is less then o(n) because it can not hold more then n names.
To implement this dictionary in C# you do as follow:
List<string> stuff = new List<string>();
var groups = stuff.GroupBy(s => s).Select(
s => new { Stuff = s.Key, Count = s.Count() });
var dictionary = groups.ToDictionary(g => g.Stuff, g => g.Count);
Taken from here or as suggested by juharr
O(n) is the minimum require as you will have to go over all names at least once.
I am trying to get use 3 foreach loops to get single values from powershell and add them in a row. My code is as follows:
string listDomains = "Get-MsolDomain";
string getLicenseInfo = "Get-MsolAccountSku";
string domainsInfo = executeCommand.runAzurePowerShellModule(listDomains, "Name");
string availableLicensesTypes = executeCommand.runAzurePowerShellModule(getLicenseInfo, "AccountSkuId");
string totalLicenses = executeCommand.runAzurePowerShellModule(getLicenseInfo, "ActiveUnits");
string consumedLicenses = executeCommand.runAzurePowerShellModule(getLicenseInfo, "ConsumedUnits");
List<string> licenseTypeArray = new List<string>();
List<string> totalLicenseArray = new List<string>();
List<string> consumedLicenseArray = new List<string>();
foreach (string s in Regex.Split(availableLicensesTypes, "\n"))
{
licenseTypeArray.Add(s);
}
foreach (string v in Regex.Split(totalLicenses, "\n"))
{
totalLicenseArray.Add(v);
}
foreach (string t in Regex.Split(consumedLicenses, "\n"))
{
consumedLicenseArray.Add(t);
}
I was wondering if this can even be done. I was looking at concatenating the three lists and then splitting them but I could not think of anything after concatenating the strings. Any help would be really appreciated.
It is difficult to understand what you're asking here. I think you are saying that you have three sequences:
var s1 = "a b c".Split(' ');
var s2 = "d e f".Split(' ');
var s3 = "g h i".Split(' ');
And you wish to concatenate them "vertically".
You need a zip-concat operation:
public static IEnumerable<string> ZipConcat(IEnumerable<string> xs, IEnumerable<string> ys)
{
return xs.Zip(ys, (x, y) => x + y);
}
And now your problem is easy:
var s4 = ZipConcat(ZipConcat(s1, s2), s3);
foreach(var s in s4)
Console.WriteLine(s);
Produces:
adg
beh
cfi
You could use the AddRange instead of iterating over the items:
licenseTypeArray.AddRange(Regex.Split(availableLicensesTypes, "\n"));
totalLicenseArray.AddRange(Regex.Split(totalLicenses, "\n"));
consumedLicenseArray.AddRange(Regex.Split(consumedLicenses, "\n"));
I hope this is what you meant with easier way. If not please elaborate your question.
You can use this code to avoid using three for loops and shorten the lines of code
licenseTypeArray = Regex.Split(availableLicensesTypes, "\n").ToList();
totalLicenseArray = Regex.Split(totalLicenses, "\n").ToList();
consumedLicenseArray = Regex.Split(consumedLicenses, "\n").ToList();
For displaying this data in a GridView or so, I think you will need just two columns, the first column to list all the license names, the second column to show if it is available or consumed (Boolean value [true or false]).
OK, fine. Please try the following code. First of all define a new entity class like the following.
public class LicenseInfo
{
public string LicenseType { get; set; }
public int TotalLicenesesCount { get; set; }
public int ConsumedLicensesCount { get; set; }
}
Then use the following code:
List<string>licenseTypeArray = Regex.Split(availableLicensesTypes, "\n").ToList();
List<string> totalLicenseArray = Regex.Split(totalLicenses, "\n").ToList();
List<string> consumedLicenseArray = Regex.Split(consumedLicenses, "\n").ToList();
//A generic list of the new entity class that wraps the three properties (columns)
List<LicenseInfo> licensesList = new List<LicenseInfo>();
//concat zip the three lists with a comma-separated for each entry in the new list with this pattern ("License Type, Total Count, Consumed Count").
//Example("Entrprise License,200,50")
List<string> licensesConcatenatedList = licenseTypeArray.Zip(totalLicenseArray.Zip(consumedLicenseArray,
(x, y) => x +","+ y),
(x1,y1) => x1 + "," + y1).ToList();
licensesConcatenatedList.ForEach(t => licensesList.Add(new LicenseInfo
{
LicenseType = t.Split(new char[] { ',' })[0],
TotalLicenesesCount = int.Parse(t.Split(new char[] { ',' })[1]),
ConsumedLicensesCount = int.Parse(t.Split(new char[] { ',' })[2])
}));
Now you have your data in one list of entities that wrap all information you want to display in the gridview, then just bind this new list to the gridview as a data source as usual. You will use the names of the properties as the field names in your GridView.
I hope the idea is clear. Please mark it as answer if this helped you.
Here is a complete code to handle this. I have tested the code and it is working fine. Just copy this code in any aspx page to test. It displays sample data in a gridview after doing the data transformation you're looking for.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace SampleTest1
{
public partial class _Default : Page
{
protected void Page_Load(object sender, EventArgs e)
{
List<string> licenseTypeArray = new List<string>() { "Type 1", "Type 2", "Type 3" };
List<string> totalLicenseArray = new List<string>() { "100", "200", "300" };
List<string> consumedLicenseArray = new List<string>() { "50", "100", "150" };
//A generic list of the new entity class that wraps the three properties (columns)
List<LicenseInfo> licensesList = new List<LicenseInfo>();
//concat zip the three lists with a comma-separated for each entry in the new list with this pattern ("License Type, Total Count, Consumed Count").
//Example("Entrprise License,200,50")
List<string> licensesConcatenatedList = licenseTypeArray.Zip(totalLicenseArray.Zip(consumedLicenseArray,
(x, y) => x + "," + y),
(x1, y1) => x1 + "," + y1).ToList();
licensesConcatenatedList.ForEach(t => licensesList.Add(new LicenseInfo
{
LicenseType = t.Split(new char[] { ',' })[0],
TotalLicenesesCount = int.Parse(t.Split(new char[] { ',' })[1]),
ConsumedLicensesCount = int.Parse(t.Split(new char[] { ',' })[2])
}));
GridView1.DataSource = licensesList;
GridView1.DataBind();
}
}
class LicenseInfo
{
public string LicenseType { get; set; }
public int TotalLicenesesCount { get; set; }
public int ConsumedLicensesCount { get; set; }
}
}
I hope this will help you.
String Array 1: (In this format: <MENU>|<Not Served?>|<Alternate item served>)
Burger|True|Sandwich
Pizza|True|Hot Dog
String Array 2: (Contains Menu)
Burger
Pizza
Grill Chicken
Pasta
I need the menu is served or any alternate item served for that particular item.
Code:
for(int i = 0; i < strArr2.Length; i++)
{
if(strArr2.Any(_r => _r.Split('|').Any(_rS => _rS.Contains(strArr1[i]))))
{
var menu = strArr2[i];
var alternate = ? // need to get alternate item
}
}
As I commented in the code, how to get the alternate item in that string array? Please help, thanks in advance.
P.S: Any help to trim if condition is also gladly welcome.
Instead of any, you may use Where to get the value matching.
#Markus is having the detailed answer, I am just using your code to find a quick fix for you.
for(int i = 0; i < strArr2.Length; i++)
{
if(strArr2.Any(_r => _r.Split('|').Any(_rS => _rS.Contains(strArr1[i]))))
{
var menu = strArr2[i];
var alternate = strArr2.Where(_rs => _rs.Split('|').Any(_rS => _rS.Contains(strArr1[i]))).First().Split('|').Last();
}
}
In order to simplify your code, it is a good idea to better separate the tasks. For instance, it will be much easier to handle the contents of string array 1 after you have converted the contents into objects, e.g.
class NotServedMenu
{
public string Menu { get; set; }
public bool NotServed { get; set; }
public string AlternateMenu { get; set; }
}
Instead of having an array of strings, you can read the strings to a list first:
private IEnumerable<NotServedMenu> NotServedMenusFromStrings(IEnumerable<string> strings)
{
return (from x in strings select ParseNotServedMenuFromString(x)).ToArray();
}
private NotServedMenu ParseNotServedMenuFromString(string str)
{
var parts = str.Split('|');
// Validate
if (parts.Length != 3)
throw new ArgumentException(string.Format("Unable to parse \"{0}\" to an object of type {1}", str, typeof(NotServedMenu).FullName));
bool notServedVal;
if (!bool.TryParse(parts[1], out notServedVal))
throw new ArgumentException(string.Format("Unable to read bool value from \"{0}\" in string \"{1}\".", parts[1], str));
// Create object
return new NotServedMenu() { Menu = parts[0],
NotServed = notServedVal,
AlternateMenu = parts[2] };
}
Once you can use the objects, the subsequent code will be much cleaner to read:
var notServedMenusStr = new[]
{
"Burger|True|Sandwich",
"Pizza|True|Hot Dog"
};
var notServedMenus = NotServedMenusFromStrings(notServedMenusStr);
var menus = new[]
{
"Burger",
"Pizza",
"Grill Chicken",
"Pasta"
};
var alternateMenus = (from m in menus join n in notServedMenus on m equals n.Menu select n);
foreach(var m in alternateMenus)
Console.WriteLine("{0}, {1}, {2}", m.Menu, m.NotServed, m.AlternateMenu);
In this sample, I've used a Linq join to find the matching items.
You could do something like that
string[] strArr1 = { "Burger|True|Sandwich", "Pizza|True|Hot Dog" };
string[] strArr2 = { "Burger", "Pizza", "Grill Chicken", "Pasta" };
foreach (string str2 in strArr2)
{
string str1 = strArr1.FirstOrDefault(str => str.Contains(str2));
if (str1 != null)
{
string[] splited = str1.Split('|');
string first = splited[0];
bool condition = Convert.ToBoolean(splited[1]);
string second = splited[2];
}
}
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();
}