Forum Discussion

djclements's avatar
djclements
Silver Contributor
Aug 12, 2026

Braille Translator Challenge

Using the Braille Alphabet lookup table provided:

  • Part 1 - convert the given phrase from Braille to words.
  • Part 2 - convert the given phrase from words to Braille.

✅ Solutions should be in the form of a single-cell formula.

✅ Generalized Lambda functions are welcome, provided the function definitions are included with your solution.

✅ Keep in mind, the "best" solution is not necessarily the shortest...

  • Strive for conciseness, if you wish; just don't let your solutions be ordinary. 😉

Good luck and have fun!

14 Replies

  • Patrick2788's avatar
    Patrick2788
    Silver Contributor

    This discussion has inspired me to create a generalized function capable of locating a 2D array within a 2D array and returning a 2D array from the Moore neighborhood.

    Excel has plenty of functions geared towards working with 1D arrays. Lookup functions return 1D arrays and shaping functions can return 2D arrays, but will only accept 1D array inputs.

    What if the grid could be left as-is and a generalized function could still pull the return?

     

    Consider this demo:

    // Signature of draft
    
    Navigate2Dλ=
    LAMBDA(
        lookup_kernel,        // 2D array (Will also accept 1D arrays)
        match_array_2D,    // 2D arrays only
        [return_direction], // Moore neighborhood - 1 to 9. 5 is center. If omitted, defaults to 8 (below)
        [return_height],   // If these two parameters are omitted, defaults to height x width of the lookup kernel
        [return_width],

     

    It's working smoothly so far. It's locating Cities using convolution windowing.  I'll have more to share when it's finalized.

    • m_tarler's avatar
      m_tarler
      Silver Contributor

      So I thought of doing something similar.  I'm not sure how yours works/acts so maybe mine is the same or maybe it is different.  Again, my Excel Labs is still not working on this computer :( so here is the formula based:

      GridLookUp, LAMBDA(pattern,lookUpGrid,[returnGrid],[not_found],[step_rows],[step_cols],[options], LET(
         InRows, ROWS(pattern),
         InCols, COLUMNS(pattern),
         OutputArray, IF((COLUMNS(lookUpGrid)=COLUMNS(returnGrid))*(ROWS(lookUpGrid)=ROWS(returnGrid)), 1, 0),
         OutRows, (ROWS(pattern)-1)*OutputArray+1,
         OutCols, (COLUMNS(pattern)-1)*OutputArray+1,
         NFout, IF(ISOMITTED(not_found),"PATTERN NOT FOUND",not_found),
         stepRows, IF(step_rows,step_rows,InRows),
         stepCols, IF(step_cols,step_cols,InCols),
         FindLocations, MAKEARRAY(QUOTIENT(ROWS(lookUpGrid)-InRows,stepRows)+1,QUOTIENT(COLUMNS(lookUpGrid)-InCols,stepCols)+1,LAMBDA(x,y, PRODUCT(--(TAKE(DROP(lookUpGrid,(x-1)*stepRows,(y-1)*stepCols),InRows,InCols)=pattern)))),
         XYcount, SEQUENCE(ROWS(FindLocations)*COLUMNS(FindLocations),,0),
         XYlist, IF({1,0},QUOTIENT(XYcount,COLUMNS(FindLocations)),MOD(XYcount,COLUMNS(FindLocations)))+1,
         XYLocations, IF(SUM(FindLocations),FILTER(XYlist, INDEX(FindLocations,TAKE(XYlist,,1),TAKE(XYlist,,-1))),0),
         Return, IF(COLUMNS(XYLocations)-1,DROP(REDUCE("",SEQUENCE(ROWS(XYLocations)),LAMBDA(p,q,HSTACK(p,TAKE(DROP(returnGrid,(INDEX(XYLocations,q,1)-1)*IF(OutputArray,stepRows,1),(INDEX(XYLocations,q,2)-1)*IF(OutputArray,stepCols,1)),OutRows,OutCols)))),,1),NFout),
         IF(ISOMITTED(returnGrid),
            FindLocations,
            IF(Return<>0,Return,""))
         ))

      so the idea is that it can take a [pattern] to match in a [lookUpGrid] and then if [returnGrid] is given it will return the corresponding location in that grid. Note, if that [returnGrid] is NOT the same dimensions as the [lookUpGrid] it will return a single cell (e.g. brail dot grid (3x2) can lookup on brail dot grid array (3i x 2j) and return from a corresponding table of letters/characters (i x j) ). Also if [returnGrid] is not provided a grid of 0/1 will be returned corresponding to the location(s) where the search pattern was found.  NOTE: this will return ALL matches for the pattern (arranged in a stacked row).  Another feature is the [step_rows] and [step_cols] so you can tell the search to step by a specific amount.  So in the example where there is a row of characters between each line you can use a [step_rows] of 4 to skip over those spacer lines.  You can also force a [step] of 1 to 'scan' all possible combinations of the search array for that pattern.  I've tested a few different combinations and it seems pretty robust.  Let me know what you think.

      BTW. on line 12 I am just creating a list of all possible x,y coordinates and tried using the trick i leaned below to use IF( c, QUOTIENT, MOD) (value1, value2) but I kept getting a memory error because I was trying to create a 2d array using c => {1,0}.  It worked fine when I declared them fully inside the IF but doing that trick to pass them as functions didn't work in this array version.

      And although it isn't a specific optional output, the XYLocations function returns a list of where the pattern is found in a 2 column row-col table of values.

      Here is an example of using it to find the first character (S) from the Answer key.  I rewrote the letters in a single spaced grid.  But if you take the overlapping grids (so the [returnGrid] is B3:Q18) you would still get S out but also with the other 5 cells of the offset 3x2 grid as shown just to the right of the highlighted cell (C25:D27).

       

  • djclements's avatar
    djclements
    Silver Contributor

    My original (unedited) no Let, no Lambda solutions:

    //Part 1:
    
    =CONCAT(XLOOKUP(BYROW(WRAPROWS(LEN(TOCOL(T5:BS7,,1)),6),CONCAT),BYROW(WRAPROWS(LEN(TOCOL((B4:Q6,B8:Q10,B12:Q14,B16:Q18),,1)),6),CONCAT),TOCOL((B3:Q3,B7:Q7,B11:Q11,B15:Q15),1)))
    
    //Part 2:
    
    =IF(--MID(CONCAT(XLOOKUP(MID(T16,SEQUENCE(LEN(T16)),1),TOCOL((B3:Q3,B7:Q7,B11:Q11,B15:Q15),1),BYROW(WRAPROWS(LEN(TOCOL((B4:Q6,B8:Q10,B12:Q14,B16:Q18),,1)),6),CONCAT))),WRAPCOLS(SEQUENCE(LEN(T16)*6),3),1),B4,"")

    I took advantage of the fact that TOCOL can accept a non-contiguous range reference, which it processes by area. The 3x2 Braille blocks are converted to 6-digit binary codes, which are used to perform the character lookup. In part 2, the lookup is reversed, whereby each character in the phrase is replaced with its corresponding 6-digit code, then MID is used with a sequence array to extract the individual binary digits and produce the 2D array.

    My attempt at generalizing it (somewhat); plus, an alternative approach for part 2:

    //Part 1:
    
    =BRA2TXT(T5:BS7, TOCOL((B3:Q3,B7:Q7,B11:Q11,B15:Q15),1), BRA2BIN((B4:Q6,B8:Q10,B12:Q14,B16:Q18)))
    
    //Part 2:
    
    =BRA2TXT(T16, TOCOL((B3:Q3,B7:Q7,B11:Q11,B15:Q15),1), BRA2BIN((B4:Q6,B8:Q10,B12:Q14,B16:Q18)), 1)
    
    //Where:
    
    BRA2BIN = LAMBDA(ref,
        BYROW(WRAPROWS(LEN(TOCOL(ref, , 1)), 6), CONCAT)
    );
    
    BRA2TXT = LAMBDA(phrase,chr_array,bin_array,[text_to_braille],
        IF(
            text_to_braille,
            IF(--MID(XLOOKUP(MID(phrase, NUM3x2(phrase, 1), 1), chr_array, bin_array), NUM3x2(phrase), 1), "●", ""),
            CONCAT(XLOOKUP(BRA2BIN(phrase), bin_array, chr_array))
        )
    );
    
    NUM3x2 = LAMBDA(txt,[opt],
        TRANSPOSE(1 + IF(opt, QUOTIENT, MOD)(SEQUENCE(LEN(txt) * 2, 3, 0), 6))
    );

    In part 2 of this version, the text phrase is exploded in a Kronecker product fashion, whereby every character is extracted 6 times each, which in turn lookup their corresponding 6-digit codes in a 3x2 block. MID is then lifted over the resulting array, extracting just a single binary digit from each code.

    • m_tarler's avatar
      m_tarler
      Silver Contributor

      OK this is why I love this forum.  I still need to dive into all these solutions more because I learn SO MUCH from all of you.  But right off the back I can say I just learned:

      a) TOCOL can take inputs like that

      b) you can pass FUNCTIONS using an IF statement (i.e.  on LINE 24:  IF(opt, QUOTIENT, MOD)(SEQUENCE(LEN(txt) * 2, 3, 0), 6)

      I will continue to review and I'm sure I will continue to learn.  Thank you all.

      • djclements's avatar
        djclements
        Silver Contributor

        m_tarler​ 

        As it so happens, I also picked up both of these little nuggets from other contributors somewhere along the way. Community forums such as this are a wonderful way to share and learn from each other. <3

        Cheers!

  • Patrick2788's avatar
    Patrick2788
    Silver Contributor

    I'm using a modular approach to solve both parts. You'll find all Lambdas in the AFE module of the workbook. The one I cannot share at present is Proto2Dλ, which is a generalized 2D lookup (Lookup values can be found in 2D array). I haven't finalized the function but it will be a part of my library in the future.

     

    /*
    Part 1
    
    =LET(
        // Re-tile 2D arrays, make phrase concise
        remap, BlockMapλ(Alphabet, 4, 2),
        ph, BYROW(BlockMapλ(Phrase, 3, 2), ARRAYTOTEXT),
    
        // Letters/characters from Braille alphabet
        letter, TAKE(remap, , 1),
    
        // Braille dots
        braille, BYROW(DROP(remap, , 2), ARRAYTOTEXT),
    
        // Lookup phrase, return letters, make consise
        result, CONCAT(XLOOKUP(ph, braille, letter)),
        ph
    )
    
    
    
    Part 2
    
    =LET(
        // Expansion kernels
        kernel, {0, 0; 1, 1; 2, 2},
        kernel_b, {0, 1; 0, 1; 0, 1},
    
        // Phrase ~ array
        ex, Explodeλ(phrase2),
    
        // Mesh grid
        i, MGridλ(Alphabet),
        j, MGridλ(Alphabet, 1),
    
        // 2D lookup: letter ~ alphabet ~ i/j
        x, 1 + Proto2Dλ(ex, Alphabet, i),
        y, Proto2Dλ(ex, Alphabet, j),
    
        // Expand indices using kernels
        r, Zoomλ(x, kernel),
        c, Zoomλ(y, kernel_b),
    
        // Get results
        final, TEXT(INDEX(Alphabet, r, c), ";;;@"),
        final
    )
    
    */

     

    • djclements's avatar
      djclements
      Silver Contributor

      Patrick2788​ 

      I was hoping to see you here! Wonderful demonstration of your extended function library. I haven't had a chance to review them all in detail yet, but plan to soon. Looks amazing!

      I think we followed the same basic logic for Part 1, with trivial variances in methods. Part 2 is very interesting... I'll have to take a closer look at the function definitions to see what's going on and keep an eye out for Proto2Dλ when it's ready.

      Great work! :)

  • m_tarler's avatar
    m_tarler
    Silver Contributor

    This was fun.  I created a few LAMBDA functions to work together:

    BrailRows,WRAPROWS(TOROW(HSTACK(B4:Q6,B8:Q10,B12:Q14,B16:Q18),,1),6), 
    
    BrailText,TAKE(WRAPROWS(HSTACK($B$3:$Q$3,$B$7:$Q$7,$B$11:$Q$11,$B$15:$Q$15),2),,1), 
    
    Brail2Char,LAMBDA(dots,IF((ROWS(dots)<>3)+(COLUMNS(dots)<>2),"*",INDEX(BrailText,XMATCH(6,MMULT(--(TOROW(dots,,1)=BrailRows),SEQUENCE(6,,,0)))))), 
    
    BrailLinesToText,LAMBDA(lines,IF((MOD(ROWS(lines),3)<>0)+(MOD(COLUMNS(lines),2)<>0),"*",CONCAT(MAKEARRAY(ROWS(lines)/3,COLUMNS(lines)/2,LAMBDA(r,c,Brail2Char(TAKE(DROP(lines,3*(r-1),2*(c-1)),3,2))))))), 
    
    Char2Brail,LAMBDA(c,IFERROR(LET(a,WRAPCOLS(CHOOSEROWS(BrailRows,XMATCH(c,BrailText)),3),IF(a=0,"",a)),"*")), 
    
    Line2Brail,LAMBDA(l,DROP(REDUCE("",MID(l,SEQUENCE(LEN(l)),1),LAMBDA(p,q,HSTACK(p,Char2Brail(q)))),,1)),

    I apologize I didn't clean this up by my Excel Labs plug-in isn't working right now...

    Basically

    BrailRows is just a reference for the brail answer key formatted as rows

    BrailText is the corresponding character for each row (i.e. make it easy to add more characters)

    Brail2Char will take a single grid of dots and convert it to the corresponding character

    BrailLinesToText will take a whole line or even multiple lines of Brail and convert it to a single string output

    Char2Brail will output the Brail grid of dots give a character

    Line2Brail will take a string of characters and output the line of Brail dots

    here is an image of the results:

    and on the right side in cell BU is using the input from the gray area just to show it can take multi-line input

    I see a couple other answers have been submitted that look interesting, I will have to check them out too.

    • djclements's avatar
      djclements
      Silver Contributor

      m_tarler​ 

      I'm loving the interconnected functions... far from ordinary indeed! You even went above and beyond with Part 1 by accepting a multi-line Braille input. Beautifully done!

      Interesting choice to leave the lookup table as an array of flattened Braille blocks... I had considered that approach as well but ultimately went another route. I'll wait a little longer to see if anyone else wants to take a stab at this before sharing my solution.

      Glad you enjoyed it! Thanks for playing. :)

      • m_tarler's avatar
        m_tarler
        Silver Contributor

        actually I initially converted each block into a binary number but decided that extra step wasn't needed.  I see others used ARRAYTOTEXT and I considered that but I know excel does matrix operations very efficiently and although it makes the 'code' simplier, I wasn't sure if adding that would make it more or less efficient over all.  Not that mine is any model of efficiency.

  • IlirU's avatar
    IlirU
    Iron Contributor

    Hi djclements​,

    (I have not used the LAMBDA function in my formula)

    I applied the first formula to cell AF10.

    =LET(
         data, B3:Q18,
       phrase, T5:BS7,
               CONCAT(XLOOKUP(BYCOL(WRAPCOLS(BYCOL(phrase, ARRAYTOTEXT), 2), ARRAYTOTEXT),
               HSTACK(BYCOL(WRAPCOLS(BYCOL(CHOOSEROWS(data, {2,3,4}), ARRAYTOTEXT), 2), ARRAYTOTEXT),
                      BYCOL(WRAPCOLS(BYCOL(CHOOSEROWS(data, {6,7,8}), ARRAYTOTEXT), 2), ARRAYTOTEXT),
                      BYCOL(WRAPCOLS(BYCOL(CHOOSEROWS(data, {10,11,12}), ARRAYTOTEXT), 2), ARRAYTOTEXT),
                      BYCOL(WRAPCOLS(BYCOL(CHOOSEROWS(data, {14,15,16}), ARRAYTOTEXT), 2), ARRAYTOTEXT)),
               HSTACK(
                      TOROW(CHOOSEROWS(data, 1), 1),
                      TOROW(CHOOSEROWS(data, 5), 1),
                      TOROW(CHOOSEROWS(data, 9), 1),
                      TOROW(CHOOSEROWS(data, 13), 1)
    ))))

    The second formula is the same as the first formula, but instead of T5:BS7, T19:BS21 should be inserted in the formula.

    Try it and let me know.

    IlirU

    • djclements's avatar
      djclements
      Silver Contributor

      IlirU​ 

      Nice job! It works perfectly for Part 1, converting Braille blocks into words.

      The reverse procedure for Part 2 may be slightly more difficult. Best of luck, if you dare! ;)

  • askan's avatar
    askan
    Copper Contributor
    Part1: 
    
    =LET(b,HSTACK(B4:Q6,B8:Q10,B12:Q14,B16:Q18),a,IF(b="",0,b),c,HSTACK(CHAR(SEQUENCE(,26,97)),TOROW(F15:Q15,1)),l,LAMBDA(i,DROP(REDUCE(0,SEQUENCE(COLUMNS(i)/2,,,2),LAMBDA(x,y,HSTACK(x,CONCAT(CHOOSECOLS(i,y,y+1))))),,1)),CONCAT(XLOOKUP(l(IF(T5:BS7="",0,T5:BS7)),l(a),c)))
    
    Part2: 
    
    =LET(b,HSTACK(B4:Q6,B8:Q10,B12:Q14,B16:Q18),a,IF(b="",0,b),c,HSTACK(CHAR(SEQUENCE(,26,97)),TOROW(F15:Q15,1)),l,LAMBDA(i,DROP(REDUCE(0,SEQUENCE(COLUMNS(i)/2,,,2),LAMBDA(x,y,HSTACK(x,CONCAT(CHOOSECOLS(i,y,y+1))))),,1)),d,REGEXEXTRACT(T16,".",1),e,XLOOKUP(d,c,l(a)),r,DROP(REDUCE(0,e,LAMBDA(x,y,HSTACK(x,WRAPROWS(REGEXEXTRACT(y,".",1),2)))),,1),SUBSTITUTE(r,0,))

     

    • djclements's avatar
      djclements
      Silver Contributor

      askan​ 

      I think I got you covered on LinkedIn already but well done once again! Nice job on both parts. :)