Для тех, кто в танке
3.38K subscribers
8 photos
2 videos
3 files
196 links
Канал создан для себя, обсуждаем вопросы использования языка M и шарим всякие полезные ссылки.
На вопросы отвечаем в комментах и тут - t.me/pbi_pq_from_tank_chat

Для желающих поддержать канал - https://sponsr.ru/pq_m_buchlotnik/
Download Telegram
1С.Разбираем многоуровневую шапку
#1C - шапка
Всем привет!
Многоуровневые шапки таблиц, выгруженных из никогда не добавляют счастья (Примеры таких таблиц найдете в Первом комментарии к посту.)
Зачастую пользователь начинает «руками» исправлять шапку таблицы, чтобы привести ее в удобную форму для дальнейшей работы.
Со своей стороны предложим несколько способов в помощь:
1. Для простой многоуровневой шапки таблицы, когда заполнены все заголовки полей (см. Пример1 в приложении) на просторах интернета можно увидеть такое:
  from=ИсточникДанных,
t=Table.Transpose(from),
mergecol = Table.CombineColumns(t,{"Column1", "Column2"},Combiner.CombineTextByDelimiter(".", QuoteStyle.None),"Headers"),
tr=Table.Transpose(mergecol),
to=Table.PromoteHeaders(tr)


Коротко про подход: Транспонируем таблицу, комбайним через разделитель , снова транспонируем и поднимаем заголовки. Далее таблицу можно свернуть для работы со сводной: Table.UnpivotOtherColumns ()

2. Для шапки, в которой заполнены не все поля (см. Пример 2) можно воспользоваться следующим кодом:
    from = ИсточникДанных,
lst=Table.ToColumns(Table.Range(from,0,2)),
newl=Table.ToList(Table.FillDown(Table.FromList(lst,(x)=>x),{"Column1"}),(x)=>x),
tr=List.Transform(newl,(x)=>Text.Combine(x,".")),
to=Table.RenameColumns(Table.Range(from,2),List.Zip({Table.ColumnNames(from),tr}))

Подход так же не сложный: «Откусили» шапку, преобразовали и приставили заново к телу таблицы.

Но что если уровней в таблице три и более или структура шапки похожа на таблицу в Примере 3 ?

3. На помощь может прийти решение, которое в большинстве случаев преобразует шапку таблицы в «плоский» формат:
fnParsHead=(table,num,optional delim,optional fill)=>
[ lst = List.Buffer(Table.ToColumns(Table.Range(table,0,num))),
delim = if delim=null then " " else delim,
f=(x,y)=>List.Accumulate( List.Zip({x,y}),
[i=false,j={}],
(s,c)=>[i=s[i]=true or c{0}<>null,j=if i then s[j]&{c{0}} else s[j]&{c{0}??c{1}}]
)[j],
g=(x)=>if fill=null then Text.Combine(x,delim) else Text.Combine(List.ReplaceValue(x,null,fill,Replacer.ReplaceValue),delim),
gen = List.Generate( ()=>[i=0,l=lst{i},o=l],
(x)=>x[i]<List.Count(lst),
(x)=>[i=x[i]+1,l=lst{i},o=f(l,x[o])],
(x)=>g(x[o])
),
out = Table.RenameColumns(Table.Range(table,num),List.Zip({Table.ColumnNames(table),gen}))][out],
from=ИсточникДанных,
to=fnParsHead(from,3,".")

Разберем данный код.
Эта функция анализирует первые несколько строк таблицы table, указанных в параметре num, далее по столбцам идёт объединение текста с использованием в качестве разделителя параметра delim. Опционально четвертый параметр fill позволяет задать строковое значение, которое будет использовано для заголовков нижних уровней в случае их отсутствия в данном столбце.
lst: Преобразуем в список строки таблицы с шапкой
delim: Опрашиваем входящий параметр-разделитель и при его отсутствии устанавливаем пробел
g: проверка параметра fill и если оно ненулевое, то добавляем к значению поля параметр, переданный в функцию fnParshead.
f:
на вход подаем два списка, связываем попарно и обрабатываем.
gen: Генератор, который собирает список для будущего заголовка таблицы в комплексе с функциями f и g.

Попробуйте на приложенных примерах разобрать каждый подход. И если что-то окажется полезным – значит все было не зря.

В большей степени пост предназначен для начинающих и продолжающих изучать функциональное программирование, хотя и профессионалы, надеюсь, смогут найти тут для себя что-то интересное.
Примеры, представленные выше показывают, как через редактор можно решить насущную задачу по разбору многоуровневой шапки. Так же предлагаю попробовать решить приложенные примеры, не прибегая к коду:
(мышкоклацем) – что получится?

С уважением к Вам. @CubRoot

PS^ Михаил, спасибо за помощь )
Table.CombineColumns или где окажется результирующий столбец?
#АнатомияФункций - Table.CombineColumns

Всем привет!
Пилил небольшую задачку и вспомнил один приём, которым и хочу поделиться.
Итак, имеем таблицу, в которой несколько столбцов нужно объединить – в примере просто нужно найти сумму и запихнуть её в столбец "sum". Собственно, ничего сложного – пишем код:
let
from = #table({"a".."f"},{{1..6},{2..7},{3..8}}),
to = Table.CombineColumns(from,{"a","c","e"},List.Sum,"sum"),//{"b","d","sum","f"}
to1 = Table.CombineColumns(from,{"a","e","c"},List.Sum,"sum"),//{"b","sum","d","f"}
to2 = Table.CombineColumns(from,{"c","e","a"},List.Sum,"sum")//{"sum","b","d","f"}
in
to2

from – таблица,
to – объединяем столбцы {"a","c","e"} и на выходе получаем таблицу со следующим порядком столбцов - {"b","d","sum","f"} – и внимательный читатель заметит, что сумма оказалась на месте столбца "e", т.е. последнего в нашем списке.
Интересно, давайте поэкспериментируем:
to1 – поставили последним столбец "c" и теперь результат оказался на его месте
to2 – а теперь результат вообще вначале, на месте столбца "a", поскольку именно его мы указали последним.

Как-то так – для CombineColumns важен порядок имён в списке объединяемых столбцов – так мы можем сразу регулировать, где окажется результат.

Надеюсь, было полезно.
Всех благ!
@buchlotnik
buchOfficePack - анонс, теперь не отверчусь
#АнатомияФункций - buchOfficePack

Всем привет!
Небольшая задачка, которую я пилил вчера, доступна в рамках следующего запроса:
Expression.Evaluate(Text.FromBinary(Binary.Buffer(Web.Contents("https://raw.githubusercontent.com/buchlotnik/buchlotnik_functions/main/buchOfficePack"))),#shared)

Ну а что это, зачем и почему - рассказываю на Ютубе
Если тема интересная, прошу наследить в каментах под роликом

Надеюсь, будет полезно.
Всех благ!
@buchlotnik
buchOfficePack – fxUnzip – часть 1 ядро
#АнатомияФункций - buchOfficePack

Всем привет!
Ну что ж – началось – пилим библиотеку на камеру.
Видос уже на Ютубе
Код для страждущих тут:
(ZIP)=>
[
u16 =BinaryFormat.ByteOrder(BinaryFormat.UnsignedInteger16,ByteOrder.LittleEndian),
u32 =BinaryFormat.ByteOrder(BinaryFormat.UnsignedInteger32,ByteOrder.LittleEndian),
get=(x)=>BinaryFormat.Record(
if Binary.Range(x,0,4)=#binary({0x50,0x4b,0x03,0x04})
then [ Name=BinaryFormat.Text(u16(Binary.Range(x,26,2))),
Extr=BinaryFormat.Binary(u16(Binary.Range(x,28,2))),
Value=BinaryFormat.Transform(BinaryFormat.Binary(u32(Binary.Range(x,18,4))),(x)=>Binary.Decompress(x,Compression.Deflate)),
flag=true]
else [flag=false]
),
lst = BinaryFormat.List(BinaryFormat.Choice(BinaryFormat.Binary(30),get),(x)=>x[flag]=true)(ZIP),
to = Table.FromRecords(List.RemoveLastN(lst,1),type table [Name=text,Value=binary])][to]


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

Надеюсь, было полезно.
Всех благ!
@buchlotnik
buchOfficePack – fxUnzip – часть 2 обвес
#АнатомияФункций - buchOfficePack

Всем привет!
Продолжаем пилить библиотеку. Видос уже на Ютубе

Добавили всё по красоте – параметры, справку и залили на гитхаб.
Итоговый код ниже, все мои комментарии в видосе.

fxUnzip=[func=(ZIP, optional options)=>
[
encod = [a=options[Encoding]?,b=if a = null then 866 else a][b],
compr = (x)=> if x =0 then Compression.None else Compression.Deflate,

u16 =BinaryFormat.ByteOrder(BinaryFormat.UnsignedInteger16,ByteOrder.LittleEndian),
u32 =BinaryFormat.ByteOrder(BinaryFormat.UnsignedInteger32,ByteOrder.LittleEndian),

get=(x)=>BinaryFormat.Record(
if Binary.Range(x,0,4)=#binary({0x50,0x4b,0x03,0x04})
then [ Name=BinaryFormat.Text(u16(Binary.Range(x,26,2)),encod),
Extr=BinaryFormat.Binary(u16(Binary.Range(x,28,2))),
Value=BinaryFormat.Transform(BinaryFormat.Binary(u32(Binary.Range(x,18,4))),(y)=>Binary.Decompress(y,compr(u16(Binary.Range(x,8,2)))))]
else []
),

lst = BinaryFormat.List(BinaryFormat.Choice(BinaryFormat.Binary(30),get),(x)=>x<>[])(ZIP),
to = Table.FromRecords(List.RemoveLastN(lst,1),type table [Name=text,Value=binary])][to],

typ =type function (ZIP as (type binary meta [Documentation.FieldCaption="ZIP - бинароное содержимое (zip, xlsx, docx, pptx и т.д.)"]), optional options as record) as table meta
[Documentation.Name = "fxUnzip (@buchlotnik)",
Documentation.LongDescription ="функция считывает содержимое архива и возвращает таблицу с полями:<p> <b>Name</b> - имя файла (с путём к файлу) <p><b>Value</b> - бинарное содержимое файла. <p>Необязательный аргумент <b>options</b> на текущий момент поддерживает следующие поля: <p>1) <b>Encoding</b> - требуется для корректного чтения не латинских имён файлов (по умолчанию <b>866 - кириллица</b>)"],

result = Value.ReplaceType(func,typ)][result]


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

Надеюсь, было полезно.
Всех благ!
@buchlotnik
buchOfficePack - fxExcelGetRowsAttributes - часть 1 - ядро
#АнатомияФункций - buchOfficePack

Всем привет!
Продолжаем пилить библиотеку. Видос уже на Ютубе

Сегодня разбираемся с атрибутами строк. Код пока не привожу - есть пара мыслей, скорее всего функций будет не одна, а две. Но это уже в последующих видео.

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

Надеюсь, было полезно.
Всех благ!
@buchlotnik
buchOfficePack - fxExcelGetRowsAttributes - часть 2 - обвес и не только
#АнатомияФункций - buchOfficePack

Всем привет!
Продолжаем пилить библиотеку. Видос уже на Ютубе

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

Надеюсь, было полезно.
Всех благ!
@buchlotnik
buchOfficePack - fxTableRemoveEmptyColumns – удаляем пустые столбцы в таблице
#АнатомияФункций - buchOfficePack

Всем привет!
Продолжаем пилить библиотеку. Видос уже на Ютубе
Само решение уже было на канале, но поскольку надо обеспечить совместимость с 2016 пришлось отказаться от второго аргумента в Table.Profile. В итоге получилось решение в три строчки )))
fxTableRemoveEmptyColumns=[func =(table, optional options)=>
[tbl = Table.Buffer(Table.Profile(table)),
lst = Table.SelectRows(tbl,(r)=>r[Count]=r[NullCount])[Column],
to = Table.RemoveColumns(table,lst)][to],
typ=type function (table as table, optional options as record) as table meta
[Documentation.Name="fxTableRemoveEmptyColumns (@buchlotnik)",
Documentation.LongDescription="функция, удаляющая пустые столбцы из таблицы"],
result = Value.ReplaceType(func,typ)
][result]

Соответственно библиотека продолжит свое развитие, но следующее видео по данной тематике будет последним - трудоёмко и мало кому интересно.

Надеюсь, было полезно.
Всех благ!
@buchlotnik
fxGetMCode – получаем код М, используя М
#АнатомияФункций – buchOfficePack

Всем привет!
Дошли руки до вынимания кода запросов из файлов. В целом, после fxUnZip это уже не должно быть особо сложным. Просто надо ещё немножко почитать спецификацию.
А там, как всегда, всё написано и по этому поводу функция (она является частью библиотеки, поэтому убедитесь, что fxUnZip у вас имеется в наличии):
 fxGetMCode=(file)=>
[u32 = BinaryFormat.ByteOrder(BinaryFormat.UnsignedInteger32,ByteOrder.LittleEndian),
f=(x)=>[a=Text.Split(x," = "),b={Text.Trim(Text.Replace(a{0},"shared","")),Text.Combine(List.Skip(a)," = ")}][b],
from = fxUnzip(file){[Name="customXml/item1.xml"]}?[Value]?,
bin = Binary.FromText(Xml.Document(from){0}[Value],BinaryEncoding.Base64),
data = BinaryFormat.Choice(BinaryFormat.Binary(8),(x)=>BinaryFormat.Binary(u32(Binary.Range(x,4,4))))(bin),
unz = fxUnzip(data){[Name="Formulas/Section1.m"]}[Value],
txt = Text.FromBinary(unz)&"#(cr,lf)",
splt = Text.Split(txt,";#(cr,lf)"),
lst = List.Range(splt,1,List.Count(splt)-2),
tbl = Table.FromList(lst,f,{"Name","Value"}),
to = if from=null then #table({"Name","Value"},{{null,null}}) else tbl][to]

По шагам
u32 – вспомогательная функция для чтения 4-байтного беззнакового целого (размер массива)
f – ещё одна вспомогательная – отрезает название запроса от собственно кода запроса

А теперь основное тело
from – открываем файл как архив и вынимаем customXml/item1.xml – код хранится там
bin – внутри этого xml лежит в виде текста сжатый бинарник (DataMashup) – возвращаем ему первозданный бинарный вид
data – из полученного бинарника пропускаем первые 4 байта, а вторые 4 указывают на размер массива, содержащего код - читаем
unz – и поскольку этот массив ещё и сжатый – снова используем fxUnZip и вынимаем Formulas/Section1.m – файл с кодом
txt – превращаем бинарку в текст и дописываем в конце перевод каретки с разрывом строки (там просто запросы отделены точкой с запятой с последующим переводом каретки, но в самом конце идёт просто точка с запятой – короче добиваемся единообразия)
splt – теперь делим общий текст на отдельные запросы
lst – отбрасываем лишние первый и последний фрагменты
tbl – собираем в табличку, параллельно отделяя название запроса от его кода
to – ну и на выход подаём либо табличку, либо пустую табличку, когда запросов в файле не оказалось.

Как обычно, код короче своего описания ))) Видос с более подробным разбором будет, но уже в новом году. Пользуйтесь!

Надеюсь, было полезно.
Всех благ!
@buchlotnik
fxDistribute – немножко решения задач оптимизации на М
#АнатомияФункций – custom

Всем привет!
Я упорно не пытаюсь решать задачи оптимизации на М, но они упорно возникают в чате. Поэтому, раз уж поддержали пост, решаем задачу:
на вход подаётся список из N позиций с указанием их «веса», список нужно разделить на M равных (почти равных если целочисленное деление невозможно) групп с максимально равной суммой весов. Ок, у меня вышло примерно так:
(lst,num)=>
let
n=Number.RoundUp(List.Count(lst)/num)*num,
sort = List.Sort(lst&List.Repeat({0},n-List.Count(lst))),
av=List.Average(sort),
tr=List.Transform(sort,(x)=>x-av),
zip = List.Zip(List.Split(tr,num)),

f=(base)=>
[tst=List.Buffer(List.Sort(base,(x)=>List.Sum(x))),
last=List.Last(tst),
sum1=Number.Abs(List.Sum(tst{0})),
sum2=Number.Abs(List.Sum(last)),
lst1=if sum2>sum1 then tst{0} else last,
lst2=if sum2>sum1 then last else tst{0},
cr=List.Sum(lst2),
lst=List.TransformMany(lst2,(x)=>lst1,(x,y)=>{x,y,cr-x+y}),
slct= if cr <0
then List.Max(List.Select(lst,(x)=>x{2}<0),null,(x)=>x{2})
else List.Min(List.Select(lst,(x)=>x{2}>0),null,(x)=>x{2}),
f=(x,y,z)=>List.ReplaceRange(x,List.PositionOf(x,y),1,{z}),
new={f(lst2,slct{0},slct{1})}&List.Range(tst,1,num-2)&{f(lst1,slct{1},slct{0})}][new],
gen=List.Generate(()=>[i=0,l=zip,s=List.Sum(List.Transform(l,(x)=>Number.Abs(List.Sum(x)))),fl=true],(x)=>x[fl],
(x)=>[i=x[i]+1,l=f(x[l]),s=List.Sum(List.Transform(l,(x)=>Number.Abs(List.Sum(x)))),fl=s<x[s]],
(x)=>x[l]),
tbl = Table.FromColumns(List.Last(gen)),
to=Table.TransformColumns(tbl,{},(x)=>x+av)
in
to

По шагам:
n – определяем общую длину списка, чтобы делилось поровну
sort – дополняем список до нужной длины нулями и сортируем
av – находим среднее по списку
tr – преобразуем фактические веса в величину отклонения от среднего
zip – нарезаем список по N элементов и получаем M достаточно одинаковых групп – опорный план

теперь немножко перескочим
gen – итеративно применяем к полученным спискам функцию f и следим за остаточной суммой отклонений – продолжаем пока она уменьшается
tbl -результат последней итерации превращаем в таблицу
to – возвращаем «весам» их первозданный вид

Теперь по функции f – реализованный алгоритм ищет локальный минимум – это важно, потому как решил не усложнять жизнь ни себе, ни оперативе.
tst, last, sum1, sum2,l st1, lst2 – находим группы с наибольшей (положительной) и наименьшей (отрицательной) суммами
cr, lst – находим все сочетания элементов этих двух групп и как их обмен повлияет на общий результат
slct – выбираем наиболее подходящую пару
new – осуществляем обмен элементами между группами

Как-то так. Поиск локального минимума не всегда оптимален, зато работает шустро и пишется просто )))

Надеюсь, было полезно.
Всех благ!
@buchlotnik
fxColNumFromText – номер столбца по адресу ячейки
#АнатомияФункций – custom

Всем привет!
Очередной видос на Ютубе.
В этот раз решаем частную задачу – определить номер столбца по его буквенному обозначению. Собственно, нужно просто вспомнить про то, что у каждого символа есть его числовой код, ну и как устроены позиционные системы исчисления. Код вышел весьма лаконичный:
f=(x)=>[a = List.Transform(List.Reverse(Text.ToList(Text.SplitAny(x,"0123456789"){0})),(x)=>Character.ToNumber(x)-64),
b = List.Sum(List.Transform(List.Zip({a,List.Positions(a)}),(x)=>x{0}*Number.Power(26,x{1})))][b]


Надеюсь, было полезно.
Всех благ!
@buchlotnik
С Новым 2024 Годом!!!

Всем привет!!!
Поздравляю с наступающим (Петропавловск-Камчатский с уже наступившим) Новым Годом!!!

Как положено, кратко по итогам года:
- количество подписчиков удвоилось (и вплотную приблизилось к 2500);
- в среднем по году пост раз в неделю;
- читают эти посты 53% подписчиков (24% в первые сутки после публикации)

Ещё в уходящем году был запущен курс по pq, запущен канал на ютубе;
Игорь (@CubRoot) начал и продолжит (да, Игорь?) серию постов по 1С;
а ещё появились посты про численные методы и даже не надейтесь – они продолжатся.

Спасибо всем, кто был с нами в этом году, читал, смотрел и даже комментировал.
Отдельно надо поблагодарить:
- серьезных дядек – Леха (@PooHkrd), Макс (@MaximZelensky), Илья (@IlyaNazarov), Серёга (@sboy_ko) – вы лучшие, спасибо вам, что вы есть и все такое;
- партнёров – Дима (@AkademiaExcel), спасибо, что распинал, продолжаем пилить учебный контент;
- конкурентов ))) – Андрей (@te1ns), продолжаем мериться скоростями – получаю искреннее удовольствие;
- и вообще всех кто это читает – значит пишу не зря )))

Немного красоты на формулах прилагается

С наступающим Новым Годом!
Всех благ!
@buchlotnik
fxGetMCode – получаем код М, используя М – обзор на Ютубе
#АнатомияФункций – buchOfficePack

Всем привет!

Как и обещал, выложил обзор fxGetMCode на Ютубчик
В целом ничего нового – опять показываю, что всё написано прямым текстом в документации, надо просто прочитать и выполнить )))

Надеюсь, было полезно.
Всех благ!
@buchlotnik
Получаем код всех запросов из всех файлов из папки
#АнатомияФункций – custom

Всем привет!
На текущий момент на канале есть fxUnzip, есть fxGetMCode и есть пост про Folder.Files.
Мне казалось, что этого достаточно, чтобы самостоятельно собрать адекватное подключение к папке и получить код запросов из всех файлов внутри неё. Но оказалось, что я просто пишу нерабочий код и всё вышеизложенное «не взлетает». ОК, напишем код целиком:
let
lst={"QueryName","Value"},
//функция
func=(file)=>[
u16 =BinaryFormat.ByteOrder(BinaryFormat.UnsignedInteger16,ByteOrder.LittleEndian),
u32 =BinaryFormat.ByteOrder(BinaryFormat.UnsignedInteger32,ByteOrder.LittleEndian),
f=(x)=>[a=Text.Split(x," = "),b={Text.Trim(Text.Replace(a{0},"shared","")),Text.Combine(List.Skip(a)," = ")}][b],
fxUnzip=(ZIP)=>
[ get=(x)=>BinaryFormat.Record(if Binary.Range(x,0,4)=#binary({0x50,0x4b,0x03,0x04})
then [ Name=BinaryFormat.Text(u16(Binary.Range(x,26,2))),
Extr=BinaryFormat.Binary(u16(Binary.Range(x,28,2))),
Value=BinaryFormat.Transform(BinaryFormat.Binary(u32(Binary.Range(x,18,4))),(y)=>Binary.Decompress(y,Compression.Deflate))]
else []),
lst = BinaryFormat.List(BinaryFormat.Choice(BinaryFormat.Binary(30),get),(x)=>x<>[])(ZIP),
to = Table.FromRecords(List.RemoveLastN(lst,1),type table [Name=text,Value=binary])][to],
from = fxUnzip(file){[Name="customXml/item1.xml"]}?[Value]?,
bin = Binary.FromText(Xml.Document(from){0}[Value],BinaryEncoding.Base64),
data = BinaryFormat.Choice(BinaryFormat.Binary(8),(x)=>BinaryFormat.Binary(u32(Binary.Range(x,4,4))))(bin),
unz = fxUnzip(data){[Name="Formulas/Section1.m"]}[Value],
splt = Text.Split(Text.FromBinary(unz)&"#(cr,lf)",";#(cr,lf)"),
tbl = Table.FromList(List.Range(splt,1,List.Count(splt)-2),f,lst),
to = if from=null then #table(lst,{{null,null}}) else tbl][to],
//собственно подключение к папке
from = Folder.Files("ПУТЬ К ПАПКЕ ВВЕСТИ САМОСТОЯТЕЛЬНО"),
filtr = Table.SelectRows(from, (r)=> (r[Extension] = ".xlsm" or r[Extension] = ".xlsx")),
rem = Table.SelectColumns(filtr,{"Name", "Content"}),
tr = Table.TransformColumns(rem,{"Content",func}),
to = Table.ExpandTableColumn(tr,"Content",lst)
in
to

где
func – собственно функция для вынимания кода запросов (fxUnzip интегрирована внутрь «во избежание»)
from – подключение к папке (увы, я заранее не знаю путь к конкретной папке – его придётся исправить на свой, ручками – если даже это станет проблемой – мои полномочия всё)
filtr, rem – оставили только эксельки и только нужные столбцы
tr,to – преобразовали бинарки файлов в таблицы и развернули их

И всё. Кто найдёт в коде выше File.Contents, без которой якобы не работает – пусть первым бросит в меня камень. Большая просьба думать перед тем, как писать, что что-то «не работает».

Надеюсь, было полезно.
Всех благ!
@buchlotnik
Раскидываем иерархию через List.Generate
#АнатомияФункций – List.Generate

Всем привет!

Несколько раз вопрос всплывал в чате и я уже давал образчики кода, но захотелось записать видос – как всегда, на Ютубе.

Код вышел несложный, складываю сюда, чтобы можно было поиском находить
let
from = Excel.CurrentWorkbook(){[Name="Источник"]}[Content],
lst = List.Buffer(Table.ToList(from,(x)=>x)),
max = List.Max(from[уровень]),
n = List.Count(lst),

gen=List.Generate(
()=>[i=0,j=lst{i},l=List.ReplaceRange(List.Repeat({null},max),j{0}-1,1,{j{1}})&List.Skip(j,2)],
(x)=>x[i]<n,
(x)=>[i=x[i]+1,j=lst{i},l=List.FirstN(x[l],j{0}-1)&{j{1}}&List.Repeat({null},max-j{0})&List.Skip(j,2)],
(x)=>if (lst{x[i]+1}?{0}? ?? 0)>x[j]{0} then null else x[l]
),

nms = List.Transform({1..max},(x)=>"заголовок "&Text.From(x))&List.Skip(Table.ColumnNames(from),2),
to=Table.FromList(List.RemoveNulls(gen),(x)=>x,nms)
in
to


Ну а объяснение по ссылке - смотрим, подписываемся, ставим лайк, пишем комменты. )))

Надеюсь, было полезно.
Всех благ!
@buchlotnik
Dax Pro Solutions: DAX + Полищук = ОГОНЬ!!!

Всем привет!
Пока я пишу тут про pq и язык М, легендарный Юрий Полищук запустил свой канал по DAX - Dax Pro Solutions

Всё как мы любим – отрыто и бесплатно, НО… файл пример «есть/надо» никто не отменял. Так что всем заинтересованным рекомендую заходить и активно участвовать (прочитав правила, разумеется) . Первые вкусняшки там уже появились.

Надеюсь, было полезно.
Всех благ!
@buchlotnik
Получаем несколько элементов списка по их номерам
#АнатомияФункций – List.Transform

Всем привет!
Уже неоднократно поднимался вопрос о том, как получить несколько элементов списка по их номерам. Смотрим код:
let
from = {"a".."z"},
/*to = from{1,4,8} - так не работает!*/
to = List.Transform({1,4,8},(x)=>from{x-1})
in
to

Всё просто – НЕЛЬЗЯ писать через запятую в фигурных скобках – так нас не понимают.
А вот через List.Transform – всё очень даже работает. Главное, помнить, что нумерация позиций начинается с нуля, а не с единицы.

Ну и по этому поводу разобрал пару задачек из чата в коротком видео на Ютубе

Надеюсь, было полезно.
Всех благ!
@buchlotnik
Раскидываем столбец в строки таблицы
#АнатомияФункций – List.Split

Всем привет!

Подогнали мне на днях ссылку на одно видео… Горит до сих пор так, что аж записал видео. Речь идёт об архисложном коде:
let
from = Excel.CurrentWorkbook(){[Name="Источник2"]}[Content][Сделки],
to = Table.FromList(List.Split(from,9),(x)=>List.FirstN(x,6))
in
to

Как вы понимаете – это как НАДО делать, а как не надо я даже публиковать тут не буду. Общая мысль – изучать М нужно, чтобы иметь возможность написать ровно то, что надо сделать для решения задачи. Без изысков и подвыпердов.

Детали, мои эмоции и промеры скорости смотрим на Ютубе

Надеюсь, было полезно.
Всех благ!
@buchlotnik
Получаем атрибуты документов Word из папки
#АнатомияФункций - Folder.Files, fxUnzip

Всем привет!
Подкинули недавно задачку – вытащить количество страниц из документов .docx в папке.
Что ж – это повод воспользоваться buchOfficePack.
Код для поиска тут:
let
f=(x)=>[a=unzip(x){[Name="docProps/app.xml"]}[Value],
b=Number.From(Xml.Tables(a){0}[Pages])][b],

unzip = Expression.Evaluate(Text.FromBinary(Web.Contents("https://raw.githubusercontent.com/buchlotnik/buchlotnik_functions/main/buchOfficePack")),#shared)[fxUnzip],
from=Folder.Files("путь к папке"),
filtr=Table.SelectRows(from,(r)=>r[Extension]=".docx" and not Text.Contains(r[Name],"~")),
tbl=Table.SelectColumns(filtr,{"Name","Content"}),
tr=Table.TransformColumns(tbl,{"Content",f})
in
tr


А комментарии и объяснения – на Ютубе
Лайк, подписка, коммент приветствуются )))

Надеюсь, было полезно.
Всех благ!
@buchlotnik
#sections – собираем результаты всех запросов
#АнатомияФункций – синтаксис

Всем привет!

В очередной раз в чат пришли с вопросом – как собрать результаты всех запросов в одну табличку? Как правило, ситуация осложняется тем фактом, что число запросов может изменяться (чаще добавляются новые).
Что ж – это несложно. Код складываю тут:

let
from = Record.ToTable(Record.RemoveFields(#sections[Section1],"Result")),
nms = List.Distinct(List.Combine(List.Transform(from[Value],Table.ColumnNames))),
to = Table.ExpandTableColumn(from,"Value",nms)
in
to


А комментарии и объяснения по традиции на Ютубе

Лайк, коммент, подписка приветствуются )))

Надеюсь, было полезно.
Всех благ!
@buchlotnik