Найти соседей и возвращения среднее значение от соседей


У меня есть список продаж записей, отсортированных по дате продажи. Мне нужно заполнить пропуски (дни без записи продаж) в этом списке с предполагаемых продаж записей. Проблемный метод вычисляет среднюю цену за предполагаемые рекорды продаж со своей зачетной соседей, которые имеют тот же SalesType. Если есть только один сосед, то нет необходимости среднем он просто возвращает цену продажи соседу. Я бы хотел улучшить читаемость этого метода.

public class SalesRecord
{
    public DateTime Date { get; set; }
    public bool IsValid { get; set; }
    public int SalesType { get; set; }
    public double UnitPrice { get; set; }
}
public class SalesDataContainter
{
    private SortedList<DateTime, SalesRecord> salesData;
    private double GetInferredPrice(SalesRecord inferredSalesRecord)
    {
        SalesRecord closestBefore = null;
        SalesRecord closestAfter = null;
        foreach (var salesRecord in salesData.Values)
        {
            //neigbor needs to be valid
            if (salesRecord.IsValid)
            {
                continue;
            }
            //has to have the same sales type
            if (salesRecord.SalesType != inferredSalesRecord.SalesType)
            {
                continue;
            }
            //listed is sorted get the last found for before neighbor
            if (salesRecord.Date < inferredSalesRecord.Date)
            {
                closestBefore = salesRecord;
            }
            //listed is sorted get the first found for after neighbor
            if (closestAfter == null && salesRecord.Date > inferredSalesRecord.Date)
            {
                closestAfter = salesRecord;
            }
        }
        // we have neighbors on both sides
        if (closestBefore != null && closestAfter != null)
        {
            return (closestAfter.UnitPrice + closestBefore.UnitPrice) / 2;
        }
        // there's only before neighbor
        if (closestBefore != null)
        {
            return closestBefore.UnitPrice;
        }
        // there's only after neighbor
        if (closestAfter != null)
        {
            return closestAfter.UnitPrice;
        }
        return 0;
    }
}


279
2
c#
задан 19 ноября 2011 в 05:11 Источник Поделиться
Комментарии
3 ответа

Просто несколько вещей, которые я хочу отметить, прежде чем взяться за это:


  • На основе комментария, ваше первое условие переворачивается. Ты прогуливаешь действительные записи вместо недействительных записей. Она должна быть:

    if (!salesRecord.IsValid)

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

  • Вы должны рассмотреть, делая цена типа десятичное. Вы имеете дело с валютой здесь, так что вы не должны использовать двойные.

Используя немного LINQ, которая может сделать это более читабельно ИМХО. Легче думать о нем, как "цикл по списку потенциальных записей", а не "цикл по всем записям и пропуская некоторые из них".

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

public class SalesRecord
{
public DateTime Date { get; set; }
public bool IsValid { get; set; }
public int SalesType { get; set; }
public decimal UnitPrice { get; set; }
}
public class SalesDataContainter
{
private SortedList<DateTime, SalesRecord> salesData;
private decimal GetInferredPrice(SalesRecord inferredSalesRecord)
{
Func<SalesRecord, bool> isValidRecord = record =>
record.IsValid //neigbor needs to be valid
&& record.SalesType == inferredSalesRecord.SalesType; //has to have the same sales type
var candidateRecords = salesData.Values
.SkipWhile(record => !isValidRecord(record))
.Where(isValidRecord);

SalesRecord closestBefore = null;
SalesRecord closestAfter = null;
foreach (var salesRecord in candidateRecords)
{
//get the last found for before neighbor
if (salesRecord.Date < inferredSalesRecord.Date)
{
closestBefore = salesRecord;
}
//get the first found for after neighbor
else if (salesRecord.Date > inferredSalesRecord.Date)
{
closestAfter = salesRecord;
break;
}
}

// there's a neighbor before
if (closestBefore != null)
{
return (closestAfter != null)
// we have neighbors on both sides
? (closestBefore.UnitPrice + closestAfter.UnitPrice) / 2M
: closestBefore.UnitPrice;
}
else
{
return (closestAfter != null)
// there's only a neighbor after
? closestAfter.UnitPrice
: 0M;
}
}
}

Альтернативой было бы позволить LINQ, которая делает всю работу:

public decimal GetInferredPrice(SalesRecord inferredSalesRecord)
{
Func<SalesRecord, bool> isValidRecord = record =>
record.IsValid //neigbor needs to be valid
&& record.SalesType == inferredSalesRecord.SalesType; //has to have the same sales type

return salesData.Values
.SkipWhile(record => !isValidRecord(record))
.Where(isValidRecord)
.GroupBy(
record => record.Date.CompareTo(inferredSalesRecord.Date),
record => record.UnitPrice as decimal?,
(key, g) => (key < 0) // assumes no records will have equal dates
? g.LastOrDefault()
: g.FirstOrDefault()
)
.Average() ?? 0M;
}

2
ответ дан 19 ноября 2011 в 08:11 Источник Поделиться

Я хотел сделать что-то вроде этого:

public class SalesRecord
{
public DateTime Date { get; set; }
public bool IsValid { get; set; }
public int SalesType { get; set; }
public decimal UnitPrice { get; set; }
}
public class SalesDataContainter
{
private SortedList<DateTime, SalesRecord> salesData;
private decimal GetInferredPrice(SalesRecord inferredSalesRecord)
{
// select valid records of the same type as inferredSalesRecord
var candidateRecords = salesData.Values.Where(r => r.IsValid && r.SalesType == inferredSalesRecord.SalesType);

SalesRecord closestBefore = candidateRecords.LastOrDefault(r => r.Date < inferredSalesRecord.Date);
SalesRecord closestAfter = candidateRecords.FirstOrDefault(r => r.Date > inferredSalesRecord.Date);

// we have neighbors on both sides
if (closestBefore != null && closestAfter != null)
{
return (closestAfter.UnitPrice + closestBefore.UnitPrice) / 2m;
}
// there's only before neighbor
if (closestBefore != null)
{
return closestBefore.UnitPrice;
}
// there's only after neighbor
if (closestAfter != null)
{
return closestAfter.UnitPrice;
}
return 0m;
}
}

2
ответ дан 19 ноября 2011 в 09:11 Источник Поделиться

Я не быстрый вентилятор, так что я бы код GetInferredPrice как таковой:

    private double GetInferredPrice(SalesRecord inferredSalesRecord)
{
var candidateRecords = this.salesData.Values.Where(salesRecord => salesRecord.IsValid
&& (salesRecord.SalesType == inferredSalesRecord.SalesType));
var closestBefore = candidateRecords.LastOrDefault(salesRecord => salesRecord.Date < inferredSalesRecord.Date);
var closestAfter = candidateRecords.FirstOrDefault(salesRecord => salesRecord.Date > inferredSalesRecord.Date);

// we have no neighbors on either side
if ((closestBefore == null) && (closestAfter == null))
{
return 0;
}

// only before neighbor, only after neighbor, or both
return closestAfter == null
? closestBefore.UnitPrice
: (closestBefore == null
? closestAfter.UnitPrice
: (closestAfter.UnitPrice + closestBefore.UnitPrice) / 2);
}

Я не знаю полного использования SalesRecord, но, несмотря на отсутствие хороших неизменный семантики, я до сих пор как неизменяемые классы если вы можете качать его:

public sealed class SalesRecord
{
private readonly DateTime date;

private readonly bool isValid;

private readonly int salesType;

private readonly double unitPrice;

public SalesRecord(DateTime date, bool isValid, int salesType, double unitPrice)
{
this.date = date;
this.isValid = isValid;
this.salesType = salesType;
this.unitPrice = unitPrice;
}

public DateTime Date
{
get
{
return this.date;
}
}

public bool IsValid
{
get
{
return this.isValid;
}
}

public int SalesType
{
get
{
return this.salesType;
}
}

public double UnitPrice
{
get
{
return this.unitPrice;
}
}
}

2
ответ дан 20 ноября 2011 в 01:11 Источник Поделиться