Подсчет групп файлов на основе размера

#c# #.net #linq

#c# #.net #linq

Вопрос:

Как я могу это упростить? Я пытаюсь получить количество файлов Excel из каталога и подкаталогов на основе их размера. У меня есть как минимум 10 различных группировок.

 var queryList2Only = from i in di.GetFiles("*.xls", SearchOption.TopDirectoryOnly)
                                 .Where(f => f.Length <= 5120)
                     select i.Length;
if (queryList2Only.Any())
{
    dest.WriteLine("Excel File <= 5 KB");
    dest.WriteLine(queryList2Only.Count());
    dest.WriteLine("");
}

var queryList3Only = from i in di.GetFiles("*.xls", SearchOption.TopDirectoryOnly)
                                 .Where(f => f.Length > 5120 amp;amp; f.Length <= 10240)
                     select i.Length;
if (queryList3Only.Any())
{
    dest.WriteLine("Excel File > 5 KB and <= 10 KB");
    dest.WriteLine(queryList3Only.Count());
    dest.WriteLine("");
  

Редактировать:
Мне нужно это

   <= 5 KB,> 5 KB and <= 10 KB,> 10 KB and <= 20 KB,> 20 KB and <= 100 KB,> 100 KB and <= 1000 KB,> 1000 KB and <=5 MB,> 5 MB and <=10 MB,> 10 MB and <=20 MB,> 20 MB and <=50 MB,> 50 MB and <=100 MB
  

 private void button1_Click(object sender, EventArgs e)
        {



            DirectoryInfo Folder = new DirectoryInfo(textBox1.Text);
            var _logFolderPath4 = Path.Combine(textBox1.Text.Trim(), "log");
            if (Folder.Exists)

                if (!Directory.Exists(_logFolderPath4))
                    Directory.CreateDirectory(_logFolderPath4);

            DirectoryInfo di = new DirectoryInfo(@"D:Material");
            bool time = false;
            using (var dest = File.AppendText(Path.Combine(_logFolderPath4, "Excel.txt")))
            {

                    if (!time)
                    {
                        dest.WriteLine("---------------------"   DateTime.Now   "---------------------");
                        dest.WriteLine("");
                        time = true;
                    }
                    CountFiles(dest, di, @"*.txt");
            }

    }
  

Комментарии:

1. Определите свои диапазоны в массиве, выполняйте цикл по нему?

2. Как это связано с шаблоном репозитория?

Ответ №1:

Вам нужно иметь свои диапазоны в коллекции и выполнять перечисление по ним. Вот пример, который должен вас подтолкнуть к работе — массив размеров содержит шаги, конечно, вы должны выбрать шаги, которые имеют смысл для вашего приложения:

 int[] sizes = Enumerable.Range(0,10).Select(n => (int)Math.Pow(2,n   8)).ToArray();
int lower = 0;
foreach(var size in sizes)
{
    var files = di.GetFiles("*.*").Where(f => f.Length >= lower amp;amp; f.Length < size);
    Console.WriteLine("Between {0} and {1} bytes:", lower,size);
    foreach(var file in files)
        Console.WriteLine("t{0}",file);
    lower = size;
}
  

Ответ №2:

Вам не обязательно нужен LINQ для этого. Для вас было бы эффективнее просто перебирать это. Хотя решение Rup отлично использует LINQ здесь.

Вот более полная версия, адаптированная именно для того, что вы хотите сделать.

 // count it
CountFiles(dest, di, @"*.xls");

public void CountFiles(TextWriter writer, DirectoryInfo directory, string searchPattern)
{
    var counter = new FileGroupCounter
    {
        { 5,    Multiplier.K },
        { 10,   Multiplier.K },
        { 20,   Multiplier.K },
        { 100,  Multiplier.K },
        { 1000, Multiplier.K },
        { 5,    Multiplier.M },
        { 10,   Multiplier.M },
        { 20,   Multiplier.M },
        { 50,   Multiplier.M },
        { 100,  Multiplier.M },
    };

    foreach (var file in directory.EnumerateFiles(searchPattern, SearchOption.AllDirectories))
                         // or use GetFiles() if you're not targeting .NET 4.0
    {
        counter.CountFile(file);
    }

    foreach (var result in counter)
    {
        writer.WriteLine("Excel File "   result);
        writer.WriteLine(result.Count);
        writer.WriteLine();
    }
}

// and the supporting classes
public enum Multiplier : long
{
    K = 1 << 10,
    M = 1 << 20,
    G = 1 << 30,
    T = 1 << 40,
}

public class FileGroupCounter : IEnumerable<FileGroupCounter.Result>
{
    public ReadOnlyCollection<long> Limits { get { return roLimits; } }
    public ReadOnlyCollection<int> Counts { get { return roCounts; } }
    public ReadOnlyCollection<Multiplier> Multipliers { get { return roMultipliers; } }

    public FileGroupCounter()
    {
        limits = new List<long>();
        counts = new List<int>();
        multipliers = new List<Multiplier>();
        roLimits= limits.AsReadOnly();
        roCounts= counts.AsReadOnly();
        roMultipliers= multipliers.AsReadOnly();
    }

    private List<long> limits;
    private List<int> counts;
    private List<Multiplier> multipliers;
    private ReadOnlyCollection<long> roLimits;
    private ReadOnlyCollection<int> roCounts;
    private ReadOnlyCollection<Multiplier> roMultipliers;

    private long CalculateLength(int index)
    {
        return limits[index] * (long)multipliers[index];
    }

    public void Add(long limit, Multiplier multiplier)
    {
        int lastIndex = limits.Count - 1;
        if (lastIndex >= 0 amp;amp; limit * (long)multiplier <= CalculateLength(lastIndex))
            throw new ArgumentOutOfRangeException("limit, multiplier", "must be added in increasing order");

        limits.Add(limit);
        counts.Add(0);
        multipliers.Add(multiplier);
    }

    public bool CountFile(FileInfo file)
    {
        if (file == null)
            throw new ArgumentNullException("file");

        for (int i = 0; i < limits.Count; i  )
        {
            if (file.Length <= CalculateLength(i))
            {
                counts[i]  ;
                return true;
            }
        }
        return false;
    }

    public IEnumerator<Result> GetEnumerator()
    {
        for (int i = 0; i < limits.Count; i  )
        {
            if (counts[i] > 0)
                yield return new Result(this, i);
        }
    }
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }

    public class Result
    {
        public long Limit { get { return counter.limits[index]; } }
        public int Count { get { return counter.counts[index]; } }
        public Multiplier Multiplier { get { return counter.multipliers[index]; } }

        internal Result(FileGroupCounter counter, int index)
        {
            this.counter = counter;
            this.index = index;
        }
        private FileGroupCounter counter;
        private int index;

        public override string ToString()
        {
            if (index > 0)
                return String.Format("> {0} {1}B and <= {2} {3}B",
                    counter.limits[index - 1], counter.multipliers[index - 1],
                    counter.limits[index], counter.multipliers[index]);
            else
                return String.Format("<= {0} {1}B",
                    counter.limits[index], counter.multipliers[index]);
        }
    }
}
  

Комментарии:

1. @bala: Да. Но вы всегда можете вернуться к использованию GetFiles() , если вы не используете .NET 4.0.

2. <= 5 КБ,> 5 КБ и <= 10 КБ,> 10 КБ и <= 20 КБ,> 20 КБ и <= 100 КБ,> 100 КБ и <= 1000 КБ,> 1000 КБ и <=5 МБ,> 5 МБ и <=10 МБ,> 10 МБ и <=20 МБ,> 20 МБ и <=50 МБ,> 50 МБ и <=100 МБ

3. @bala: Я добавил более полную версию. Должна быть возможность подсчитывать нужные диапазоны. Делайте с этим, что хотите.

4. @Jeff: Не удалось найти коллекцию типа или пространства имен, доступного только для чтения.

5. @bala: Он использует ReadOnlyCollection найденное в System.Collections.ObjectModel пространстве имен. Вам придется включить using System.Collections.ObjectModel; в свой код.

Ответ №3:

Я думаю, что единственной реальной оптимизацией здесь было бы убедиться, что вы вызываете di.GetFiles("*.xls", SearchOption.TopDirectoryOnly) только один раз — поскольку это фактически попадет в файловую систему, а не будет лениво выполняться, как большинство LINQ. Конечно, файловая система будет кэшировать результаты этого, но не может быть медленнее, чтобы оставаться в памяти и повторно использовать список.

Как только вы окажетесь в памяти, Джефф, возможно, будет прав — просто посчитайте сами — подумал, что это выглядит не очень элегантно 🙂 и, вероятно, здесь это не имеет большого значения, если вы не имеете дело с огромными числами. Вы просто хотите попытаться уменьшить количество выделений / перераспределений. С таким количеством LINQ, сколько я могу втиснуть

 var files = di.GetFiles("*.xls", SearchOption.TopDirectoryOnly);
// map to a list of numbers, 0 = up to 5K, 1 = 5-10, etc.
var sizes = files.Select(f => (f.Length / 5120));
var countsBySize = sizes.GroupBy(s => s)
                        .Select(g => new { Size = g.Key, Count = g.Count() })
                        .OrderBy(s => s.Size);
var results = countBySize.ToList();
  

который возвращает список из 5 ТЫСЯЧ сегментов и количество файлов в каждом сегменте. Если вы просто собираетесь использовать foreach для этого, то не делайте final ToList . Если вам нужны отдельные файлы в каждой корзине, вы должны сгруппировать их по (f.Length / 5120), не выбирая их предварительно.

Комментарии:

1. Ах, группировка, еще одна отличная альтернатива, использующая LINQ.

2. @Jeff, Однако, группировать по номерам только для того, чтобы потом их посчитать, кажется пустой тратой времени. В идеале вы бы просто создавали сегменты и подсчитывали по мере работы со списком (т. Е. с вашим решением).

3. @Rup: О да, для этого случая … немного излишне, если мы просто считаем. Но если бы нам в противном случае понадобилось что-то сделать с файлами, это сработало бы хорошо.

4. p.s., опечатка: s.Count() -> g.Count()

5. <= 5 КБ,> 5 КБ и <= 10 КБ,> 10 КБ и <= 20 КБ,> 20 КБ и <= 100 КБ,> 100 КБ и <= 1000 КБ,> 1000 КБ и <=5 МБ,> 5 МБ и <=10 МБ,> 10 МБ и <=20 МБ,> 20 МБ и <=50 МБ,> 50 МБ и <=100 МБ