Creating a human-readable list from the values of a map

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
2
down vote

favorite












I'm creating a human-readable list from the keys of a map object. I want it to be comma-separated, but include the word "and" at the end before the last element, supplying a given locale for the word "and".



E.g., my map might have the strings:



"AK", "Alaska"
"CA", "California"
"NY", "New York"


I would want as output:



Alaska, California, and New York


for English and



Alaska, California, y New York


for Spanish.



So I have:



public static String getHumanReadableStates(Map<String, String> stateMap) 

long count = stateMap.values().stream().map(Object::toString).count();
return stateMap.values().stream().map(Object::toString)
.limit(count - 1)
.collect(Collectors.joining(", "))
.concat(", ")
.concat(LocaleUtil.getBundleValue("theWordAnd"))
.concat(" ")
.concat(Objects.requireNonNull(stateMap.values().stream().reduce((first, second) -> second)
.orElse(null)));



where LocaleUtil has a method called getBundleValue() to simply get the word "and" from messages.properties.



Is there a simpler way to achieve this? Or should streams be avoided altogether?







share|improve this question





















  • Probably belongs on SO or se.SE though?
    – Will Crawford
    Jan 6 at 6:21










  • Yeah, I actually posted to SO, but deleted and thought it may actually fit better here.
    – bphilipnyc
    Jan 6 at 19:32
















up vote
2
down vote

favorite












I'm creating a human-readable list from the keys of a map object. I want it to be comma-separated, but include the word "and" at the end before the last element, supplying a given locale for the word "and".



E.g., my map might have the strings:



"AK", "Alaska"
"CA", "California"
"NY", "New York"


I would want as output:



Alaska, California, and New York


for English and



Alaska, California, y New York


for Spanish.



So I have:



public static String getHumanReadableStates(Map<String, String> stateMap) 

long count = stateMap.values().stream().map(Object::toString).count();
return stateMap.values().stream().map(Object::toString)
.limit(count - 1)
.collect(Collectors.joining(", "))
.concat(", ")
.concat(LocaleUtil.getBundleValue("theWordAnd"))
.concat(" ")
.concat(Objects.requireNonNull(stateMap.values().stream().reduce((first, second) -> second)
.orElse(null)));



where LocaleUtil has a method called getBundleValue() to simply get the word "and" from messages.properties.



Is there a simpler way to achieve this? Or should streams be avoided altogether?







share|improve this question





















  • Probably belongs on SO or se.SE though?
    – Will Crawford
    Jan 6 at 6:21










  • Yeah, I actually posted to SO, but deleted and thought it may actually fit better here.
    – bphilipnyc
    Jan 6 at 19:32












up vote
2
down vote

favorite









up vote
2
down vote

favorite











I'm creating a human-readable list from the keys of a map object. I want it to be comma-separated, but include the word "and" at the end before the last element, supplying a given locale for the word "and".



E.g., my map might have the strings:



"AK", "Alaska"
"CA", "California"
"NY", "New York"


I would want as output:



Alaska, California, and New York


for English and



Alaska, California, y New York


for Spanish.



So I have:



public static String getHumanReadableStates(Map<String, String> stateMap) 

long count = stateMap.values().stream().map(Object::toString).count();
return stateMap.values().stream().map(Object::toString)
.limit(count - 1)
.collect(Collectors.joining(", "))
.concat(", ")
.concat(LocaleUtil.getBundleValue("theWordAnd"))
.concat(" ")
.concat(Objects.requireNonNull(stateMap.values().stream().reduce((first, second) -> second)
.orElse(null)));



where LocaleUtil has a method called getBundleValue() to simply get the word "and" from messages.properties.



Is there a simpler way to achieve this? Or should streams be avoided altogether?







share|improve this question













I'm creating a human-readable list from the keys of a map object. I want it to be comma-separated, but include the word "and" at the end before the last element, supplying a given locale for the word "and".



E.g., my map might have the strings:



"AK", "Alaska"
"CA", "California"
"NY", "New York"


I would want as output:



Alaska, California, and New York


for English and



Alaska, California, y New York


for Spanish.



So I have:



public static String getHumanReadableStates(Map<String, String> stateMap) 

long count = stateMap.values().stream().map(Object::toString).count();
return stateMap.values().stream().map(Object::toString)
.limit(count - 1)
.collect(Collectors.joining(", "))
.concat(", ")
.concat(LocaleUtil.getBundleValue("theWordAnd"))
.concat(" ")
.concat(Objects.requireNonNull(stateMap.values().stream().reduce((first, second) -> second)
.orElse(null)));



where LocaleUtil has a method called getBundleValue() to simply get the word "and" from messages.properties.



Is there a simpler way to achieve this? Or should streams be avoided altogether?









share|improve this question












share|improve this question




share|improve this question








edited Jan 6 at 3:39
























asked Jan 6 at 3:33









bphilipnyc

137119




137119











  • Probably belongs on SO or se.SE though?
    – Will Crawford
    Jan 6 at 6:21










  • Yeah, I actually posted to SO, but deleted and thought it may actually fit better here.
    – bphilipnyc
    Jan 6 at 19:32
















  • Probably belongs on SO or se.SE though?
    – Will Crawford
    Jan 6 at 6:21










  • Yeah, I actually posted to SO, but deleted and thought it may actually fit better here.
    – bphilipnyc
    Jan 6 at 19:32















Probably belongs on SO or se.SE though?
– Will Crawford
Jan 6 at 6:21




Probably belongs on SO or se.SE though?
– Will Crawford
Jan 6 at 6:21












Yeah, I actually posted to SO, but deleted and thought it may actually fit better here.
– bphilipnyc
Jan 6 at 19:32




Yeah, I actually posted to SO, but deleted and thought it may actually fit better here.
– bphilipnyc
Jan 6 at 19:32










2 Answers
2






active

oldest

votes

















up vote
2
down vote



accepted










A stream-based approach is still feasible, by materializing your state names into a List first.



By using a List, you can retrieve all the names except the last using List.subList(int, int) (this may be empty, for a single-entry Map), and then retrieving the last (or the only, for a single-entry Map) directly using a simple List.get(int). Afterwards, you just need to handle for an empty Map or String values.



public static String getHumanReadableStates(Map<String, String> stateMap) 
List<String> names = new ArrayList<>(stateMap.values());
if (names.isEmpty())
return "";

String exceptLastPlusBlank = Stream.concat(
names.subList(0, names.size() - 1).stream(), Stream.of(""))
.collect(Collectors.joining(", "));
return Stream.of(exceptLastPlusBlank, names.get(names.size() - 1))
.filter(v -> !v.isEmpty())
.collect(Collectors.joining(LocaleUtil.getBundleValue("theWordAnd") + " "));



Stream.of("") is introduced to append the final comma.






share|improve this answer




























    up vote
    1
    down vote













    To get the different behaviour for the final element, you will need to be able to tell how many items are left in the list before you hit the end, so that seems to rule out streams, since you only have access to one element at a time, and the "built in" joining collector doesn't appear to allow any special treatment for the final element.



    So really, you're reduced to StringBuilder and the logic (pseudocode):



    • if (have any elements at all)

      • add first element to string

      • while (have more than one element remaining)

      • add delimiter (", " I guess), then the next element from the list

      • add " and " (or translation), then the final element from the list


    Or, for slightly more readable code [citation needed], still using streams a bit, take the list of values, and:



    • if (have any elements at all)

      • remove final element and keep safe somewhere

      • if (any elements remaining)

      • apply .stream().collect(Collectors.joining(", ")) to list

      • concatenate with " and " (or translation) and the saved final element, then return it

      • else, just return the saved final element






    share|improve this answer





















      Your Answer




      StackExchange.ifUsing("editor", function ()
      return StackExchange.using("mathjaxEditing", function ()
      StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
      StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
      );
      );
      , "mathjax-editing");

      StackExchange.ifUsing("editor", function ()
      StackExchange.using("externalEditor", function ()
      StackExchange.using("snippets", function ()
      StackExchange.snippets.init();
      );
      );
      , "code-snippets");

      StackExchange.ready(function()
      var channelOptions =
      tags: "".split(" "),
      id: "196"
      ;
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function()
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled)
      StackExchange.using("snippets", function()
      createEditor();
      );

      else
      createEditor();

      );

      function createEditor()
      StackExchange.prepareEditor(
      heartbeatType: 'answer',
      convertImagesToLinks: false,
      noModals: false,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: null,
      bindNavPrevention: true,
      postfix: "",
      onDemand: true,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      );



      );








       

      draft saved


      draft discarded


















      StackExchange.ready(
      function ()
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f184416%2fcreating-a-human-readable-list-from-the-values-of-a-map%23new-answer', 'question_page');

      );

      Post as a guest






























      2 Answers
      2






      active

      oldest

      votes








      2 Answers
      2






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes








      up vote
      2
      down vote



      accepted










      A stream-based approach is still feasible, by materializing your state names into a List first.



      By using a List, you can retrieve all the names except the last using List.subList(int, int) (this may be empty, for a single-entry Map), and then retrieving the last (or the only, for a single-entry Map) directly using a simple List.get(int). Afterwards, you just need to handle for an empty Map or String values.



      public static String getHumanReadableStates(Map<String, String> stateMap) 
      List<String> names = new ArrayList<>(stateMap.values());
      if (names.isEmpty())
      return "";

      String exceptLastPlusBlank = Stream.concat(
      names.subList(0, names.size() - 1).stream(), Stream.of(""))
      .collect(Collectors.joining(", "));
      return Stream.of(exceptLastPlusBlank, names.get(names.size() - 1))
      .filter(v -> !v.isEmpty())
      .collect(Collectors.joining(LocaleUtil.getBundleValue("theWordAnd") + " "));



      Stream.of("") is introduced to append the final comma.






      share|improve this answer

























        up vote
        2
        down vote



        accepted










        A stream-based approach is still feasible, by materializing your state names into a List first.



        By using a List, you can retrieve all the names except the last using List.subList(int, int) (this may be empty, for a single-entry Map), and then retrieving the last (or the only, for a single-entry Map) directly using a simple List.get(int). Afterwards, you just need to handle for an empty Map or String values.



        public static String getHumanReadableStates(Map<String, String> stateMap) 
        List<String> names = new ArrayList<>(stateMap.values());
        if (names.isEmpty())
        return "";

        String exceptLastPlusBlank = Stream.concat(
        names.subList(0, names.size() - 1).stream(), Stream.of(""))
        .collect(Collectors.joining(", "));
        return Stream.of(exceptLastPlusBlank, names.get(names.size() - 1))
        .filter(v -> !v.isEmpty())
        .collect(Collectors.joining(LocaleUtil.getBundleValue("theWordAnd") + " "));



        Stream.of("") is introduced to append the final comma.






        share|improve this answer























          up vote
          2
          down vote



          accepted







          up vote
          2
          down vote



          accepted






          A stream-based approach is still feasible, by materializing your state names into a List first.



          By using a List, you can retrieve all the names except the last using List.subList(int, int) (this may be empty, for a single-entry Map), and then retrieving the last (or the only, for a single-entry Map) directly using a simple List.get(int). Afterwards, you just need to handle for an empty Map or String values.



          public static String getHumanReadableStates(Map<String, String> stateMap) 
          List<String> names = new ArrayList<>(stateMap.values());
          if (names.isEmpty())
          return "";

          String exceptLastPlusBlank = Stream.concat(
          names.subList(0, names.size() - 1).stream(), Stream.of(""))
          .collect(Collectors.joining(", "));
          return Stream.of(exceptLastPlusBlank, names.get(names.size() - 1))
          .filter(v -> !v.isEmpty())
          .collect(Collectors.joining(LocaleUtil.getBundleValue("theWordAnd") + " "));



          Stream.of("") is introduced to append the final comma.






          share|improve this answer













          A stream-based approach is still feasible, by materializing your state names into a List first.



          By using a List, you can retrieve all the names except the last using List.subList(int, int) (this may be empty, for a single-entry Map), and then retrieving the last (or the only, for a single-entry Map) directly using a simple List.get(int). Afterwards, you just need to handle for an empty Map or String values.



          public static String getHumanReadableStates(Map<String, String> stateMap) 
          List<String> names = new ArrayList<>(stateMap.values());
          if (names.isEmpty())
          return "";

          String exceptLastPlusBlank = Stream.concat(
          names.subList(0, names.size() - 1).stream(), Stream.of(""))
          .collect(Collectors.joining(", "));
          return Stream.of(exceptLastPlusBlank, names.get(names.size() - 1))
          .filter(v -> !v.isEmpty())
          .collect(Collectors.joining(LocaleUtil.getBundleValue("theWordAnd") + " "));



          Stream.of("") is introduced to append the final comma.







          share|improve this answer













          share|improve this answer



          share|improve this answer











          answered Jan 6 at 13:42









          h.j.k.

          18.1k32490




          18.1k32490






















              up vote
              1
              down vote













              To get the different behaviour for the final element, you will need to be able to tell how many items are left in the list before you hit the end, so that seems to rule out streams, since you only have access to one element at a time, and the "built in" joining collector doesn't appear to allow any special treatment for the final element.



              So really, you're reduced to StringBuilder and the logic (pseudocode):



              • if (have any elements at all)

                • add first element to string

                • while (have more than one element remaining)

                • add delimiter (", " I guess), then the next element from the list

                • add " and " (or translation), then the final element from the list


              Or, for slightly more readable code [citation needed], still using streams a bit, take the list of values, and:



              • if (have any elements at all)

                • remove final element and keep safe somewhere

                • if (any elements remaining)

                • apply .stream().collect(Collectors.joining(", ")) to list

                • concatenate with " and " (or translation) and the saved final element, then return it

                • else, just return the saved final element






              share|improve this answer

























                up vote
                1
                down vote













                To get the different behaviour for the final element, you will need to be able to tell how many items are left in the list before you hit the end, so that seems to rule out streams, since you only have access to one element at a time, and the "built in" joining collector doesn't appear to allow any special treatment for the final element.



                So really, you're reduced to StringBuilder and the logic (pseudocode):



                • if (have any elements at all)

                  • add first element to string

                  • while (have more than one element remaining)

                  • add delimiter (", " I guess), then the next element from the list

                  • add " and " (or translation), then the final element from the list


                Or, for slightly more readable code [citation needed], still using streams a bit, take the list of values, and:



                • if (have any elements at all)

                  • remove final element and keep safe somewhere

                  • if (any elements remaining)

                  • apply .stream().collect(Collectors.joining(", ")) to list

                  • concatenate with " and " (or translation) and the saved final element, then return it

                  • else, just return the saved final element






                share|improve this answer























                  up vote
                  1
                  down vote










                  up vote
                  1
                  down vote









                  To get the different behaviour for the final element, you will need to be able to tell how many items are left in the list before you hit the end, so that seems to rule out streams, since you only have access to one element at a time, and the "built in" joining collector doesn't appear to allow any special treatment for the final element.



                  So really, you're reduced to StringBuilder and the logic (pseudocode):



                  • if (have any elements at all)

                    • add first element to string

                    • while (have more than one element remaining)

                    • add delimiter (", " I guess), then the next element from the list

                    • add " and " (or translation), then the final element from the list


                  Or, for slightly more readable code [citation needed], still using streams a bit, take the list of values, and:



                  • if (have any elements at all)

                    • remove final element and keep safe somewhere

                    • if (any elements remaining)

                    • apply .stream().collect(Collectors.joining(", ")) to list

                    • concatenate with " and " (or translation) and the saved final element, then return it

                    • else, just return the saved final element






                  share|improve this answer













                  To get the different behaviour for the final element, you will need to be able to tell how many items are left in the list before you hit the end, so that seems to rule out streams, since you only have access to one element at a time, and the "built in" joining collector doesn't appear to allow any special treatment for the final element.



                  So really, you're reduced to StringBuilder and the logic (pseudocode):



                  • if (have any elements at all)

                    • add first element to string

                    • while (have more than one element remaining)

                    • add delimiter (", " I guess), then the next element from the list

                    • add " and " (or translation), then the final element from the list


                  Or, for slightly more readable code [citation needed], still using streams a bit, take the list of values, and:



                  • if (have any elements at all)

                    • remove final element and keep safe somewhere

                    • if (any elements remaining)

                    • apply .stream().collect(Collectors.joining(", ")) to list

                    • concatenate with " and " (or translation) and the saved final element, then return it

                    • else, just return the saved final element







                  share|improve this answer













                  share|improve this answer



                  share|improve this answer











                  answered Jan 6 at 6:20









                  Will Crawford

                  1112




                  1112






















                       

                      draft saved


                      draft discarded


























                       


                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function ()
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f184416%2fcreating-a-human-readable-list-from-the-values-of-a-map%23new-answer', 'question_page');

                      );

                      Post as a guest













































































                      Popular posts from this blog

                      Greedy Best First Search implementation in Rust

                      Function to Return a JSON Like Objects Using VBA Collections and Arrays

                      C++11 CLH Lock Implementation