C# Combining lines - c#

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.

Related

Count occurence of a string in a list and display it in console

I'm creating a Logparser right now i'm able to go trought all the file in a folder line by line and extracting the substring i wanted which is the value after "fct=". All that using Regex and i put the result in a List.
Now i want to Count the occurence of every string in my list and display it.
I'm using GroupBy but when i display the result all the occurence are at 1.
Actual:
720 1x
720 1x
710 1x
And it should be:
720 2x
710 1x
I was able to find that the problem is that i read my file line by line so if the "fct=" value is not twice on the same line it won't count it a 2 but only at 1 for every line that its appears.
So i need to find a way to count my list and not my file line by line.
I'm really beginner so not sure how to do this any tips would be appreciated.
Here's the log data example:
<dat>FCT=10019,XN=KEY,CN=ROHWEPJQSKAUMDUC</dat></logurl>
<dat>XN=KEY,CN=RTU FCT=4515</dat>LBZ=test.sqi</logurl>
<dat>XN=KEY,CN=RT</dat>FCT=10019</logurl>
I want to display:
FCT=10019 2x
FCT=4515 1x
My Code:
class Program
{
static void Main(string[] args)
{
int counter = 0;
string[] dirs = Directory.GetFiles(#"C:/LogParser/LogParserV1", "*.txt");
StreamWriter sw = new StreamWriter("C:/LogParser/LogParserV1/test.txt");
char[] delimiters = { '<', ',', '&', ':', ' ', '\\', '\'' };
string patternfct = "(?<=FCT=)[0-9]*";
foreach (string fileName in dirs)
{
StreamReader sr = new StreamReader(fileName);
{
String lineRead;
while ((lineRead = sr.ReadLine()) != null)
{
//To find all the value of fct= occurence
var listfct = Regex.Matches(lineRead, patternfct,
RegexOptions.IgnoreCase).Cast<Match>().Select(x => x.Value).ToList();
var fctGroups = listfct.GroupBy(i => i);
foreach (var grp in fctGroups)
{
var fct = grp.Key;
var total = grp.Count();
System.Console.WriteLine("fct=" + fct + " " + "Total=" + total);
}
counter++;
}
System.Console.WriteLine(fileName);
sr.Close();
sw.Close();
}
}
// Suspend the screen.
System.Console.ReadLine();
}
}
}
You can try querying data with a help of Linq:
using System.Linq;
using System.Text.RegularExpressions;
...
Regex regex = new Regex("(?<=FCT=)[0-9]*", RegexOptions.IgnoreCase);
var records = Directory
.EnumerateFiles(#"C:/LogParser/LogParserV1", "*.txt")
.SelectMany(file => File.ReadLines(file))
.SelectMany(line => regex
.Matches(line)
.Cast<Match>()
.Select(match => match.Value))
.GroupBy(number => number)
.Select(group => $"FCT={group.Key} {group.Count()}x");
foreach (string record in records)
Console.WriteLine(record);
Demo: We can't mimic directory and files, so I've removed
Directory
.EnumerateFiles(#"C:/LogParser/LogParserV1", "*.txt")
.SelectMany(file => File.ReadLines(file))
but added testLines
string[] testLines = new string[] {
"<dat>FCT=10019,XN=KEY,CN=ROHWEPJQSKAUMDUC</dat></logurl>",
"<dat>XN=KEY,CN=RTU FCT=4515</dat>LBZ=test.sqi</logurl>",
"<dat>XN=KEY,CN=RT</dat>FCT=10019</logurl>",
};
Regex regex = new Regex("(?<=FCT=)[0-9]*", RegexOptions.IgnoreCase);
var records = testLines
.SelectMany(line => regex
.Matches(line)
.Cast<Match>()
.Select(match => match.Value))
.GroupBy(number => number)
.Select(group => $"FCT={group.Key} {group.Count()}x");
foreach (string record in records)
Console.WriteLine(record);
Outcome:
FCT=10019 2x
FCT=4515 1x
Edit: If you want to include file into records, you can use anonymous objects:
var records = Directory
.EnumerateFiles(#"C:/LogParser/LogParserV1", "*.txt")
.SelectMany(file => File
.ReadLines(file)
.Select(line => new {
file = file,
line = line,
}))
.SelectMany(item => regex
.Matches(item.line)
.Cast<Match>()
.Select(match => new {
file = item.file,
number = match.Value
}))
.GroupBy(item => new {
file = item.file,
number = item.number
})
.OrderBy(group => group.Key.file)
.ThenBy(group => group.Key.number)
.Select(group => $"{group.Key.file} has FCT={group.Key.number} {group.Count()}x")

Finding filenames containing version numbers in 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();
}

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.

Extracting unique and non-unique strings to separate output files

I am have trouble trying to extract only lines that are not duplicated and only lines that are only duplicates from a test file. The input file contains both duplicates and non-duplicate lines.
I have created a logging function and I can extract all unique lines from it to a separate file but that includes lines that are duplicates and lines that aren't, I need to separate them.
This is what I have so far;
static void Dupes(string path1, string path2)
{
string log = log.txt;
var sr = new StreamReader(File.OpenRead(path1));
var sw = new StreamWriter(File.OpenWrite(path2));
var lines = new HashSet<int>();
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
int hc = line.GetHashCode();
if (lines.Contains(hc))
continue;
lines.Add(hc);
sw.WriteLine(line);
}
sw.Close();
}
Ideally this would be in two functions, so they can be called to perform different actions on the output contents.
use LINQ to Group items, then check the count:
var lines = File.ReadAllLines(path1);
var distincts = lines.GroupBy(l => l)
.Where(l => l.Count() == 1)
.Select(l => l.Key)
.ToList();
var dupes = lines.Except(distincts).ToList();
It's worth noting that Except doesn't return duplicates - something I just learned. So no need to call Distinct afterwards.
You can do as follow
var lines = File.ReadAllLines(path1);
var countLines = lines.Select(d => new
{
Line = d,
Count = lines.Count(f => f == d),
});
var UniqueLines = countLines.Where(d => d.Count == 1).Select(d => d.Line);
var NotUniqueLines = countLines.Where(d => d.Count > 1).Select(d => d.Line);

Mapping numbers to letters

I had an interview question asking this:
text file has following lines>
1: A C D
4: A B
5: D F
7: A E
9: B C
*Every line has a unique integer followed by a colon and one or
more letters. These letters are
delimited spaces (one or more)>
#2 Write a short program in the language
of your choice that outputs a sorted
list like
A: 1 4 7
B: 4 9
C: 1 9
D: 1 5
E: 7
F: 5
I'm not looking for someone to solve it, but I always get confused with problems like this. I'd like to do it in C# and was wondering should I store each line in a 2d array? What is the best way to handle this. After storing it how do I relist each line with letters rather then numbers?
Just looking for pointers here.
You can solve the problem by creating a Lookup mapping letters to a collection of numbers. You can use the extension method ToLookup to create a Lookup.
Warning: Spoilers ahead
Using LINQ you can do it like this (breaks on invalid input):
var text = #"1: A C D
4: A B
5: D F
7: A E
9: B C";
var lookup = text
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Select(
line => new {
Number = Int32.Parse(line.Split(':').First()),
Letters = line.Split(':').Skip(1).First().Split(
new[] {' '}, StringSplitOptions.RemoveEmptyEntries
)
}
)
.SelectMany(x => x.Letters, (x, letter) => new { x.Number, Letter = letter })
.OrderBy(x => x.Letter)
.ToLookup(x => x.Letter, x => x.Number);
foreach (var item in lookup)
Console.WriteLine(item.Key + ": " + String.Join(" ", item.ToArray()));
In case you are familiar with LINQ the below code can give you what you are looking for:
var result = File.ReadAllLines("inFile").SelectMany(line =>
{
var ar = line.Split(" ".ToCharArray());
var num = int.Parse(ar[0].Split(":".ToCharArray())[0]);
return ar.Skip(1).Select(s => new Tuple<string, int>(s, num));
}).GroupBy(t => t.Item1).OrderByDescending(g => g.Count())
.Select(g => g.Key + ": " + g.Select(t => t.Item2.ToString()).Aggregate( (a,b) => a + " " + b));
File.WriteAllLines("outFile", result);
I know you said you didn't want full answers, but this kind of thing is fun. It looks like others have come up with similar solutions, but here's another way to represent it - in "one line" of code (but lots of brackets!) :)
var data = #"1: A C D
4: A B
5: D F
7: A E
9: B C";
Console.WriteLine(
String.Join(
Environment.NewLine,
(from line in data.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
let lineParts = line.Split(new[] { ':', ' ' }, StringSplitOptions.RemoveEmptyEntries)
from letter in lineParts.Skip(1)
select new { Number = lineParts[0], Letter = letter })
.ToLookup(l => l.Letter, l => l.Number)
.OrderBy(l => l.Key)
.Select(l => String.Format("{0}: {1}", l.Key, String.Join(" ", l)))));
Oh, and would I write code like that in production? Probably not, but it's fun in an exercise like this!
The thing that will help you solve this
IDictionary<char, IList<int> >
Yet Another Linq Masturbatory Implementation ("Look Ma! No loops!")
using System;
using System.IO;
using System.Linq;
public static class Program
{
public static void Main(string[] args)
{
File.ReadAllLines("input.txt")
.Select(line =>
{
var split = line.Split(":".ToCharArray(), 2);
return new { digit = split[0].Trim().Substring(0,1),
chars = split[1]
.Split(" \t".ToCharArray())
.Select(s=>s.Trim())
.Where(s => !String.IsNullOrEmpty(s))
.Select(s => s[0])
};
})
.SelectMany(p => p.chars.Select(ch => new { p.digit, ch }))
.GroupBy(p => p.ch, p => p.digit)
.ToList()
.ForEach(g => Console.WriteLine("{0}: {1}", g.Key, string.Join(" ", g)));
}
}
Of course you can replace GroupBy with ToLookup
I will use a Dictionary<string,List<int>> I will read the input and add 1 into the list at keys A,C,D, A at keys A,B etc, so having the result is just a lookup by letter.
So like this, in a non esoteric way:
string inp = #"1: A C D
4: A B
5: D F
7: A E
9: B C";
Dictionary<string, List<int>> res = new Dictionary<string, List<int>>();
StringReader sr = new StringReader(inp);
string line;
while (null != (line = sr.ReadLine()))
{
if (!string.IsNullOrEmpty(line))
{
string[] tokens = line.Split(": ".ToArray(),StringSplitOptions.RemoveEmptyEntries);
int idx = int.Parse(tokens[0]);
for (int i = 1; i < tokens.Length; ++i)
{
if (!res.ContainsKey(tokens[i]))
res[tokens[i]] = new List<int>();
res[tokens[i]].Add(int.Parse(tokens[0]));
}
}
}
res will contain the result of letter->list of numbers.
String parsing using Split(":") and Split(" ").
Then fill
Dictionary<int, List<string>>
and translate it into
Dictionary<string, List<int>>
You could store the input in an IDictionary, and reverse it to produce your output.
Take a look at this question.
I see that multiple similar (loops) and not so similar (linq) solutions were already posted but since i've written this i thought i'd throw it in the mix.
static void Main(string[] args)
{
var result = new SortedDictionary<char, List<int>>();
var lines = System.IO.File.ReadAllLines(#"input.txt");
foreach (var line in lines)
{
var split = line.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);
var lineNumber = Int32.Parse(split[0].Substring(0,1));
foreach (var letter in split.Skip(1))
{
var key = letter[0];
if (!result.ContainsKey(key))
{
result.Add(key, new List<int> { lineNumber });
}
else
{
result[key].Add(lineNumber);
}
}
}
foreach (var item in result)
{
Console.WriteLine(String.Format("{0}: {1}", item.Key, String.Join(" ", item.Value)));
}
Console.ReadKey();
}
An important part of the interview process is asking about and verifying assumptions. Although your description states the file is structured as an integer followed by letters, the example you give shows the integers in increasing order. If that's the case, you can avoid all of the LINQ craziness and implement a much more efficient solution:
var results = new Dictionary<char, List<int>>();
foreach (var line in File.ReadAllLines(#"input.txt"))
{
var split = line.Split(new []{' '}, StringSplitOptions.RemoveEmptyEntries);
var num = int.Parse(split[0].TrimEnd(':'));
for (int i = 1; i < split.Length; i++)
{
char letter = split[i][0];
if (!results.ContainsKey(letter))
results[letter] = new List<int>();
results[letter].Add(num);
}
}

Categories

Resources