У меня есть эта строка в одной ячейке (A1):
Apple – 1
Banana – 6
Cherry – 10
Peach – 100
Apple – 1000
Kiwi – 10999
Peach – 44
Fig – 3/100
Fig – 3/100
Мне нужно найти формулу, которая сможет объединять повторяющиеся записи и суммировать их значения. Итак, желаемый результат таков (также в одной ячейке, скажем, B1):
Apple – 1001
Banana – 6
Cherry – 10
Peach – 144
Kiwi – 10999
Fig – 6/100
Я пробовал это часами, но не могу понять, как это сделать. Я попробовал сначала найти уникальные значения. Пока проблем нет:
=UNIQUE(
ARRAYFORMULA(
REGEXEXTRACT(
TRANSPOSE(SPLIT(A1, CHAR(10))),
"(.*) – [0-9.]+/?[0-9]?"
)
)
)
возвращает массив уникальных значений
{"Apple", "Banana", "Cherry", "Peach", "Kiwi", "Fig"}
Лучший результат, который я смог придумать, — это формула для работы с Apple и Banana вручную:
=ARRAYFORMULA(
{
"Apple" & " – " & SUM(
ARRAYFORMULA(
IF(
REGEXMATCH(
TRANSPOSE(SPLIT(A1, CHAR(10))),
"Apple" & " – [0-9.]+/?[0-9]?"
),
VALUE(
REGEXEXTRACT(
TRANSPOSE(SPLIT(A1, CHAR(10))),
"Apple – ([0-9.]+)/?[0-9]?"
)
),
""
)
)
);
"Banana" & " – " & SUM(
ARRAYFORMULA(
IF(
REGEXMATCH(
TRANSPOSE(SPLIT(A1, CHAR(10))),
"Banana" & " – [0-9.]+/?[0-9]?"
),
VALUE(
REGEXEXTRACT(
TRANSPOSE(SPLIT(A1, CHAR(10))),
"Banana – ([0-9.]+)/?[0-9]?"
)
),
""
)
)
)
}
)
это правильно выводит массив
{"Apple – 1001", "Banana – 6"}
Но как бы я ни старался, мне не удается заставить его работать динамически для всех уникальных элементов. Мне кажется, что я разгадал обе части головоломки, но просто не могу сложить их вместе.
желательно да. однако преобразовать его из массива в одну ячейку должно быть довольно просто (что-то вроде TEXTJOIN(CHAR(10), TRUE, [array here])), поэтому, если вы сможете понять, как составить формулу массива, этого, вероятно, тоже будет достаточно.
Извините за путаницу. Я не предлагал решения, я просто пытался прояснить вопрос. Было бы полезно, если бы вы отредактировали вопрос, чтобы было ясно, что предлагаемый результат должен содержаться в одной ячейке.
да, да, сделаю!





Вот возможное решение:
=ARRAYFORMULA(
LET(data,WRAPROWS(SUBSTITUTE(SPLIT(SUBSTITUTE(A1,"/","ζ")," –"&CHAR(10)),"ζ","/"),2),
items,INDEX(data,,1),
qty,MAP(INDEX(data,,2),LAMBDA(n,SORTN(QUERY(,"select "&n)))),
JOIN(CHAR(10),MAP(UNIQUE(items),LAMBDA(item,item&" – "&SUM((item=items)*qty))))))
Массив data форматирует данные так, чтобы мы могли с ними работать:
WRAPROWS(SUBSTITUTE(SPLIT(SUBSTITUTE(A1,"/","ζ")," –"&CHAR(10)),"ζ","/"),2)
Массив qty преобразует числа в их недробное представление:
MAP(INDEX(data,,2),LAMBDA(n,SORTN(QUERY(,"select "&n))))
Затем мы перебираем каждый уникальный фрукт, используя функцию MAP:
MAP(UNIQUE(items),LAMBDA(item,
И для каждого фрукта мы выполняем условную сумму, где диапазон критериев — это массив всех фруктов, критерий — текущий фрукт, а диапазон сумм — это массив величин, связанных с каждым фруктом:
SUM((item=items)*qty)
Наконец, соединяем все вместе:
JOIN(CHAR(10),...)
Альтернативные представления дробных величин
Уменьшенные фракции
=ARRAYFORMULA(
LET(data,WRAPROWS(SUBSTITUTE(SPLIT(SUBSTITUTE(A1,"/","ζ")," –"&CHAR(10)),"ζ","/"),2),
items,INDEX(data,,1),
qty,INDEX(data,,2),
JOIN(CHAR(10),
MAP(UNIQUE(items),LAMBDA(item,
LET(qty_,FILTER(qty,items=item),
s,SPLIT(TOCOL(REGEXREPLACE(qty_,"^[^/]+$","$0/1")),"/"),
nums,INDEX(s,,1),dens,INDEX(s,,2),l,LCM(dens),nnum,SUM(l/dens*nums),g,GCD(nnum,l),
item&" – "&IF(COUNTIF(qty_,"*/*")=0,SUM(--qty_),nnum/g&"/"&l/g)))))))
Нередуцированные дроби
=ARRAYFORMULA(
LET(data,WRAPROWS(SUBSTITUTE(SPLIT(SUBSTITUTE(A1,"/","ζ")," –"&CHAR(10)),"ζ","/"),2),
items,INDEX(data,,1),
qty,INDEX(data,,2),
JOIN(CHAR(10),
MAP(UNIQUE(items),LAMBDA(item,
LET(qty_,FILTER(qty,items=item),
s,SPLIT(TOCOL(REGEXREPLACE(qty_,"^[^/]+$","$0/1")),"/"),
nums,INDEX(s,,1),dens,INDEX(s,,2),l,LCM(dens),
item&" – "&IF(COUNTIF(qty_,"*/*")=0,SUM(--qty_),SUM(l/dens*nums)&"/"&l)))))))
Не работает, когда для фрукта есть как дробные, так и не дробные части. Например, введите первую запись «Яблоко — 1/4», и формула не даст правильный результат. (Я очень ценю ответ. Хотя я не могу проголосовать за него прямо сейчас, поскольку не проверил правильный ответ, я хотел бы поучиться на вашем подходе и должен вернуться, чтобы проголосовать за него позже.)
Позвольте мне сформулировать: проблема не в смешивании дробей и недробий. Проблема в том, что числа, выглядящие как дроби, можно интерпретировать как дату (серийный номер) (Google Sheet).
Удивительный! Однако, похоже, возникает проблема, когда мы добавляем в меню элементы, содержащие пробелы, например, если мы добавляем Apple Pie – 8 в меню. Мне удалось решить эту проблему, удалив пробел из параметра SPLIT и добавив вместо него TRIM: WRAPROWS(TRIM(SUBSTITUTE(SPLIT(SUBSTITUTE(B2,"/","ζ"),"–"&CHAR(10)),"ζ","/")),2)
Сначала разделите данные на правильный двумерный массив, а затем используйте запрос для группировки данных:
=ARRAYFORMULA(QUERY(
SPLIT(TOCOL(SPLIT(A1,CHAR(10))),"– "),
"Select Col1,sum(Col2) group by Col1",0)
)
Отсюда,
Дроби не преобразуются в числа и, следовательно, не суммируются по запросу. Функция «Оценка» недоступна в таблицах Google, но вы сможете использовать мой трюк с запросом, чтобы преобразовать дроби в реальные числа.
Данные не объединяются в одну ячейку. Использование BYROW/TEXTJOIN должно быть тривиальным.
Десятичные дроби можно преобразовать обратно в дроби с помощью TEXT
=ARRAYFORMULA(
LET(splitData, SPLIT(TOCOL(SPLIT(A1,CHAR(10))),"– "),
numData,MAP(splitData,LAMBDA(a,IFERROR(--query(,"Select "&a&" label "&a&" ''",0),a))),
groupedData, QUERY(numData,
"Select Col1,sum(Col2) group by Col1 label sum(Col2) ''",0
),
rowJointData, BYROW(groupedData, LAMBDA(r, JOIN("-", TEXT(r,"# #/100")))),
TEXTJOIN(CHAR(10),,rowJointData)
)
)
Это решение предназначено для имитации «цикла» по входному массиву (однократно).
Чтобы выполнить вышеизложенное, мы используем reduce для хранения совокупного одномерного массива формата {name1,val1,name2,val2,...} при прохождении входного массива. Конечно, нам нужно сначала использовать tocol(split(..., чтобы преобразовать одну входную ячейку в массив. Нам также потребуются промежуточные шаги для определения совпадающих имен и обработки значений, чтобы они могли sum легко это сделать.
В конце концов, мы можем обработать окончательный агрегат во что угодно. В приведенном ниже примере я использовал makearray, чтобы преобразовать однострочный массив в массив n-by-1 с помощью concat, как будто сначала преобразую в массив n-by-2, а затем concat по столбцам. Здесь makearray представляет собой нечто общее, что вы всегда можете заставить работать. Любое конкретное преобразование может иметь свой собственный, более простой или более эффективный подход.
Идея проста. Хотя код будет длинным, поскольку в нем много вызовов hstack, index(...,i,j) и choosecols(...,1,sequence(... для доступа и хранения элементов массива. Они создают много беспорядка. Надеюсь, расстояние ниже поможет прояснить ход вещей.
Сначала создайте именованную функцию для обработки потенциальных дробей следующим образом:
let(sepByVal,split(x,"/"),if (columns(sepByVal)>1,index(sepByVal,1,1)/index(sepByVal,1,2),sepByVal))
Вы можете обновить эту функцию, если форматирование ввода изменится.
Или, если вам хочется сохранить целочисленную дробь (в виде текста), именованная функция — это то, что вам нужно.
Затем в B1 следующая формула даст желаемый результат.
=let(sep,"–"
,input,tocol(split(A1,char(10)))
,arr,reduce(hstack(index(regexextract(index(input,1,1),"[A-Za-z]+"),1,1),0)
,input
,lambda(a,c
,let(sepByVal,regexextract(c,"([A-Za-z]+)[ "&sep&"]*([0-9]+[./0-9]*)")
,ind,iferror(match(index(sepByVal,1,1),a,0),0)
,if (ind
,hstack(choosecols(a,sequence(1,ind))
,index(a,1,ind+1)
+FRACTION2NUM(index(sepByVal,1,2))
,if (ind<columns(a)-2
,choosecols(a,sequence(1,columns(a)-ind-1,ind+2))
,tocol(,3)))
,hstack(a,index(sepByVal,1,1)
,FRACTION2NUM(index(sepByVal,1,2)))))))
,join(char(10),makearray(columns(arr)/2,1,lambda(i,j,index(arr,1,2*i-1)&" "&sep&" "&index(arr,1,2*i)))))
Обязательно скопируйте символ – прямо из введенного вами текста.
Замена последней строки на
,out,wraprows(arr,2)
,join(char(10),arrayformula(index(out,,1)&" "&sep&" "&index(out,,2))))
может сделать формулу более знакомой.
Приведенная выше формула была намеренно создана для совместимости с вводом массива. Вы можете заменить определение input соответствующим образом, если у вас есть входной массив. (Как показано на изображении ниже.)
Но если у вас есть входной массив, обязательно отметьте пустое внутри lambda, в основном, если оно пустое, то передайте a, иначе следуйте остальной логике (или используйте filter снаружи reduce). Таким образом, вы можете работать с бесконечными диапазонами.
Наконец, совет о том, как составить такую формулу самостоятельно: при использовании reduce (в столбце) используйте горизонтальный массив в качестве агрегата. Если вы столкнулись с какой-либо ошибкой, измените диапазон ввода на A$1:A1 и перетащите его вниз. После этого вы визуально увидите промежуточные агрегаты. В результате любую ошибку будет легко обнаружить. Именно поэтому лучше сначала преобразовать одну ячейку в массив и создать формулы, которые работают с входными данными массива.
Немного философских мыслей... Почему
makepivottableне является встроенной функцией...? Почемуreshape(как представление, если вывод используется в строке) не является встроенной функцией...? Почемуset(val,arr,i,j)не является встроенной функцией...?
Формула в B1:
=INDEX(JOIN(CHAR(10),BYROW(LET(x,SPLIT(TOCOL(SPLIT(REGEXREPLACE(A1,"/\d+",),CHAR(10)))," – ",0),u,UNIQUE(INDEX(x,,1)),HSTACK(u,MAP(u,LAMBDA(y,SUM(FILTER(INDEX(x,,2),INDEX(x,,1)=y))&IFERROR(REGEXEXTRACT(A1,y&" – \d+(/\d+)"),))))),LAMBDA(r,JOIN(" – ",r)))))
Это предполагает, что входные данные последовательны, и суммы в ваших данных имеют один и тот же тип; таким образом, либо целые числа, либо части одного и того же порядка!
Однако, если вашей целью было суммировать все виды значений (включая различные типы арифметических операторов), попробуйте следующее:
=INDEX(JOIN(CHAR(10),BYROW(LET(x,SPLIT(TOCOL(SPLIT(A1,CHAR(10)))," – ",0),QUERY({INDEX(x,,1),MAP(INDEX(x,,2),LAMBDA(y,QUERY(,"select "&y&" label "&y&" ''")))},"Select Col1, Sum(Col2) Group By Col1 Label Sum(Col2) ''")),LAMBDA(r,JOIN(" – ",r)))))
So the desired output is this:Это тоже в одной камере?