Breadcrumb Generator, CodeWars Kata

Breadcrumb Generator

As breadcrumb menùs are quite popular today, I won't digress much on explaining them, leaving the wiki link to do all the dirty work in my place.

What might not be so trivial is instead to get a decent breadcrumb from your current url. For this kata, your purpose is to create a function that takes a url, strips the first part (labelling it always HOME) and then builds it making each element but the last a <a> element linking to the relevant path; last has to be a <span> element getting the active class.

All elements need to be turned to uppercase and separated by a separator, given as the second parameter of the function; the last element can terminate in some common extension like .html, .htm, .php or .asp; if the name of the last element is index.something, you treat it as if it wasn't there, sending users automatically to the upper level folder.

A few examples can be more helpful than thousands of words of explanation, so here you have them:

Solution.generate_bc("mysite.com/pictures/holidays.html", " : ").equals( '<a href="/">HOME</a> : <a href="/pictures/">PICTURES</a> : <span class="active">HOLIDAYS</span>' );
Solution.generate_bc("www.codewars.com/users/GiacomoSorbi", " / ").equals( '<a href="/">HOME</a> / <a href="/users/">USERS</a> / <span class="active">GIACOMOSORBI</span>' );
Solution.generate_bc("www.microsoft.com/docs/index.htm", " * ").equals( '<a href="/">HOME</a> * <span class="active">DOCS</span>' );

Seems easy enough?

Well, probably not so much, but we have one last extra rule: if one element (other than the root/home) is longer than 30 characters, you have to shorten it, acronymizing it (i.e.: taking just the initials of every word); url will be always given in the format this-is-an-element-of-the-url and you should ignore words in this array while acronymizing: ["the","of","in","from","by","with","and", "or", "for", "to", "at", "a"]; a url composed of more words separated by - and equal or less than 30 characters long needs to be just uppercased with hyphens replaced by spaces.

Ignore anchors (www.url.com#lameAnchorExample) and parameters (www.url.com?codewars=rocks&pippi=rocksToo) when present.

Examples:

Solution.generate_bc("mysite.com/very-long-url-to-make-a-silly-yet-meaningful-example/example.htm", " > ").equals( '<a href="/">HOME</a> > <a href="/very-long-url-to-make-a-silly-yet-meaningful-example/">VLUMSYME</a> > <span class="active">EXAMPLE</span>' );
Solution.generate_bc("www.very-long-site_name-to-make-a-silly-yet-meaningful-example.com/users/giacomo-sorbi", " + ").equals( '<a href="/">HOME</a> + <a href="/users/">USERS</a> + <span class="active">GIACOMO SORBI</span>' );

You will always be provided valid url to webpages in common formats, so you probably shouldn't bother validating them.

If you like to test yourself with actual work/interview related kata, please also consider this one about building a string filter for Angular.js.

Special thanks to the colleague that, seeing my code and commenting that I worked on that as if it was I was on CodeWars, made me realize that it could be indeed a good idea for a kata :)

 

문제 해석

주어진 URL로부터 BreadCrumb을 생성하는 문제입니다. 사이트 루트는 항상 HOME으로 표시하고요. 마지막 문서는 확장자를 제외해야 합니다. 30자가 넘어가는 메뉴의 경우는 이니셜로 표현하는데요. ["the","of","in","from","by","with","and", "or", "for", "to", "at", "a"]는 이니셜에서 제외합니다. index 파일을 가지고 있는 주소의 경우 index 문서는 BreadCrumb에 포함이 되지 않습니다. URL에 포함된 파라미터들도 제거하고 수행해야 합니다.

 

 

입력값

  • String url: BreadCrumb을 추출할 URL
  • String separator: BreadCrumb의 각 단계별 구분자

출력값

  • String result: 생성된 BreadCrumbs

 

My Solution

먼저 주소를 자르기 위해서 http://의 프로토콜을 제거해주고 /split()했습니다. 그런 다음 0번째는 무조건 HOME이기 때문에 1번째 요소부터 Stream을 생성해서 주소 가공 작업을 해주었습니다. a태그에 들어갈 주소는 StringBuffer locations에 담아 주었고요. 전체 결과는 StringBuffer output에 담았습니다.

String getBcString(String path) 메소드를 하나 정의했습니다. 30자가 넘어가는 주소에 대한 가공 작업을 하기 위한 메소드입니다. 주소가 항상 -를 구분자로 하고 있기 때문에 30자가 넘어가는 경우 -split()해주고, 마찬가지로 Stream을 이용해서 제거 문자열을 필터 해준 후 map으로 첫 글자만 남겨서 collect해주었습니다.

솔루션 코드

테스트 코드

import java.util.Arrays;
import java.util.stream.Collectors;

public class Solution {
    
    public static String generate_bc(String url, String separator) {
        String[] urlStrings = url.replaceFirst("http[s]?://", "").split("/");
        String[] paths = Arrays.stream(urlStrings, 1, urlStrings.length).filter(item -> !item.matches("index[.].*"))
                .map(item -> item.substring(0, item.contains("#") ? item.indexOf("#") : item.length()))
                .map(item -> item.substring(0, item.contains("?") ? item.indexOf("?") : item.length()))
                .map(item -> item.substring(0, item.contains(".") ? item.indexOf(".") : item.length()))
                .filter(item -> !"".equals(item)).toArray(String[]::new);

        StringBuffer locations = new StringBuffer("/");
        StringBuffer output = new StringBuffer();

        if (paths.length > 0) {
            output.append(String.format("<a href=\"%s\">%s</a>", locations.toString(), "HOME"));
        } else {
            output.append(String.format("<span class=\"active\">%s</span>", "HOME"));
        }

        for (int i = 0; i < paths.length - 1; i++) {
            locations.append(paths[i]).append("/");
            output.append(separator)
                    .append(String.format("<a href=\"%s\">%s</a>", locations.toString(), getBcString(paths[i])));
        }

        if (paths.length > 0) {
            output.append(separator)
                    .append(String.format("<span class=\"active\">%s</span>", getBcString(paths[paths.length - 1])));
        }
        System.out.println(output.toString());
        return output.toString();
    }

    public static String getBcString(String path) {
        String bcString = "";
        if (path.length() > 30) {
            bcString = Arrays.stream(path.split("-"))
                    .filter(item -> !item.matches("the|of|in|from|by|with|and|or|for|to|at|a"))
                    .map(item -> item.substring(0, 1).toUpperCase()).collect(Collectors.joining(""));
        } else {
            bcString = path.toUpperCase().replaceAll("-", " ");
        }

        return bcString;
    }
}

 

Best Practice

Best Practice와 저의 코드가 작업 순서 자체는 유사한데요. url.replaceAll("https?://|/index[.].*|([.]\\w+)?([?#].*)?|/$", "") 정규식 활용이 대박입니다. 정규식 하나로 파라미터 문자열과 index문서를 모두 제거할 수 있다는 것이 충격이네요.

import java.util.*;
import java.util.stream.*;

public class Solution {
    
    
    private static Set<String> IGNORE_WORDS = new HashSet<String>(Arrays.asList("the of in from by with and or for to at a".toUpperCase().split(" ")));
    
    
    public static String generate_bc(String url, String separator) {
        
        List<String> bcLst = new ArrayList();
        
        String[] urlArray = url.replaceAll("https?://|/index[.].*|([.]\\w+)?([?#].*)?|/$", "")
                               .split("/");
        
        for (int i = 0 ; i < urlArray.length-1 ; i++) {
            if (i == 0) bcLst.add("<a href=\"/\">HOME</a>");
            else        bcLst.add(String.format("<a href=\"/%1$s/\">%2$s</a>", String.join("/", Arrays.copyOfRange(urlArray, 1, i+1)),
                                                                               formatStr(urlArray[i])));
        }
        
        bcLst.add(String.format("<span class=\"active\">%s</span>", urlArray.length == 1 ? "HOME"
                                                                                         : formatStr(urlArray[urlArray.length-1]) ));
        
        return String.join(separator, bcLst.toArray(new String[0]));
    }
    
    
    public static String formatStr(String item) {
    
        item = item.toUpperCase().replace("-"," ");
        if (item.length() > 30)
            item = Arrays.stream(item.split(" "))
                         .filter( s -> !IGNORE_WORDS.contains(s))
                         .map( s -> s.substring(0,1) )
                         .collect(Collectors.joining(""));
        return item;
    }
}

 

728x90
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기