Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Iterative/Looped Substitute without VBA

Abridged Question:

If I have a concatenated string of "|#|#|#|...|#|", how can I apply a multiplier to each of the numbers and update the concatenated text? For example, for |4|12|8|, multiply by a factor of 2 and update the concatenated text to |8|24|16|.

Background

I have three columns of interest. The first column contains a date, the second an amount or factor, and the third column concatenates data into the format "|#|#|...|#|" (e.g., |2|5|, |2|5|12|, |4|12|, etc.). At times, a multiplying factor needs to be applied to the concatenated data, and the individual numbers would need to be updated accordingly.

An example would be—

Date        Amt     Concatenated Data
01/01/18    2       |2|
01/05/18    5       |2|5|
02/06/18    12      |2|5|12|
03/25/18    -3      |4|12|
03/31/18    8       |4|12|8|
04/01/18    F2      |8|24|16|  (factor of 2 applied)
04/15/18    12      |8|24|16|12|
04/01/18    F1/4    |2|6|4|3|  (factor of 1/4 applied)

With a formula, how can I apply the factor to the concatenated data, and update the individual numbers?

I'm bound by the following conditions:

  • Excel 2007, so no TEXTJOIN function
  • No VBA or UDFs (due to security policies)
  • Individual numbers are dynamic (i.e., I can't use a static value for the "old_text" parameter of the SUBSTITUTE formula)
  • Amount of individual numbers within concatenated data is also dynamic (may contain one number, or may contain dozens of different numbers)

I can pull out the individual numbers using an array formula. I can even then multiply those numbers by the factor to produce an array result. However, I can't rebuild the concatenated data, because CONCATENATE doesn't work on an array. I've also tried SUBSTITUTE, but I can't iterate through the "|" separators. I can only substitute a given segment (e.g., change all entries of "|2|" to "|4|"). Nesting SUBSTITUTE or using individual columns won't work, since it could potentially involve dozens of instances.

Just to add some info on the concatenated data:

  • Amt>0, then value is concatenated to the end of the previous concatenated value
  • Amt<0, begin reducing individual numbers in concatenated value (CV) until reduction amount reached (e.g., for |2|5|12| and Amt=-3, reduce CV to |4|12|, which is -2 from the first segment and -1 from the second segment)
  • Amt reduction is limited to the sum of the previous CV's individual numbers (e.g., for |4|12|, the reduction cannot exceed 16)
  • Amt=F#, indicates a multiplying factor, and the CV's numbers need to be updated
  • The CV has no max (could have dozens to hundreds of individual numbers, with numbers going from 1 to 100,000+), other than any max applied by Excel itself on string length
like image 508
E33 Avatar asked Nov 07 '22 03:11

E33


1 Answers

HIGH LEVEL

  1. Four parts to this solution
  2. They satisfy pre-requisites (2007 compatibility, no VB, no Office 365 requirement, no custom VB functions, provide for complete 'dynamic' nature of variable length of cells to concatenate)
  3. Caveat: to best knowledge / research, there is no parsimonious single-cell function & therefore an interim step has been proposed)
  4. One more caveat: I imagine the simple 'hack' of wrapping a graph around the delimited data is out of question (see 'Other/Various' below ☺)

PARTS 1-4

Accompanying parts 1-4 below are functions which relate to the following screenshot:

Excel Depiction of Dynamic Concatenation Solution - Excel 2007

I have also uploaded / amended to meet requirements of Google Sheets (see here) Parts 1 & 2:

Similar in that they rely upon FilterXML technique to count component / terms, and split cells respectively:

Part 1:

=COUNT(2*TRANSPOSE(FILTERXML("<AllText><Num>"&SUBSTITUTE(LEFT(MID(D12,2,LEN(D12)-1),LEN(MID(D12,2,LEN(D12)-1))-1),"|","</Num><Num>")&"</Num></AllText>","//Num")))

Note: google sheets doesn't recognise FilterXML, so have amended technique/functions accordingly. For instance, above can be determined using counta on the split cells in Part 2 (easier / much more simpler than proposed approach above, albeit less robust given any cells lying to the right of the split cells will interfere with ordinary functionality of this approach).

Part 2:

It's either a manual approach, a fancy series of 'mid' &/or substitute / left/right functions, or the following FilterXML code which, per various sources (e.g. here) should be compatible with Excel 2007:

=IF(LEFT(C12,1)="F",1*SUBSTITUTE(C12,"F",""),1)*TRANSPOSE(FILTERXML("<AllText><Num>"&SUBSTITUTE(LEFT(MID(D12,2,LEN(D12)-1),LEN(MID(D12,2,LEN(D12)-1))-1),"|","</Num><Num>")&"</Num></AllText>","//Num"))

Commonality with Part 1 (re: FilterXML) can be seen - the only difference is that the count(Part 1) has been replaced with the transformation (multiplicative factor, as given in O.P's Q).

Part 3

Nothing fancy here - a simple concatenation (which is a far cry from a 'recursive' substitution function, I know, but hey - it does the trick and can always be placed in a mirror copy of the original sheet to avoid space issues/cell interaction issues)

=IF(H12="","",IF(G24="","|","")&G24&H12&"|")

Part 4

Thanks to the number of terms derived in Part 1, an offset function can easily determine the final cell pertaining to the concatenated 'build up' of 'transformed' values (per Part 3):

=OFFSET(H31,0,E31-1,1,1)

OTHER / VARIOUS

Various other proposals and 'workarounds' exist; unfortunately, these appear to fall short in one way or another of the pre-requisites set forth, videlicit:

a) Function/formula based b) No VB c) Excel 2007 d) Dynamic (variable/unknown number of terms)

  1. Manual: e.g. function = concatenate(transpose(desired range)), and then components of the concatenate function and pressing F9 to convert to calculated values, which are readily applicable in the concatenate function. Disadvantage: time consuming in relation to 'automated solution' (needs to be done for each applicable toy). Advantage: no additional 'spreadsheet real estate' required, quicker/straightforward implementation in first instance.
  2. Variants of the 'build-up' method: e.g. per Part 3, however, this alone does not ensure for an automated approach across an unknown number of terms in the original concatenated list.
  3. Have mentioned in a previous solution (here), but may be case that you are eligible for Office 365 functionality whilst on a previous version of Excel (see Office Insider here)
  4. Other proposals (above/below in this forum mind you) propose textjoin (so not sure if this is a comprehension issue or what ☺)
  5. And yes, as alluded to at outset, you can easily achieve the desired outcome using a simple graph! Just for fun then, sort the data in reverse order, and include the split/delimited values as a bar graphs' "x-values" (which, by defn. for this type of graph, will now appear along the ordinary Cartesian 'y/vertical' axis)...

Graphical solution

Zero points for this but thought it was an interesting discovery on my part! (and if still in doubt, here's what the 'graph' would look like if I didn't kill everything except for the axis labels...):

Funny little graph - probably quickest solution!!

Numerous references for relevant other items above, including research areas, as follows:

  • Excel Champs
  • StackOverflow - alternative applications for FilterXML

JUST ONE MORE THING...

In true Columbo style modus operandi, other ideas/approaches considered:

  • Application of pivot table?
  • Constructing matrices: I got a solution with a series of offset functions, but couldn't think of a feasible way to implement given space issues
  • Converting the split cells into a long digit through summation: e.g. 8 22 16 = 80 000 + 22 00 + 16. Using a substitute function with text (long digit, "General") I was able to successfully introduce the delimiter character ('|') for pairs of adjacent 'tuples' (e.g. I could get '8|2216', '822|16', but then a 'build up' formula where one cell depends upon converted values of the previous and so one was required once more, which landed me back to the proposal I have set out above
  • fyi - the matrix consideration only solves tuples of 2, for n-dimensional /combination one would need to 'pass' a string of characters over its mirror copy - e.g. {6,10,22} would pass over {6,10,22}, ignoring duplicate values would yield a trapezium as follows:
6 10 22
6 10 22
6 10 22

after the copy has 'passed' over the original (first row), we have the desired combination (22,10,6) (on the 'diagonal' such a matrix). This is akin to how Fourier Transforms work (kind of); but that aside, it was tempting to construct a matrix like this, but couldn't be bothered at this stage.

Will probably turn out to be a far simpler way that someone comes up with (I won't be the only person surprised based upon the various sources I've considered...)

like image 122
JB-007 Avatar answered Nov 17 '22 08:11

JB-007