Здравствуйте, помогите, пожалуйста, найти красивое решение. Я ищу работу C# стажера, и мне предложили выполнить тестовое задание. Я его с треском провалил. Но дело не в этом, я просто хочу узнать, как такое задание делают нормальные программисты. Хочу научится. Опыта нет, а желание работать есть.
Вот текст задания:
Создать приложение, ищущее текст в файлах, находящихся в указанном каталоге и его подкаталогах. Приложение должно использовать многозадачность. Можно сделать его консольным.
А вот мое решение:
Main.cs
using System;
using System.IO;
using System.Text;
namespace FindText
{
public class App
{
public static void Main(string[] args)
{
//Если программа запущена без аргументов или каталог задан,
//а строка нет, то информируем о параметрах запуска и выходим if((args.Length < 1)||(args.Length < 3 && args[0] == "-d"))
{
PrintInfo();
return;
}
//Получаем текущий каталог на случай, если в опциях не будет задан другойstring dir = Directory.GetCurrentDirectory();
//Считаем, что каталог не задан, а значит текст для поиска идет с нулевого
//элемента массива аргументовint textBegining = 0;
StringBuilder text = new StringBuilder();
//Задан ли каталог?if(args[0] == "-d")
{
dir = args[1];
//Задан существующий каталог?if(!Directory.Exists(dir))
{
Console.WriteLine("Directory does not exists");
return;
}
//Текст в массиве аргументов идет со второй позиции
textBegining = 2;
}
//Получаем строку для поиска путем слияния всех аргументов,
//не относящихся к каталогуfor(int i = textBegining; i < args.Length; i++)
{
text.Append(args[i]);
}
//Вызываем статический метод поиска
TextFinder.Find(dir, text.ToString());
Console.WriteLine("OK");
return;
}
private static void PrintInfo()
{
Console.WriteLine("findtext. Test job. Vladimir V Davydov. 2005");
Console.WriteLine("Usage: findtext [-d directory] \"text to find\"");
}
}
}
TextFinder.cs
/*
* Created by SharpDevelop.
* User: admin
* Date: 10.02.2006
* Time: 17:54
*
* To change this template use Tools | Options | Coding | Edit Standard Headers.
*/using System;
using System.IO;
using System.Collections;
using System.Threading;
namespace FindText
{
/// <summary>
/// Класс, осуществляющий поиск в каталоге.
/// </summary>public class TextFinder
{
//Счетчик запущенных потоков, обрабатывающих каталогиprivate static uint _directoryThreadCounter;
//Счетчик запущенных потоков, обрабатывающих файлыprivate static uint _fileThreadCounter;
//Мьютекс для работы с очередью каталоговprivate static Mutex _dirMutex;
//Мьютекс для работы с очередью файловprivate static Mutex _fileMutex;
//Мьютекс для работы со счетчиком каталоговprivate static Mutex _dirCounterMutex;
//Мьютекс для работы со счетчиком файловprivate static Mutex _fileCounterMutex;
//Очередь каталогов для обработкиprivate static Queue _dirFIFO;
//Очередь файлов для обработкиprivate static Queue _fileFIFO;
//Текст для поискаprivate static string _text;
/// <summary>
/// Конструктор
/// </summary>
/// <remarks>
/// Конструктор спрятал от греха подальше,
/// т.к. будет вызывать только один статический метод
///</remarks>private TextFinder()
{
}
///<summary>Функция потока обработки каталогов</summary> private static void DirProc(object data)
{
string currentDir;
//Пытаемся получить имя каталога из переданного потоку объектаtry
{
currentDir = (string)data;
}
catch(InvalidCastException)
{
//Производим захват мьютекса для работы со счетчиком потоков
_dirCounterMutex.WaitOne();
//Закончили работу и себя за поток уже не считаем ;)
_directoryThreadCounter--;
_dirCounterMutex.ReleaseMutex();
return;
}
//Console.WriteLine("Processing directory " + currentDir);
//получаем список подкаталогов в текущем каталогеstring [] subDirectories = Directory.GetDirectories(currentDir);
//производим захват мьютекса для работы с очередью подкаталогов
_dirMutex.WaitOne();
//добавляем в очередь подкаталогиforeach(string dir in subDirectories)
{
_dirFIFO.Enqueue(dir);
}
//Высвобождаем мьютекс
_dirMutex.ReleaseMutex();
//получаем список файлов в текущем каталогеstring [] files = Directory.GetFiles(currentDir);
//производим захват мьютекса для работы с очередью файлов
_fileMutex.WaitOne();
//Добавляем в очередь обработки файлыforeach(string fileName in files)
{
_fileFIFO.Enqueue(fileName);
}
_fileMutex.ReleaseMutex();
//Производим захват мьютекса для работы со счетчиком потоков
_dirCounterMutex.WaitOne();
//Закончили работу и себя за поток уже не считаем ;)
_directoryThreadCounter--;
_dirCounterMutex.ReleaseMutex();
return;
}
/// <summary>
/// Функция потока обработки файлов
/// </summary>
/// <param name="data"></param>private static void FileProc(object data)
{
string line;
string file;
//Получаем имя файла из переданного объектаtry
{
file = (string)data;
}
catch(InvalidCastException)
{
_fileCounterMutex.WaitOne();
_fileThreadCounter--;
_fileCounterMutex.ReleaseMutex();
return;
}
//Console.WriteLine("Processing file " + file);#region Сам поиск
try
{
//Если ищем текст, и все файлы рассматриваем как текстовые,
//то должно быть что-то подобноеusing(StreamReader rs = new StreamReader(file))
{
while ((line = rs.ReadLine()) != null)
{
if(line.IndexOf(_text) != -1)
Console.WriteLine("Text founded in " + file);
}
}
}
catch
{
_fileCounterMutex.WaitOne();
_fileThreadCounter--;
_fileCounterMutex.ReleaseMutex();
return;
}
#endregion Сам поиск
//Выходим из потока, а перед выходом уменьшаем кол-во ссылок
_fileCounterMutex.WaitOne();
_fileThreadCounter--;
_fileCounterMutex.ReleaseMutex();
return;
}
public static void Find(string directory, string text)
{
//Создаем очереди
_dirFIFO = new Queue();
_fileFIFO = new Queue();
//Создаем мьютексы
_dirMutex = new Mutex();
_fileMutex = new Mutex();
_dirCounterMutex = new Mutex();
_fileCounterMutex = new Mutex();
_text = text;
//Помещаем в очередь каталогов стартовый
_dirFIFO.Enqueue(directory);
uint dirCount = 0;
uint fileCount = 0;
string dir = string.Empty;
string file = string.Empty;
do
{
_dirMutex.WaitOne();
//Смотрим, как там поживает наша очередьif(_dirFIFO.Count > 0)
{
//Если в ней есть, что обрабатывать,
//то извлекаем элемент
dir = (string)_dirFIFO.Dequeue();
_dirCounterMutex.WaitOne();
++_directoryThreadCounter;
_dirCounterMutex.ReleaseMutex();
}
else
dir = string.Empty;
_dirMutex.ReleaseMutex();
//Добавляем в пул потоков поток для выбранного каталогаif(dir != string.Empty)
ThreadPool.QueueUserWorkItem(new WaitCallback(DirProc), dir);
_fileMutex.WaitOne();
//То же, что и с каталогами, пока очередь не пуста,
//обрабатываем очередь, извлекая файлы и добавляя в
//пул потоков вызовif(_fileFIFO.Count > 0)
{
file = (string)_fileFIFO.Dequeue();
_fileCounterMutex.WaitOne();
++_fileThreadCounter;
_fileCounterMutex.ReleaseMutex();
}
else
file = string.Empty;
_fileMutex.ReleaseMutex();
if(file != string.Empty)
ThreadPool.QueueUserWorkItem(new WaitCallback(FileProc), file);
//Получаем кол-во запущенных потоков, обрабатывающих каталоги
_dirCounterMutex.WaitOne();
dirCount = _directoryThreadCounter;
_dirCounterMutex.ReleaseMutex();
//Получаем кол-во запущенных потоков, обрабатывающих файлы
_fileCounterMutex.WaitOne();
fileCount = _fileThreadCounter;
_fileCounterMutex.ReleaseMutex();
//Заснем (~ 40 мс)
Thread.Sleep(0);
//Если хоть один поток еще жив, ждем его
}while(dirCount > 0 || fileCount > 0);
}
}
}
Дополнительные "ненужные" счетчики и связанная с ними задержка возникли по причине того, что в .NET Framework 1.1 глюк с заврешением работы приложения: если поток из пула с установленным IsBackground в false завершается позже потока, его в пул поместившего, приложение зависает. Я с этим столкнулся при выполнении задания. Пришлось искусственно порождающий поток останавливать.
Как все это сделать правильней и элегантней? Научите идиота.
14.02.06 22:42: Перенесено модератором из 'О жизни' — Кодт
Честно говоря, страшно даже на твой код смотреть. Зачем так наворачивать-то? И на фига тебе мьютексы тут дались? Все это значительно проще делается. Типа такого (не компилил но должно работать):
using System;
using System.IO;
using System.Threading;
class Program
{
delegate void SearchCallBack(DirectoryInfo dir, string searchString);
static void Main(params string[] args)
{
IAsyncResult res = ((SearchCallBack)ScanDirectory).
BeginInvoke(new DirectoryInfo("C:\\projects"), "Namespace", null, null);
res.AsyncWaitHandle.WaitOne();
}
static void ScanDirectory(DirectoryInfo dir, string searchString)
{
foreach (FileSystemInfo fi in dir.GetFileSystemInfos())
if (fi is DirectoryInfo)
ScanDirectory(fi as DirectoryInfo, searchString);
else
{
try
{
ScanFile(fi.FullName, searchString);
}
catch (IOException)
{
Console.WriteLine("Unable to open file '{0}'", fi.FullName);
}
}
}
static void ScanFile(string fileName, string searchString)
{
using (StreamReader sr = File.OpenText(fileName))
{
string line = null;
while ((line = sr.ReadLine()) != null)
{
int index = -1;
if ((index = line.IndexOf(searchString)) != -1)
Console.WriteLine("Found in '{0}' at pos {1}", fileName, index);
}
}
}
}
Кстати
А какие пожелания по поводу работы? Интересует именно C#? (из .NET)
Можно было еще вместо стандартного поиска подстроки в строке использовать алгоритм Кнута-Мориса-Прата (прошу прощения если ошибся в написании фамилий, если проще то КМП).
Здравствуйте, zubr, Вы писали:
Z>Здравствуйте, Воронков Василий, Вы писали:
Z>Можно было еще вместо стандартного поиска подстроки в строке использовать алгоритм Кнута-Мориса-Прата (прошу прощения если ошибся в написании фамилий, если проще то КМП).
Лучше БМ, но он сложнее в реализации
Здравствуйте, zubr, Вы писали:
Z>Можно было еще вместо стандартного поиска подстроки в строке использовать алгоритм Кнута-Мориса-Прата (прошу прощения если ошибся в написании фамилий, если проще то КМП).
Если честно, мне кажется что не лучше. В каком году придумали этот алгоритм? С современными мегагерцами String.IndexOf — вполне производительное решение. Например, поиск в файле состоящем из *миллиона* строк длиной в 80 символов занимает на дохлом Celeron 2400 меньше 300 миллисекунд. Конечно, не слишком шустро, но если вспомнить что файл такого размера записанный в 8-битной кодировке занимает под 80 мегабайт..
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Здравствуйте, zubr, Вы писали:
Z>>Можно было еще вместо стандартного поиска подстроки в строке использовать алгоритм Кнута-Мориса-Прата (прошу прощения если ошибся в написании фамилий, если проще то КМП).
ВВ>Если честно, мне кажется что не лучше. В каком году придумали этот алгоритм? С современными мегагерцами String.IndexOf — вполне производительное решение. Например, поиск в файле состоящем из *миллиона* строк длиной в 80 символов занимает на дохлом Celeron 2400 меньше 300 миллисекунд. Конечно, не слишком шустро, но если вспомнить что файл такого размера записанный в 8-битной кодировке занимает под 80 мегабайт..
Какая, говорите, пропускная способность у вашего винчестера?
Здравствуйте, GlebZ, Вы писали:
GZ>Сомневаюсь что они имели ввиду просто открытие второго потока вначале. Скорее многопоточность касалась операции поиска значения.
Это в принципе можно прикрутить с минимальными изменениями. Так как мы ищем не первый матч а все совпадения то процедуры поиска вообще друг от друга не зависят. Правда, смысла от такого распараллеливания будет ИМХО немного.
Здравствуйте, krasin, Вы писали:
K>Какая, говорите, пропускная способность у вашего винчестера?
А вообще причем здесь винт? Тест я проводил на основе нескольких строк в памяти. И оверхед на IO будет неизбежен какой бы мы алгоритм не избрали. Что как раз и показывает что не в скорость алгоритма поиска мы упираемся.
Здравствуйте, vitaly_spb, Вы писали:
GZ>>Сомневаюсь что они имели ввиду просто открытие второго потока вначале. Скорее многопоточность касалась операции поиска значения. _>странно оно как-то, но звучит действительно как будто надо для подкаталогов по потоку создать
Судя по постановке, наиболее эффективным приложением многопоточности будет поиск текста для каждого файла в отдельном потоке. И вероятней всего, через очередь потоков.
ВВ>А вообще причем здесь винт? Тест я проводил на основе нескольких строк в памяти. И оверхед на IO будет неизбежен какой бы мы алгоритм не избрали. Что как раз и показывает что не в скорость алгоритма поиска мы упираемся.
Если честно, мне кажется что не лучше. В каком году придумали этот алгоритм? С современными мегагерцами String.IndexOf — вполне производительное решение. Например, поиск в файле состоящем из *миллиона* строк длиной в 80 символов занимает на дохлом Celeron 2400 меньше 300 миллисекунд. Конечно, не слишком шустро, но если вспомнить что файл такого размера записанный в 8-битной кодировке занимает под 80 мегабайт..
эээ... нескольких?!
...Ei incumbit probatio, qui dicit, non qui negat...
Здравствуйте, Воронков Василий, Вы писали:
K>>Какая, говорите, пропускная способность у вашего винчестера?
ВВ>А вообще причем здесь винт? Тест я проводил на основе нескольких строк в памяти. И оверхед на IO будет неизбежен какой бы мы алгоритм не избрали. Что как раз и показывает что не в скорость алгоритма поиска мы упираемся.
Если они не находятся в кэше.
Здравствуйте, GlebZ, Вы писали:
GZ>Если они не находятся в кэше.
Если имеется в виду программный кэш, то в случае с Find in Files (о чем собственно и идет речь, если я конечно правильно понял) он как бы не особо поможет. Да и кэш винта строго говоря тоже.