loading...

Daily Challenge #222 - Parse Bank Account Numbers

thepracticaldev profile image dev.to staff ・1 min read

You work for a bank, which has recently purchased an ingenious machine to assist in reading letters and faxes sent in by branch offices.

The machine scans the paper documents, and produces a string with a bank account that looks like this:

    _  _     _  _  _  _  _
  | _| _||_||_ |_   ||_||_|
  ||_  _|  | _||_|  ||_| _|

... why did we buy this machine?

Anyway, your task is to write a function that can take bank account string and parse it into actual account numbers.

Example

 _  _  _  _  _  _  _  _  _ 
| | _| _|| ||_ |_   ||_||_|     => 23056789
|_||_  _||_| _||_|  ||_| _|

Tests

 _  _  _  _  _  _  _  _  _ 
|_| _| _||_||_ |_ |_||_||_| 
|_||_  _||_| _||_| _||_| _|

  _  _     _  _  _  _  _
| _| _||_||_ |_   ||_||_|
||_  _|  | _||_|  ||_| _|

Good luck!


This challenge comes from arhigod on CodeWars. Thank you to CodeWars, who has licensed redistribution of this challenge under the 2-Clause BSD License!

Want to propose a challenge idea for a future post? Email yo+challenge@dev.to with your suggestions!

Posted on by:

thepracticaldev profile

dev.to staff

@thepracticaldev

The hardworking team behind dev.to ❤️

Discussion

markdown guide
 

F# solution. The problem was badly specified, so here some assumptions:

  1. Input comes as a string array, with each element representing one row, top to bottom.
  2. Unrecognized characters will show up as ? in the output.
  3. I decided not to strip the leading zero in the 023056789 example because that seems odd for a bank account number.
module DailyChallenge

[<Literal>]
let DigitRows = 3

[<Literal>]
let DigitCols = 3

let private isValidInput (input : string list) =
    input.Length % DigitRows = 0
    && List.forall (fun (row : string) -> row.Length % DigitCols = 0) input

let private range current max =
    let rStart = current * max
    (rStart, rStart + max - 1)

let private parseChar =
    function
    | [ " _ "; "| |"; "|_|" ] -> "0"
    | [ "   "; "  |"; "  |" ] -> "1"
    | [ " _ "; " _|"; "|_ " ] -> "2"
    | [ " _ "; " _|"; " _|" ] -> "3"
    | [ "   "; "|_|"; "  |" ] -> "4"
    | [ " _ "; "|_ "; " _|" ] -> "5"
    | [ " _ "; "|_ "; "|_|" ] -> "6"
    | [ " _ "; "  |"; "  |" ] -> "7"
    | [ " _ "; "|_|"; "|_|" ] -> "8"
    | [ " _ "; "|_|"; " _|" ] -> "9"
    | _ -> "?"

let private parseInput (input : string list) =
    seq {
        for row in 0 .. input.Length / DigitRows - 1 do
            let rStart, rEnd = range row DigitRows
            for col in 0 .. input.[0].Length / DigitCols - 1 do
                let cStart, cEnd = range col DigitCols
                yield input.[rStart..rEnd]
                      |> List.map (fun (row : string) -> row.[cStart..cEnd])
                      |> parseChar
    }
    |> System.String.Concat

let convert input =
    if isValidInput input then Some(parseInput input) else None

Tests:

module DailyChallengeTests

open FsUnit.Xunit
open Xunit
open DailyChallenge

[<Fact>]
let ``023056789``() =
    let rows =
        [ " _  _  _  _  _  _  _  _  _ "
          "| | _| _|| ||_ |_   ||_||_|"
          "|_||_  _||_| _||_|  ||_| _|" ]
    convert rows |> should equal (Some "023056789")

[<Fact>]
let ``823856989``() =
    let rows =
        [ " _  _  _  _  _  _  _  _  _ "
          "|_| _| _||_||_ |_ |_||_||_|"
          "|_||_  _||_| _||_| _||_| _|" ]
    convert rows |> should equal (Some "823856989")

[<Fact>]
let ``123456789``() =
    let rows =
        [ "    _  _     _  _  _  _  _ "
          "  | _| _||_||_ |_   ||_||_|"
          "  ||_  _|  | _||_|  ||_| _|" ]
    convert rows |> should equal (Some "123456789")

[<Fact>]
let ``invalid``() =
    let rows =
        [ "    _  _     _  _  _  _  _ " ]
    convert rows |> should equal None

[<Fact>]
let ``uncrecognized character``() =
    let rows =
        [ "    _  _     _  _  _  _  _ "
          "|   _| _||_||_ |_   ||_||_|"
          "|  |_  _|  | _||_|  ||_| _|" ]
    convert rows |> should equal (Some "?23456789")
 

Rust. Who needs error checking when you can support multiline instead?

use itertools::{izip, Itertools}; // 0.9.0
fn smol(big: &str) -> String {
    big.lines()
        .tuples()
        .flat_map(|(l1, l2, l3)| {
            izip!(l1.chars(), l2.chars(), l3.chars())
                .tuples()
                .map(|digit| match digit {
                    ((' ', ' ', ' '), (' ', ' ', ' '), (' ', '|', '|')) => '1',
                    ((' ', ' ', '|'), ('_', '_', '_'), (' ', '|', ' ')) => '2',
                    ((' ', ' ', ' '), ('_', '_', '_'), (' ', '|', '|')) => '3',
                    ((' ', '|', ' '), (' ', '_', ' '), (' ', '|', '|')) => '4',
                    ((' ', '|', ' '), ('_', '_', '_'), (' ', ' ', '|')) => '5',
                    ((' ', '|', '|'), ('_', '_', '_'), (' ', ' ', '|')) => '6',
                    ((' ', ' ', ' '), ('_', ' ', ' '), (' ', '|', '|')) => '7',
                    ((' ', '|', '|'), ('_', '_', '_'), (' ', '|', '|')) => '8',
                    ((' ', '|', ' '), ('_', '_', '_'), (' ', '|', '|')) => '9',
                    x => unreachable!(),
                })
        })
        .collect()
}

fn main() {
    println!(
        "{}",
        smol(
            r"    _  _     _  _  _  _  _ 
  | _| _||_||_ |_   ||_||_|
  ||_  _|  | _||_|  ||_| _|
    _  _     _  _  _  _  _ 
  | _| _||_||_ |_   ||_||_|
  ||_  _|  | _||_|  ||_| _|
    _  _     _  _  _  _  _ 
  | _| _||_||_ |_   ||_||_|
  ||_  _|  | _||_|  ||_| _|"
        )
    );
}

Look at it go

 

Python solution

# stores the modified string representation of a digit
digitStringDict = {
    " _ | ||_|" : "0",
    "     |  |" : "1",
    " _  _||_ " : "2",
    " _  _| _|" : "3",
    "   |_|  |" : "4",
    " _ |_  _|" : "5",
    " _ |_ |_|" : "6",
    " _   |  |" : "7",
    " _ |_||_|" : "8",
    " _ |_| _|" : "9"
}

# takes a multiline string as input
# outputs the int representation of that
def stringToAccountNumber(account: str) -> int:
    account = account.strip("\n")
    parts = account.split("\n") # split the actual string into three strings based on new-line character
    numberStr = "" # stores the string representation of the number

    for i in range(0,len(parts[0]),3):
        numberStr += digitStringDict[parts[0][i:i+3]+parts[1][i:i+3]+parts[2][i:i+3]]

    return int(numberStr)

Output,

test1 = '''
 _  _  _  _  _  _  _  _  _ 
|_| _| _||_||_ |_ |_||_||_|
|_||_  _||_| _||_| _||_| _|

'''

test2 = '''
    _  _     _  _  _  _  _ 
  | _| _||_||_ |_   ||_||_|
  ||_  _|  | _||_|  ||_| _|

'''

test3 = '''
 _  _  _  _  _  _  _  _  _ 
| | _| _|| ||_ |_   ||_||_|
|_||_  _||_| _||_|  ||_| _|

'''

print(stringToAccountNumber(test1)) # output -> 823856989
print(stringToAccountNumber(test2)) # output -> 123456789
print(stringToAccountNumber(test3)) # output -> 23056789
 

This is a rather hacky Python solution that relies on a dictionary of strings mapping to digits. I made the assumption that input would come as an array of strings, one for each level of the digits.

Code

digit_dict = {" _ | | _ ": 0, "     |  |": 1,
              " _  _||_ ": 2, " _  _| _|": 3,
              "   |_|  |": 4, " _ |_  _|": 5,
              " _ |_ |_|": 6, " _   |  |": 7,
              " _ |_||_|": 8, " _ |_| _|" : 9}

def parse_bank_code(str_list, digit_dict):
  """
  Function that takes awkward digit string and and digit dict;
  returns a normal string of digits.
  """

  digits = []  # Holder for digits

  # Loop through three places at a time
  for i in range(0, len(str_list[0]), 3):

    # Take three characters from each level as a list
    digit = [x[i:i+3] for x in str_list]

    # Join the characters together and get the dict value
    digit = digit_dict["".join(digit)]

    # Add the digit as a string to the list
    digits.append(str(digit))

  # Return the final code
  return "".join(digits)