Я думаю, не только мне, но и многим другим часто требуется сцепить диапазон ячеек в одну. Стандартная функция Excel крайне неудобна, но можно найти несколько UDF, отличающихся по: - ресурсоёмкости и рабочему диапазону (ActiveSheet / CurrentRegion / UsedRange) - количеству критериев для сцепки (от 3 до 0, т.е. просто сцепить) - ограничениям на тип данных (числа, текст, даты) - способу указания разделителей, диапазонов, критериев и операторов сравнения (=, >/<, ≥/≤) и др.
По мере необходимости подгружаешь то одну, то другую UDF. И в итоге сам начинаешь путаться в них, а файл - тормозить при пересчёте.
Может кто-нибудь сможет написать / найти быструю UDF для сцепки любых данных по 3 критериям, которые можно задавать по необходимости. Примерно такого вида
Приложил вариант функции Concat(), допускающей до 9 условий в Excel 2003 и до 84 условий в Excel 2007+.
Порядок аргументов функции: 1. Диапазон ячеек (столбец или несколько смежных), которые должны попасть в результат 2. Разделитель результата 3. Столбец №1 ячеек, участвующих в условии №1 4. Оператор условия №1, может быть одним из: "=", ">=", "<=", "<>" 5. Значение условия №1, с которым сравниваются значения ячеек из п.3 и т.д. следующие условия аналогично п.п. 3,4,5
Все условия работают по И-логике (And)
Пример вызова функции: =Concat(A1:A6; ","; B1:B6;"=";3; C1:C6;">=";20; D1:D6;"=";"text3")
Погонял функцию: - Правильно работает с текстом, числами и датами - Операторы и разделители можно задавать ссылкой на ячейку - Время выполнения в массиве 4х1048576 (4х10.000 строк заполнено): 10.98/ 9.11/ 4.98 сек. (Excel 2007/ 2010/ 2013)
Несколько вопросов: можно ли - добавить в макрос операторы > и < (есть только =, >=, <=, <> ? - подрезать диапазон строк под UsedRange? - изменить синтаксис
Код
=Concat(B1:B6;","; C1:C6;"=";3; D1:D6;">=";20; E1:E6;"=";"text3") до вида
=Concat(B1:B6;","; C1:C6=3; D1:D6>=20; E1:E6="text3") ?
Несколько вопросов: 1. как добавить в макрос операторы > и < (есть только =, >=, <=, <> ? 2. если "пробить" 1048576 строк, функция прорабатывает их все (или пустые отбрасывает)? 3. можно ли изменить синтаксис
Код
на =Concat(B1:B6;","; C1:C6=3; D1:D6>=20; E1:E6="text3")
1. Не понял вопроса, все условия итак считаются по И. Если в строке найдено первое несовпадение условия, то проверка в этой строке прекращается.
2. Пустые ячейки в данных пропускаются, но задавать полностью столбцы, когда много пустых ячеек, будет слишком роскошно. Впрочем, могу добавить подрезку до последней используемой строки листа.
3. Так будет плохо, потому что даты придется задавать текстом, а Excel тогда может произвольно поменять местами месяц и число, если ему так захочется
nerv пишет: можно за evaluate-ить немного Правда в этом случае вряд ли будет "быструю UDF"
Александр, да, тогда будет медленнее, как плата за универсальность. Встроенная функция СУММЕСЛИМН() использует аналогичные моему варианту простые операторы и этого хватает для большинства случаев.
> 1. Не понял вопроса... Я имел ввиду, что, если заменить C1:C6;"=";3 на C1:C6;">";3, то функция выдаёт #Cond1? > 2. могу добавить подрезку до последней используемой строки листа. Было бы здорово. Если при этом она будет правильно работать на всех листах... > 3. Так будет плохо Тогда лучше оставить, как есть.
Ещё 2 вопроса: - можно ли дописать кусок кода, который будет выдавать подсказку при вводе UDF (так, как это реализовано для "родных" формул) - как Вам удалось сделать выполнение макроса в 2013 офисе быстрее, чем в 2007 (во всех моих макросах получается наоборот)
ZVI, как-то так на скорую руку. Правда, не уверен, что будет работать в Office x64
Код
Function Foo(ByVal Valid As String, a, b) As Boolean
With CreateObject("ScriptControl")
.Language = "JScript"
.AddCode "function fn(a,b,c,d,e){return " & Valid & "}"
Foo = .Run("fn", a, b)
End With
End Function
Sub Example()
MsgBox Foo("a > b", 4, 3) ' True
MsgBox Foo("a > b", 4, 7) ' False
End Sub
Чебурашка стал символом олимпийских игр. А чего достиг ты? Тишина - самый громкий звук
> 1. Не понял вопроса... Я имел ввиду, что, если заменить C1:C6;"=";3 на C1:C6;">";3 , то функция выдаёт #Cond1? > 2. могу добавить подрезку до последней используемой строки листа. Было бы здорово. Если при этом она будет правильно работать на всех листах... > 3. Так будет плохо Тогда лучше оставить, как есть.
Ещё 2 вопроса: - можно ли дописать кусок кода, который будет выдавать подсказку при вводе UDF (так, как это реализовано для "родных" формул) - как Вам удалось сделать выполнение макроса в 2013 офисе быстрее, чем в 2007 (во всех моих макросах получается наоборот)
По п.1 это я забыл про ">" и "<". Добавил в приложенной версии. По п.2 - добавил подрезку диапазонов. По п.3 - оставил из-за дат.
Полноценные подсказки при вводе UDF в ячейку не поддерживаются. При вводе с помощью мастера функций можно добавить подсказки, но для старых версиях 20003/2007 это делается очень криво. Поэтому просто добавил пример использования, который виден в мастере функций. Вот встроенную справку для данной функции было бы сделать разумнее, но это требует немало времени, попробуйте сделать сами и подключить через Application.MacroOptions.
По поводу того, чтобы в Excel 2013 работало быстрее, специально не старался, просто всю обработку сделал в VBA-массивах. Это стандартный метод ускорения обработки для всех версий. Еще для операторов заменил сравнение текста сравнением с числами, это тоже выполняется быстрее.
осмелюсь предположить, что не значительно. Функция эвалится один раз. В дальнейшем вызывается с параметрами. Вместо
Код
.Language = "JScript"
можно писать на "родном" vbs.
Александр, хотелось бы в теме автора решать проблемы автора. У меня есть правило: решая проблему сначала убедиться, проблема ли это вообще. А автор темы не озвучивал проблему, которые Вы предлагаете решать.
Что тормозит - проверьте, конечно. Я когда-то проверял. Но тот факт, что и JScript и VBScript могут быть отключены администратором, уменьшают мой интерес к данному решению. Тем более, что есть гораздо более быстрые методы, но они на грани здравого смысла, поэтому о них тоже не распространяюсь
Жаль, что нельзя сделать полноценные подсказки при вводе UDF в ячейку... Но это не критично, ведь синтаксис прост. Завтра обязательно проверю Concat1 на рабочем файле (сейчас нет под рукой). Думаю, проблем не возникнет.
Acid Burn пишет: To ZVI: огромное спасибо за UDF и советы!
Рад был помочь, справку могу в принципе и нарисовать, но тогда придется все это засовывать в надстройку. Задачка была специфическая, но интересная, спасибо и Вам - за развлечение
To RAN:
Цитата
А для общего развития можно познакомить?
Андрей, там же было продолжение (подчеркнуто): "есть гораздо более быстрые методы, но они на грани здравого смысла, поэтому о них тоже не распространяюсь" Не готов грузить этим сумасшествием.
О подсказках / справке к UDF я поинтересовался для общего развития. Вы сделали отличную вещь, которая не нуждается в каких-либо комментариях. Не тратьте на это Ваше драгоценное время.
PS: Проверил новую версию. Время обработки на том же тестовом файле сократилось до 0,06/ 0,05/ 0,04 сек. (Excel 2007/10/13), т.к. из 1.048.576 строк заполнено лишь 10.000. Нормально работает (не пересчитывается и не выдаёт ошибку) на не активных листах с другим UsedRange. Это просто фантастика! Всем рекомендую к применению!
Все же приведу код для сравнения скорости вызова простейшей функции в VBScript и VBA. У меня VBA оказалась быстрее в 21 раз (0.4 секунды) чем VBScript (9.5 секунды)
Код
Option Explicit
Dim objScript As Object
' Сравнение скорости выполнения простейшей функции в VBScript и VBA
Sub Time_Test()
Const N& = 1000000
Dim i&, t1!, t2!, ok As Boolean
Debug.Print "N = " & N
t1 = Timer
For i = 1 To N
ok = Foo1(i, "Text3")
Next
t1 = Timer - t1
Debug.Print "VBScript", Round(t1, 3) & " sec"
t2 = Timer
For i = 1 To N
ok = Foo2(i, "Text3")
Next
t2 = Timer - t2
Debug.Print "VBA", Round(t2, 3) & " sec"
Debug.Print "VBA is faster in " & Round(t1 / t2, 0) & " times"
End Sub
' VBScript function
Function Foo1(a, b)
If objScript Is Nothing Then
Set objScript = CreateObject("ScriptControl")
With objScript
.Language = "VBScript"
.AddCode "Function Fn(a,b):Fn = a=1 And b=""text3"":End Function"
End With
End If
Foo1 = objScript.Run("Fn", a, b)
End Function
' VBA function
Function Foo2(a, b)
Foo2 = a = 1 And b = "text3"
End Function
'
2 вопроса: - как убрать ошибку #ЗНАЧ! при отсутствии критериев сцепления (когда просто надо сцепить диапазон)? - как к этой UDF добавить опцию "Без повторов"?
1. В моем примере работает: =Concat(A1:A6; "," ) 2. Нужно сначала придумать, как задавать , что требуется именно "уникально". Наверное, проще сделать еще одну функция с другим именем, например, ConcatUniq(), чтобы не усложнять синтаксис аргументов. Или требуется, чтобы всегда были только уникальные? Потом еще и сортировка потребуется?
> работает: =Concat(A1:A6; ",") Да, но есть ограничение количества строк (где-то ~5652 строк). Т.е. =Concat(A1:A10000; ",") уже не сработает.
> или требуется, чтобы всегда были только уникальные? > Потом еще и сортировка потребуется? Нет, по ситуации требуется выбрать все или только уникальные. А сортировку и разбивку по строкам выполняет мой примитивный макрос. На самом деле здорово бы сделать ConcatUniq() с сортировкой... Но я уже стесняюсь просить Вас об этом...
Acid Burn пишет: > работает: =Concat(A1:A6; "," ) Да, но есть ограничение количества строк: =Concat(A1:A10000; "," ) уже не сработает.
Это не ограничение функции, а ограничение Excel - в ячейке не может быть больше 32767 символов. Если в ячейках A1:A10000 значения длиной не более 2-х символов, то все сработает (3 символа на ячейку с учетом одного символа разделителя, итого 30000 символов). С учетом этого, Вы уверены, что правильно поступаете, накапливая все в одной ячейке? Что потом с такой ячейкой планируете делать?
Цитата
А сортировку и разбивку по строкам выполняет мой примитивный макрос.
Может, нужно было сразу в одном (нашем) макросе все это и сделать?
ZVI пишет: А автор темы не озвучивал проблему, которые Вы предлагаете решать
вообще-то я ничего не предлагаю решать) Я только высказываю свое мнение о том, что у одного автора одни критерии отбора
Цитата
=, >/<, ≥/≤
у другого другие, и общим оптимальным решением (с точки зрения проверки условия истинности) будет вариант предложенный мной. Т.е. вот такое
Код
a * 2 > b - 1
'или такое
a / 2 + a - b <= b - a
'можно даже так (надо только одну строчку в коде дописать, чтобы была возможность работы с приложением)
Target.Offset(1).Value = a + b
в Вашу функцию просто так не запишешь, придется лезть в код. Т.о. если потребуется n "аналогичных" замысловатых условий, придется писать n вариантов сравнения
Цитата
ZVI пишет: Все же приведу код для сравнения скорости вызова простейшей функции в VBScript и VBA.
ч и т.д. Ибо, как мне кажется, слабым местом (в данном случае) как раз является передача параметров и частый вызов функции. Мой пример.
Цитата
ZVI пишет: У меня VBA оказалась быстрее в 21 раз (0.4 секунды) чем VBScript (9.5 секунды)
Вместе с тем, вариант реализации через объект ScriptControl, на мой взгляд, будет быстрее варианта Evaluate().
Цитата
ZVI пишет: Александр, хотелось бы в теме автора решать проблемы автора.
считаю отхождение от темы делом обычным и не подсудным, т.к. эти отхождения, зачастую, гораздо более интересны основной поставленной задачи
Цитата
RAN пишет: А для общего развития можно познакомить?
я за
Чебурашка стал символом олимпийских игр. А чего достиг ты? Тишина - самый громкий звук
Может, нужно было сразу в одном (нашем) макросе все это и сделать?
Если можно будет включать и отключать эти опции, и это не вызовет разрастания кода и трудностей в реализации... То было бы отлично
Мне же неизвестно, в какие ячейки помещать результат. Раз ситуация с 32787 символами не типичная, то пока просто добавил сортировку и сделал еще одну функцию для уникальных значений CondUnique(), аргументы - те же
Сделал. Порядок аргументов функции: 1. Диапазон ячеек (столбец или несколько смежных), которые должны попасть в результат 2. Разделитель результата 3. Признак уникальности: 0/1 = Все/Уникальные 4. Признак сортировки: 0/1 = НеСортировать/Сортировать 5. Столбец №1 ячеек, участвующих в условии №1 6. Оператор условия №1, может быть одним из: "=", ">", "<", ">=", "<=", "<>" 7. Значение условия №1, с которым сравниваются значения ячеек из п.5 и т.д. следующие условия аналогично п.п. 5,6,7
Все условия работают по И-логике (And)
Пример вызова функции: =Concat(A:A; ","; 1;1; B:B;"=";3; C:C;">=";20; D:D;"=";"text3")
Критерий Должно быть По факту
(0, 0) Все, без сорт. +
(1, 0) Уникал., без сорт. Все, сорт.
(0, 1) Все, с сорт. Уникал., без сорт.
(1, 1) Уникал., с сорт. +
Т.е. (1, 0) и (0, 1) работают с точностью до наоборот. Буду благодарен, если подправите (сам пока не понял, как )...