rogrp6
rogrp6

Reputation: 57

Golang regexp MatchString() isn't idempotent

I not sure what's happening. The same function with same input return different results when using regexp library of golang.

package main

import (
    "fmt"
    "regexp"
)

type PaymentNetworkData struct {
    Regex string
    Name  string
}

var PAYMENT_NETWORKS = map[string]PaymentNetworkData{
    "Mastercard": {
        Regex: "^5[1-5][0-9]{14}|^(222[1-9]|22[3-9]\\d|2[3-6]\\d{2}|27[0-1]\\d|2720)[0-9]{12}$",
        Name:  "Mastercard",
    },
    "VisaMaster": {
        Regex: "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14})$",
        Name:  "VisaMaster",
    },
}

func resolvePaymentNetwork(cardIn string) string {
    payNet := "Unknown"
    for _, v := range PAYMENT_NETWORKS {
        regex := regexp.MustCompile(v.Regex)

        if regex.MatchString(cardIn) {
            payNet = v.Name
        }
    }
    return payNet
}

func main() {

    in := "5103901404433835"

    for i := 1; i < 100; i++ {
        payNet := resolvePaymentNetwork(in)
        fmt.Println("Payment Network is: ", payNet)
    }
}

Input: 5103901404433835

Regex:

Mastercard: ^5[1-5][0-9]{14}|^(222[1-9]|22[3-9]\\d|2[3-6]\\d{2}|27[0-1]\\d|2720)[0-9]{12}$
VisaMaster: ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14})$

Golang Output:

Payment Network is:  VisaMaster
Payment Network is:  Mastercard
Payment Network is:  Mastercard
Payment Network is:  VisaMaster
Payment Network is:  VisaMaster
Payment Network is:  VisaMaster
Payment Network is:  VisaMaster
Payment Network is:  VisaMaster
Payment Network is:  VisaMaster

I tested the same code with NodeJS and in this case the result was always the same.

JS Output:

Payment Network is:  VisaMaster
Payment Network is:  VisaMaster
Payment Network is:  VisaMaster
Payment Network is:  VisaMaster
Payment Network is:  VisaMaster
Payment Network is:  VisaMaster
Payment Network is:  VisaMaster
Payment Network is:  VisaMaster
Payment Network is:  VisaMaster
Payment Network is:  VisaMaster

Upvotes: -1

Views: 123

Answers (1)

markalex
markalex

Reputation: 13432

You code has a couple problems:

  1. use map for no apparent reason,
  2. both regexes match supplied card number.

These problems, and a fact that iteration through map is not guaranteed to produce same sequence, results in non-idempotent function.

Here is corrected code:

package main

import (
    "fmt"
    "regexp"
)

type PaymentNetworkData struct {
    Regex *regexp.Regexp
    Name  string
}

var PAYMENT_NETWORKS = [2]PaymentNetworkData{
    {
        Regex: regexp.MustCompile("^(?:5[1-5][0-9]{14}|(?:222[1-9]|22[3-9]\\d|2[3-6]\\d{2}|27[0-1]\\d|2720)[0-9]{12})$"),
        Name:  "Mastercard",
    },
    {
        Regex: regexp.MustCompile("^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14})$"),
        Name:  "VisaMaster",
    },
}

func resolvePaymentNetwork(cardIn string) string {
    for _, v := range PAYMENT_NETWORKS {
        if v.Regex.MatchString(cardIn) {
            return v.Name
        }
    }
    return "Unknown"
}

func main() {
    in := "5103901404433835"

    for i := 1; i < 100; i++ {
        payNet := resolvePaymentNetwork(in)
        fmt.Println("Payment Network is: ", payNet)
    }
}

It uses array instead of map to guarantee sequence.

Also, I've changed you structure to compile regexes only once.

It outputs Payment Network is: Mastercard every time.

Demo here.

Notice, it still uses same regexes (with correction recommended by @WiktorStribiżew in comments). They don't look very good, especially this part (?:4[0-9]{12}(?:[0-9]{3})? - it will match 13 digits too.
You'll better check expected formats for card numbers, and correct expressions accordingly.

Upvotes: 6

Related Questions