Comparing N Objects In A Complex Structure C# - c#

I have a set of instances of the Data class that I want to compare.
Each instance has an unknown number of items in it's Files property.
I want to compare each instance of Data to the others and set FoundDifference to true if a version difference is found between two files with the same Name value.
Is there a simple algorithm to accomplish this?
Here is a sample setup of how the objects might look.
In this example you'd want everything except for f1, f21, and f31 to set the FoundDifference to true
class Data
{
public string DC { get; set; }
public List<File> Files { get; set; }
}
class File
{
public string Name { get; set; }
public string Version { get; set; }
public bool FoundDifference { get; set; }
}
class Program
{
static void Main(string[] args)
{
Data d1 = new Data();
d1.DC = "DC1";
File f1 = new File();
f1.Name = "File1";
f1.Version = "1";
d1.Files.Add(f1);
File f2 = new File();
f2.Name = "File2";
f2.Version = "1";
d1.Files.Add(f2);
File f3 = new File();
f3.Name = "File3";
f3.Version = "1";
d1.Files.Add(f3);
//Another
Data d2 = new Data();
d2.DC = "DC2";
File f21 = new File();
f21.Name = "File1";
f21.Version = "1";
d2.Files.Add(f21);
File f22 = new File();
f22.Name = "File2";
f22.Version = "2";
d2.Files.Add(f22);
File f23 = new File();
f23.Name = "File3";
f23.Version = "1";
d2.Files.Add(f23);
//Another
Data d3 = new Data();
d3.DC = "DC3";
File f31 = new File();
f31.Name = "File1";
f31.Version = "1";
d3.Files.Add(f31);
File f32 = new File();
f32.Name = "File2";
f32.Version = "2";
d3.Files.Add(f32);
File f33 = new File();
f33.Name = "File3";
f33.Version = "5";
d3.Files.Add(f33);
//How Can I change All Files FoundDifference prop to true if FileName is the same and a difference is in Version is found??
Console.ReadLine();
}

I'd handle that by using a Dictionary<string, List<File>> to keep track of the files from each Data like this. First iterate all the files in all the datas then lookup the file name in the dictionary and if not found create a new list and add it. Then check if that list has any files with a different version. If one is found set all the flags and finally add the file to the list.
public void SetDifferences(IEnumerable<Data> datas)
{
var fileLookup = new Dictionary<string, List<File>>();
foreach(var file in datas.SelectMany(d => d.Files))
{
if(!fileLookup.TryGetValue(file.Name, out var fileList))
{
fileList = new List<File>();
fileLookup.Add(file.Name, fileList);
}
if(fileList.Any(f => f.Version != file.Version))
{
foreach(var other in fileList)
{
other.FoundDifference = true;
}
file.FoundDifference = true;
}
fileList.Add(file);
}
}

Related

Read bulk audio files faster

I am creating a music player in c# WinForms.
I have read all audio files from folder->subfolders and stored them in struct list.
I have displayed them in datagridview, I want to know which control do i need to use at this point to show all songs with a button in each row to play song? The audio files are more than 60k.
I have three questions regarding this
Do i need to use datagridview or list or another?
how to read bulk audio files from folder and show them in a control(datagridview, list or another) faster like in few seconds?
If i switch from AllMusic usercontrol to another and again click back to AllMusic usercontrol the application would again load 60k audio files to datagridview control which is time taking, what is the solution for that.
Please help.
Reading audio files path and storing them in struct list
public struct SongInfoStruct
{
public string Key;
public string Value;
public string Artist;
public double Duration;
};
public List<SongInfoStruct> ReadPathFromSource(string Source)
{
MessageBox.Show(Source);
var listpaths = new List<SongInfoStruct>();
string[] PathsClickSong_temp = Directory.GetFiles(Source, "*.*", SearchOption.AllDirectories);
FileInfo tempInfo;
string extentionName;
foreach (string path in PathsClickSong_temp)
{
tempInfo = new FileInfo(path);
extentionName = tempInfo.Extension.ToLower();
if (extentionName == ".mp3" || extentionName == ".wav"
|| extentionName == ".m4a" || extentionName == ".flac")
{
try
{
var item = GetSongInfo(path);
listpaths.Add(item);
}
catch
{
continue;
}
}
}
Reading audio file metadata
public SongInfoStruct GetSongInfo(string url)
{
TagLib.File tagFile;
var songinfo = new SongInfoStruct();
tagFile = TagLib.File.Create(url);
var songName = tagFile.Tag.Title;
var artist = tagFile.Tag.FirstPerformer;
var duration = tagFile.Properties.Duration.TotalSeconds;
if (string.IsNullOrEmpty(artist))
artist = "Unknown Artist";
if (string.IsNullOrEmpty(songName))
{
var Name = new FileInfo(url).Name;
songName = Path.GetFileNameWithoutExtension(Name);
}
songinfo = new SongInfoStruct
{
Key = url,
Value = songName,
Artist = artist,
Duration = duration
};
return songinfo;
}
Reading struct list to store in datatable to set datagridview data source
private async void AllMusicUserControl_Load(object sender, EventArgs e)
{
DataTable dt = new DataTable();
dt.Columns.Add("Songs", typeof(string));
dt.Columns.Add("Artists", typeof(string));
dt.Columns.Add("Duration", typeof(string));
var user = await Task.Run(() =>
{
for (int i = 0; i < ListSongInfo.Count; i++)
{
TimeSpan time = TimeSpan.FromSeconds(ListSongInfo[i].Duration);
dt.Rows.Add(ListSongInfo[i].Value, ListSongInfo[i].Artist, time.ToString(#"mm\:ss"));
}
return dt;
});
ProgressIndicator.Visible = false;
DataGridViewAllMusic.DataSource = user;
}

LINQ foreach statement to get items not working

public partial class Form1 : Form
{
List<Doctor> doctors = new List<Doctor>();
List<Episode> episodes = new List<Episode>();
List<Companion> companions = new List<Companion>();
public Form1()
{
InitializeComponent();
doctorCombo.Enabled = false;
}
private void openMenu_Click(object sender, EventArgs e)
{
String input;
using (OpenFileDialog openFileDialog = new OpenFileDialog())
{
//Give a title to the popup and a filter for what files to open. Then opens the created dialogue
openFileDialog.Title = "Browse Text Files";
openFileDialog.Filter = "Text|*.txt"; ;
openFileDialog.ShowDialog();
//Read the contents of the file into a stream
var fileStream = openFileDialog.OpenFile();
//Opens a streamreader
using (StreamReader reader = new StreamReader(fileStream))
{
while ((input = reader.ReadLine()) != null)
{
String[] exploded = input.Split('|');
if (exploded[0].Contains("D "))
{
Doctor d = new Doctor(Convert.ToInt32(exploded[1]), exploded[2], Convert.ToInt32(exploded[3]), Convert.ToInt32(exploded[4]), exploded[5]);
doctors.Add(d);
}
if (exploded[0].Contains("E "))
{
Episode ep = new Episode(exploded[1], Convert.ToInt32(exploded[2]), Convert.ToInt32(exploded[3]), exploded[4]);
episodes.Add(ep);
}
if (exploded[0].Contains("C "))
{
Companion c = new Companion(exploded[1], exploded[2], Convert.ToInt32(exploded[3]), exploded[4]);
companions.Add(c);
}
}
reader.Close();
}
doctorCombo.Enabled = true;
doctorCombo.SelectedIndex = 0;
var doctorsCompanion =
from currentDoctor in doctors
join currentCompanion in companions on currentDoctor.debut equals currentCompanion.debut
select new {currentDoctor.actor, currentDoctor.series, currentDoctor.age, currentCompanion.debut, currentCompanion.name, currentCompanion.actor };
foreach (var item in doctorsCompanion)
{
outputList.Items.Add("");
}
The foreach statement at the end here keeps saying that is cannot operate on variables of type '?' because '?' does not contain a public instance definition for 'getenumerator'
I'm merging a class called doctor with a class called companion.
You have two actor members in your anonymous type:
select new {currentDoctor.actor, /* ... */ currentCompanion.actor };
This is an error, so the compiler cannot go on.
You must give them distinct names:
select new { doctor_actor = currentDoctor.actor, /* ... */
companion_actor = currentCompanion.actor };

C# Adding an array or list into an List

I've got a List of Document
public class Document
{
public string[] fullFilePath;
public bool isPatch;
public string destPath;
public Document() { }
public Document(string[] fullFilePath, bool isPatch, string destPath)
{
this.fullFilePath = fullFilePath;
this.isPatch = isPatch;
this.destPath = destPath;
}
The fullFilepath should a List or an Array of Paths.
For example:
Document 1
---> C:\1.pdf
---> C:\2.pdf
Document 2
---> C:\1.pdf
---> C:\2.pdf
---> C:\3.pdf
etc.
My problem if I am using an array string all Documents got "null" in its fullFilePath.
If I'm using a List for the fullFilePath all Documents got the same entries from the last Document.
Here is how the List is filled:
int docCount = -1;
int i = 0;
List<Document> Documents = new List<Document>();
string[] sourceFiles = new string[1];
foreach (string file in filesCollected)
{
string bc;
string bcValue;
if (Settings.Default.barcodeEngine == "Leadtools")
{
bc = BarcodeReader.ReadBarcodeSymbology(file);
bcValue = "PatchCode";
}
else
{
bc = BarcodeReader.ReadBacrodes(file);
bcValue = "009";
}
if (bc == bcValue)
{
if(Documents.Count > 0)
{
Array.Clear(sourceFiles, 0, sourceFiles.Length);
Array.Resize<string>(ref sourceFiles, 1);
i = 0;
}
sourceFiles[i] = file ;
i++;
Array.Resize<string>(ref sourceFiles, i + 1);
Documents.Add(new Document(sourceFiles, true,""));
docCount++;
}
else
{
if (Documents.Count > 0)
{
sourceFiles[i] = file;
i++;
Array.Resize<string>(ref sourceFiles, i + 1);
Documents[docCount].fullFilePath = sourceFiles;
}
}
}
You are using the same instance of the array for every document. The instance is updated with a new list of files at every inner loop, but an array is a reference to an area of memory (oversimplification, I know but for the purpose of this answer is enough) and if you change the content of that area of memory you are changing it for every document.
You need to create a new instance of the source files for every new document you add to your documents list. Moreover, when you are not certain of the number of elements that you want to be included in the array, it is a lot better to use a generic List and remove all that code that handles the resizing of the array.
First change the class definition
public class Document
{
public List<string> fullFilePath;
public bool isPatch;
public string destPath;
public Document() { }
public Document(List<string> fullFilePath, bool isPatch, string destPath)
{
this.fullFilePath = fullFilePath;
this.isPatch = isPatch;
this.destPath = destPath;
}
}
And now change your inner loop to
foreach (string file in filesCollected)
{
string bc;
string bcValue;
....
if (bc == bcValue)
{
List<string> files = new List<string>();
files.Add(file);
Documents.Add(new Document(files, true, ""));
docCount++;
}
else
Documents[docCount].fullFilePath.Add(file);
}
Notice that when you need to add a new Document I build a new List<string>, add the current file and pass everything at the constructor (In reality this should be moved directly inside the constructor of the Document class). When you want to add just a new file you could add it directly to the public fullFilePath property
Moving the handling of the files inside the Documents class could be rewritten as
public class Document
{
public List<string> fullFilePath;
public bool isPatch;
public string destPath;
public Document()
{
// Every constructory initializes internally the List
fullFilePath = new List<string>();
}
public Document(string aFile, bool isPatch, string destPath)
{
// Every constructory initializes internally the List
fullFilePath = new List<string>();
this.fullFilePath.Add(aFile);
this.isPatch = isPatch;
this.destPath = destPath;
}
public void AddFile(string aFile)
{
this.fullFilePath.Add(aFile);
}
}
Of course, now in you calling code you pass only the new file or call AddFile without the need to check for the list initialization.
The issue should be here:
string[] sourceFiles = new string[1];
If you move this line of code in your foreach you should solve this problem because in your foreach you always use the same variable, so the same reference.
int docCount = -1;
int i = 0;
List<Document> Documents = new List<Document>();
foreach (string file in filesCollected)
{
string[] sourceFiles = new string[1];
string bc;
string bcValue;
if (Settings.Default.barcodeEngine == "Leadtools")
{
bc = BarcodeReader.ReadBarcodeSymbology(file);
bcValue = "PatchCode";
}
else
{
bc = BarcodeReader.ReadBacrodes(file);
bcValue = "009";
}
if (bc == bcValue)
{
if(Documents.Count > 0)
{
Array.Clear(sourceFiles, 0, sourceFiles.Length);
Array.Resize<string>(ref sourceFiles, 1);
i = 0;
}
sourceFiles[i] = file ;
i++;
Array.Resize<string>(ref sourceFiles, i + 1);
Documents.Add(new Document(sourceFiles, true,""));
docCount++;
}
else
{
if (Documents.Count > 0)
{
sourceFiles[i] = file;
i++;
Array.Resize<string>(ref sourceFiles, i + 1);
Documents[docCount].fullFilePath = sourceFiles;
}
}
}

Reading specific lines in a .Log file

I have a log file that I am reading into different objects. One object starts at a Line that contains the words "Announce message" and the following lines contain the data that belongs to that message. This entry stops at a line that contains the word "Disposed".
I want to read all the data from between these 2 lines that, contains certain words.
Im currently using a Dictionary because the line with "Announce message" also contains a UID but the following lines contain the data for that UID.
How would you do that?
This is what i have come up with so far.
public static void P2PLogParser(List<FileInfo> fileList)
{
foreach (FileInfo fi in fileList)
{
//Læser alle linier i csv fil
foreach (var line in File.ReadAllLines(fi.FullName))
{
string MeterUID = GetMeterUID(line);
string MimHashcode = GetMimHashcode(line);
string FirmwareUploadStatus = GetFirmwareUploadStatus(line);
string IsKnown = GetIsKnown(line);
DateTime P2PTimeStamp = GetTimestamp(line);
if (IsMeterEntry(line) && !meters.ContainsKey(MeterUID))
{
string MeterNr = GetMeterUID(line).Replace("4B414D", "");
int meternr = int.Parse(MeterNr, System.Globalization.NumberStyles.HexNumber);
meters.Add(MeterUID, new Meter()
{
MeterUID = MeterUID,
MeterNR = meternr,
P2Pmeterentry = new List<P2PMeterEntry>()
});
}
if (IsMeterEntry(line))
{
P2PMeterEntry p2pmeter = new P2PMeterEntry
{
P2PTimeStamp = P2PTimeStamp,
MimHashcode = MimHashcode,
FirmwareUploadStatus = FirmwareUploadStatus,
IsKnown = IsKnown,
P2PMetersession = new List<P2PMeterSession>()
};
if (IsNoLongerMeterEntry(line))
{
string SessionLevel = GetLevel(line);
string SessionMessage = GetSessionMessage(line);
string Context = GetSessionContext(line);
P2PMeterSession MeterSession = new P2PMeterSession
{
SessionTimeStamp = P2PTimeStamp,
SessionLevel = SessionLevel,
SessionMessage = SessionMessage,
Context = Context
};
meterSession.Add(MeterSession);
}
meters[MeterUID].P2Pmeterentry.Add(p2pmeter);
}
}
}
}
and the IsMeterEntry and IsNoLongerMeterEntry
//IsMeterSession
public static bool IsMeterEntry(string text)
{
return text.ToLower().Contains("announce message received:");
}
public static bool IsNoLongerMeterEntry(string text)
{
return text.ToLower().Contains("context - disposed");
}
Implement a simple state machine with two states: IgnoreLine (initial state) and Announce.
for each line in log
if line contains "Announce message"
read UID
create a StringBuilder
set state=Announce
else if line contains "Disposed"
store the StringBuilder's content in the dictionary[uid]
set state=IgnoreLine
else if state==Announce and line contains "certain words"
append line to StringBuilder

Using word interop create multiple documents from a template for print preview

I have an app where the user selects from a list all the students they want to print an award document for.
I have a template .doc file that contains 3 text boxes that I populate from code. I can populate the file and show it in the print preview for a single student, but based on how many students are selected I want to create a large document with many pages I can see in print preview before printing and print all at once.
The following is my attempt to turn my working code for a single word document being created from the template to show in print preview. Any ideas?
public void AddStudentToDocument(IEnumerable<StudentToPrint> studentsToPrint )
{
_Application oWordApp = new Application();
string folder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string specificFolder = Path.Combine(folder, "FoothillsAcademy");
string fileLocation = Path.Combine(specificFolder, "CertTemplate.doc");
if (File.Exists(fileLocation))
{
var oWordDoc = oWordApp.Documents.Open(fileLocation);
oWordDoc.Activate();
oWordApp.Selection.TypeParagraph();
foreach (var studentToPrint in studentsToPrint)
{
_Document oDoc = oWordApp.Documents.Add();
Selection oSelection = oWordApp.Selection;
string docText = oWordDoc.Content.Text;
if (docText != null)
{
int boxNumber = 1;
foreach (Shape shape in oWordApp.ActiveDocument.Shapes)
{
if (shape.Type == Microsoft.Office.Core.MsoShapeType.msoTextBox)
{
if (boxNumber == 1)
{
shape.TextFrame.TextRange.Text = studentToPrint.StudentName;
}
if (boxNumber == 2)
{
shape.TextFrame.TextRange.Text = studentToPrint.Rank;
}
if (boxNumber == 3)
{
shape.TextFrame.TextRange.Text = studentToPrint.DateAcheved;
}
boxNumber++;
}
}
_Document oCurrentDocument = oWordApp.Documents.Add(oWordDoc);
copyPageSetup(oCurrentDocument.PageSetup, oDoc.Sections.Last.PageSetup);
oCurrentDocument.Range().Copy();
oSelection.PasteAndFormat(WdRecoveryType.wdFormatOriginalFormatting);
//if (!Object.ReferenceEquals(oWordDoc.Content, oWordDoc.Last()))
oSelection.InsertBreak(WdBreakType.wdSectionBreakNextPage);
}
oWordApp.Visible = true;
oWordApp.ShowStartupDialog = true;
oWordApp.ActiveDocument.PrintPreview();
}
}
}
private void copyPageSetup(PageSetup source, PageSetup target)
{
target.PaperSize = source.PaperSize;
if (!source.Orientation.Equals(target.Orientation))
target.TogglePortrait();
target.TopMargin = source.TopMargin;
target.BottomMargin = source.BottomMargin;
target.RightMargin = source.RightMargin;
target.LeftMargin = source.LeftMargin;
target.FooterDistance = source.FooterDistance;
target.HeaderDistance = source.HeaderDistance;
target.LayoutMode = source.LayoutMode;
}
public class StudentToPrint
{
public string StudentName { get; set; }
public string Rank { get; set; }
public string DateAcheved { get; set; }
}
Currently I am testing this with a collection of StudentsToPrint added below. Based on the data below I would expect to see 3 certificates personalized for each of the 3 students. Each certificate would be on its own page.
List<StudentToPrint> listOfStudents = new List<StudentToPrint>
{
new StudentToPrint
{
DateAcheved = DateTime.Now.ToShortDateString(),
Rank = "5th Degree",
StudentName = "Scott LaFoy"
},
new StudentToPrint
{
DateAcheved = DateTime.Now.ToShortDateString(),
Rank = "3rd Degree",
StudentName = "John Doe"
},
new StudentToPrint
{
DateAcheved = DateTime.Now.ToShortDateString(),
Rank = "2nd Degree",
StudentName = "Jane Doe"
}
};
The template is a word doc that has 3 text boxes. Using the text box lets me set font and position different for each of them as well as making the background transparent so the template background shows through. I am sure there is another way to do this as well but there is not much on this topic around.

Categories