Проблему замены значений по списку в Excel я уже затрагивал в статье Массовая замена слов. Там я использовал макросы, т.к. других вариантов по сути не было. Но с появлением PowerQuery многие задачи стало возможно решить и без применения VBA(макросов). В том числе и замену значений по списку соответствия. Правда, чуть менее универсально получается и в применении может быть даже не проще. Но это в любом случае выход для тех, у кого выполнение макросов запрещено политикой компании или когда даже само слово "макрос" уже вызывает легкую рвоту :)
Например, необходимо перевести текст из ячеек столбца на транслит или наоборот(с транслита на русский):
Для этого есть список соответствия:
Список представляет собой умную таблицу(Вставка
Прежде чем читать далее и пробовать применить, необходимо знать азы работы в Power Query(Power Query - что такое и почему её необходимо использовать в работе?), уметь создавать и редактировать запросы и вызвать расширенный редактор: переходим в нужный запрос -Главная -Расширенный редактор:
К статье приложен файл с готовым запросом замены по списку, который разберем ниже:
Скачать пример: Замена по списку через PowerQuery.xlsx (39,3 КиБ, 1 409 скачиваний)
Для начала загружаем в PowerQuery таблицу для перевода: переходим на вкладку Вставка и выбираем – Таблица. Снимаем галку с пункта "Таблица с заголовками" -Ок:
Сразу переходим на вкладку Данные(или на вкладку Power Query) -группа Загрузить и преобразовать -Из таблицы
Появится окно редактора запросов. В нем сразу переходим на вкладку Главная -Расширенный редактор:
Появится редактор текста запросов со следующим запросом:
Источник = Excel.CurrentWorkbook(){[Name="rus"]}[Content],
#"Измененный тип" = Table.TransformColumnTypes(Источник,{{"Текст", type text}})
in
#"Измененный тип"
удаляем все, начиная с in и до конца, в конце второй строки(
//определяем направление перевода(0 - с транслита на русский, 1 - с русского на транслит) from="1", WhatChange = if (from="0") then "WhatChange" else "ChangeWith", ChangeWith = if (from="0") then "ChangeWith" else "WhatChange", //определяем столбцы со значениями для поиска и замены слов/букв t_1 = Table.Column(Excel.CurrentWorkbook(){[Name="translate_list"]}[Content],WhatChange), t_2 = Table.Column(Excel.CurrentWorkbook(){[Name="translate_list"]}[Content],ChangeWith), //определяем длину текста каждого слова из списка соответствия t1_len = List.Generate(()=> [c=1, t=Text.Length(t_1{1})], each [c]<=List.Count(t_1), each [c=[c]+1, t=Text.Length(t_1{[c]})], each [t]), //сортируем таблицу на основании списка длин слов, чтобы сначала заменялись самые длинные tbl1 = Table.Sort(Table.FromColumns({t_1, t_2, t1_len}),{{"Column3", Order.Descending},"Column1","Column2","Column3"}), //берем столбцы отсортированной таблицы(t1 - что заменяем, t2 - на что заменяем) t1 = Table.Column(tbl1,"Column1"), t2 = Table.Column(tbl1,"Column2"), //функция замены значений по списку TranslateFunction = (InputText)=> let //функция замены значений DoRep = List.Generate( ()=> [cnt=0, text=Text.Upper(InputText)], each [cnt]<=List.Count(t1), each [cnt=[cnt]+1, text=Text.Replace([text], t1{[cnt]}, t2{[cnt]})], each [text]), GetLastValue = List.Last(DoRep) in GetLastValue, Output = Table.AddColumn(#"Измененный тип", "Changed text", each TranslateFunction([Текст])) in Output |
должен получиться такой запрос(вместо "rus" в строке
let Источник = Excel.CurrentWorkbook(){[Name="rus"]}[Content], #"Измененный тип" = Table.TransformColumnTypes(Источник,{{"Текст", type text}}), //определяем направление перевода(0 - с транслита на русский, 1 - с русского на транслит) from="1", WhatChange = if (from="0") then "WhatChange" else "ChangeWith", ChangeWith = if (from="0") then "ChangeWith" else "WhatChange", //определяем столбцы со значениями для поиска и замены слов/букв t_1 = Table.Column(Excel.CurrentWorkbook(){[Name="translate_list"]}[Content],WhatChange), t_2 = Table.Column(Excel.CurrentWorkbook(){[Name="translate_list"]}[Content],ChangeWith), //определяем длину текста каждого слова из списка соответствия t1_len = List.Generate(()=> [c=1, t=Text.Length(t_1{1})], each [c]<=List.Count(t_1), each [c=[c]+1, t=Text.Length(t_1{[c]})], each [t]), //сортируем таблицу на основании списка длин слов, чтобы сначала заменялись самые длинные tbl1 = Table.Sort(Table.FromColumns({t_1, t_2, t1_len}),{{"Column3", Order.Descending},"Column1","Column2","Column3"}), //берем столбцы отсортированной таблицы(t1 - что заменяем, t2 - на что заменяем) t1 = Table.Column(tbl1,"Column1"), t2 = Table.Column(tbl1,"Column2"), //функция замены значений по списку TranslateFunction = (InputText)=> let //функция замены значений DoRep = List.Generate( ()=> [cnt=0, text=Text.Upper(InputText)], each [cnt]<=List.Count(t1), each [cnt=[cnt]+1, text=Text.Replace([text], t1{[cnt]}, t2{[cnt]})], each [text]), GetLastValue = List.Last(DoRep) in GetLastValue, Output = Table.AddColumn(#"Измененный тип", "Changed text", each TranslateFunction([Текст])) in Output |
Нажимаем Готово. В получившемся запросе уже будет создан новый столбец с именем Changed text, в котором все значения будут заменены согласно списку соответствия. В нашем случае весь русский текст будет переведен на транслит.
Здесь стоит отметить, что при помощи PowerQuery данным методом удобно будет переводить один столбец. Если надо перевести еще какой-то столбец, то переходим на вкладку Добавить столбец -Настраиваемый столбец. В окне задаем имя столбца и вписываем следующее:
Вместо Текст указывается имя столбца, который надо перевести. Таким образом мы вызываем функцию перевода, которую вставили ранее в редактор запросов. Появится новый столбец, в котором весь текст указанного столбца будет переведен на транслит.
Единственная ложка дегтя - весь текст будет переведен в верхний регистр. В данном случае это необходимость и пока я не придумал, как обойти этот момент именно для перевода с транслита. Но текст можно преобразовывать уже на выходе, применив функцию
Теперь
Начнем с того, что в коде есть возможность изменять направление перевода - либо данные из первого столбца таблицы соответствий заменяем на соответствующие им значения второго, либо наоборот. За это отвечает отдельный параметр, который мы обозначаем в самом начале:
Таким образом для изменения направления надо всего лишь изменить один символ в запросе. Его можно сделать и динамическим параметром, если использовать трюк из этой статьи - Относительный путь к данным PowerQuery. Создаем таблицу параметров и далее просто ссылаемся на ячейку:
Далее на основании именно этого значения мы определяем в каком столбце искать и из какого брать значения на замену:
WhatChange = if (from="0") then "WhatChange" else "ChangeWith", ChangeWith = if (from="0") then "ChangeWith" else "WhatChange", //определяем столбцы со значениями для поиска и замены слов/букв t_1 = Table.Column(Excel.CurrentWorkbook(){[Name="translate_list"]}[Content],WhatChange), t_2 = Table.Column(Excel.CurrentWorkbook(){[Name="translate_list"]}[Content],ChangeWith), |
Т.к. список должен быть обязательно отсортирован по убыванию на основании количества символов в слове, то создаем дополнительный виртуальный столбец с количеством символов в каждой строке столбца значений на замену:
//определяем длину текста каждого слова из списка соответствия t1_len = List.Generate(()=> [c=1, t=Text.Length(t_1{1})], each [c]<=List.Count(t_1), each [c=[c]+1, t=Text.Length(t_1{[c]})], each [t]), |
Здесь при помощи
Далее надо объединить все столбцы
//сортируем таблицу на основании списка длин слов, чтобы сначала заменялись самые длинные tbl1 = Table.Sort(Table.FromColumns({t_1, t_2, t1_len}),{{"Column3", Order.Descending},"Column1","Column2","Column3"}), |
на всякий случай там же указываем порядок столбцов(т.к. их у нас всего три) -
Теперь для простоты обращения к столбцам списка соответствия назначаем им краткие имена:
//берем столбцы отсортированной таблицы(t1 - что заменяем, t2 - на что заменяем) t1 = Table.Column(tbl1,"Column1"), t2 = Table.Column(tbl1,"Column2"), |
и непосредственно функция замены значений. Она чуть сложнее в понимании:
//функция замены значений по списку TranslateFunction = (InputText)=> let //функция замены значений DoRep = List.Generate( ()=> [cnt=0, text=Text.Upper(InputText)], each [cnt]<=List.Count(t1), each [cnt=[cnt]+1, text=Text.Replace([text], t1{[cnt]}, t2{[cnt]})], each [text]), GetLastValue = List.Last(DoRep) in GetLastValue, |
Здесь мы опять используем
И последним шагом является добавление нового столбца, в котором мы в функцию
Output = Table.AddColumn(#"Измененный тип", "Changed text", each TranslateFunction([Текст])) |
С легкой руки Максима Зеленского изменил слегка текст запроса для более быстрой сортировки:
let Источник = Excel.CurrentWorkbook(){[Name="translit"]}[Content], #"Измененный тип" = Table.TransformColumnTypes(Источник,{{"Текст", type text}}), //определяем направление перевода from="0", WhatChange = if (from="0") then "WhatChange" else "ChangeWith", ChangeWith = if (from="0") then "ChangeWith" else "WhatChange", //определяем столбцы со значениями для поиска и замены слов/букв t_1 = Table.Column(Excel.CurrentWorkbook(){[Name="translate_list"]}[Content],WhatChange), t_2 = Table.Column(Excel.CurrentWorkbook(){[Name="translate_list"]}[Content],ChangeWith), //определяем длину текста каждого слова из списка соответствия //и сортируем таблицу на основании длин слов, чтобы сначала заменялись самые длинные tbl1 = Table.Sort(Table.FromColumns({t_1, t_2}), each -Text.Length(Record.Field(_, "Column1"))), t1 = Table.Column(tbl1,"Column1"), t2 = Table.Column(tbl1,"Column2"), //функция замены значений по списку TranslateFunction = (InputText)=> let //функция замены значений DoRep = List.Generate( ()=> [cnt=0, text=Text.Upper(InputText)], each [cnt]<=List.Count(t1), each [cnt=[cnt]+1, text=Text.Replace([text], t1{[cnt]}, t2{[cnt]})], each [text]), GetLastValue = List.Last(DoRep) in GetLastValue, Output = Table.AddColumn(#"Измененный тип", "Changed text", each TranslateFunction([Текст])) in Output |
В принципе изменилась исключительно часть, отвечающая за сортировку. Весь остальной запрос остался таким же.
Несложно догадаться, что таблицу соответствия можно использовать не только для перевода на транслит и обратно - туда можно забивать любые значения и слова для замены.
Замена по списку через PowerQuery.xlsx (39,3 КиБ, 1 409 скачиваний)
Так же см.:
Power Query - что такое и почему её необходимо использовать в работе?
Получить данные из файлов XML при помощи Power Query
Относительный путь к данным PowerQuery
Оставить цифры или текст при помощи PowerQuery
Дмитрий, добрый день! Хорошее решение, только с сортировкой, мне кажется, перегружено, генератор тут кажется излишним
Сортируем таблицу соответствий сразу по убыванию длины строки в столбце (после определения направления):
= Table.Sort(translate_table, each -Text.Length(Record.Field(_, WhatChange)))
Мы ведь длину используем только для сортировки?
С уважением,
Максим
Максим, спасибо. Да, используем только для сортировки.
Дмитрий, день добрый! Если еще актуально
"Единственная ложка дегтя - весь текст будет переведен в верхний регистр. В данном случае это необходимость и пока я не придумал, как обойти этот момент именно для перевода с транслита"
1. Я незнаю правил транслита, но мне кажеться, что для верхнего регистра можно ставить первую букву в верхнем регистре, а остальные в нижнем. Например для Щ вместо SHCH, можно указать Shch
2. Если в таблице "translate_list" хранить буквы в верхнем и нижнем регистре (Щ - Shch, щ - shch), то можно будет получать результат корректным.
Сергей, тут проблема в том, что мы заранее не знаем будет ли текст с заглавной буквы или нет.
1. Плохая идея, т.к. заранее неизвестно как в реальности в тексту будет записана эта самая Щ.
2. Здесь Вы правы, но мы не знаем заранее одна буква будет или все заглавные. Если расширять справочник - то придется записывать весьма много разных комбинаций для получения 100% результата(Shch, SHch, SHCH, cShc и т.д. - фантазия у людей богатая :)). Я этого делать не стал и если кому-то реально надо - думаю сделает по своим правилам, благо справочники расширяемые.