Есть ли более быстрая / более эффективная альтернатива для нахождения наиболее близкого соответствия значения из списка в M / PowerQuery, кроме рекурсии? - PullRequest
0 голосов
/ 25 июня 2019

первый пост о переполнении стека. Я пытаюсь получить представление о том, как найти наиболее подходящие значения из списков и столбцов таблицы с помощью Power Query.

Так что, если у вас есть список {1.1, 3.2, 4.5, 6.6, 7.8} и вы ищете «4.6», то возвращается «4.5».

Очевидно, в Power Query нет встроенных функций, которые делают то, что мне нужно. Я искал сценарий, но подходы, которые приводят к желаемому результату, обычно включают вычитание значения поиска из каждого значения в списке ссылок, преобразование результатов в абсолютные различия и затем возвращение минимума. Вы должны сделать несколько дополнительных шагов, чтобы получить исходное значение. Та же самая техника сделана и на стороне DAX.

Я хочу сопоставить, скажем, 1000+ значений в списке из 50000+ значений, так что это может занять довольно много времени каждый раз, когда запрос обновляется, делая это с абсолютной разницей.

Я искал вокруг и не мог найти двоичный поиск в M, поэтому я написал следующий. Кажется, что это работает и значительно повышает производительность, но я хотел знать, есть ли лучший подход, который кто-либо нашел. Я читал, что использование функции List.Generate () предпочтительнее рекурсии в M, но не понимаю, почему.

/*
Performs a binary search for a number within a sorted list
Function returns a number
*/
let
    funcLookup = (myList as list, myNum as number) =>
        let 
            n = List.Count(myList),
            n_2 = Number.RoundUp(n/2,0),  //Do not use Number.IntegerDivide()
        newDiffSign = Number.Sign(myNum - myList{n_2}), //Pos = 1, Neg = -1, Zero = 0 

        A =  if newDiffSign = 1 and n > 1 then  List.Range(myList, n_2, n-n_2) //Second half of the list
            else if newDiffSign = -1 and n > 1 then List.Range(myList, 0, n_2) //First half of the list
            else {myList{n_2}},                                                //Return the matched number, as a list 

//Recursively iterate to winnow the list to 1 number which will be the closest match
        Match = if n > 1 
            then @funcLookup(A, myNum)
            else myList{0}    
    in
        Match
in
funcLookup

-----

Я вызываю вышеупомянутую функцию, используя это:

listMyList = List.Buffer(List.Sort(MyTable[MyColumn])), //sorts and buffers the list in memory
MyNewTable = Table.AddColumn(MyTable, "Matched Value", each funcLookUp(listMyList, [MyColumn]))

Ответы [ 2 ]

0 голосов
/ 26 июня 2019

Ооо, я дурак. Я не читаю ваш пост полностью. Вы можете попробовать этот код. Я не автор, но я думаю, что это здорово. Постскриптум Извините, что алгоритм работает для когорт, но не для вашей проблемы. P.P.S. Это финальная версия кода, работает правильно и быстро

     let
    TData = Table.FromList({3.2,7.5,8.6,2.3,9.3,4.4,1.1,5.6,6.7}, Splitter.SplitByNothing(), {"id"} ),
    TDataWrap = Table.AddColumn(Table.DuplicateColumn(TData, "id", "other"), "priority", each 1),
    TSearch = Table.AddColumn(Table.FromList({0.7,0.8,1,2.3,2.1,5.4,9.7,0}, Splitter.SplitByNothing(), {"id"}), "priority", each 0),
    merged = TDataWrap & TSearch,
    setOrder = Table.Sort(merged,{{"id", Order.Ascending}, {"priority", Order.Ascending}}),
    addForDown = Table.AddColumn(setOrder, "down", each if [priority] = 1 then [other] else null),
    fillDown = Table.FillDown(addForDown,{"down"}),
    addForUp =  Table.AddColumn(fillDown, "up", each if [priority] = 1 then [other] else null),
    fillUp = Table.FillUp(addForUp, {"up"}),
    preResult = Table.AddColumn(fillUp, "base", each if [down] = null then [up] else if [up] = null then [down] else 
        if Number.Abs([up] - [id]) > Number.Abs([down] - [id]) then [down] else [up]),
    FilteredRows = Table.SelectRows(preResult, each ([priority] = 0)),
    RemovedColumns = Table.RemoveColumns(FilteredRows,{"other", "priority", "down", "up"})
in
    RemovedColumns
0 голосов
/ 26 июня 2019

Как то так?

let
    Source = {1.1, 3.2, 4.5, 6.6, 7.8},
    SearchFor = 4.6,
    Search = let abs = List.Buffer(List.Transform( Source, (x)=> Number.Abs( x - SearchFor ) ) ) in Source{ List.PositionOf( abs, List.Min( abs ) ) }
in
    Search
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...