Поиск данных в функциональном стиле
От: Аноним  
Дата: 16.10.09 20:32
Оценка:
Здравствуйте.

Ситуация следующая: мне нужно в файлах искать вхождения некоторых данных. Раньше файлы были маленькие и данных были в одном экземпляре (т.е. требовалось найти вхождение одних и тех же данных), сейчас же условия изменились и нужно найти разные данные, плюс к этому файлы очень сильно выросли (около 1Гб).
Задачу я решил (код ниже), но т.к. время время позволяет, то я решил попробовать написать в функциональном стиле, но как не пишу, всё равно получается, то, что есть.
Помогите, пожалуйста, переписать этот код. Я пытался и на F# (недавно начал изучать) и на C#, никак не выходит.

Для C# написал только такой код, а что дальше с ним делать — не понятно

        IEnumerable<byte> GetBytes(string fileName)
        {
            int bytesReaden;
            byte[]buffer = new byte[4096];

            using (var file = File.OpenRead(fileName))
                while ((bytesReaden = file.Read(buffer, 0, buffer.Length))>0)
                {
                    for (int i = 0; i < bytesReaden; i++)
                    {
                        yield return buffer[i];
                    }
                }
        }



А вот существующий код:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;

namespace TestProgram
{
    internal class Entry
    {
        public string Item;
        public int Offset;
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            string fileName = new StackTrace(true).GetFrame(0).GetFileName();
            var entries = new[]
                {
                    "Microsoft.Build.Conversion.v3.5.xml",
                    "Microsoft.Build.Engine.xml",
                    "Microsoft.Build.Framework.xml",
                    "Microsoft.Build.Utilities.v3.5.xml",
                    "Microsoft.VisualC.STLCLR.xml",
                    "System.AddIn.Contract.xml",
                    "System.AddIn.xml",
                    "System.Core.xml",
                    "System.Data.DataSetExtensions.xml",
                    "System.Data.Linq.xml",
                    "System.DirectoryServices.AccountManagement.xml",
                    "System.Management.Instrumentation.xml",
                    "System.Net.xml",
                    "System.ServiceModel.Web.xml",
                    "System.Web.Extensions.Design.xml",
                    "System.Web.Extensions.xml",
                    "System.Windows.Presentation.xml",
                    "System.WorkflowServices.xml",
                    "System.Xml.Linq.xml",
                };

            var list = new List<Entry>();
            list.AddRange(FindEntries(fileName, entries));
            Console.WriteLine("list: {0}", list.Count);
        }

        private static IEnumerable<Entry> FindEntries(string fileName, string[] entries)
        {
            var list = new List<Entry>();

            using (FileStream file = File.OpenRead(fileName))
            {
                foreach (string entryString in entries)
                {
                    file.Seek(0, SeekOrigin.Begin);

                    var buffer = new byte[4096];
                    int bytesReaden;
                    int offset = 0;

                    byte[] entryBytes = Encoding.ASCII.GetBytes(entryString);
                    int curPos = 0;

                    while ((bytesReaden = file.Read(buffer, offset, buffer.Length - offset)) > 0)
                    {
                        bytesReaden += offset;
                        for (int i = 0; i < bytesReaden - entryBytes.Length; i++)
                        {
                            bool match = true;
                            for (int j = 0; j < entryBytes.Length; j++)
                            {
                                if (buffer[i + j] != entryBytes[j])
                                {
                                    match = false;
                                    break;
                                }
                            }

                            if (match)
                            {
                                var entry = new Entry
                                    {
                                        Item = entryString,
                                        Offset = curPos + i
                                    };
                                i += entryBytes.Length;
                                list.Add(entry);
                            }
                        }
                        offset = entryBytes.Length;
                        curPos += bytesReaden - offset;
                        for (int i = 0; i < entryBytes.Length; i++)
                        {
                            buffer[i] = buffer[bytesReaden - entryBytes.Length + i];
                        }
                    }
                }
            }

            return list;
        }
    }
}

//"Microsoft.Build.Conversion.v3.5.xml",
Re: Поиск данных в функциональном стиле
От: Lloyd Россия  
Дата: 17.10.09 00:11
Оценка: 2 (1)
Здравствуйте, Аноним, Вы писали:

А>Помогите, пожалуйста, переписать этот код. Я пытался и на F# (недавно начал изучать) и на C#, никак не выходит.


Лучше потратьте это время на изучение алгоритмов поиска подстроки. Например, есть алгоритмы, не требующие возвращаться "назад".
Когда вы реализуете этот алгоритм у себя, будет гораздо проще понять, куда тут можно применить функциональное программирование. В частности, потом может получиться что-то типа Rx
Re: Поиск данных в функциональном стиле
От: Lloyd Россия  
Дата: 17.10.09 01:57
Оценка: 3 (1)
Здравствуйте, Аноним, Вы писали:

А>Задачу я решил (код ниже), но т.к. время время позволяет, то я решил попробовать написать в функциональном стиле, но как не пишу, всё равно получается, то, что есть.

А>Помогите, пожалуйста, переписать этот код. Я пытался и на F# (недавно начал изучать) и на C#, никак не выходит.

Как-то вот так.

//System.AddIn.xml
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;

namespace ConsoleApplication7 {
    internal class Program {
        internal class Entry {
            public string Item;
            public int Offset;

            public override string ToString() {
                return new { Item, Offset }.ToString();
            }
        }

        private static Action<T> GetMatcher<T>(IEnumerable<T> pattern, Action found) {
            IEqualityComparer<T> comparer = EqualityComparer<T>.Default;

            var states = new List<int>();
            return next => {
                for (int i = 0; i < states.Count; i++) {
                    states[i]++;
                }
                states.RemoveAll(n => !comparer.Equals(pattern.ElementAt(n), next));
                if (states.Count > 0 && states[0] == pattern.Count() - 1) {
                    found();
                    states.RemoveAt(0);
                }
                if (comparer.Equals(pattern.ElementAt(0), next)) {
                    states.Add(0);
                }
            };
        }

        private static Action<T> Compose<T>(IEnumerable<Action<T>> actions) {
            return t => {
                foreach (var action in actions) {
                    action(t);
                }
            };
        }

        private static void Main() {
            string fileName = new StackTrace(true).GetFrame(0).GetFileName();
            var entries = new[] {
                "Microsoft.Build.Conversion.v3.5.xml",
                "Microsoft.Build.Engine.xml",
                "Microsoft.Build.Framework.xml",
                "Microsoft.Build.Utilities.v3.5.xml",
                "Microsoft.VisualC.STLCLR.xml",
                "System.AddIn.Contract.xml",
                "System.AddIn.xml",
                "System.Core.xml",
                "System.Data.DataSetExtensions.xml",
                "System.Data.Linq.xml",
                "System.DirectoryServices.AccountManagement.xml",
                "System.Management.Instrumentation.xml",
                "System.Net.xml",
                "System.ServiceModel.Web.xml",
                "System.Web.Extensions.Design.xml",
                "System.Web.Extensions.xml",
                "System.Windows.Presentation.xml",
                "System.WorkflowServices.xml",
                "System.Xml.Linq.xml",
            };

            var log = new List<Entry>();

            using (var stream = File.OpenRead(fileName))
            using (var reader = new StreamReader(stream)) {
                int pos = 0;

                var q = from e in entries
                        select GetMatcher(e, () => log.Add(new Entry { Item = e, Offset = pos - e.Length }));

                Action<char> composite = Compose(q.ToArray());

                while (!reader.EndOfStream) {
                    string line = reader.ReadLine();
                    foreach (var ch in line) {
                        composite(ch);
                        pos++;
                    }
                }
            }
        }
    }
}

//"Microsoft.Build.Conversion.v3.5.xml",
Re: Поиск данных в функциональном стиле
От: Аноним  
Дата: 17.10.09 10:10
Оценка: 1 (1) -1
А почему не напрячь виндовозную систему поиска в файлах — этож ее прямая задача — и зачем таки вы сибе морочите голову чужыми проблемами!?
Re: Поиск данных в функциональном стиле
От: Пельмешко Россия blog
Дата: 17.10.09 11:28
Оценка: 1 (1)
Здравствуйте, Аноним, Вы писали:

А>Я пытался и на F# (недавно начал изучать).


Если забить на многие факторы, то отлично ложиться на F#

(* Microsoft.Build.Conversion.v3.5.xml *)
open System.Diagnostics
open System.IO

let main () =

    let fileName =
        StackTrace(true).GetFrame(0).GetFileName()

    let entries =
      [ "Microsoft.Build.Conversion.v3.5.xml";
        "Microsoft.Build.Engine.xml";
        "Microsoft.Build.Framework.xml";
        "Microsoft.Build.Utilities.v3.5.xml";
        "Microsoft.VisualC.STLCLR.xml";
        "System.AddIn.Contract.xml";
        "System.AddIn.xml";
        "System.Core.xml";
        "System.Data.DataSetExtensions.xml";
        "System.Data.Linq.xml";
        "System.DirectoryServices.AccountManagement.xml";
        "System.Management.Instrumentation.xml";
        "System.Net.xml";
        "System.ServiceModel.Web.xml";
        "System.Web.Extensions.Design.xml";
        "System.Web.Extensions.xml";
        "System.Windows.Presentation.xml";
        "System.WorkflowServices.xml";
        "System.Xml.Linq.xml"; ]
    
    (* построчное чтение файла *)
    seq { use file = File.OpenRead(fileName)
          use reader = new StreamReader(file)
          while not reader.EndOfStream do
          yield reader.ReadLine() }
    
    (* добавление позиции начала строк *)
    |> Seq.scan (fun (p,o:string) s -> p+o.Length, s) (0,"")
    |> Seq.skip 1
    
    (* отображение в последовательности совпадений *)
    |> Seq.map (fun (pos,line) ->
    
       entries
       |> Seq.map (fun e -> (* поиск entry в строке *)
          e, line.IndexOf(e, System.StringComparison.Ordinal))
       |> Seq.filter (snd >> (<=) 0)      (* отбираем совпадения  *)
       |> Seq.map (fun (e,m) -> e, m+pos) (* корректируем позицию *)
       )
    
    (* соединение в единую последовательность и вывод на экран *)
    |> Seq.concat
    |> Seq.iter (printfn "%A")


На первый взгляд сразу видно что оно не умеет:
1. Не найдёт два совпадения в одной строке
2. Походу не учитывает crlf при подсчёте позиции в файле...
3. Не знаю как работает String.IndexOf(), но думаю оно бэктрекится

Зато ни единого mutable closure и выглядит как единый pipe.
Re[2]: Поиск данных в функциональном стиле
От: Lloyd Россия  
Дата: 17.10.09 11:33
Оценка:
Здравствуйте, Пельмешко, Вы писали:

П>Зато ни единого mutable closure и выглядит как единый pipe.


1. Расход по памяти потенциально выше (кто знает, какой длины там строки, может там весь файл в одну строку выстроен)
2. Не найдет построки с переводм строки внутри.
Re[3]: Поиск данных в функциональном стиле
От: Пельмешко Россия blog
Дата: 17.10.09 12:39
Оценка:
Здравствуйте, Lloyd, Вы писали:

L>Здравствуйте, Пельмешко, Вы писали:


П>>Зато ни единого mutable closure и выглядит как единый pipe.


L>1. Расход по памяти потенциально выше (кто знает, какой длины там строки, может там весь файл в одну строку выстроен)

L>2. Не найдет построки с переводм строки внутри.
L>)

Не спорю Меня просто прёт как выглядит

Честно говоря не понимаю зачем анониму функциональность, раз объёмы данных возросли, а сложность алгоритма выросла незначительно...
Я бы оставил императивный вариант, применив побольше декомпозиции, где это возможно...

p.s. Можно переписать чтобы находило совпадения в одной строке:
    (* отображение в последовательности совпадений *)
    |> Seq.map (fun (pos,line) -> seq {
    
       for e in entries do
       let rec matches (i:int) = seq {
           let i = line.IndexOf(e, i)
           if i >= 0 then
              yield  e, i + pos
              yield! matches (i+1) }
       
       yield! matches 0 })
Круто, конечно, выглядит рекурсивный генератор, но на списках короче будет и не лениво:
    (* отображение в последовательности совпадений *)
    |> Seq.map (fun (pos,line) ->

       entries
       |> Seq.map (fun e ->
          let rec matches (i:int) ls =
              match line.IndexOf(e,i) with
              | i when i = -1 -> ls
              | i -> matches (i+1) ((e,i+pos)::ls)
          matches 0 []))
       |> Seq.concat


p.s.s. Может кто знает, ввели в C# 4.0 yield! (yield many или хз как назвать) или нет?
Re[4]: Поиск данных в функциональном стиле
От: Lloyd Россия  
Дата: 17.10.09 13:38
Оценка:
Здравствуйте, Пельмешко, Вы писали:

П>Здравствуйте, Lloyd, Вы писали:


П>Честно говоря не понимаю зачем анониму функциональность, раз объёмы данных возросли, а сложность алгоритма выросла незначительно...

П>Я бы оставил императивный вариант, применив побольше декомпозиции, где это возможно...

В первоначальном варианте ваще ужас-ужас. У него так файл пробегается столько раз, сколько подстрока ищется.

П>p.s.s. Может кто знает, ввели в C# 4.0 yield! (yield many или хз как назвать) или нет?


Не ввели.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.