How to concatenate multiple list value and generate unique name - c#

I have a list object with 3-4 item like (color, size, unit) and every list has another list like color list have multiple value (red, green, blue). I want to generate names like (red-xl-pcs)(red-xxl-pcs)(blue-xl-pcs)(blue-xxl-pcs)
My Model:
public class Attributes
{
public int AttributeId { get; set; }
public string AttributeName { get; set; }
public IEnumerable<AttributesValue> AttributesValues { get; set; }
}
public class AttributesValue
{
public int AttributeValueId { get; set; }
public string AttributeValueName { get; set; }
}
My controller:
public async Task<ActionResult> GetProductAttribute(int productId)
{
LoadSession();
var productAttributes = await _objProductDal.GetProductAttribute(productId, _strWareHouseId, _strShopId);
foreach (var attribute in productAttributes)
{
attribute.AttributesValues = await _objProductDal.GetProductAttributeValue(productId, attribute.AttributeId, _strWareHouseId, _strShopId);
}
return PartialView("_AttributeTablePartial", productAttributes);
}
My output is like this:
Now I want another list of names concatenated with all value names like:
(12/y - cotton - green), (12/y - cotton - yellow) .... it will generate 8 unique product names.
How can I do this?

Is this what you're after? Iterating over each list and combining all possibilities?
var first = new List<string> { "one", "two" };
var second = new List<string> { "middle" };
var third = new List<string> { "a", "b", "c", "d" };
var all = new List<List<string>> { first, second, third };
List<string> GetIds(List<List<string>> remaining)
{
if (remaining.Count() == 1) return remaining.First();
else
{
var current = remaining.First();
List<string> outputs = new List<string>();
List<string> ids = GetIds(remaining.Skip(1).ToList());
foreach (var cur in current)
foreach (var id in ids)
outputs.Add(cur + " - " + id);
return outputs;
}
}
var names = GetIds(all);
foreach (var name in names)
{
Console.WriteLine(name);
}
Console.Read();
results in the following:
one - middle - a
one - middle - b
one - middle - c
one - middle - d
two - middle - a
two - middle - b
two - middle - c
two - middle - d
copied and slightly adapted from Generate all Combinations from Multiple (n) Lists

Here is method that uses nested functions to stringify the objects:
public static string GetUniqueName(IEnumerable<Attributes> source)
{
return "[{" + String.Join("},{", source.Select(AttributeToString)) + "}]";
string AttributeToString(Attributes a)
{
return a.AttributeId + ":" + a.AttributeName + "[" + String.Join(",",
a.AttributesValues.Select(ValueToString)) + "]";
}
string ValueToString(AttributesValue av)
{
return av.AttributeValueId + ":" + av.AttributeValueName;
}
}
Usage example:
var productAttributes = new string[] {"Car", "Bike"}.Select((s, i) => new Attributes()
{
AttributeId = i + 1,
AttributeName = s,
AttributesValues = new AttributesValue[]
{
new AttributesValue{AttributeValueId = 1, AttributeValueName = s + "Attr1"},
new AttributesValue{AttributeValueId = 2, AttributeValueName = s + "Attr2"},
}
});
Console.WriteLine(GetUniqueName(productAttributes));
Output:
[{1:Car[1:CarAttr1,2:CarAttr2]},{2:Bike[1:BikeAttr1,2:BikeAttr2]}]

Related

How can i create an object from this single string i've split?

im trying to make a report about supplies used by connecting to the technical service database and filtering supply audits, inside audits what i care about is ActionNotes which is a single long string formatted like this:
New Supplie Mobile added: /n Id: 1/n Name: Bateria /n Stock. 0/n Minimum Stock: 10/n IdSquad: 1/n IdSupplie: 1/n
I've managed to write this code which creates an array of strings after splitting and filtering the values that i don't need and comes out something like this:
private void ImportarServicioTecnico()
{
var auditList = db3.Audit.ToList();
var suppliesList = (from t in auditList where t.ActionNotes.ToLower().Contains("new supplie mobile added") select t).ToList();
foreach (var i in suppliesList)
{
InsumosST o = new InsumosST();
var note = i.ActionNotes;
Debug.WriteLine("Audit ID: " + i.Id.ToString() + " Date: " + i.AuditDate);
string[] lines = Regex.Split(note, "/n");
foreach (var l in lines)
{
var checkstring = l.ToLower();
string actual = l;
if (checkstring.Contains("new supplie mobile added") || checkstring.Contains("description:")) {continue;}
if (checkstring.Contains("stock."))
{
int pos2 = actual.IndexOf(".");
Debug.WriteLine(actual.Substring(pos2 + 1));
continue;
}
int pos = actual.IndexOf(":");
Debug.WriteLine(actual.Substring(pos + 1));
}
}
}
Audit ID: 21 Date: 15-11-2021 10:43:59
1 Bateria 0 1 0 1 1
The question being is: is it possible to create an object from my db model with this code?
This is my model:
public partial class InsumosST
{
public int id { get; set; }
public string supply { get; set; }
public Nullable<System.DateTime> entrydate { get; set; }
public string squad { get; set; }
}
enter code here
Sure.. Let's have some dictionary of delegates that assign values to the properties of InsumosST, split the line on /n then again on : to get an id and value, look for the id in the dictionary and if it;s there, call the delegate passing the value
private void ImportarServicioTecnico()
{
var auditList = db3.Audit.ToList();
var suppliesList = (from t in auditList where t.ActionNotes.ToLower().Contains("new supplie mobile added") select t).ToList();
//make a dictionary of the string we look for and the "method" to execute if we find that string
//the methods are one-liners that set the appropriate value on the object
var d = new Dictionary<string, Action<string, InsumosST>();
d["id"] = (v, i) => i.id = int.TryParse(v, out int z) ? z : SOME_DEFAULT_ID_HERE;
d["idsupplie"] = (v, i) => i.supply = v;
d["idsquad"] = (v, i) => i.squad = v;
foreach (var i in suppliesList)
{
var o = new InsumosST();
var bits = i.ActionNotes.Split("/n");
foreach (var line in lines)
{
var bits = line.Split(new[]{':','.'}, 2); //line is e.g. "idsupplie: 1"
if(bits.Length < 2) continue; //if we don't have an id and value
var id = bits[0].ToLower(); //e.g. "idsupplie"
var val = bits[1].Trim(); //e.g. "1"
if(d.TryGetValue(id, out var m)) //if we know this id, e.g. "id", "idsupplie", "idsquad"
m(val, o); //call the delegate; it will assign val to the right property of o
}
context.InsumosSts.Add(o);
}
context.SaveChanges();
}
I suppose the hardest thing to "get" about this is if you're not really used to delegates; you've used them (conceptually where t.ActionNotes.ToLower().Contains("new supplie mobile added") is such a thing => it's a method that produces a bool from some input value determined by from) but it doesn't mean you'd be familiar with them.
Simply put, delegates are a way of storing method code in a variable just like we store data. You can assign a method to a variable, and then execute the variable to run the code:
var someMethod = () => Debug.WriteLine("hello");
someMethod(); //prints hello
You can make them take parameters:
var someMethod = (string s) => Debug.WriteLine(s);
someMethod("hello"); //prints hello
So all we did here was make up a bunch of methods that assign props to a passed-in InsumosSt
var x = (InsumosST i, string v) => i.idsupplie = v;
x(someInsumosObject, "hello"); //assigns "hello" to someInsumosObject.idsupplie property
It means to support more properties, you just add them to the InsumosST and
write a dictionary entry that assigns them:
public partial class InsumosST
{
public int id { get; set; }
public string supply { get; set; }
public Nullable<System.DateTime> entrydate { get; set; }
public string squad { get; set; }
//new!
public string Name {get;set;}
}
d["name"] = (v, i) => i.Name = v;

How to get specific parts of string and assign them to variables in c#?

passing a string like this to the code behind:
0,-1|1,-1|2,-1|3,-1|4,-1|5,-1|6,-1|7,-1|8,-1
I need to be able to asign the values before and after the "," symbol per each "|" symbol that exits in the string, into separated variables, "line" for first value (before the ",") and "group" for the next one (after the ",").
Right now I'm trying with this, but I'm having some issues with converting from string[] to string.
public static string GuardarGrupos(string parametro){
var data = parametro.Split(Convert.ToChar("|"));
var datos = "";
string[] linea;
var grupo = "";
//Iterate through each of the letters
foreach (var check in data)
{
datos = data[0];
linea = data[0].Split(Convert.ToChar(","));
}
return linea;
}
Any Idea how can I achieve this?
Make a class or struct to hold your values:
public class DataObject
{
public string X {get; set;}
public string Y {get; set;}
}
Return a List<T> of type DataObject
public static List<DataObject> GuardarGrupos(string parametro){
//List to return
List<DataObject> returnList = new List<DataObject>();
//Split the string on pipe to get each set of values
var data = parametro.Split('|'); //No need to do a convert.char(),
//String.Split has an overload that takes a character, use single quotes for character
//Iterate through each of the letters
foreach (var check in data)
{
//Split on comma to get the individual values
string[] values = check.Split(',');
DataObject do = new DataObject()
{
X = values[0];
Y = values[1];
};
returnList.Add(do);
}
return returnList;
}
Once you have the List<DataObject>, you can form line and group using linq and string.Join:
List<DataObject> myList = GuardarGrupos("0,-1|1,-1|2,-1|3,-1|4,-1|5,-1|6,-1|7,-1|8,-1");
string line = string.Join(",", myList.Select(x => x.X);
string group = string.Join(",", myList.Select(y => y.Y);
Instead of using local variables , create a Class that stores your retrieved values. then in the main you could handle/manipulate those values as required.
public class MyData
{
public string Datos { get; set; }
public string Linea { get; set; }
public string Grupo { get; set; }
}
public static List<MyData> myFunction(string parametro)
{
List<MyData> result = new List<MyData>();
var data = parametro.Split(Convert.ToChar("|"));
foreach (var check in data)
{
MyData temp = new MyData();
var line = check.Split(',');
temp.Datos = line[0];
temp.Linea = line[1];
temp.Grupo = check;
result.Add(temp);
}
return result;
}
static void Main(string[] args)
{
var t = myFunction("0,-1|1,-1|2,-1|3,-1|4,-1|5,-1|6,-1|7,-1|8,-1");
}
Here is a robust solution that's just a simple iteration over a string.
void Main()
{
var xs = "0,-1|1,-1|2,-1|3,-1|4,-1|5,-1|6,-1|7,-1|8,-1";
var stack = new Stack<Pair>();
stack.Push(new Pair());
foreach(var x in xs)
if(x == '|')
stack.Push(new UserQuery.Pair());
else
stack.Peek().Accept(x);
foreach (var x in stack.Reverse())
Console.WriteLine(x);
}
sealed class Pair
{
Action<char> _Accept;
readonly List<char> Line = new List<char>();
readonly List<char> Group = new List<char>();
public Pair()
{
_Accept = x => Line.Add(x);
}
public void Accept(char c)
{
if(c == ',')
_Accept = x => Group.Add(x);
else
_Accept(c);
}
public override string ToString()
{
return "Line:" + new string(Line.ToArray()) + " Group:" + new string(Group.ToArray());
}
}

How to group only subsequent items with the same property using linq

I have input that could look like this:
A 1 2 C,D
A 2 3 C,E
B 4 5 F
A 6 7
A 7 8 D
A 9 10 E
I store this in my model class:
public class Item {
public String Name {get;set;}
public int Start {get;set;}
public int End {get;set;}
public List<string> Orders {get;set;}
}
I tried to use Linq to merge all subsequent items if the items have the same name and generate a new item that has the start value of the first item in the group, the end value of the last item in the group and a union of all order lists. It should then look like this:
A 1 3 C,D,E
B 4 5 F
A 6 10 D, E
I tried the following Linq statement, however, it groups all As and Bs together, independent of whether there are any other items in between. What do I need to change? The union of the order list is also missing.
var groups = items.GroupBy(i => i.Name).ToList();
foreach (var group in groups)
{
result.Add(new Item {
Start = group.First().Start,
End = group.Last().End,
Name = group.First().Name });
}
Use a classic loop for this:
var List<List<Item>> groups = new List<List<Item>>()
var currentGroup = new List<Item> { items.First() };
int i = 0;
foreach(var item in items.Skip(1))
{
if(currentGroup.First().Name != item.Name)
{
groups.Add(currentGroup);
currentGroup = new List<Item> { item };
}
else
{
currentGroup.Add(item);
if(i == items.Count - 2)
groups.Add(currentGroup);
}
i++;
}
Now you can continue with your code by iterating the groups-list.
Maybe not the best or fastest way but I got bored:
int groupID = -1;
var result = items.Select((item, index) =>
{
if (index == 0 || items[index - 1].Name != item.Name)
++groupID;
return new { group = groupID, item = item };
}).GroupBy(item => item.group).Select(group =>
{
Item item = new Item();
var first = group.First().item;
var last = group.Last().item;
item.Name = first.Name;
item.Start = first.Start;
item.End = last.End;
item.Orders = group.SelectMany(g => g.item.Orders).Distinct().ToList();
return item;
});
The variable items should be your input collection like a List<Item>. The result will be stored in result. This is an IEnumerable<Item> but you may add .ToList() or .ToArray() as you like to convert it to List<Item> or Item[].
The result will contain new created items. I did this on purpose to not mess up the input data.
The trick here is to use a local variable as a group id. It is increased if it is the first item or the last item had a different name. Then we group by the group id and the rest of the code will just create the item. The SelectMany method will join all Orders-values from the entire group and Distinct will then remove all duplicates.
This is not done by Linq. I just played a bit using simpler methods. But it gives same result which you wanted.
using System;
using System.Collections.Generic;
public class Item
{
public static List<Item> Database;
static Item()
{
Database = new List<Item>();
}
public Item(string name, int start, int end, params string[] orders)
{
Name = name;
Start = start;
End = end;
Orders = new List<string>();
foreach (string s in orders)
Orders.Add(s);
//putting newly created Item to database
Database.Add(this);
}
//overload for creating tmp Items in GroupThem(), could be done using optinional parameter
public Item(bool AddToDatabase, string name, int start, int end, params string[] orders)
{
Name = name;
Start = start;
End = end;
Orders = new List<string>();
foreach (string s in orders)
Orders.Add(s);
if (AddToDatabase) Database.Add(this);
}
public string Name { get; set; }
public int Start { get; set; }
public int End { get; set; }
public List<string> Orders { get; set; }
public List<Item> GroupedItems()
{
List<Item> groupedItems = new List<Item>();
Item previous = Database[0];
Stack<Item> sameItems = new Stack<Item>();
foreach (Item item in Database)
{
if (previous.Name == item.Name)
{
sameItems.Push(item);
}
else
{
groupedItems.Add(GroupThem(sameItems));
previous = item;
sameItems.Push(item);
}
}
groupedItems.Add(GroupThem(sameItems));
return groupedItems;
}
private Item GroupThem(Stack<Item> sameItems)
{
string newName = "";
int newEnd = 0;
int newStart = int.MaxValue;
List<string> newOrders = new List<string>();
Item tmp = null;
while (sameItems.Count > 0)
{
tmp = sameItems.Pop();
if (tmp.Start < newStart)
newStart = tmp.Start;
if (tmp.End > newEnd)
newEnd = tmp.End;
foreach (string s in tmp.Orders)
if (!newOrders.Contains(s))
newOrders.Add(s);
newName = tmp.Name;
}
return new Item(false, newName, newStart, newEnd, newOrders.ToArray());
}
public override string ToString()
{
string tmp = "";
foreach (string s in Orders)
tmp += " " + s;
return "Name = " + Name + ", Start = " + Start + ", End = " + End +", Orders = "+ tmp;
}
}
class Program
{
static void Main(string[] args)
{
Item item1 = new Item("A", 1, 2, "C", "D");
Item item2 = new Item("A", 2, 3, "C", "E");
Item item3 = new Item("B", 4, 5, "F");
Item item4 = new Item("A", 6, 7);
Item item5 = new Item("A", 7, 8, "D");
Item item6 = new Item("A", 9, 10, "E");
foreach (Item item in item1.GroupedItems())
{
Console.WriteLine(item);
}
}
}
A bit late, I know, but I think the following solution will still help someone.
It includes the original Item class, enhanced with:
A ToString method to simplify inspection.
A CreateSamples method to generate the sample items.
A bonus nested class ComparerByStartAndEnd to sort items based on Start and End properties.
The solution resides in the EXTENSIONS.GroupWhenChanging method and the Item.FromGroup method.
The TEST class provides code to verify everything works as expected.
The actual grouping logic (EXTENSIONS.GroupWhenChanging) simply implements an enumerator that does not invoke Linq methods and allocates only a List object for each group, thus saving both in performance and memory resources.
The method is generic and accepts a comparison predicate, so it is not restricted to the sample Item class.
The creation of the result items, representing the groups with merged orders, is kept in the separate method Item.FromGroup. It uses some Linq to ease the task.
The TEST.Test method does the following:
Creates the list of samples.
Ensures the samples are ordered based on Start and End.
Enumerates the groups (by means of GroupWhenChanging) and creates the corresponing items (through Item.FromGroup).
The Item class:
public static class MODEL
{
public class Item
{
public String Name { get; set; }
public int Start { get; set; }
public int End { get; set; }
public List<string> Orders { get; set; }
/// <inheritdoc/>
public override string ToString()
{
return string.Format("{0} {1} .. {2} {3}", this.Name, this.Start, this.End, string.Join(",", this.Orders));
}
public static Item? FromGroup(IEnumerable<Item> group)
{
var array = group as Item[] ?? group.ToArray();
if (array.Length > 0)
{
var newName = array[0].Name;
var newStart = array.Min(item => item.Start);
var newEnd = array.Max(item => item.End);
var newOrders = array.SelectMany(item => item.Orders).Distinct().OrderBy(orderID => orderID).ToList();
var newItem = new Item()
{
Name = newName,
Start = newStart,
End = newEnd,
Orders = newOrders
};
return newItem;
}
return null;
}
public static IEnumerable<Item> CreateSamples()
{
yield return new Item() { Name = "A", Start = 1, End = 2, Orders = new List<string>() { "C", "D" } };
yield return new Item() { Name = "A", Start = 2, End = 3, Orders = new List<string>() { "C", "E" } };
yield return new Item() { Name = "B", Start = 4, End = 5, Orders = new List<string>() { "F" } };
yield return new Item() { Name = "A", Start = 6, End = 7, Orders = new List<string>() };
yield return new Item() { Name = "A", Start = 7, End = 8, Orders = new List<string>() { "D" } };
yield return new Item() { Name = "A", Start = 9, End = 10, Orders = new List<string>() { "E" } };
}
public sealed class ComparerByStartAndEnd : Comparer<Item>
{
/// <inheritdoc/>
public override int Compare(Item x, Item y)
{
if (x == y)
return 0;
return x.End.CompareTo(y.Start);
}
}
}
}
The EXTENSIONS class:
public static class EXTENSIONS
{
public static IEnumerable<IEnumerable<T>> GroupWhenChanging<T>(this IEnumerable<T> items, Func<T, T, bool> predicate)
{
List<T> group = null;
foreach (var item in items)
{
if (group is null)
group = new List<T>() { item };
else if (predicate(group[group.Count - 1], item))
group.Add(item);
else
{
yield return group;
group = new List<T>() { item };
}
}
if (group is not null)
yield return group;
}
}
The TEST class:
public static class TEST
{
public static void Test()
{
var items = MODEL.Item.CreateSamples().ToList();
items.Sort(new MODEL.Item.ComparerByStartAndEnd());
var groups = items
.GroupWhenChanging((prev, next) => prev.Name == next.Name)
.Select(MODEL.Item.FromGroup)
.ToList();
}
}

Improve performance of code

Requirement:I have two string array. Array of empDetails contain four field assume field one is ID and other fields are details. Array of empToRemove contain IDs of employee to remove. Create string of array which will not contain IDs which are present in empToRomove array. Please note I have to use this code which contain more than 100000 data in empDetails and more then 20000 data in empToRemove.
Any suggestion much appropriated.
string[] empDetails = { "1,abc,2,11k", "2,de,3,11k", "3,abc,2,18k", "4,abdc,2,12k" };
string[] empToRemove = { "1","3" };
My Solution
class Program
{
static void Main(string[] args)
{
string[] empDetails = { "1,abc,2,11k", "2,de,3,11k", "3,abc,2,18k", "4,abdc,2,12k" };
string[] empToRemove = { "1","3" };
//Add emp details in list of employee
List<emp> e = new List<emp>();
foreach (var item in empDetails)
{
Dictionary<int, string> tempEmployee = new Dictionary<int, string>();
int i = 1;
foreach (string details in item.Split(','))
{
tempEmployee.Add(i, details);
i++;
}
e.Add(new emp { ID = int.Parse(tempEmployee[1]), Details1 = tempEmployee[2], Details2 = tempEmployee[3], Details3 = tempEmployee[4] });
}
foreach (string item in empToRemove)
{
emp employeeToRemove = e.Where(x => x.ID == int.Parse(item)).Single();
e.Remove(employeeToRemove);
}
foreach (var item in e)
{
Console.WriteLine(item.ID + item.Details1 + item.Details2 + item.Details3);
}
Console.ReadLine();
}
}
class emp
{
public int ID { get; set; }
public string Details1 { get; set; }
public string Details2 { get; set; }
public string Details3 { get; set; }
}
Thanks
If I correctly understood your requirement and the only thing you need - is to print (or manipulate somehow else) elements of empDetails which ID's not in empToRemove - than your code is totally overkill.
Following will be pretty sufficient:
string[] empDetails = { "1,abc,2,11k", "2,de,3,11k", "3,abc,2,18k", "4,abdc,2,12k" };
string[] empToRemove = { "1", "3" };
var remove = new HashSet<string>(empToRemove);
foreach (var item in empDetails)
{
string id = item.Substring(0, item.IndexOf(','));
if (!remove.Contains(id))
Console.WriteLine(item); // or your custom action with this item
}
string[] empDetails = { "1,abc,2,11k", "2,de,3,11k", "3,abc,2,18k", "4,abdc,2,12k" };
string[] empToRemove = { "1","3" };
foreach (string item in empToRemove)
empDetails = empDetails.Where(val => val.Substring(0, val.IndexOf(',')) != item).ToArray();
Is one way. Cant get more effeciant than that?
based on research from:
How to delete an element from an array in C#

Join a string property which is in a List of objects using Linq

I have a set of object like this:
class ObjectA
{
public string NameA {get;set;}
public List<ObjectB> ListObjectB {get;set;}
}
class ObjectB
{
public string NameB {get;set;}
public ObjectBDetails Details {get;set;}
}
class ObjectBDetails
{
public string Code {get;set;}
public string Sector {get;set;}
}
I have a List of ObjectA with a few items in there.
What I like to do is get the Sector string of all the items and write that out as a comma separated string.
So for example, I could do something like this:
string tmp = "";
foreach(var objA in ListObjectA)
{
foreach(var objB in objA.ListObjectB)
{
tmp += objB.ObjectDetails.Sector + ", ";
}
}
This "Would" work. Only problem is that it will always end with a ,, which is not so nice.
But I also think that there is a much nicer solution to do this with Linq. But I can't figure out how to do it. I tried something like this, but obviously it didn't work out:
var a = objA.ListObjectB.Aggregate((current, next) => current.ObjectBDetails.Sector + ", " + next.ObjectBDetails.Sector);
So I'm actually looking for a way to do this with Linq. Anyone any idea how to do this?
You can use string.Join and LINQ SelectMany combination:
var a = string.Join(", ", ListObjectA.SelectMany(x => x.ListObjectB)
.Select(x => x.Details.Sector));
This for data:
var ListObjectA = new List<ObjectA>();
ListObjectA.Add(new ObjectA()
{
ListObjectB = new List<ObjectB>() {
new ObjectB() { Details = new ObjectBDetails() { Sector = "A1" }},
new ObjectB() { Details = new ObjectBDetails() { Sector = "A2" }},
}
});
ListObjectA.Add(new ObjectA()
{
ListObjectB = new List<ObjectB>() {
new ObjectB() { Details = new ObjectBDetails() { Sector = "B1" }}
}
});
produces "A1, A2, B1"
Try this:
var a = objA.ListObjectB.Aggregate((current, next) => current.Sector + ", " + next.Sector);

Categories

Resources