Stacking the same lines into packet, and writing to new file - c#

I would like to know how to stack the same lines titled to packet and write to next file. For example, I had the following problem:
I read CSV file line by line, and I want to stack lines with the same titles to one packet.
file1:
Test;param1
Test;param2
Test1;param1
Test1;param2
Test1;param3
Test2;param1
result file:
Test;[param1,param2]
Test1;[param1,param2,param3]
Test2;[param1]
It does not have to be identical, but it is a hint on how to do something like that.
My code:
var enumLines = System.IO.File.ReadLines(pathZamowienia, Encoding.UTF8);
int factor = 0;
foreach (var line in enumLines)
{
var tabLine = line.Split(';').ToList();
if (factor == 0)
{
Console.WriteLine();
}
else
{
try
{
Title = tabLine[0];
}
catch (FormatException ex)
{
Console.WriteLine("Failure");
}
try
{
Param = tabLine[1];
}
catch (FormatException ex)
{
Console.WriteLine("Failure");
}
factor++;
}

You can use a LINQ query to group the lines
// Test input
var enumLines = new List<string> {
"Test;param1",
"Test;param2",
"Test1;param1",
"Test1;param2",
"Test1;param3",
"Test2;param1"
};
// Re-group the parameters
var newLines = enumLines
.Select(s => s.Split(';'))
.GroupBy(a => a[0], a => a[1])
.Select(g => g.Key + ";[" + String.Join(",", g) + "]");
// Test output:
foreach (string line in newLines) {
Console.WriteLine(line);
}
Output:
Test;[param1,param2]
Test1;[param1,param2,param3]
Test2;[param1]
Note that the group g itself is an enumeration of the aggregated values and also has a property Key. The first argument of GroupBy selects the Key, the second optional parameter selects the value to be aggregated. If it is omitted, the input (the string array a) is aggregated.
If the input includes misshaped lines, you could also exclude them with an additional Where-clause:
var newLines = enumLines
.Select(s => s.Split(';'))
.Where(a => a.Length >= 2)
.GroupBy(a => a[0], a => a[1])
.Select(g => g.Key + ";[" + String.Join(",", g) + "]");

This is what you can do. Parse your file first and then transform
var data - new Dictionary<string, List<string>>();
string[] lines = File.ReadAllLines(fileName);
foreach(string line in Lines)
{
string parts = line.Split(';');
if (!data.ContainsKey(parts[0]))
data.Add(parts[0], new List<string>());
data[parts[0]].Add(parts[1]);
}
// then you open stream and write this
foreach(var kvp in data)
{
string line = $"{kvp.Key};[{string.Join(',', kvp.Value)}]"
// write line here
}
// close stream

Related

Remove duplicate combination of numbers in C# from csv

I'm trying to remove the duplicate combination from a csv file.
I tried using Distinct but it seems to stay the same.
string path;
string newcsvpath = #"C:\Documents and Settings\MrGrimm\Desktop\clean.csv";
OpenFileDialog openfileDial = new OpenFileDialog();
if (openfileDial.ShowDialog() == DialogResult.OK)
{
path = openfileDial.FileName;
var lines = File.ReadLines(path);
var grouped = lines.GroupBy(line => string.Join(", ", line.Split(',').Distinct())).ToArray();
var unique = grouped.Select(g => g.First());
var buffer = new StringBuilder();
foreach (var name in unique)
{
string value = name;
buffer.AppendLine(value);
}
File.WriteAllText(newcsvpath ,buffer.ToString());
label5.Text = "Complete";
}
For example, I have a combination of
{ 1,1,1,1,1,1,1,1 } { 1,1,1,1,1,1,1,2 }
{ 2,1,1,1,1,1,1,1 } { 1,1,1,2,1,1,1,1 }
The output should be
{ 1,1,1,1,1,1,1,1 }
{ 2,1,1,1,1,1,1,1 }
From you example, it seems that you want to treat each line as a sequence of numbers and that you consider two lines equal if one sequence is a permutation of the other.
So from reading your file, you have:
var lines = new[]
{
"1,1,1,1,1,1,1,1",
"1,1,1,1,1,1,1,2",
"2,1,1,1,1,1,1,1",
"1,1,1,2,1,1,1,1"
};
Now let's convert it to an array of number sequences:
var linesAsNumberSequences = lines.Select(line => line.Split(',')
.Select(int.Parse)
.ToArray())
.ToArray();
Or better, since we are not interested in permutations, we can sort the numbers in the sequences immediately:
var linesAsSortedNumberSequences = lines.Select(line => line.Split(',')
.Select(int.Parse)
.OrderBy(number => number)
.ToArray())
.ToArray();
When using Distinct on this, we have to pass a comparer which considers two array equal, if they have the same elements. Let's use the one from this SO question
var result = linesAsSortedNumberSequences.Distinct(new IEnumerableComparer<int>());
Try it
HashSet<string> record = new HashSet<string>();
foreach (var row in dtCSV.Rows)
{
StringBuilder textEditor= new StringBuilder();
foreach (string col in columns)
{
textEditor.AppendFormat("[{0}={1}]", col, row[col].ToString());
}
if (!record.Add(textEditor.ToString())
{
}
}

an item with the same key has already been added mvc

Hello I am trying to split the results from a string into a dictionary so I can add the numbers together. This is information received from a texting api a client will text in an account + the amount they want to donate and multiple accounts are separated by commas ex th 20.00, bf 10.00 etc.
When I run the code it worked find in windows form's but when i converted over to MVC I get the error "an item with the same key has already been added" which i know means its duplicating an key. I tried entering an if statement during the foreach loop:
if(!tester.containsKey(j){}
but that did not always solve the problem and created a new error about out of range. Below is my current code:
public ActionResult register(text2give reg)
{
string body = reg.body;
try
{
var items = body.Split(',');
Dictionary<string, float> tester = new Dictionary<string, float>();
var j = 0;
var total = 0f;
while (j < body.Length)
{
foreach (var item in items)
{
var s = item.Trim().Split(' ');
tester.Add(s[0], float.Parse(s[1]));
total += float.Parse(s[1]);
j++;
}
}
ViewBag.total = total;
}
catch (Exception ex)
{
Response.Write(ex.ToString());
}
return View(reg);
}
Your code is OK, but it makes quite a few assumptions:
It assumes the body is split properly
It assumes all items are unique (apparently they aren't, hence the error)
It assumes there are two elements in each item (it isn't, hence the indexOutOfRangeException)
Here's how I would write this code to make sure it correctly guards against these cases:
public ActionResult register(text2give reg)
{
string body = reg.body;
try
{
var items = body.Split(',');
var splitItems = items.Select(i => i.Split(' ')).ToList();
var itemsWithTwoValues = splitItems.Where(s => s.Length == 2);
var uniqueItems = itemsWithTwoValues.GroupBy(s => s[0])
.Where(g => g.Count() == 1)
.SelectMany(g => g);
var tester = uniqueItems.ToDictionary(s => s[0], s => float.Parse(s[1]));
var total = tester.Sum(s => s.Value);
ViewBag.total = total;
}
catch (Exception ex)
{
Response.Write(ex.ToString());
}
return View(reg);
}
Or, the shorter, condensed version:
public ActionResult register(text2give reg)
{
string body = reg.body;
try
{
var tester = body.Split(',') // Split the initial value into items
.Select(i => i.Split(' ')) // Split each item into elements
.Where(s => s.Length == 2) // Take only those that have 2 items
.GroupBy(s => s[0]) // Group by the key
.Where(g => g.Count() == 1) // Remove all those that have a duplicate key
.SelectMany(g => g) // Ungroup them again
.ToDictionary(s => s[0],
s => float.Parse(s[1])); // Create a dictionary where the first item is the key and the second is the parsed float
var total = tester.Sum(s => s.Value);
ViewBag.total = total;
}
catch (Exception ex)
{
Response.Write(ex.ToString());
}
return View(reg);
}
s[0] is the duplicate key not j. You would need to use the following
var s = item.Trim().Split(' ');
if(!tester.containsKey(s[0]){
tester.Add(s[0], float.Parse(s[1]));
total += float.Parse(s[1]);
j++;
}
You might be getting duplicate data, be careful ignoring the keys as you might actually need the data. I'm just showing you how to suppress the error.

IndexOutOfRangeException was unhandled

I'm getting an error IndexOutOfRangeException was unhandled at the line int euros = int.Parse(values[1]).
My .csv file looks:
name, 1, 2
name1, 3, 4
name2, 5, 6
public static void ReadData(out Turistai[] tourists, out int amount)
{
amount = 0;
tourists = new Turistai[MaxTourists];
using (StreamReader reader = new StreamReader("C:\\Users\\Andrius\\Desktop\\Mokslams\\C#\\Pratybos\\P2\\P2.1\\turistai.csv"))
{
string line = null;
while( (line = reader.ReadLine()) != null)
{
string[] values = line.Split(';');
string name = values[0];
int euros = int.Parse(values[1]);
int cents = int.Parse(values[2]);
Console.WriteLine(euros);
//Turistai tourists = new Turistai(name, euros, cents);
amount++;
}
}
}
Probably, you have some empty lines in the CSV file.
I suggest using Linq to sum up the euros:
var data = File
.ReadLines("C:\\Users\\Andrius\\Desktop\\Mokslams\\C#\\Pratybos\\P2\\P2.1\\turistai.csv")
.Select(line => line.Split(','))
.Where(items => items.Length >= 2) // filter out empty/incomplete lines
// To debug, let's take euros only
.Select(items => int.Parse(items[1]));
// In the final solution we'll create Touristai instances
// .Select(items => new Touristai(items[0], int.Parse(items[1]), int.Parse(items[2])))
.ToArray();
Console.WriteLine(String.Join(Environment.NewLine, data));
Console.WriteLine(data.Sum());
Final solution will be
public static void ReadData(out Turistai[] tourists, out int amount) {
tourists = File
.ReadLines(#"C:\Users\Andrius\Desktop\Mokslams\C#\Pratybos\P2\P2.1\turistai.csv")
.Select(line => line.Split(','))
.Where(items => items.Length >= 2) // filter out empty/incomplete lines
.Select(items => new Touristai(items[0], int.Parse(items[1]), int.Parse(items[2])))
.ToArray();
//TODO: check syntax (I've sugested Touristai should have Euro property)
amount = tourists.Sum(tourist => tourist.Euro);
}
Your CSV input is comma separated while in the code you're splitting by semicolons. Change the split() parameter to ,:
string[] values = line.Split(',');
You may also want to add input format check to ensure the values array contains at least three items and the numeric fields do actually contain integer values (int.TryParse() may help with this):
while( (line = reader.ReadLine()) != null)
{
string[] values = line.Split(',');
if (values.Length < 3)
{
Console.Error.WriteLine("Invalid input line: " + line);
continue;
}
string name = values[0];
int euros;
if (!int.TryParse(values[1], out euros))
{
Console.Error.WriteLine("Invalid euros value in the line: " + line);
continue;
}
int cents;
if (!int.TryParse(values[2], out cents))
{
Console.Error.WriteLine("Invalid cents value in the line: " + line);
continue;
}
Console.WriteLine(euros);
//Turistai tourists = new Turistai(name, euros, cents);
amount++;
}

C#: Loop over Textfile, split it and Print a new Textfile

I get many lines of String as an Input that look like this. The Input is a String that comes from
theObjects.Runstate;
each #VAR;****;#ENDVAR; represents one Line and one step in the loop.
#VAR;Variable=Speed;Value=Fast;Op==;#ENDVAR;#VAR;Variable=Fabricator;Value=Freescale;Op==;#ENDVAR;
I split it, to remove the unwanted fields, like #VAR,#ENDVAR and Op==.
The optimal Output would be:
Speed = Fast;
Fabricator = Freescale; and so on.
I am able to cut out the #VAR and the#ENDVAR. Cutting out the "Op==" wont be that hard, so thats now not the main focus of the question. My biggest concern right now is,thatI want to print the Output as a Text-File. To print an Array I would have to loop over it. But in every iteration, when I get a new line, I overwrite the Array with the current splitted string. I think the last line of the Inputfile is an empty String, so the Output I get is just an empty Text-File. It would be nice if someone could help me.
string[] w;
Textwriter tw2;
foreach (EA.Element theObjects in myPackageObject.Elements)
{
theObjects.Type = "Object";
foreach (EA.Element theElements in PackageHW.Elements)
{
if (theObjects.ClassfierID == theElements.ElementID)
{
t = theObjects.RunState;
w = t.Replace("#ENDVAR;", "#VAR;").Replace("#VAR;", ";").Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
foreach (string s in w)
{
tw2.WriteLine(s);
}
}
}
}
This linq-query gives the exptected result:
var keyValuePairLines = File.ReadLines(pathInputFile)
.Select(l =>
{
l = l.Replace("#VAR;", "").Replace("#ENDVAR;", "").Replace("Op==;", "");
IEnumerable<string[]> tokens = l.Split(new[]{';'}, StringSplitOptions.RemoveEmptyEntries)
.Select(t => t.Split('='));
return tokens.Select(t => {
return new KeyValuePair<string, string>(t.First(), t.Last());
});
});
foreach(var keyValLine in keyValuePairLines)
foreach(var keyVal in keyValLine)
Console.WriteLine("Key:{0} Value:{1}", keyVal.Key, keyVal.Value);
Output:
Key:Variable Value:Speed
Key:Value Value:Fast
Key:Variable Value:Fabricator
Key:Value Value:Freescale
If you want to output it to another text-file with one key-value pair on each line:
File.WriteAllLines(pathOutputFile, keyValuePairLines.SelectMany(l =>
l.Select(kv => string.Format("{0}:{1}", kv.Key, kv.Value))));
Edit according to your question in the comment:
"What would I have to change/add so that the Output is like this. I
need AttributeValuePairs, for example: Speed = Fast; or Fabricator =
Freescale ?"
Now i understand the logic, you have key-value pairs but you are interested only in the values. So every two key-values belong together, the first value of a pair specifies the attibute and the second value the value of that attribute(f.e. Speed=Fast).
Then it's a little bit more complicated:
var keyValuePairLines = File.ReadLines(pathInputFile)
.Select(l =>
{
l = l.Replace("#VAR;", "").Replace("#ENDVAR;", "").Replace("Op==;", "");
string[] tokens = l.Split(new[]{';'}, StringSplitOptions.RemoveEmptyEntries);
var lineValues = new List<KeyValuePair<string, string>>();
for(int i = 0; i < tokens.Length; i += 2)
{
// Value to a variable can be found on the next index, therefore i += 2
string[] pair = tokens[i].Split('=');
string key = pair.Last();
string value = null;
string nextToken = tokens.ElementAtOrDefault(i + 1);
if (nextToken != null)
{
pair = nextToken.Split('=');
value = pair.Last();
}
var keyVal = new KeyValuePair<string, string>(key, value);
lineValues.Add(keyVal);
}
return lineValues;
});
File.WriteAllLines(pathOutputFile, keyValuePairLines.SelectMany(l =>
l.Select(kv=>string.Format("{0} = {1}", kv.Key, kv.Value))));
Output in the file with your single sample-line:
Speed = Fast
Fabricator = Freescale

In file, if line contains substring, get all of the line from the right

I have a file. Each line looks like the following:
[00000] 0xD176234F81150469: foo
What I am attempting to do is, if a line contains a certain substring, I want to extract everything on the right of the substring found. For instance, if I were searching for 0xD176234F81150469: in the above line, it would return foo. Each string is of variable length. I am using C#.
As a note, every line in the file looks like the above, having a base-16 number enclosed in square brackets on the left, followed by a hexadecimal hash and a semicolon, and an english string afterwards.
How could I go about this?
Edit
Here is my code:
private void button1_Click(object sender, EventArgs e)
{
Form1 box = new Form1();
if(MessageBox.Show("This process may take a little while as we loop through all the books.", "Confirm?", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes)
{
XDocument doc = XDocument.Load(#"C:\Users\****\Desktop\books.xml");
var Titles = doc.Descendants("Title");
List<string> list = new List<string>();
foreach(var Title in Titles)
{
string searchstr = Title.Parent.Name.ToString();
string val = Title.Value;
string has = #"Gameplay/Excel/Books/" + searchstr + #":" + val;
ulong hash = FNV64.GetHash(has);
var hash2 = string.Format("0x{0:X}", hash);
list.Add(val + " (" + hash2 + ")");
// Sample output: "foo (0xD176234F81150469)"
}
string[] books = list.ToArray();
File.WriteAllLines(#"C:\Users\****\Desktop\books.txt", books);
}
else
{
MessageBox.Show("Aborted.", "Aborted");
}
}
I also iterated through every line of the file, adding it to a list<>. I must've accidentally deleted this when trying the suggestions. Also, I am very new to C#. The main thing I am getting stumped on is the matching.
You could use File.ReadLines and this Linq query:
string search = "0xD176234F81150469:";
IEnumerable<String> lines = File.ReadLines(path)
.Select(l => new { Line = l, Index = l.IndexOf(search) })
.Where(x => x.Index > -1)
.Select(x => x.Line.Substring(x.Index + search.Length));
foreach (var line in lines)
Console.WriteLine("Line: " + line);
This works if you don't want to use Linq query.
//"I also iterated through every line of the file, adding it to a list<>." Do this again.
List<string> li = new List<string>()
//However you create this string make sure you include the ":" at the end.
string searchStr = "0xD176234F81150469:";
private void button1_Click(object sender, EventArgs e)
{
foreach (string line in li)
{
string[] words;
words = line.Split(' '); //{"[00000]", "0xD176234F81150469:", "foo"}
if (temp[1] == searchStr)
{
list.Add(temp[2] + " (" + temp[1] + ")");
// Sample output: "foo (0xD176234F81150469)"
}
}
}
string file = ...
string search= ...
var result = File.ReadLines(file)
.Where(line => line.Contains(search))
.Select(line => line.Substring(
line.IndexOf(search) + search.Length + 1);
Unfortunately, none of the other solutions worked for me. I was iterating through the hashes using foreach, so I would be iterating through all the items millions of times needlessly. In the end, I did this:
using (StreamReader r = new StreamReader(#"C:\Users\****\Desktop\strings.txt"))
{
string line;
while ((line = r.ReadLine()) != null)
{
lines++;
if (lines >= 6)
{
string[] bits = line.Split(':');
if(string.IsNullOrWhiteSpace(line))
{
continue;
}
try
{
strlist.Add(bits[0].Substring(10), bits[1]);
}
catch (Exception)
{
continue;
}
}
}
}
foreach(var Title in Titles)
{
string searchstr = Title.Parent.Name.ToString();
string val = Title.Value;
string has = #"Gameplay/Excel/Books/" + searchstr + ":" + val;
ulong hash = FNV64.GetHash(has);
var hash2 = " " + string.Format("0x{0:X}", hash);
try
{
if (strlist.ContainsKey(hash2))
{
list.Add(strlist[hash2]);
}
}
catch (ArgumentOutOfRangeException)
{
continue;
}
}
This gave me the output I expected in a short period of time.

Categories