0

I have two strings, s1 and s2

I want a list of characters (from both strings), sorted:

  • first by how many times the char appears in one of the strings (the greater value)

e.g. "hello", and "goodbye" would be "lloogbehyd"

  • second by whether the max chars appear in s1 or s2

e.g. "hello", and "goodbye" would be "lloohegdby"

  • third alphabetically

e.g. "hello", and "goodbye" would be "lloohebdgy"

Currently I have this code:

letters = (s1+s2).chars.uniq.sort_by{ |s| [s1.count(s), s2.count(s)].max }.reverse

This sorts by the first condition, how do I add the other levels?

3
  • Hint: Arrays are sorted by their first element, and in the case of a tie, their second, and so on. Commented Nov 8, 2016 at 0:25
  • You need to be more precise in describing "second". There are three possibilities: greater number in s1, equal number in s1 and s2 (In my answer and greater number in s2. How are these to be ordered in the sort? (In my answer I assumed the order in which I listed the three possibilities applies.) Please clarify by editing as not everyone may notice an explanatory comment. Commented Nov 8, 2016 at 5:20
  • @jtbandes, I don't think so. Yes, the question is partly about multilevel sorting but it's also about the construction of the particular arrays that are to be sorted. Commented Nov 8, 2016 at 5:26

2 Answers 2

0

What I eventually did:

(s1+s2).chars.sort_by{ |s| 
  [-([s1.count(s), s2.count(s)].max), 
    s1.count(s) > s2.count(s) ? -1 : 1, 
    s]
}
#=> "lloohebdgy"

The generalised answer is that giving an Array to the sort_by block allows you to specify levels of sorting.

Sign up to request clarification or add additional context in comments.

1 Comment

You forgot .join after the closing brace. That returns "llooohbdeegy".
0

Using String#count is relatively inefficient, as the entire string is traversed for each unique letter. It would be more efficient, at the expense of more lines of code, to create a counting hash, which can be done as follows (see Hash::new).

s1 = "hello"
s2 = "goodbye"

h1 = s1.each_char.with_object(Hash.new(0)) { |c,h| h[c] += 1 }
  #=> {"h"=>1, "e"=>1, "l"=>2, "o"=>1} 
h2 = s2.each_char.with_object(Hash.new(0)) { |c,h| h[c] += 1 }
  #=> {"g"=>1, "o"=>2, "d"=>1, "b"=>1, "y"=>1, "e"=>1}
hmx = (h1.keys | h2.keys).each_with_object({}) do |c,h| h[c] =
  case h1[c] <=> h2[c]
  when 1 then [-h1[c], -1]
  when 0 then [-h1[c], 0]
  else [-h2[c], 1]
  end
end
  #=> {"h"=>[-1, -1], "e"=>[-1, 0], "l"=>[-2, -1], "o"=>[-2, 1],
  #    "g"=>[-1, 1], "d"=>[-1, 1], "b"=>[-1, 1], "y"=>[-1, 1]} 

We can now sort using Enumerable#sort_by

(s1+s2).each_char.sort_by { |c| [*hmx[c], c] }.join
  #=> "lloooheebdgy"

See Array#<=> (third paragraph) for an explanation of how arrays are sorted.

I've made the assumption that if two characters tie for the most occurrences in one of the two strings, each character is assigned a score of -1, 0 or 1. The character with the lowest score (if they don't have the same score) precedes the other character in the sort. A character c is assigned a score of -1 if the string s1 contains more c's than does string s1; a score of 0 if both strings contain the same number of c's; and a score of 1 if s2 contains more c's than does s1.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.