Excel.Workbook - собираем все листы из файла
#АнатомияФункций – Excel.Workbook, Table.Combine, Table.ExpandTableColumn
Всем привет!
В продолжение этой темы давайте разберем вопрос – как собрать информацию со всех листов в файле. Мы помним, что для этого нам понадобится Excel.Workbook, а вот дальше есть варианты:
Вариант 0 – нужна только информация с листов
sheets – выбрали только листы (если вам нужны таблицы - [Kind]="Table")
to – ну и соединили таблицы воедино
Вариант 1 – нужно сохранить названия листов, данные на листах типовые
cols – выбрали столбцы с названиями листов и данными
nms – получили заголовки столбцов из первой таблицы
to – раскрыли табличный столбец
Вариант 2 - нужно сохранить названия листов, шапки таблиц могут различаться
Как бы и всё. Если таблицы не требуют дополнительных преобразований, количество кода минимально (особенно в варианте 0), а пользы приносит много.
Надеюсь, было полезно.
Всех благ!
@buchlotnik
#АнатомияФункций – Excel.Workbook, Table.Combine, Table.ExpandTableColumn
Всем привет!
В продолжение этой темы давайте разберем вопрос – как собрать информацию со всех листов в файле. Мы помним, что для этого нам понадобится Excel.Workbook, а вот дальше есть варианты:
Вариант 0 – нужна только информация с листов
letfrom – подключение к файлу, второй аргумент true – использование первой строки в качестве заголовков
from = Excel.Workbook(File.Contents("путь\файл.xlsx"), true),
sheets = Table.SelectRows(from, each [Kind]="Sheet"),
to = Table.Combine(sheets[Data])
in
to
sheets – выбрали только листы (если вам нужны таблицы - [Kind]="Table")
to – ну и соединили таблицы воедино
Вариант 1 – нужно сохранить названия листов, данные на листах типовые
letпервые два шага те же
from = Excel.Workbook(File.Contents("путь\файл.xlsx "), true),
sheets = Table.SelectRows(from, each [Kind]="Sheet"),
cols = Table.SelectColumns(sheets,{"Name", "Data"}),
nms = Table.ColumnNames(cols{0}[Data]),
to = Table.ExpandTableColumn(cols, "Data",nms)
in
to
cols – выбрали столбцы с названиями листов и данными
nms – получили заголовки столбцов из первой таблицы
to – раскрыли табличный столбец
Вариант 2 - нужно сохранить названия листов, шапки таблиц могут различаться
letменяем только шаг nms – в этой ситуации мы получаем списки заголовков со всех таблиц, собираем в один список и оставляем только уникальные
from = Excel.Workbook(File.Contents("путь\файл.xlsx "), true),
sheets = Table.SelectRows(from, each [Kind]="Sheet"),
cols = Table.SelectColumns(sheets,{"Name", "Data"}),
nms = List.Distinct(List.Combine(List.Transform(cols[Data], Table.ColumnNames))),
to = Table.ExpandTableColumn(cols, "Data",nms)
in
to
Как бы и всё. Если таблицы не требуют дополнительных преобразований, количество кода минимально (особенно в варианте 0), а пользы приносит много.
Надеюсь, было полезно.
Всех благ!
@buchlotnik
Telegram
Для тех, кто в танке
Excel.CurrentWorkbook() vs Excel.Workbook() или в чём же разница
#АнатомияФункций - Excel.CurrentWorkbook, Excel.Workbook
Всем привет! Только что разбирали Excel.CurrentWorkbook, но есть ещё одна функция - Excel.Workbook. Из названий понятно, что обе функции…
#АнатомияФункций - Excel.CurrentWorkbook, Excel.Workbook
Всем привет! Только что разбирали Excel.CurrentWorkbook, но есть ещё одна функция - Excel.Workbook. Из названий понятно, что обе функции…
Table.FromList + GroupKind.Local – шустрое подобие регулярок
#АнатомияФункций - Table.FromList, GroupKind.Local
Всем привет!
Опять зачастили вопросы по регуляркам, которые не подвезли. Посему хочу разобрать небольшой пример и предложить принцип решения - разбиваем текст посимвольно, а затем группируем их в соответствии с заданным условием.
Ну поехали:
to – собрали из списка таблицу с использованием функции f
Ну а теперь о функции.
Для начала заведём два вспомогательных списка – ld и ldp
ld – список цифр (именно символов, кавычки не забываем) - это список символов, которые нас интересуют в конечном результате
ldp – к списку ld добавили символы, которые могут встречаться в номере телефона – скобки, пробелы, дефис - это список символов, которые могут быть объединены в одну группу
Обращаю внимание – списки вынесены в отдельные шаги и помещены в List.Buffer – это важно для ускорения работы
Теперь сами шаги функции:
a – собираем таблицу из списка символов анализируемого значения. На выход получим таблицу из двух столбцов – y это сам символ, а z – будет нулевым, если символ может содержаться в номере и единица – если не может
b – группируем по z, локально, пятый аргумент не пишем (подробно про пятый аргумент разбиралось тут), агрегируем в текст. Таким образом все потенциальные символы из номера телефона будут сгруппированы в отдельные текстовые значения
c – чистим полученные тексты, оставляя только символы из списка ld
d – выбираем не пустые тексты
Собственно, всё. Тонкую настройку работы функции можно осуществить меняя списки ld и ldp, экспериментируйте )))
Надеюсь, было полезно.
Всех благ!
@buchlotnik
#АнатомияФункций - Table.FromList, GroupKind.Local
Всем привет!
Опять зачастили вопросы по регуляркам, которые не подвезли. Посему хочу разобрать небольшой пример и предложить принцип решения - разбиваем текст посимвольно, а затем группируем их в соответствии с заданным условием.
Ну поехали:
letlst – наш источник, для простоты в виде списка – задача вынуть номера телефонов
ld = List.Buffer({"0".."9"}),
ldp = List.Buffer(ld&{"(",")"," ","-"}),
f=(x)=>[a = Table.FromList(Text.ToList(x),(x)=>{x,if List.Contains(ldp,x) then 0 else 1},{"y","z"}),
b = Table.Group(a,"z",{"i",each Text.Combine([y])},GroupKind.Local)[i],
c = List.Transform(b,(x)=>Text.Select(x,ld)),
d = List.Select(c,(x)=>x<>"")][d],
lst = { " +7 (495) 617-61-16б доб. 1503",
"тел: +7-913-728-64-87 Владимир",
"(4152), доб. 23-81-47 +7962 215 17 17",
"8 (423) 2 60-84-84, 143(внутренний)",
"8 (495) 229-49-69, доб. 114",
"+7 (383) 286-87-08 ext.24120"},
to = Table.FromList(lst,(x)=>{x}&f(x),4)
in
to
to – собрали из списка таблицу с использованием функции f
Ну а теперь о функции.
Для начала заведём два вспомогательных списка – ld и ldp
ld – список цифр (именно символов, кавычки не забываем) - это список символов, которые нас интересуют в конечном результате
ldp – к списку ld добавили символы, которые могут встречаться в номере телефона – скобки, пробелы, дефис - это список символов, которые могут быть объединены в одну группу
Обращаю внимание – списки вынесены в отдельные шаги и помещены в List.Buffer – это важно для ускорения работы
Теперь сами шаги функции:
a – собираем таблицу из списка символов анализируемого значения. На выход получим таблицу из двух столбцов – y это сам символ, а z – будет нулевым, если символ может содержаться в номере и единица – если не может
b – группируем по z, локально, пятый аргумент не пишем (подробно про пятый аргумент разбиралось тут), агрегируем в текст. Таким образом все потенциальные символы из номера телефона будут сгруппированы в отдельные текстовые значения
c – чистим полученные тексты, оставляя только символы из списка ld
d – выбираем не пустые тексты
Собственно, всё. Тонкую настройку работы функции можно осуществить меняя списки ld и ldp, экспериментируйте )))
Надеюсь, было полезно.
Всех благ!
@buchlotnik
Telegram
Для тех, кто в танке
Table.Group – Часть 3.1 Пятый аргумент - основной пример и описание
#АнатомияФункций – Table.Group, #buchlotnik
Всем привет!
Нам осталось сталось самое интересное. Мы уже разобрались тем, что агрегации при группировке могут быть любыми, их удобно делать с…
#АнатомияФункций – Table.Group, #buchlotnik
Всем привет!
Нам осталось сталось самое интересное. Мы уже разобрались тем, что агрегации при группировке могут быть любыми, их удобно делать с…
Table.Skip – отделяем лишнее в таблице
#АнатомияФункций - Table.Skip
Всем привет!
По мотивам недавнего обсуждения в чате обсудим вопрос пропуска лишних строк в таблице.
Основной пример:
Итак, в примере выше функции Table.Skip передан только один аргумент – сама таблица, поэтому будет пропущена только первая строка. Результат не впечатляющий, пропустить нужно несколько, поэтому:
Фишка состоит в том, что во второй аргумент можно передать условие:
Результат не очень – давайте просто сменим столбец:
Понятно, что мы не знали сколько строк пропустить, но знали в каком столбце искать. Причём искать можно не только null:
Но что, если мы знаем какое слово искать, но не знаем ни строки, ни столбца? Да пожалуйста:
Наконец, в самом плохом случае, мы можем даже значение целиком не знать, только его фрагмент. Но тоже не проблема:
Как-то так. Простая функция, несложные приёмы, но очень лаконично можно получить нужный результат в одну строку.
Надеюсь, было полезно.
Всех благ!
@buchlotnik
#АнатомияФункций - Table.Skip
Всем привет!
По мотивам недавнего обсуждения в чате обсудим вопрос пропуска лишних строк в таблице.
Основной пример:
letДалее будем менять только функцию skip.
from = Table.FromRecords(Json.Document(Binary.Decompress(Binary.FromText("3VO7DsIwDPwV5LlDHTuEsvIZhaEVMIUyZUL8O6qDVAeUSK0YgOXk6+Nsn+32BrurD5cBYTsE76snNSmllHJKbUrXkd6rP9CGfaiZziMySmwFjSALnuTtRuJ6+p66+AR+se+yNqjuY8fHCbmPPoHKjh9OLUPgZkSLq7nVmC8xOfbiVOE0LRT1KjYq7vRCLZRAeLN0tobRli7UIFB2gboa+YPjZTlQJi5MZCFxHvMjdfl5IpWmaZSmze8IFm4BXaJI+SqbQpX2tcrDAw=="),Compression.Deflate))),
skip=(x)=>Table.Skip(x),
to = skip(from)
in
to
Итак, в примере выше функции Table.Skip передан только один аргумент – сама таблица, поэтому будет пропущена только первая строка. Результат не впечатляющий, пропустить нужно несколько, поэтому:
skip=(x)=>Table.Skip(x,8)Так уже лучше, но мы задали количество строк в явном виде. Но что если мы не знаем сколько строк необходимо пропустиь? Почему-то многие начинают именно вычислять число пропускаемых строк, сооружая жуткие конструкции с индексацией и прочим.
Фишка состоит в том, что во второй аргумент можно передать условие:
skip=(x)=>Table.Skip(x,(r)=>r[Column1]=null)Обращаю внимание – функцию во втором аргументе пишем от записи. Будут пропущены все строки, которые СООТВЕТСТВУЮТ условию. В данном случае пока в Column1 будет встречаться null.
Результат не очень – давайте просто сменим столбец:
skip=(x)=>Table.Skip(x,(r)=>r[Column2]=null)Теперь результат как надо.
Понятно, что мы не знали сколько строк пропустить, но знали в каком столбце искать. Причём искать можно не только null:
skip=(x)=>Table.Skip(x,(r)=>r[Column1]<>"заголовок")в данном случае поиск по Column1 закончится на строке, содержащей «заголовок».
Но что, если мы знаем какое слово искать, но не знаем ни строки, ни столбца? Да пожалуйста:
skip=(x)=>Table.Skip(x,(r)=>not List.Contains(Record.ToList(r),"ключ"))в этой ситуации каждую строку мы превращаем в список и проверяем в нём наличие конкретного значения.
Наконец, в самом плохом случае, мы можем даже значение целиком не знать, только его фрагмент. Но тоже не проблема:
skip=(x)=>Table.Skip(x,(r)=>not Text.Contains(Text.Combine(List.Select(Record.ToList(r),(x)=>x is text)),"клю"))Тут финт ушами состоит в том, что превратив строку в список, мы выбираем только текстовые значения (is text), сцепляем все в одну строку (при этом пропускаем все null) и уже её проверяем на наличие фрагмента. Выходит достаточно шустро.
Как-то так. Простая функция, несложные приёмы, но очень лаконично можно получить нужный результат в одну строку.
Надеюсь, было полезно.
Всех благ!
@buchlotnik
Обновление навигации - второе меню у PQfromtankbot
Всем привет! Наш главный админ - @PQfromtankbot
- уже несколько месяцев активно трудится на благо помощи в навигации по каналу.
Анализ запросов показал, что все предпочитают жмакать кнопки, а не набирать текст. Поэтому немного обновил меню /help
Теперь доступны две команды -
/category - вызывает уже многим привычное меню категорий
/key - вызывает меню выбора ключевого слова для поиска
если новое меню будет пользоваться популярностью, будем его развивать и расширять
Также напоминаю, что боту можно отсылать любой текст - для него это сигнал для поиска по базе. Если какой-то запрос окажется в лидерах, добавим его в новое меню. Как-то так.
Надеюсь, будет полезно.
Всех благ!
@buchlotnik
Всем привет! Наш главный админ - @PQfromtankbot
- уже несколько месяцев активно трудится на благо помощи в навигации по каналу.
Анализ запросов показал, что все предпочитают жмакать кнопки, а не набирать текст. Поэтому немного обновил меню /help
Теперь доступны две команды -
/category - вызывает уже многим привычное меню категорий
/key - вызывает меню выбора ключевого слова для поиска
если новое меню будет пользоваться популярностью, будем его развивать и расширять
Также напоминаю, что боту можно отсылать любой текст - для него это сигнал для поиска по базе. Если какой-то запрос окажется в лидерах, добавим его в новое меню. Как-то так.
Надеюсь, будет полезно.
Всех благ!
@buchlotnik
Propis - сумма прописью
#АнатомияФункций - custom
Всем привет! Всплыл скелет из шкафа - когда-то давно писал функцию для суммы прописью, там нашлась бага. Багу исправил, выкладываю как есть, код хоть и старый но вполне годный:
допиливайте самостоятельно (уговорили - допилил)😉
Надеюсь, было полезно.
Всех благ!
@buchlotnik
#АнатомияФункций - custom
Всем привет! Всплыл скелет из шкафа - когда-то давно писал функцию для суммы прописью, там нашлась бага. Багу исправил, выкладываю как есть, код хоть и старый но вполне годный:
Propis=(num)=>
[ ln = { {"десять ","одиннадцать ","двенадцать ","тринадцать ","четырнадцать ","пятнадцать ","шестнадцать ","семнадцать ","восемнадцать ","девятнадцать "},
{"","сто ","двести ","триста ","четыреста ","пятьсот ","шетьсот ","семьсот ","восемьсот ","девятьсот "},
{"","","двадцать ","тридцать ","сорок ","пятьдесят ","шестьдесят ","семьдесят ","восемьдесят ","девяносто "},
{"","один ","два ","три ","четыре ","пять ","шесть ","семь ","восемь ","девять "},
{"","одна ","две ","три ","четыре ","пять ","шесть ","семь ","восемь ","девять "}},
lr = { {"","миллиард ","миллиарда ","миллиарда ","миллиарда ","миллиардов "},
{"","миллион ","миллиона ","миллиона ","миллиона ","миллионов "},
{"","тысяча ","тысячи ","тысячи ","тысячи ","тысяч "},
{""," "," "," "," "," "}},
fn = (n,r)=> [ t = List.Transform(Text.ToList(n), each Number.From(_)),
x = ln{1}{t{0}} & (if t{1}<>1 then ln{2}{t{1}} & (if r = 2 then ln{4}{t{2}} else ln{3}{t{2}}) else ln{0}{t{2}}),
y = lr{r}{if t{1}=1 or t{2}=0 then 5 else List.Min({t{2},5})},
z = if Number.From(n)=0 then "" else x&y][z],
t = Splitter.SplitTextByRepeatedLengths(3)(Number.ToText(num,"000000000000")),
z = if num = 0 then "ноль" else Text.Combine(List.Transform({0..3},each fn(t{_},_)),"")][z]
Работает с целыми числами от нуля до 999 999 999 999. Кому нужны триллионы - завидую, но Надеюсь, было полезно.
Всех благ!
@buchlotnik
Telegram
Для тех, кто в танке
PropisRub - сумма прописью (дополнение)
#АнатомияФункций – custom
Всем привет!
Начали тут. Не думал, что добавить триллионы рублей такая проблема, но раз уж надо:
PropisRub=(x)=>
[ ln = { {"десять ","одиннадцать ","двенадцать ","тринадцать ","четырнадцать…
#АнатомияФункций – custom
Всем привет!
Начали тут. Не думал, что добавить триллионы рублей такая проблема, но раз уж надо:
PropisRub=(x)=>
[ ln = { {"десять ","одиннадцать ","двенадцать ","тринадцать ","четырнадцать…
Зачем нужны вспомогательные запросы при объединении файлов из папки?
#НеВсеЙогуртыОдинаковоПолезны
Всем добра! Регулярно то тут, то там всплывает проблема по сборке данных из кучи файлов, которые сначала нужно причесать.
Сегодня расскажу про механизм автоматической генерации запросов интерфейсом редактора.
Итак, когда делаете запрос к папке и разворачиваете структуру бинарников как на картинке☝️, то автоматически генерится вот такая структура из запросов:
где
123 - это собственно запрос к папке с названием "123"
Запрос "Пример файла" - это запрос, который по заданному в нем алгоритму выбирает тот единственный файл, из папки по образцу которого будет формироваться шаблонный запрос обработки и функция на его основе.
Запарос "Параметр1" - это собственно параметр, значением которого является бинарное содержимое файла из запроса "Пример файла"
Запрос "Преобразовать пример файла" - это шаблонный запрос, в который в качестве параметра передается бинарник из предыдущего шага и в нем же формируется алгоритм обработки единичного файла из папки перед сборкой в единый массив
Функция "Преобразовать файл" - это функция, которая связана с запросом "Преобразовать пример файла", Что это значит? Это значит что код функции берется напрямую из этого запроса, т.е. если изменить запрос-шаблон, то автоматически изменится и функция. Эта самая функция как раз и вызывается для обработки бинарников в запросе "123".
Что из вышеизложенного следует? Лезем в этот самый запрос-шаблон (он же "Преобразовать пример файла") и в нем производим причесывание по выбранному вами алгоритму, а далее, когда будем разворачивать содержимое файлов в единый массив, там уже будут все нужные вам строки и столбцы. Такая обработка всегда работает гораздо быстрее.
Зачем все это надо? Для обеспечения юзерам возможности легкого редактирования шаблонного запроса после подключения к папке: нет мороки с написанием этого запроса вручную, с переделыванием ее в функцию, с отлавливанием багов, если ошибка скрывается именно в этой функции, и много чего еще.
Продолжение следует...
#НеВсеЙогуртыОдинаковоПолезны
Всем добра! Регулярно то тут, то там всплывает проблема по сборке данных из кучи файлов, которые сначала нужно причесать.
Сегодня расскажу про механизм автоматической генерации запросов интерфейсом редактора.
Итак, когда делаете запрос к папке и разворачиваете структуру бинарников как на картинке☝️, то автоматически генерится вот такая структура из запросов:
где
123 - это собственно запрос к папке с названием "123"
Запрос "Пример файла" - это запрос, который по заданному в нем алгоритму выбирает тот единственный файл, из папки по образцу которого будет формироваться шаблонный запрос обработки и функция на его основе.
Запарос "Параметр1" - это собственно параметр, значением которого является бинарное содержимое файла из запроса "Пример файла"
Запрос "Преобразовать пример файла" - это шаблонный запрос, в который в качестве параметра передается бинарник из предыдущего шага и в нем же формируется алгоритм обработки единичного файла из папки перед сборкой в единый массив
Функция "Преобразовать файл" - это функция, которая связана с запросом "Преобразовать пример файла", Что это значит? Это значит что код функции берется напрямую из этого запроса, т.е. если изменить запрос-шаблон, то автоматически изменится и функция. Эта самая функция как раз и вызывается для обработки бинарников в запросе "123".
Что из вышеизложенного следует? Лезем в этот самый запрос-шаблон (он же "Преобразовать пример файла") и в нем производим причесывание по выбранному вами алгоритму, а далее, когда будем разворачивать содержимое файлов в единый массив, там уже будут все нужные вам строки и столбцы. Такая обработка всегда работает гораздо быстрее.
Зачем все это надо? Для обеспечения юзерам возможности легкого редактирования шаблонного запроса после подключения к папке: нет мороки с написанием этого запроса вручную, с переделыванием ее в функцию, с отлавливанием багов, если ошибка скрывается именно в этой функции, и много чего еще.
Продолжение следует...
Telegram
Для тех, кто в танке
Table.ReplaceErrorValues, Table.RemoveRowsWithErrors – чистим входящие данные
#АнатомияФункций - Table.ReplaceErrorValues, Table.RemoveRowsWithErrors
Всем привет!
Дважды за последние сутки всплывал вопрос как поступать с ошибками данных в исходнике. Имеются ввиду ошибки на листах - #ДЕЛ/0!, #ССЫЛКА!, #Н/Д и т.д. Понятное дело, что наилучшее решение – влезть в исходник и ошибки исправить. Но если у нас доступ только на чтение или, например, в файле протянуты формулы, а ячейки не все заполнены, что и вызывает ошибки – в такой ситуации приходится заниматься обработкой при загрузке.
Здесь два варианта – заменить или удалить.
Начнём с замены – нам в помощь Table.ReplaceErrorValues.
Тут всё просто:
Хотя если мышкоклацать, код будет выглядеть так:
Теперь с удалением строк. Тут работает Table.RemoveRowsWithErrors.
Синтаксис ещё проще:
Как-то так – ошибки в исходных данных можно очистить, но всегда важно понимать – не повлечет ли такое удаление или замена искажения аналитики, может все-таки лучше поправить исходник.
Надеюсь, было полезно.
Всех благ!
@buchlotnik
#АнатомияФункций - Table.ReplaceErrorValues, Table.RemoveRowsWithErrors
Всем привет!
Дважды за последние сутки всплывал вопрос как поступать с ошибками данных в исходнике. Имеются ввиду ошибки на листах - #ДЕЛ/0!, #ССЫЛКА!, #Н/Д и т.д. Понятное дело, что наилучшее решение – влезть в исходник и ошибки исправить. Но если у нас доступ только на чтение или, например, в файле протянуты формулы, а ячейки не все заполнены, что и вызывает ошибки – в такой ситуации приходится заниматься обработкой при загрузке.
Здесь два варианта – заменить или удалить.
Начнём с замены – нам в помощь Table.ReplaceErrorValues.
Тут всё просто:
Table.ReplaceErrorValues(table, {"Column2", null})Вторым аргументом – {имя столбца,на что заменить ошибку}
Хотя если мышкоклацать, код будет выглядеть так:
Table.ReplaceErrorValues(table, {{"Column2", null}})Т.е. автоматом вводится список списков - ведь за один раз можно обрабатывать более чем один столбец:
Table.ReplaceErrorValues(table, {{"Column2", null},{"Column4", null}})
Причём в разных столбцах значение для замены может быть разным:Table.ReplaceErrorValues(table, {{"Column2", null},{"Column4", 0}})Но что, если мы хотим обработать сразу все столбцы:
Table.ReplaceErrorValues(table, List.Transform(Table.ColumnNames(table),(x)=>{x,null}))Т.е. получили список всех столбцов и превратили его в список списков вида {имя, null}
Теперь с удалением строк. Тут работает Table.RemoveRowsWithErrors.
Синтаксис ещё проще:
Table.RemoveRowsWithErrors(table, {"Column2"})Но обращаю внимание – функция ищет ошибки только в указанном столбце, хотя удаляет всю строку с ошибкой. Для нескольких столбцов всё также несложно:
Table.RemoveRowsWithErrors(table, {"Column1", "Column2", "Column3"})А как удалить строки с ошибками сразу по всем столбцам? И вот тут мы не будем сооружать ничего:
Table.RemoveRowsWithErrors(table)Т.е. по умолчанию без второго аргумента функция обрабатывает все столбцы (обратите на это внимание – через мышкоклац второй аргумент всегда добавляется и может порождать неприятные сюрпризы).
Как-то так – ошибки в исходных данных можно очистить, но всегда важно понимать – не повлечет ли такое удаление или замена искажения аналитики, может все-таки лучше поправить исходник.
Надеюсь, было полезно.
Всех благ!
@buchlotnik
UnFill или как учесть значения из предыдущих или последующих строк
#АнатомияФункций – Table.ToColumns, Table.FromColumns
Всем привет! В чате подкинули задачку – сделать «FillDown наоборот». Сложность задачи состоит в том, что при преобразовании столбца нам необходимо учесть значения в предыдущих строках. Напрямую M так не умеет, решения через индексы – это в категорию #НеВсеЙогуртыОдинаковоПолезны; но есть приём, который и используем.
Сразу код:
По шагам:
a – получили список имен столбцов
b – нашли номер интересующего нас столбца
c – получили список списков – список, где каждый элемент – это список значений конкретного столбца
d – взяли интересующий нас столбец
e – а вот и начинается магия: если у нас Down – мы берем пустой элемент и добавляем к нему наш список без последнего значения, т.е. получаем список предыдущих значений; если Up – наоборот, удаляем первый элемент и получаем список последующих значений
f – попарно объединяем элементы исходного и преобразованного списков и сравниваем их – если одинаковые – возвращаем null, иначе сохраняем элемент исходного списка
g – теперь дело за малым – подсовываем результат наших преобразований на место исходного столбца (обратите внимание на конструкцию {f} – нам нужно передать элемент списка)
h – ну и собираем таблицу обратно
Всё, задача решена. unfilldown, unfillup, unfilldown2 – просто примеры корректного синтаксиса при использовании.
Вот так, курите списки, чтобы шаманить таблицы 😉
Надеюсь, было полезно.
Всех благ!
@buchlotnik
#АнатомияФункций – Table.ToColumns, Table.FromColumns
Всем привет! В чате подкинули задачку – сделать «FillDown наоборот». Сложность задачи состоит в том, что при преобразовании столбца нам необходимо учесть значения в предыдущих строках. Напрямую M так не умеет, решения через индексы – это в категорию #НеВсеЙогуртыОдинаковоПолезны; но есть приём, который и используем.
Сразу код:
letВидим функцию UnFill – я немного обобщил задачу, поэтому функция принимает анализируемую таблицу, столбец для преобразования и опциональный аргумент, который отвечает за выбор FillDown или FillUp мы хотим «отменить».
UnFill =(tbl,col,optional up)=>
[ a = Table.ColumnNames(tbl),
b = List.PositionOf(a,col),
c = Table.ToColumns(tbl),
d = c{b},
e = if up is null or up = 0 then {null}&List.RemoveLastN(d,1) else List.Skip(d),
f = List.Transform(List.Zip({d,e}),(x)=>if x{0}=x{1} then null else x{0}),
g = List.ReplaceRange(c,b,1,{f}),
h = Table.FromColumns(g,a)
][h],
from = Table.FromRecords(Json.Document(Binary.Decompress(Binary.FromText("xdExCoAwDAXQu3R2aNJUoVdRF6tOxa2TeHcxESq4qENcPh1+8qBpV9NlS0BHOss5lqSB394EruF8KWAZIcloKq4RcEph4pQ9YAJslTaJ+uSSU9JX3VtSar6Qgp2Dw2OY/oK//POPNsTbXfGB5HWYWodpdBi987DU7w=="),1))),
unfilldown = UnFill(from,"Данные"),
unfillup = UnFill(from,"Данные",1),
unfilldown2 = UnFill(from,"Данные",0)
in
unfilldown2
По шагам:
a – получили список имен столбцов
b – нашли номер интересующего нас столбца
c – получили список списков – список, где каждый элемент – это список значений конкретного столбца
d – взяли интересующий нас столбец
e – а вот и начинается магия: если у нас Down – мы берем пустой элемент и добавляем к нему наш список без последнего значения, т.е. получаем список предыдущих значений; если Up – наоборот, удаляем первый элемент и получаем список последующих значений
f – попарно объединяем элементы исходного и преобразованного списков и сравниваем их – если одинаковые – возвращаем null, иначе сохраняем элемент исходного списка
g – теперь дело за малым – подсовываем результат наших преобразований на место исходного столбца (обратите внимание на конструкцию {f} – нам нужно передать элемент списка)
h – ну и собираем таблицу обратно
Всё, задача решена. unfilldown, unfillup, unfilldown2 – просто примеры корректного синтаксиса при использовании.
Вот так, курите списки, чтобы шаманить таблицы 😉
Надеюсь, было полезно.
Всех благ!
@buchlotnik
Telegram
Igor in Power Query ru
Господа, всем привет. Направьте на путь истинный, как решить задачу без Индекса ?. Руками попробовал решить, но само решение не нравится. Спасибо заранее
PropisRub - сумма прописью (дополнение)
#АнатомияФункций – custom
Всем привет!
Начали тут. Не думал, что добавить триллионы рублей такая проблема, но раз уж надо:
Надеюсь, было полезно.
Всех благ!
@buchlotnik
#АнатомияФункций – custom
Всем привет!
Начали тут. Не думал, что добавить триллионы рублей такая проблема, но раз уж надо:
PropisRub=(x)=>
[ ln = { {"десять ","одиннадцать ","двенадцать ","тринадцать ","четырнадцать ","пятнадцать ","шестнадцать ","семнадцать ","восемнадцать ","девятнадцать "},
{"","сто ","двести ","триста ","четыреста ","пятьсот ","шестьсот ","семьсот ","восемьсот ","девятьсот "},
{"","","двадцать ","тридцать ","сорок ","пятьдесят ","шестьдесят ","семьдесят ","восемьдесят ","девяносто "},
{"","один ","два ","три ","четыре ","пять ","шесть ","семь ","восемь ","девять "},
{"","одна ","две ","три ","четыре ","пять ","шесть ","семь ","восемь ","девять "}},
lr = { {"","триллион ","триллиона ","триллиона ","триллиона ","триллионов "},
{"","миллиард ","миллиарда ","миллиарда ","миллиарда ","миллиардов "},
{"","миллион ","миллиона ","миллиона ","миллиона ","миллионов "},
{"","тысяча ","тысячи ","тысячи ","тысячи ","тысяч "},
{"","","","","",""}},
lc = { {"лей","ль","ля","руб"},
{"ек","йка","йки","копе"}},
txt=(n,r)=>[ a = List.Transform(Text.ToList(n), each Number.From(_)),
b = ln{1}{a{0}} & (if a{1}<>1 then ln{2}{a{1}} & (if r = 3 then ln{4}{a{2}} else ln{3}{a{2}}) else ln{0}{a{2}}),
c = lr{r}{if a{1}=1 or a{2}=0 then 5 else List.Min({a{2},5})},
d = if Number.From(n)=0 then "" else b&c][d],
cur=(x,y)=>[ a = Number.From(x),
b = if Number.IntegerDivide(a,10)=1 or Number.Mod(a+9,10)>3 then lc{y}{0} else if Number.Mod(a,10)=1 then lc{y}{1} else lc{y}{2},
c = lc{y}{3}&b][c],
prp=(x)=>[ a = Splitter.SplitTextByRepeatedLengths(3)(x),
b = Text.Combine(List.Transform({0..4},each txt(a{_},_)),""),
c = if Number.From(x)=0 then "ноль " else b][c],
lst = Text.Split(Number.ToText(Number.Round(x,2),"000000000000000.00","en-US"),"."),
res = prp(lst{0})&cur(Text.End(lst{0},2),0)&" "&lst{1}&" "&cur(lst{1},1)][res]
Расширен диапазон, рубли и копейки склоняются. Юзайте на здоровье. Багрепорты по использованию приветствуются, а вот перламутровых пуговиц с расправленными крыльями не держим.Надеюсь, было полезно.
Всех благ!
@buchlotnik
Telegram
Для тех, кто в танке
Propis - сумма прописью
#АнатомияФункций - custom
Всем привет! Всплыл скелет из шкафа - когда-то давно писал функцию для суммы прописью, там нашлась бага. Багу исправил, выкладываю как есть, код хоть и старый но вполне годный:
Propis=(num)=>
[ ln = { …
#АнатомияФункций - custom
Всем привет! Всплыл скелет из шкафа - когда-то давно писал функцию для суммы прописью, там нашлась бага. Багу исправил, выкладываю как есть, код хоть и старый но вполне годный:
Propis=(num)=>
[ ln = { …
Buffer 1 – List.Buffer
#АнатомияФункций – List.Buffer
Всем привет! Что-то назрело обсуждение буферов в M. Начнем с самого, на мой взгляд, простого – List.Buffer. В качестве аргумента функция принимает список и … возвращает список. Суть в том, что список помещается в память и является стабильным (stable). Иными словами, после помещения в буфер список заново не вычисляется.
Зачем оно надо?
Возьмем такой пример:
Фамилия прилипла к имени и отчеству. Разделяем через Splitter.SplitTextByCharacterTransition и обратно собираем, но уже через пробел. Всё просто, но не совсем.
Дело в том, что функция f будет вызываться для каждого элемента столбца. А в качестве аргументов у нас указаны последовательности ({"a".."z"} - это не сокращённая запись, это именно последовательность, которая вычисляется на основании кодов символов) – таким образом аргументы будут вычисляться при каждом вызове. И вот чтобы этого избежать пишем так:
Собственно, всё то же самое, только списки вынесли в отдельные шаги и поместили их в буфер. Теперь аргументы нашей функции будут вычислены однократно и далее будут использоваться в обработке.
На массиве в 50k строк добавление буфера позволило сократить обработку с 4 до 2 секунд (ну то есть в два раза).
Вот так – следите за многократно используемыми списками (списки имен столбцов, списки дат и т.п.) и буферите их - это действительно влияет на скорость обработки.
Надеюсь, было полезно.
Всех благ!
@buchlotnik
#АнатомияФункций – List.Buffer
Всем привет! Что-то назрело обсуждение буферов в M. Начнем с самого, на мой взгляд, простого – List.Buffer. В качестве аргумента функция принимает список и … возвращает список. Суть в том, что список помещается в память и является стабильным (stable). Иными словами, после помещения в буфер список заново не вычисляется.
Зачем оно надо?
Возьмем такой пример:
let
from = #table({"fio"},{{"Иван ИвановичИванов"},{"Пётр ПетровичПетров"},{"Евлампий ЕвлампиевичЕвлампиев"},{"JohnSmith"}}),
f=(x)=>Text.Combine(Splitter.SplitTextByCharacterTransition({"a".."z"}&{"а".."я"},{"A".."Z"}&{"А".."Я"})(x)," "),
to = Table.TransformColumns(from,{"fio",f})
in
to
Фамилия прилипла к имени и отчеству. Разделяем через Splitter.SplitTextByCharacterTransition и обратно собираем, но уже через пробел. Всё просто, но не совсем.
Дело в том, что функция f будет вызываться для каждого элемента столбца. А в качестве аргументов у нас указаны последовательности ({"a".."z"} - это не сокращённая запись, это именно последовательность, которая вычисляется на основании кодов символов) – таким образом аргументы будут вычисляться при каждом вызове. И вот чтобы этого избежать пишем так:
let
from = #table({"fio"},{{"Иван ИвановичИванов"},{"Пётр ПетровичПетров"},{"Евлампий ЕвлампиевичЕвлампиев"},{"JohnSmith"}}),
a = List.Buffer({"a".."z"}&{"а".."я"}),
b = List.Buffer({"A".."Z"}&{"А".."Я"}),
f=(x)=>Text.Combine(Splitter.SplitTextByCharacterTransition(a,b)(x)," "),
to = Table.TransformColumns(from,{"fio",f})
in
to
Собственно, всё то же самое, только списки вынесли в отдельные шаги и поместили их в буфер. Теперь аргументы нашей функции будут вычислены однократно и далее будут использоваться в обработке.
На массиве в 50k строк добавление буфера позволило сократить обработку с 4 до 2 секунд (ну то есть в два раза).
Вот так – следите за многократно используемыми списками (списки имен столбцов, списки дат и т.п.) и буферите их - это действительно влияет на скорость обработки.
Надеюсь, было полезно.
Всех благ!
@buchlotnik
? ?? – Field Access и Coalesce – или зачем задавать вопросы в коде
#АнатомияФункций – основы
Всем привет!
Давайте поставим точку в вопросе знаков вопроса 😉. Уже разбирали тут, поэтому просто напомню:
Но что если нам не нужен null? Ведь результат может использоваться далее в расчётах и нам может понадобиться 0, {}, [], "" (ноль, пустой список, пустая запись, пустая строка и т.п.) или просто служебное сообщение вроде «не найдено».
Во тут нам на помощь и приходит оператор ?? – объединение (coalesce). В общем виде это выглядит так:
Ну а в нашем случае:
Вот так – никаких try, никаких if then else, просто знаки вопроса, поставленные в правильном месте.
В чате можно глянуть на боевой пример – берите на вооружение.
Надеюсь, было полезно.
Всех благ!
@buchlotnik
#АнатомияФункций – основы
Всем привет!
Давайте поставим точку в вопросе знаков вопроса 😉. Уже разбирали тут, поэтому просто напомню:
{1,2,3,4}{7} //ошибка – элементов в перечислении недостаточноПроблема решается путем использования ? (знак вопроса):
[a= "Вася", b= "Петя", c="Коля"][d]//ошибка – поле записи не найдено
Excel.CurrentWorkbook(){[Name="Таблица124"]}[Content]//ошибка, если нет такой таблицы
{1,2,3,4}{7}? //nullТ.е. при обращении к элементу списка или полю таблицы, в случае их отсутствия оператор ? вернёт нам null – это лучше, чем ошибка.
[a= "Вася", b= "Петя", c="Коля"][d]? //null
Excel.CurrentWorkbook(){[Name="Таблица124"]}?[Content]? //null
Но что если нам не нужен null? Ведь результат может использоваться далее в расчётах и нам может понадобиться 0, {}, [], "" (ноль, пустой список, пустая запись, пустая строка и т.п.) или просто служебное сообщение вроде «не найдено».
Во тут нам на помощь и приходит оператор ?? – объединение (coalesce). В общем виде это выглядит так:
x??yесли x не null – будет возвращён x, иначе – y.
Ну а в нашем случае:
{1,2,3,4}{7}? ??0 //0 – нольвозвращается нужное значение - главное не забыть поставить пробел между ? и ?? .
[a= "Вася", b= "Петя", c="Коля"][d]? ?? ""//пустая строка
Excel.CurrentWorkbook(){[Name="Таблица124"]}?[Content]? ??"данные не найдены"// данные не найдены
Вот так – никаких try, никаких if then else, просто знаки вопроса, поставленные в правильном месте.
В чате можно глянуть на боевой пример – берите на вооружение.
Надеюсь, было полезно.
Всех благ!
@buchlotnik
Telegram
Для тех, кто в танке
Excel.CurrentWorkbook(){[Name=…]}[Content] или зачем нам столько скобок?
#АнатомияФункций - Excel.CurrentWorkbook
Всем привет!
Уже разбирали вопрос обращения к полям и элементам списка в функциях, но поскольку регулярно возвращаемся к этому в чате, стоит…
#АнатомияФункций - Excel.CurrentWorkbook
Всем привет!
Уже разбирали вопрос обращения к полям и элементам списка в функциях, но поскольку регулярно возвращаемся к этому в чате, стоит…
Text.Combine – собираем текст воедино
#АнатомияФункций – Text.Combine
Всем привет! Проанализировал свои сообщения из чата – в лидерах используемых функций List.Transform (потому что на списках быстрее), далее Excel.CurrentWorkbook (без комментариев), далее Table.ColumnNames (её я не разбирал, но тут и разбирать нечего – даёт список имён столбцов таблицы), а вот четвертой оказалась Text.Combine – если честно, удивлён – но думается тут есть о чём поговорить.
Итак, справка:
separator – необязательный аргумент, без него объединение идет через «ничего», а точнее пустую строку:
Ну а если использовать не нулевую строку:
Ещё одна типичная задача – собрать текстовые значения, но чтобы каждое было с новой строки:
Escape-последовательности можно комбинировать с обычными символами:
Как-то так. Ничего сложного – главное всё преобразовать в текст и выяснить код нужного разделителя.
Надеюсь, было полезно.
Всех благ!
@buchlotnik
#АнатомияФункций – Text.Combine
Всем привет! Проанализировал свои сообщения из чата – в лидерах используемых функций List.Transform (потому что на списках быстрее), далее Excel.CurrentWorkbook (без комментариев), далее Table.ColumnNames (её я не разбирал, но тут и разбирать нечего – даёт список имён столбцов таблицы), а вот четвертой оказалась Text.Combine – если честно, удивлён – но думается тут есть о чём поговорить.
Итак, справка:
Text.Combine(texts as list, optional separator as nullable text) as texttexts – список текстовых значений, проверяем:
Text.Combine({"мама","мыла","раму"}) //мамамыларамуПоследний вариант - это одна из типичных ошибок, объединять можно только текст.
Text.Combine({"a".."f"}) //abcdef
Text.Combine({"1".."5"}) //12345
Text.Combine({1..5}) //Expression.Error: Не удается преобразовать значение 1 в тип Text
separator – необязательный аргумент, без него объединение идет через «ничего», а точнее пустую строку:
Text.Combine({"мама","мыла","раму"},"") //мамамыларамуПолучили то же самое, что и выше.
Ну а если использовать не нулевую строку:
Text.Combine({"мама","мыла","раму"}," ") // мама мыла рамуТ.е. строка произвольной длины просто подставляется между элементами.
Text.Combine({"мама","мыла","раму"},"-") // мама-мыла-раму
Text.Combine({"мама","мыла","раму"},"-&-") // мама-&-мыла-&-раму
Ещё одна типичная задача – собрать текстовые значения, но чтобы каждое было с новой строки:
Text.Combine({"мама","мыла","раму"},"#(lf)")Здесь "#(lf)"- escape-последовательность для обозначения непечатаемого символа - перевода строки (кому нужна табуляция - "#(tab) " – правда Excel её не отобразит, но если вы потом копируете текст в другое место – табуляция там будет).
Escape-последовательности можно комбинировать с обычными символами:
Text.Combine({"мама","мыла","раму"},";#(lf)")Наконец, символ не обязательно должен быть непечатаемым:
Text.Combine({"мама","мыла","раму"},"#(00A0)")Кто не узнал – это неразрывный пробел (A0 - это 160 в шестнадцатеричной системе). Т.е. пишем "#(шестнадцатеричный код Юникода) " – и подставляем любой код по вкусу. Ну а если у вас плохо с шестнадцатеричной системой, символы можно вызвать и по старинке:
Text.Combine({"мама","мыла","раму"},Character.FromNumber(160))Character.FromNumber понимает обычное (десятичное) представление.
Как-то так. Ничего сложного – главное всё преобразовать в текст и выяснить код нужного разделителя.
Надеюсь, было полезно.
Всех благ!
@buchlotnik
Telegram
Для тех, кто в танке
Зачем писать (x)=> вместо each _
#АнатомияФункций – основы, List.Transform
Возьмём простую задачку – имеем список текста
lst = {“1”,”2”,”3”}
и хотим получить список чисел
Решение банальное:
to = List.Transform(lst, each Number.From(_))
Продвинутые могут…
#АнатомияФункций – основы, List.Transform
Возьмём простую задачку – имеем список текста
lst = {“1”,”2”,”3”}
и хотим получить список чисел
Решение банальное:
to = List.Transform(lst, each Number.From(_))
Продвинутые могут…
Date + Time = ? получаем датувремя из даты и времени
#АнатомияФункций - DateTime.From, DateTime.FromText
Всем привет! Что-то последнее время несколько раз проскочила задачка по объединению даты и времени в датувремя. Зачем оно надо? Да просто реально частенько дата и время хранятся в отдельных столбцах, а надо анализировать периоды – т.е. разницы между строками и можно заморочиться на вычисление разницы дат и отдельно времени, а можно получить датувремя и просто считать разницу – реально удобнее.
Ну поехали. Пример будет предельно простой:
Теперь по шагам:
datetime0 – самое простое и естественное, что может придумать эксельщик – попробовать сложить два значения. Но так не выйдет – здесь нет автоматического преобразования типов, а дата и время – это разные типы данных. Поэтому придется колдовать.
datetime1 – раз нельзя просто сложить – пробуем привести типы – и дату, и время превращаем в числа, суммируем, из результата получаем датувремя – работает, прям как в Excel – и надо сказать это самый быстрый (с точки зрения производительности) способ
datetime2 – альтернативный способ – превратить оба значения в текст, сконкатенить и получить датувремя из текста – здесь есть проблема – Text.From "съедает" секунды.
datetime3 – решение проблемы секунд – использовать Time.ToText – в этом шаге представлен старый синтаксис, который работает в любой версии – вторым аргументом указан формат
datetime4 – то же самое, но в новом синтаксисе – здесь вторым аргументом идёт запись, одно из полей которой – формат. Обращаю внимание, что пробел можно "загнать" в формат
datetime5 – ещё один способ, который особенно хорош, если у вас дата и время уже на входе в виде текста – превратить дату в датувремя, а время – в длительность и сложить (этот способ быстрее, чем через конкатенацию текста, но медленнее чем через Number.From)
Ну и обсуждая DateTime.From, нельзя не вспомнить про DateTime.FromText – специализированная функция для вынимания датывремени из текста.
datetime6 – оказывается можно не заморачиваться с пробелами, а просто "объяснить" в каком формате данные находятся на входе. Причём формат может быть самый разный.
datetime7 – кто получал такие значения в запросах и потом парился со строковыми преобразованиями и локалями сейчас наверное прослезились от умиления – так тоже можно.
UPD datetime8 - так увлекся преобразованиями, что не написал главное (спасибо @sboy_ko - напомнил) - дату и время нельзя складывать, но можно конкатенировать (всё-таки и то и другое - особая форма record )
Собственно, всё. Вроде ничего запредельного – главное помнить про типы данных и форматы. Через текст нагляднее, через числа быстрее – но всё зависит от конкретной задачи и типов данных на входе.
Надеюсь, было полезно.
Всех благ!
@buchlotnik
#АнатомияФункций - DateTime.From, DateTime.FromText
Всем привет! Что-то последнее время несколько раз проскочила задачка по объединению даты и времени в датувремя. Зачем оно надо? Да просто реально частенько дата и время хранятся в отдельных столбцах, а надо анализировать периоды – т.е. разницы между строками и можно заморочиться на вычисление разницы дат и отдельно времени, а можно получить датувремя и просто считать разницу – реально удобнее.
Ну поехали. Пример будет предельно простой:
letДля начала – date и time – исходные дата и время. Обращаю внимание date – это просто имя, а #date() – функция, создающая значение даты, с time аналогично – не забываем и не путаем – диез решает )))
date = #date(2022,8,6),
time = #time(1,41,26),
datetime0 = date + time, // Не удается применить оператор + к типам Date и Time
datetime1 = DateTime.From(Number.From(date) + Number.From(time)),//06.08.2022 1:41:26
datetime2 = DateTime.From(Text.From(date)&" "&Text.From(time)), //06.08.2022 1:41:00
datetime3 = DateTime.From(Text.From(date)&" "&Time.ToText(time,"hh:mm:ss")),//06.08.2022 1:41:26
datetime4 = DateTime.From(Text.From(date)&Time.ToText(time,[Format=" hh:mm:ss"])),//06.08.2022 1:41:26
datetime5 = DateTime.From(date) + Duration.From("0."&Time.ToText(time,[Format="T"])),//06.08.2022 1:41:26
datetime6 = DateTime.FromText("06.08.2022"&"01:41:26",[Format="dd.MM.yyyyhh:mm:ss"]),//06.08.2022 1:41:26
datetime7 = DateTime.FromText("2022-08-06T01:41:26",[Format="yyyy-MM-ddThh:mm:ss"]),//06.08.2022 1:41:26
datetime8 = date & time //06.08.2022 1:41:26
in
datetime8
Теперь по шагам:
datetime0 – самое простое и естественное, что может придумать эксельщик – попробовать сложить два значения. Но так не выйдет – здесь нет автоматического преобразования типов, а дата и время – это разные типы данных. Поэтому придется колдовать.
datetime1 – раз нельзя просто сложить – пробуем привести типы – и дату, и время превращаем в числа, суммируем, из результата получаем датувремя – работает, прям как в Excel – и надо сказать это самый быстрый (с точки зрения производительности) способ
datetime2 – альтернативный способ – превратить оба значения в текст, сконкатенить и получить датувремя из текста – здесь есть проблема – Text.From "съедает" секунды.
datetime3 – решение проблемы секунд – использовать Time.ToText – в этом шаге представлен старый синтаксис, который работает в любой версии – вторым аргументом указан формат
datetime4 – то же самое, но в новом синтаксисе – здесь вторым аргументом идёт запись, одно из полей которой – формат. Обращаю внимание, что пробел можно "загнать" в формат
datetime5 – ещё один способ, который особенно хорош, если у вас дата и время уже на входе в виде текста – превратить дату в датувремя, а время – в длительность и сложить (этот способ быстрее, чем через конкатенацию текста, но медленнее чем через Number.From)
Ну и обсуждая DateTime.From, нельзя не вспомнить про DateTime.FromText – специализированная функция для вынимания датывремени из текста.
datetime6 – оказывается можно не заморачиваться с пробелами, а просто "объяснить" в каком формате данные находятся на входе. Причём формат может быть самый разный.
datetime7 – кто получал такие значения в запросах и потом парился со строковыми преобразованиями и локалями сейчас наверное прослезились от умиления – так тоже можно.
UPD datetime8 - так увлекся преобразованиями, что не написал главное (спасибо @sboy_ko - напомнил) - дату и время нельзя складывать, но можно конкатенировать (всё-таки и то и другое - особая форма record )
Собственно, всё. Вроде ничего запредельного – главное помнить про типы данных и форматы. Через текст нагляднее, через числа быстрее – но всё зависит от конкретной задачи и типов данных на входе.
Надеюсь, было полезно.
Всех благ!
@buchlotnik
Table.AddIndexColumn – снова получаем значения из предыдущих или последующих строк
#АнатомияФункций – Table.AddIndexColumn, List.Buffer
Всем привет! Сегодня в чате немножко без подготовки и с пол оборота случился челленж на тему получения значения из предыдущей строки. Мы уже решали такую задачу в ходе реализации UnFill. Сегодня же выяснилось, что во-первых, можно и через Table.Join (очень шустрый вариант от @MaximZelensky), ну а во-вторых… можно просто через Table.AddIndexColumn и список.
Собственно код:
lst – список значений нужного столбца, с добавленным в начало нулевым значением (и не забываем про буфер)
add – добавили столбец индекса
to – трансформируем индекс в значение из списка – поскольку мы добавили в начало списка нулевое значение, по индексу мы получим значение из предыдущей строки, что и требовалось )))
Сделаем другой вариант (он медленнее буквально на пару процентов, но интереснее):
Зачем было усложнять? - да всё дело в том, что просто играя с третьим аргументом AddIndexColumn можно сдвигаться на любое число позиций вверх или вниз:
А теперь объединяем всё в одну функцию:
Просто, лаконично и главное шустро! Юзайте на здоровье.
Надеюсь, было полезно.
Всех благ!
@buchlotnik
#АнатомияФункций – Table.AddIndexColumn, List.Buffer
Всем привет! Сегодня в чате немножко без подготовки и с пол оборота случился челленж на тему получения значения из предыдущей строки. Мы уже решали такую задачу в ходе реализации UnFill. Сегодня же выяснилось, что во-первых, можно и через Table.Join (очень шустрый вариант от @MaximZelensky), ну а во-вторых… можно просто через Table.AddIndexColumn и список.
Собственно код:
lettbl – сама таблица,
tbl = Table.FromColumns({{"a".."i"},{1..9}},{"a","b"}),
lst = List.Buffer({null}&tbl[b]),
add = Table.AddIndexColumn(tbl,"c",0,1),
to = Table.TransformColumns(add,{"c",(x)=>lst{x}})
in
to
lst – список значений нужного столбца, с добавленным в начало нулевым значением (и не забываем про буфер)
add – добавили столбец индекса
to – трансформируем индекс в значение из списка – поскольку мы добавили в начало списка нулевое значение, по индексу мы получим значение из предыдущей строки, что и требовалось )))
Сделаем другой вариант (он медленнее буквально на пару процентов, но интереснее):
letЗдесь мы просто получили список, а вот нумерацию начали с -1. Т. е. точно также сдвинули позицию на 1 назад, правда пришлось предотвратить ошибку отрицательного индекса: (x)=>if x<0 then null else lst{x}
tbl = Table.FromColumns({{"a".."i"},{1..9}},{"a","b"}),
lst = List.Buffer(tbl[b]),
add = Table.AddIndexColumn(tbl,"c",-1,1),
to = Table.TransformColumns(add,{"c",(x)=>if x<0 then null else lst{x}})
in
to
Зачем было усложнять? - да всё дело в том, что просто играя с третьим аргументом AddIndexColumn можно сдвигаться на любое число позиций вверх или вниз:
letВсё тоже самое, но сдвиг теперь вперед на 1 – так мы получаем следующую строку (ну и не забыли вопросик)
tbl = Table.FromColumns({{"a".."i"},{1..9}},{"a","b"}),
lst = List.Buffer(tbl[b]),
add = Table.AddIndexColumn(tbl,"c",1,1),
to = Table.TransformColumns(add,{"c",(x)=>lst{x}?})
in
to
А теперь объединяем всё в одну функцию:
letПо аргументам всё думаю понятно – таблица, столбец, имя нового столбца и на сколько строк сдвинуть.
tbl = Table.FromColumns({{"a".."i"},{1..9}},{"a","b"}),
AddOtherRowColumn = (tbl,col,newcol,index) =>
[ lst = List.Buffer(Table.Column(tbl,col)),
add = Table.AddIndexColumn(tbl,newcol,index,1),
f = if index <0 then (x)=> if x <0 then null else lst{x}
else (x)=>lst{x}?,
to = Table.TransformColumns(add,{newcol,f})][to],
to = AddOtherRowColumn(tbl,"b","c",-1),
to1 = AddOtherRowColumn(tbl,"b","c",-3),
to2 = AddOtherRowColumn(tbl,"b","c",2)
in
to2
Просто, лаконично и главное шустро! Юзайте на здоровье.
Надеюсь, было полезно.
Всех благ!
@buchlotnik
Telegram
Mikhail Muzykin in Power Query ru
https://t.me/PQ_ru/35708 - начало
https://t.me/PQ_ru/35731 - провокация
https://t.me/PQ_ru/35759 - адекватный ответ
https://t.me/PQ_ru/35772 - удар под дых
https://t.me/PQ_ru/35792 - жалкие потуги
https://t.me/PQ_ru/35796 - воля в кулак
https://t.me/PQ_ru/35811…
https://t.me/PQ_ru/35731 - провокация
https://t.me/PQ_ru/35759 - адекватный ответ
https://t.me/PQ_ru/35772 - удар под дых
https://t.me/PQ_ru/35792 - жалкие потуги
https://t.me/PQ_ru/35796 - воля в кулак
https://t.me/PQ_ru/35811…
Измерение скорости запросов - Мерка 1.7
#ПолезныеСсылки
Всем привет!
Вышла новая версия Мерки - 1.7
Добавлена возможность выбора числа холостых прогонов, в окно теперь выводится информация о запросе - тип, число диапазонов в книге, находится ли запрос в модели. Обновлена строка состояния - отображается процент выполнения, текущий запрос и номер итерации.
Как всегда, сложена на гитхаб.
Надеюсь, будет полезна.
Всех благ!
@buchlotnik
#ПолезныеСсылки
Всем привет!
Вышла новая версия Мерки - 1.7
Добавлена возможность выбора числа холостых прогонов, в окно теперь выводится информация о запросе - тип, число диапазонов в книге, находится ли запрос в модели. Обновлена строка состояния - отображается процент выполнения, текущий запрос и номер итерации.
Как всегда, сложена на гитхаб.
Надеюсь, будет полезна.
Всех благ!
@buchlotnik
GitHub
Merka/Мерка_1.7.xlam at main · buchlotnik/Merka
небольшая надстройка, предназначенная для оценки скорости выполнения запросов PQ и формул на листах в среде MS Excel в среде MS Excel - buchlotnik/Merka
List.Accumulate + Record.Field против Join-ов или словарь на записях
#АнатомияФункций - Record.Field, List.Accumulate
Всем привет!
Периодически в чате всплывает приём, по которому задают вопрос «что за магия»?
На самом деле приём широко известен в узких кругах, бывает эффективен, поэтому разберем.
Итак задача: есть таблица ФИО/должность, есть таблица Должность/оклад/премия, на выходе хотим таблицу ФИО/должность/оклад/премия. Понятное дело, что это решается через Join:
Тогда о чём пост? Да просто хочу предложить альтернативу:
Возник странный шаг c – с аккумулятором и пустой записью. Здесь мы таблицу соответствий разбили на строки (Table.ToRows) и аккумулируем полученные значения – первый столбец превращаем в название поля записи, остальной список – в значение данного поля. Т.е. на выходе получили запись, где поля содержат нужные значения (словарь). Зачем оно надо? Да просто при обращении к записи будет сразу выниматься значение нужного нам поля, без поиска.
Далее функция d – её аргументом будет список - мы увидим это на следующем шаге – она берет сам список и добавляет к нему значение нужного поля из словаря. Т.е. к списку значений по строке добавили список значений из таблицы подстановок
Ну и на шаге e – мы эту функцию подсовываем в мой любимый FromList.
Как бы всё. Наверняка немножко непривычно, но так может оказаться быстрее, особенно на больших таблицах. Пробуйте )))
Надеюсь, было полезно.
Всех благ!
@buchlotnik
#АнатомияФункций - Record.Field, List.Accumulate
Всем привет!
Периодически в чате всплывает приём, по которому задают вопрос «что за магия»?
На самом деле приём широко известен в узких кругах, бывает эффективен, поэтому разберем.
Итак задача: есть таблица ФИО/должность, есть таблица Должность/оклад/премия, на выходе хотим таблицу ФИО/должность/оклад/премия. Понятное дело, что это решается через Join:
letНо сразу можно сказать – жульничество – там Должность, там – Профессия, где заморочки с переименованием? Где удаление лишних столбцов, которые всегда есть? И т.д. Ну ОК, тогда NestedJoin:
a = #table({"ФИО","Профессия"},{{"Вася","Игрок"},{"Петя","Игрок"},{"Коля","Тренер"},{"Евлампий","Врач"}}),
b = #table({"Должность","Оклад","Премия"},{{"Игрок",1000,100},{"Тренер",2000,5000},{"Врач",100,1}}),
c = Table.Join(a,"Профессия",b,"Должность"),
d = Table.RemoveColumns(c,"Должность")
in
d
letЗдесь, думаю ситуация и решение многим знакомы.
a = #table({"ФИО","Профессия"},{{"Вася","Игрок"},{"Петя","Игрок"},{"Коля","Тренер"},{"Евлампий","Врач"}}),
b = #table({"Должность","Оклад","Премия"},{{"Игрок",1000,100},{"Тренер",2000,5000},{"Врач",100,1}}),
c = Table.NestedJoin(a,"Профессия",b,"Должность","tmp"),
d = Table.ExpandTableColumn(c, "tmp", {"Оклад","Премия"})
in
d
Тогда о чём пост? Да просто хочу предложить альтернативу:
letЧто поменялось?
a = #table({"ФИО","Профессия"},{{"Вася","Игрок"},{"Петя","Игрок"},{"Коля","Тренер"},{"Евлампий","Врач"}}),
b = #table({"Должность","Оклад","Премия"},{{"Игрок",1000,100},{"Тренер",2000,5000},{"Врач",100,1}}),
с = List.Accumulate(Table.ToRows(b),[],(s,c)=>Record.AddField(s,c{0},List.Skip(c))),
d = (x)=>x&Record.Field(с,x{1}),
e = Table.FromList(Table.ToRows(a),d,{"ФИО","Профессия","Оклад","Премия"})
in
e
Возник странный шаг c – с аккумулятором и пустой записью. Здесь мы таблицу соответствий разбили на строки (Table.ToRows) и аккумулируем полученные значения – первый столбец превращаем в название поля записи, остальной список – в значение данного поля. Т.е. на выходе получили запись, где поля содержат нужные значения (словарь). Зачем оно надо? Да просто при обращении к записи будет сразу выниматься значение нужного нам поля, без поиска.
Далее функция d – её аргументом будет список - мы увидим это на следующем шаге – она берет сам список и добавляет к нему значение нужного поля из словаря. Т.е. к списку значений по строке добавили список значений из таблицы подстановок
Ну и на шаге e – мы эту функцию подсовываем в мой любимый FromList.
Как бы всё. Наверняка немножко непривычно, но так может оказаться быстрее, особенно на больших таблицах. Пробуйте )))
Надеюсь, было полезно.
Всех благ!
@buchlotnik
Telegram
Для тех, кто в танке
List.Accumulate – а надо ли?
#АнатомияФункций - List.Accumulate
Всем привет!
По просьбам трудящихся разберем функцию List.Accumulate. Читаем справку:
List.Accumulate(list as list, seed as any, accumulator as function)
list – наш список, seed - опорное значение…
#АнатомияФункций - List.Accumulate
Всем привет!
По просьбам трудящихся разберем функцию List.Accumulate. Читаем справку:
List.Accumulate(list as list, seed as any, accumulator as function)
list – наш список, seed - опорное значение…
Record.FieldOrDefault, Record.FromTable – немножко оптимизации в словари на записях
#АнатомияФункций - Record.FieldOrDefault, Record.FromTable
Всем привет!
В прошлый раз мы посмотрели, как можно реализовать подстановку данных из другой таблицы через записи.
Возникает закономерный вопрос – а что делать, если в словаре есть не все соответствия?
Мы знаем про вот такой синтаксис
И даже такой:
Но они применимы при обращении к элементам записи или списка, а вот функция Record.Field выдаст ошибку и вопросик не спасет.
Зато спасёт другая, не менее полезная функция - Record.FieldOrDefault.
Т.е. без третьего аргумента имеем эквивалент [ ]?, с третьим аргументом – эквивалент [ ]? ??
Ну и применяем это к коду из прошлого поста:
Смотрим на шаг d – в качестве альтернативного значения указан не null, а пустой список {} – это важно, иначе будет ошибка несовпадения типов при конкатенации (ну а если нужно возвращать не null, а значение по умолчанию – также пишем список, но уже со значениями: {"не найдено","не положено"})
Таже обращаю внимание на шаг с. В прошлый раз мы использовали Accumulate, но если таблица соответствий большая, он может замедлять работу (на тестовом словаре в 50k строк сегодня просто умер). В этой ситуации есть более элегантный способ собрать большую запись - Record.FromTable. Но есть нюанс - на вход нельзя просто подать исходную таблицу, таблица обязательно должна состоять из двух столбцов - "Name" и "Value". Ну и в примере показано как это можно сделать через Table.ToRows+Table.FromList.
Собственно, всё - с отсутствующими совпадениями разобрались, большие таблицы в записи превратили, дело за малым – попробовать на практике. Успехов!
Надеюсь, было полезно.
Всех благ!
@buchlotnik
#АнатомияФункций - Record.FieldOrDefault, Record.FromTable
Всем привет!
В прошлый раз мы посмотрели, как можно реализовать подстановку данных из другой таблицы через записи.
Возникает закономерный вопрос – а что делать, если в словаре есть не все соответствия?
Мы знаем про вот такой синтаксис
[a=1,b=2,c=3][d]? // null
И даже такой:
[a=1,b=2,c=3][d]? ?? "не найдено"// не найдено
Но они применимы при обращении к элементам записи или списка, а вот функция Record.Field выдаст ошибку и вопросик не спасет.
Зато спасёт другая, не менее полезная функция - Record.FieldOrDefault.
Record.FieldOrDefault([a=1,b=2,c=3], "d") //null
Record.FieldOrDefault([a=1,b=2,c=3], "d","не найдено") // не найдено
Т.е. без третьего аргумента имеем эквивалент [ ]?, с третьим аргументом – эквивалент [ ]? ??
Ну и применяем это к коду из прошлого поста:
let
a = #table({"ФИО","Профессия"},{{"Вася","Игрок"},{"Петя","Игрок"},{"Коля","Тренер"},{"Евлампий","Врач"}}),
b = #table({"Должность","Оклад","Премия"},{{"Игрок",1000,100},{"Тренер",2000,5000}}),
с = Record.FromTable(Table.FromList(Table.ToRows(b),(x)=>{x{0},List.Skip(x)},{"Name","Value"})),
d = (x)=>x&Record.FieldOrDefault(с,x{1},{}),
e = Table.FromList(Table.ToRows(a),d,{"ФИО","Профессия","Оклад","Премия"})
in
e
Смотрим на шаг d – в качестве альтернативного значения указан не null, а пустой список {} – это важно, иначе будет ошибка несовпадения типов при конкатенации (ну а если нужно возвращать не null, а значение по умолчанию – также пишем список, но уже со значениями: {"не найдено","не положено"})
Таже обращаю внимание на шаг с. В прошлый раз мы использовали Accumulate, но если таблица соответствий большая, он может замедлять работу (на тестовом словаре в 50k строк сегодня просто умер). В этой ситуации есть более элегантный способ собрать большую запись - Record.FromTable. Но есть нюанс - на вход нельзя просто подать исходную таблицу, таблица обязательно должна состоять из двух столбцов - "Name" и "Value". Ну и в примере показано как это можно сделать через Table.ToRows+Table.FromList.
Собственно, всё - с отсутствующими совпадениями разобрались, большие таблицы в записи превратили, дело за малым – попробовать на практике. Успехов!
Надеюсь, было полезно.
Всех благ!
@buchlotnik
Telegram
Для тех, кто в танке
List.Accumulate + Record.Field против Join-ов или словарь на записях
#АнатомияФункций - Record.Field, List.Accumulate
Всем привет!
Периодически в чате всплывает приём, по которому задают вопрос «что за магия»?
На самом деле приём широко известен в узких…
#АнатомияФункций - Record.Field, List.Accumulate
Всем привет!
Периодически в чате всплывает приём, по которому задают вопрос «что за магия»?
На самом деле приём широко известен в узких…
TableRenameColumnsByPositions или как переименовать столбцы по номеру
#АнатомияФункций – custom functions
Всем привет! Сегодня очередной раз был поднят вопрос о переименовании столбцов в таблице по номеру (позиции). По этому поводу функция:
func – сама функция
typ – описание её типа – что-то подобное мы уже смотрели в постах про типизацию и документацию
result – возвращаем функцию со всеми метаданными, чтоб при вызове глаз радовался)))
Ну а теперь по функции.
Общая идея состоит в том, что обращаться к столбцам (полям) можно только по имени, но у нас есть возможность получить список имен столбцов, а вот со списком можно прекрасно работать по номерам элементов. Поехали:
a – получили список имен столбцов
b – нашли их количество, нам это нужно, если нумерация идёт с конца
c – функция преобразования, которая превращает номер в имя столбца и возвращает пару {изменяемое_имя,новое_имя}. Логика такая: если номер положительный – берем просто нужный элемент списка (там -1 поскольку нумерация с нуля), а если отрицательный – берем с конца, т.е. складываем общее число столбцов с тем, на сколько столбцов от конца нужно отступить
d – поскольку преобразование одного столбца – это просто список, а нескольких – список списков, то проверяем, чем у нас является первый элемент переданного аргумента – если он список, то преобразованию нужно подвергать список списков с использованием List.Transform, в противном случае однократно применяем функцию преобразования c к аргументу list
e – подсовываем список преобразования штатной Table.RenameColumns
Собственно, всё. Немножко пришлось пошаманить для универсальности, но в целом, надеюсь, сложностей с пониманием возникнуть не должно. Юзайте на здоровье!
Надеюсь, было полезно.
Всех благ!
@buchlotnik
#АнатомияФункций – custom functions
Всем привет! Сегодня очередной раз был поднят вопрос о переименовании столбцов в таблице по номеру (позиции). По этому поводу функция:
[func =(table,list) =>Глобально по шагам:
[ a = List.Buffer(Table.ColumnNames(table)),
b = List.Count(a),
c = (x) =>{a{if x{0}>0 then x{0}-1 else b+x{0}},x{1}},
d = if list{0} is list then List.Transform(list,c) else c(list),
e = Table.RenameColumns(table,d)
][e],
typ = type function (
table as (type table meta [Documentation.FieldCaption = "исходная таблица"]),
list as (type list meta [Documentation.FieldCaption = "параметры переименования"])
)
as table meta [
Documentation.Name = "TableRenameColumnsByPositions> (@buchlotnik)",
Documentation.LongDescription = "Функция переименовывает столбцы таблицы по их номеру. Параметры переименования задаются в виде списка {номер, новое название} либо списка списков {{номер1, новое название1},{номер2, новое название2}}. Положительное значение номера задает нумерацию с начала таблицы, отрицательное - с конца.",
Documentation.Examples = {
[Description = "переименовать первый столбец", Code = "=TableRenameColumnsByPositions(#table({""a"",""b"",""c""},{}),{1,""первый""})",Result="#table({""первый"",""b"",""c""},{})"],
[Description = "перименовать первый с конца столбец", Code = "=TableRenameColumnsByPositions(#table({""a"",""b"",""c""},{}),{-1,""первый с конца""})",Result="#table({""a"",""b"",""первый с конца""},{})"],
[Description = "переименовать первый и последний столбцы", Code = "=TableRenameColumnsByPositions(#table({""a"",""b"",""c""},{}),{{1,""первый""},{-1,""последний""}})",Result="#table({""первый"",""b"",""последний""},{})"]
}
],
result = Value.ReplaceType(func,typ)
][result]
func – сама функция
typ – описание её типа – что-то подобное мы уже смотрели в постах про типизацию и документацию
result – возвращаем функцию со всеми метаданными, чтоб при вызове глаз радовался)))
Ну а теперь по функции.
Общая идея состоит в том, что обращаться к столбцам (полям) можно только по имени, но у нас есть возможность получить список имен столбцов, а вот со списком можно прекрасно работать по номерам элементов. Поехали:
a – получили список имен столбцов
b – нашли их количество, нам это нужно, если нумерация идёт с конца
c – функция преобразования, которая превращает номер в имя столбца и возвращает пару {изменяемое_имя,новое_имя}. Логика такая: если номер положительный – берем просто нужный элемент списка (там -1 поскольку нумерация с нуля), а если отрицательный – берем с конца, т.е. складываем общее число столбцов с тем, на сколько столбцов от конца нужно отступить
d – поскольку преобразование одного столбца – это просто список, а нескольких – список списков, то проверяем, чем у нас является первый элемент переданного аргумента – если он список, то преобразованию нужно подвергать список списков с использованием List.Transform, в противном случае однократно применяем функцию преобразования c к аргументу list
e – подсовываем список преобразования штатной Table.RenameColumns
Собственно, всё. Немножко пришлось пошаманить для универсальности, но в целом, надеюсь, сложностей с пониманием возникнуть не должно. Юзайте на здоровье!
Надеюсь, было полезно.
Всех благ!
@buchlotnik
Telegram
Для тех, кто в танке
Сложно о функциях 1 - Типизация
#АнатомияФункций - Type.ForFunction
Всем привет!
Недавно разбирали Table.TransformColumns и я там немного слукавил, сказав, что только с помощью функции нельзя передать тип столбца. На самом деле при написании функции мы…
#АнатомияФункций - Type.ForFunction
Всем привет!
Недавно разбирали Table.TransformColumns и я там немного слукавил, сказав, что только с помощью функции нельзя передать тип столбца. На самом деле при написании функции мы…
(x)=>x или зачем нужна функция, которая ничего не делает?
#АнатомияФункций – общие вопросы
Всем привет!
В свое время мы разбирали как написать «пустой аргумент» - нам это было нужно, поскольку различные операции осуществляются с разными типами данных и важно задать правильный. Но точно так же бывают ситуации, когда в качестве аргумента мы должны передать функцию, а на самом деле никаких преобразований не требуется.
Вот в таких ситуациях нас и выручает
Зачем оно надо? Смотрим пример и ниже разберём по шагам:
group – мы группируем по столбцу n и хотим получить все данные, поэтому функция агрегации нам и возвращает их все. Заодно мы узнали, что при группировке получается таблица (table), т.е. если вы заранее не знаете от какого типа данных писать агрегатор – напишите (x)=>x и увидев результат, делайте выводы
add – добавление столбца к таблице, аналогично – функция возвращает всё, что поступило на вход – и в данном случае это запись (record). Т.е. мы выяснили, что на вход функции подается строка целиком в виде записи
pivot – не люблю эту функцию, но сказать необходимо – при сведении данных от нас также хотят функцию агрегации, которой на вход приходит… список (list). Мышкоклацем так не сделать – если выбрать «не агрегировать» - функция будет записана без пятого аргумента и выдаст ошибку, а так всё работает.
Все примеры выше больше направлены на понимание того, с каким типом данных мы работаем, но есть вполне прикладные таблично-списочные операции:
comb – хотим собрать значения из ряда столбцов в список, от нас требуют Combiner как функцию – только у нас на входе и так список значений, и теперь мы знаем что с этим делать
split – этот пример уже разбирали - нам вроде как нужно разделить столбец, только он уже разделен, поэтому не делаем ничего
from – ну и для меня наиболее частый случай – сбор таблицы из списков – «почему не просто через Table.FromRows?» спросите вы – «потому что попробуйте прям на этом примере» отвечу я – FromRows требует списков одинаковой длины, а FromList – это функция пяти аргументов и с ней можно договориться, просто в данном случае её аргумент Splitter не должен делать ничего.
Как-то так. В М крайне важно использовать/передавать/обрабатывать корректные типы данных. Функция – это тоже тип данных и сегодня мы выяснили, как её написать, чтобы она была, но «ничего не делала».
Надеюсь, было полезно.
Всех благ!
@buchlotnik
#АнатомияФункций – общие вопросы
Всем привет!
В свое время мы разбирали как написать «пустой аргумент» - нам это было нужно, поскольку различные операции осуществляются с разными типами данных и важно задать правильный. Но точно так же бывают ситуации, когда в качестве аргумента мы должны передать функцию, а на самом деле никаких преобразований не требуется.
Вот в таких ситуациях нас и выручает
(x)=>xЭто функция, принимающая аргумент x и возвращающая этот самый аргумент. Фактически мы написали функцию, которая ничего не делает (но при этом является функцией!).
Зачем оно надо? Смотрим пример и ниже разберём по шагам:
lettbl – входные данные
tbl = #table({"n","a".."f"},{{"i",1..5,{1,2,3}},{"j",2..6,{1,2}},{"k",3..7,{1}},{"i",4..8,{1,2}},{"j",5..9,{1}}}),
group = Table.Group(tbl,"n",{"tmp",(x)=>x}),
add = Table.AddColumn(tbl,"tmp",(x)=>x),
pivot = Table.Pivot(tbl[[n],[a]], {"i".."k"}, "n", "a",(x)=>x),
comb = Table.CombineColumns(tbl,List.Skip(Table.ColumnNames(tbl)),(x)=>x,"tmp"),
splt = Table.SplitColumn(tbl,"f",(x)=>x),
from = Table.FromList(tbl[f],(x)=>x)
in
from
group – мы группируем по столбцу n и хотим получить все данные, поэтому функция агрегации нам и возвращает их все. Заодно мы узнали, что при группировке получается таблица (table), т.е. если вы заранее не знаете от какого типа данных писать агрегатор – напишите (x)=>x и увидев результат, делайте выводы
add – добавление столбца к таблице, аналогично – функция возвращает всё, что поступило на вход – и в данном случае это запись (record). Т.е. мы выяснили, что на вход функции подается строка целиком в виде записи
pivot – не люблю эту функцию, но сказать необходимо – при сведении данных от нас также хотят функцию агрегации, которой на вход приходит… список (list). Мышкоклацем так не сделать – если выбрать «не агрегировать» - функция будет записана без пятого аргумента и выдаст ошибку, а так всё работает.
Все примеры выше больше направлены на понимание того, с каким типом данных мы работаем, но есть вполне прикладные таблично-списочные операции:
comb – хотим собрать значения из ряда столбцов в список, от нас требуют Combiner как функцию – только у нас на входе и так список значений, и теперь мы знаем что с этим делать
split – этот пример уже разбирали - нам вроде как нужно разделить столбец, только он уже разделен, поэтому не делаем ничего
from – ну и для меня наиболее частый случай – сбор таблицы из списков – «почему не просто через Table.FromRows?» спросите вы – «потому что попробуйте прям на этом примере» отвечу я – FromRows требует списков одинаковой длины, а FromList – это функция пяти аргументов и с ней можно договориться, просто в данном случае её аргумент Splitter не должен делать ничего.
Как-то так. В М крайне важно использовать/передавать/обрабатывать корректные типы данных. Функция – это тоже тип данных и сегодня мы выяснили, как её написать, чтобы она была, но «ничего не делала».
Надеюсь, было полезно.
Всех благ!
@buchlotnik
Telegram
Для тех, кто в танке
List.Accumulate – как написать «пустой» второй аргумент
#АнатомияФункций – List.Accumulate
Всем привет!
Давно зрело и вот нашёлся информационный повод.
Итак, возвращаемся к нашему List.Accumulate (помним, что медленный, но иногда удобный).
List.Accumulate(list…
#АнатомияФункций – List.Accumulate
Всем привет!
Давно зрело и вот нашёлся информационный повод.
Итак, возвращаемся к нашему List.Accumulate (помним, что медленный, но иногда удобный).
List.Accumulate(list…
Table.SelectColumns, Table.RemoveColumns, Table.ReorderColumns – или жонглируем столбцами таблицы
#АнатомияФункций - Table.SelectColumns, Table.RemoveColumns, Table.ReorderColumns
Всем привет!
В М существует ряд функций для работы со столбцами, но писать по каждой отдельный пост как-то скучно, а вот если объединить – может будет информативно.
Итак SelectColumns, RemoveColumns, ReorderColumns – имеют два обязательных и один необязательный аргумент:
table – таблица, которую преобразуем
columns/columnsOrder – имя столбца или список имен столбцов (выбирать или удалять можно и один столбец, а вот менять порядок – только у списка)
missingfield – необязательный аргумент, говорящий что делать с отсутствующими столбцами – как водится имеет два варианта - MissingField.UseNull или MissingField.Ignore
Смотрим код и ниже разбираем шаги:
select1 и reorder1 – ничего не поменялось – одна функция выбрала столбцы, другая расположила столбцы в том порядке, в котором они и шли, а remove1 – вернула пустую таблицу – поскольку все столбцы пришлось удалить. Всё логично, но как-то не показательно. Другое дело, что со списком столбцов можно поиграть, и вот тут начинается интересное
lst2 – взяли только последнее и первое имена из списка
select2 – получили только последний и первый столбцы
remove2 – таблица без первого и последнего столбцов
reorder2 – таблица, в которой первый и последний столбцы поменяны местами. Обращаю внимание – Select просто берет те столбцы, которые указали, в том порядке, в котором указали, а вот Reorder – столбцы из второго аргумента берет в нужном порядке, но остальные оставляет на местах
lst3 – более сложный пример – List.Alternate даст нам все столбцы на нечётных позициях, а Reverse – расположит их в обратном порядке (иначе не будет понятно зачем нам reorder)
select3 – все нечётные столбцы в обратном порядке
remove3 – все четные столбцы (нечетные были удалены)
reorder3 – чётные столбцы на местах, нечётные – в обратном порядке
lst4 – ну и типичный вариант при сборе из кучи файлов – надо собрать/удалить столбцы, а не во всех файлах они присутствуют – чтобы не было ошибки прописываем MissingField
remove41, remove42 – вернулась таблица со столбцом а – остальные удалены, лишние были проигнорированы
select41 – таблица без первого столбца, лишние проигнорированы
reorder41 – исходная таблица (лишние проигнорированы, первый столбец был пропущен в списке и поэтому остался на месте)
select42 – таблица со всеми столбцами из второго аргумента, отсутствующие столбцы заполнены null
reorder42 – а вот это самое интересное – столбец a остался на месте, а далее все столбцы из второго аргумента
Думаю понятно, что это просто небольшая демонстрация. Ключевым является правильно построить второй аргумент и понимать, как его анализирует функция. Логика у них похожая но не идентичная, экспериментируйте!
Надеюсь, было полезно.
Всех благ!
@buchlotnik
#АнатомияФункций - Table.SelectColumns, Table.RemoveColumns, Table.ReorderColumns
Всем привет!
В М существует ряд функций для работы со столбцами, но писать по каждой отдельный пост как-то скучно, а вот если объединить – может будет информативно.
Итак SelectColumns, RemoveColumns, ReorderColumns – имеют два обязательных и один необязательный аргумент:
table – таблица, которую преобразуем
columns/columnsOrder – имя столбца или список имен столбцов (выбирать или удалять можно и один столбец, а вот менять порядок – только у списка)
missingfield – необязательный аргумент, говорящий что делать с отсутствующими столбцами – как водится имеет два варианта - MissingField.UseNull или MissingField.Ignore
Смотрим код и ниже разбираем шаги:
letlst1 – список имён столбцов таблицы
from = #table({"a".."h"},{{1..8}}),
lst1 = Table.ColumnNames(from),
select1 = Table.SelectColumns(from,lst1),
remove11 = Table.RemoveColumns(from,lst1),
reorder1 = Table.ReorderColumns(from,lst1),
lst2 = {List.Last(lst1),List.First(lst1)},
select2 = Table.SelectColumns(from,lst2),
remove2 = Table.RemoveColumns(from,lst2),
reorder2 = Table.ReorderColumns(from,lst2),
lst3 = List.Reverse(List.Alternate(lst1,1,1,1)),
select3 = Table.SelectColumns(from,lst3),
remove3 = Table.RemoveColumns(from,lst3),
reorder3 = Table.ReorderColumns(from,lst3),
lst4 = {"k","j","i"}&List.Skip(lst1),
select41 = Table.SelectColumns(from,lst4,MissingField.Ignore),
remove41 = Table.RemoveColumns(from,lst4,MissingField.Ignore),
reorder41 = Table.ReorderColumns(from,lst4,MissingField.Ignore),
select42 = Table.SelectColumns(from,lst4,MissingField.UseNull),
remove42 = Table.RemoveColumns(from,lst4,MissingField.UseNull),
reorder42 = Table.ReorderColumns(from,lst4,MissingField.UseNull)
in
reorder42
select1 и reorder1 – ничего не поменялось – одна функция выбрала столбцы, другая расположила столбцы в том порядке, в котором они и шли, а remove1 – вернула пустую таблицу – поскольку все столбцы пришлось удалить. Всё логично, но как-то не показательно. Другое дело, что со списком столбцов можно поиграть, и вот тут начинается интересное
lst2 – взяли только последнее и первое имена из списка
select2 – получили только последний и первый столбцы
remove2 – таблица без первого и последнего столбцов
reorder2 – таблица, в которой первый и последний столбцы поменяны местами. Обращаю внимание – Select просто берет те столбцы, которые указали, в том порядке, в котором указали, а вот Reorder – столбцы из второго аргумента берет в нужном порядке, но остальные оставляет на местах
lst3 – более сложный пример – List.Alternate даст нам все столбцы на нечётных позициях, а Reverse – расположит их в обратном порядке (иначе не будет понятно зачем нам reorder)
select3 – все нечётные столбцы в обратном порядке
remove3 – все четные столбцы (нечетные были удалены)
reorder3 – чётные столбцы на местах, нечётные – в обратном порядке
lst4 – ну и типичный вариант при сборе из кучи файлов – надо собрать/удалить столбцы, а не во всех файлах они присутствуют – чтобы не было ошибки прописываем MissingField
remove41, remove42 – вернулась таблица со столбцом а – остальные удалены, лишние были проигнорированы
select41 – таблица без первого столбца, лишние проигнорированы
reorder41 – исходная таблица (лишние проигнорированы, первый столбец был пропущен в списке и поэтому остался на месте)
select42 – таблица со всеми столбцами из второго аргумента, отсутствующие столбцы заполнены null
reorder42 – а вот это самое интересное – столбец a остался на месте, а далее все столбцы из второго аргумента
Думаю понятно, что это просто небольшая демонстрация. Ключевым является правильно построить второй аргумент и понимать, как его анализирует функция. Логика у них похожая но не идентичная, экспериментируйте!
Надеюсь, было полезно.
Всех благ!
@buchlotnik