我想从列表中找到最接近的较大纸张尺寸

时间:2019-02-11 10:16:43

标签: c# linq

我想从代码中显示的列表中找到最接近的相等或更大的纸张尺寸。

List<PaperSize> paper = new List<PaperSize>();

paper.Add(new PaperSize("B5", 516, 729));
paper.Add(new PaperSize("A5", 420, 595));
paper.Add(new PaperSize("A4", 595, 842));
paper.Add(new PaperSize("B4", 729, 1032));
paper.Add(new PaperSize("A4L", 842, 595));
paper.Add(new PaperSize("A3", 1191, 842));
paper.Add(new PaperSize("A2", 1685, 1190));
paper.Add(new PaperSize("A1", 2384, 1685));
paper.Add(new PaperSize("A0", 3371, 2384));

int width = 1189;
int height = 840;

string name = paper.SkipWhile(p => p.PaperWidth < width && p.PaperHeight < 
    height).First().PaperName;

Console.WriteLine("Nearest equal or larger papersize is " + name);

如果我提供宽度1189和高度840,我希望选择纸张A3,但结果是A4。 如何退回A3?

编辑:确实,列表并不总是以特定的方式排序。因此,@ Johnny和@Knoop的建议解决了我的问题。紧接着,我忘记将PaperSize及其属性PaperName,PaperWidth和PaperHeight包括在内,但是你们所有人都正确地认为我拥有此属性。

3 个答案:

答案 0 :(得分:6)

将您的&&更改为||。您想在宽度太小时跳过。

string name = paper.SkipWhile(p => p.PaperWidth < width || p.PaperHeight < 
    height).First().PaperName;

当然,另一种也许更清晰的书写方式是:

string name = paper
    .First(p => p.PaperWidth >= width && p.PaperHeight >= height)
    .PaperName;

还应该考虑以下事实:纸张尺寸可能不够大。在这种情况下,会将name设置为null。

string name = paper
    .FirstOrDefault(p => p.PaperWidth >= width && p.PaperHeight >= height)
    ?.PaperName;

当然,只有在您的纸张尺寸列表严格按区域排序(从最小到最小)的情况下,这些选项才起作用。如果不是这种情况,则可以选择浪费空间最小的纸张尺寸:

int area = width * height;

string name = paper
    .Where(p => p.PaperWidth >= width && p.PaperHeight >= height)
    .OrderBy(p => p.PaperWidth * p.PaperHeight - area)
    .FirstOrDefault()
    ?.PaperName;

答案 1 :(得分:3)

尝试此操作,paper列表不应预先排序:

paper.Where(p => p.PaperWidth  - width >= 0 && p.PaperHeight  - height >= 0)
    .OrderBy(p => p.PaperWidth - width + p.PaperHeight  - height)
    .FirstOrDefault();

答案 2 :(得分:0)

  

要求:
  给定宽度和高度后,请给我命名为小于此宽度和高度的最小PaperSize的名称

对此有些棘手。

假设宽度和高度分别为100和100。您有两张纸:

  • PaperSize A的宽度为105,高度为2000,
  • PaperSize B的宽度为110,高度为110。

两张纸都足够大。如果您先看一下宽度,您会选择纸张A,因为它的宽度最小。但是,纸B似乎比纸A小得多。

因此,首先您必须自己思考:什么时候Paper比另一Paper小?。您将需要实现IComparer<PaperSize>的PaperComparer类:

class PaperSizeComparer : Comparer<PaperSize>
{
    public static IComparer AreaComparer {get;} = new PaperSizeComparer();

    public override int Compare(PaperSize x, PaperSize y)
    {
        ... TODO: implement your definition of smaller PaperSize
    }
}

现在,您可以决定何时考虑一个PaperSize小于另一个PaperSize。您可以选择宽度,然后选择高度。就我个人而言,我会选择“面积”:

private static readonly IComparer<int> areaComparer = Comparer<int>.Default;

public int CalcArea(PaperSize x)
{
    return x.Width * x.Height;
}

public override int Compare(PaperSize x, PaperSize y)
{
    // null PaperSizes are smallest
    if (x == null)
    {
        if (y == null)
           // both null: equal
           return 0;
        else
           // x null, y not null: x is smaller
           return -1;
    }
    else if (y == null) return +1; // x not null; y null: larger
    else
    {   // x and y both not null: compare areas:
        int areaX = this.CalcArea(x);
        int areaY = this.CalcArea(y);
        return areaComparer.Compare(areaX, areaY);
    }
}

现在,您已经定义了比较器,您的LINQ很简单:

IEnumerable<PaperSize> availablePaperSizes = ...
int width = ...
int height = ...
  

请给我最小宽度大于高度和宽度的最小纸张的名称

var smallestFittingPapersizeName = availablePaperSizes
    .Where(paperSize => paperSize.Width >= width && paperSize.Height >= height)
    .OrderBy(paperSize, PaperSizeComparer.AreaComparer)
    .Select(paperSize => paperSize.Name)
    .FirstOrDefault();
  

在连接LINQ语句时,请始终确保只有最后一个语句是不返回IEnumerable的语句!

注意:

  • 此LINQ语句始终有效,即使是在可用纸张大小的空集或未排序的集合上也是如此
  • 如果将来您决定要不同地定义“较小的纸张尺寸”,则LINQ语句不必更改

优化

如果只需要最小尺寸的纸张,则订购所有纸张尺寸是非常浪费的。考虑使用Enumerable.Aggregate获得最小的PaperSize

    // instead of OrderBy:
    .Aggregate( (paperSizeX, paperSizeY) =>
       ((PaperSizeComparer.AreaComparer.Compare(paperSizeX, paperSizeY) <= 0) ??
          // paperSizeX smaller or equal; X remains the smallest
          paperSizeX :
          // paperSizeX larger: Y is the new smalles:
          paperSizeY)

A,这不适用于空集合。除此之外,它看起来还很朦胧。

考虑编写扩展函数,该函数在任何序列上返回最小或默认值:

public TSource MinOrDefault<TSource> (this IEnumerable<TSource> source,
    IComparer<TSource) comparer)
{
    // TODO: exception if source == null
    if (comparer == null)
       comparer = Comparer<TSource>.Default; // use default comparer

    var enumerator = source.GetEnumerator();
    if (enumerator.MoveNext())
    {   // we have at least one source element:
        var smallest = enumerator.Current;

        // continue with the rest of the input sequence:
        while (enumerator.MoveNext())
        {
            // there is still another item
            if (comparer.CompareTo(smallest, enumerator.Current) > 0)
            {
                 // found a smaller item:
                 smallest = enumerator.Current;
            }
         }
         return smallest;
    }
    else
    {   // input collection empty; return default
        return default(TSource);
    }
}

用法:

PaperSize smallestFittingPaperSize = availablePaperSizes
    .Where(paperSize => paperSize.Width >= width && paperSize.Height >= height)
    .Min(PaperSizeComparer.AreaCompaerer);