1

Working on an internal website where the URL contains the source reference from other systems. This is a business requirement and cannot be changed. i.e. "http://localhost:9000/source.address.com/7808/project/repo" "http://localhost:9000/build.address.com/17808/project/repo"

I need to remove these strings from the "project/repo" string/variables using a trait so this can be used natively from multiple services. I also want to be able to add more sources to this list (which already exists) and not modify the method.

"def normalizePath" is the method accessed by services, 2 non-ideal but reasonable attempts so far. Getting stuck on a on using foldLeft which I woudl like some help with or an simpler way of doing the described. Code Samples below.

1st attempt using an if-else (not ideal as need to add more if/else statements down the line and less readable than pattern match)

trait NormalizePath {
    def normalizePath(path: String): String = {
        if (path.startsWith("build.address.com/17808")) {
            path.substring("build.address.com/17808".length, path.length)
        } else {
            path
        }
    }
}

and 2nd attempt (not ideal as likely more patterns will get added and it generates more bytecode than if/else)

trait NormalizePath {
    val pattern = "build.address.com/17808/"
    val pattern2 = "source.address.com/7808/"
    def normalizePath(path: String) = path match {
        case s if s.startsWith(pattern) => s.substring(pattern.length, s.length)
        case s if s.startsWith(pattern2) => s.substring(pattern2.length, s.length)
        case _ => path
    }
}

Last attempt is to use an address list(already exists elsewhere but defined here as MWE) to remove occurrences from the path string and it doesn't work:

trait NormalizePath {
    val replacements = (
        "build.address.com/17808",
        "source.address.com/7808/")

    private def remove(path: String, string: String) = {
        path-string
    }

    def normalizePath(path: String): String = {
        replacements.foldLeft(path)(remove)
    }
}   

Appreciate any help on this!

crowgers
  • 232
  • 3
  • 19
  • Do you mean something like `path.replace("build.address.com/17808/", "").replace("source.address.com/7808/", "")` – Xavier Guihot Mar 28 '18 at 14:59
  • Then to chain string replacements, this might help: https://stackoverflow.com/q/11349480/9297144 – Xavier Guihot Mar 28 '18 at 15:08
  • yes something like that but instead of hardcoding the "build.address.com/17808/" etc. in the method I'd like to pull it from a list/map(both exist elsewhere in the code) and then method doesn't need to be updated just the list. – crowgers Mar 28 '18 at 15:10

3 Answers3

2

If you are just stripping out those strings:

val replacements = Seq(
  "build.address.com/17808",
  "source.address.com/7808/")


replacements.foldLeft("http://localhost:9000/source.address.com/7808/project/repo"){
  case(path, toReplace) => path.replaceAll(toReplace, "")
}
// http://localhost:9000/project/repo

If you are replacing those string by something else:

val replacementsMap = Seq(
  "build.address.com/17808" -> "one",
  "source.address.com/7808/" -> "two/")


replacementsMap.foldLeft("http://localhost:9000/source.address.com/7808/project/repo"){
  case(path, (toReplace, replacement)) => path.replaceAll(toReplace, replacement)
}
// http://localhost:9000/two/project/repo

The replacements collection can come from elsewhere in the code and will not need to be redeployed.

// method replacing by empty string
def normalizePath(path: String) = {
  replacements.foldLeft(path){
    case(startingPoint, toReplace) => startingPoint.replaceAll(toReplace, "")
  }
}

normalizePath("foobar/build.address.com/17808/project/repo")
// foobar/project/repo

normalizePath("whateverPath")
// whateverPath

normalizePath("build.address.com/17808build.address.com/17808/project/repo")
// /project/repo
user3097405
  • 813
  • 1
  • 8
  • 16
  • Thanks for your reply! the 2nd example I can definitely use elsewhere in the code! However I'm restricted to type list or Map. Will check and see if this will accept that as input. What is the advantage of using case here as opposed to another method like Antot's below? – crowgers Mar 28 '18 at 16:02
  • 1
    You can easily replace `Seq` by `List` or `Map`. The code will still work – user3097405 Mar 28 '18 at 16:07
  • 1
    with `foldLeft` you will iterate over the collection just once, instead of multiple times. `case` is just a pattern match that makes the code more readable. It is possible to do it without, but compromising readability – user3097405 Mar 28 '18 at 16:14
1

A very simple replacement could be made as follows:

val replacements = Seq(
  "build.address.com/17808",
  "source.address.com/7808/")

def normalizePath(path: String): String = {
  replacements.find(path.startsWith(_)) // find the first occurrence
              .map(prefix => path.substring(prefix.length)) // remove the prefix
              .getOrElse(path) // if not found, return the original string
}

Since the expected replacements are very similar, have you tried to generalize them and use regex matching?

Antot
  • 3,904
  • 1
  • 21
  • 27
  • Thanks for your reply! Can you comment on the method you use here versus user3097405 above? the Regex is a good suggestion however the addresses can follow different patterns (older systems etc.) and there's no visibility on what they will look like in future. Plus the list is available for other services so just using what's already available. – crowgers Mar 28 '18 at 16:05
1

There are a million and one ways to extract /project/repo from a String in Scala. Here are a few I came up with:


val list = List("build.address.com/17808", "source.address.com/7808") //etc
def normalizePath(path: String) = {
  path.stripPrefix(list.find(x => path.contains(x)).getOrElse(""))
}

Output:

scala> normalizePath("build.address.com/17808/project/repo")
res0: String = /project/repo

val list = List("build.address.com/17808", "source.address.com/7808") //etc
def normalizePath(path: String) = {
  list.map(x => if (path.contains(x)) {
    path.takeRight(path.length - x.length)
  }).filter(y => y != ()).head
}

Output:

scala> normalizePath("build.address.com/17808/project/repo")
res0: Any = /project/repo

val list = List("build.address.com/17808", "source.address.com/7808") //etc
def normalizePath(path: String) = {
  list.foldLeft(path)((a, b) => a.replace(b, ""))
}

Output:

scala> normalizePath("build.address.com/17808/project/repo")
res0: String = /project/repo

Depends how complicated you want your code to look (or how silly you want to be), really. Note that the second example has return type Any, which might not be ideal for your scenario. Also, these examples aren't meant to be able to just take the String out of the middle of your path... they can be fairly easily modified if you want to do that though. Let me know if you want me to add some examples just stripping things like build.address.com/17808 out of a String - I'd be happy to do so.

James Whiteley
  • 3,363
  • 1
  • 19
  • 46
  • The examples you've provided are great thank you! Four things I want: 1. not have to make changes to the method once implemented (deployment is a pain) 2. use a list that will change regularly (app doesn't need to be redeployed for this), 3. be reasonable easy to read, 4. be reasonably efficient (up to 1k+ users using simultaneously and this is growing). – crowgers Mar 28 '18 at 15:52
  • The `foldLeft` example (or some variation on it) would probably be best for you then. You could always pass the list as a parameter to the method if you don't want it hard-coded, making it even more flexible (something like `def normalizePath(path: String, list: List[String])`). I just used `list`, `a` and `b` for simplicity's sake - these could be renamed to make it more obvious what they do if you'd like. Also, `replace` can be swapped out for `replaceAll` if you think it'd be worth it. Hope this helps! – James Whiteley Mar 29 '18 at 09:13