Custom Sort Groovy JSONArray - Custom Values First, Then Alphabetical - arrays

I have a static list of values that is in a JSONArray. Here is my example array:
JSONArray json = new JSONArray()
json = ["B", "E", "C", "Z", "A", "X", "F", "H"]
I need to sort this json array in a custom way. I need to put "E" first, "F" second, and then sort the rest by alphabetical order.
I want my end result to be this:
json = ["E", "F", "A", "B", "C", "H", X", "Z"]
Groovy has the basic sort functionality that I can sort alphabetically or reverse alphabetically using:
json.sort()
or
json.reverse()
I'm looking for an easy way to do a custom sort.

in my 5-min experiment I used weights:
def json = ["B", "E", "C", "Z", "A", "X", "F", "H"]
def weights = [ E:10, F:9 ]
json.sort{
it.charAt( 0 ) - ( weights[ it ] ?: 0 )
}
assert '[E, F, A, B, C, H, X, Z]' == json.toString()
you might want to include some error checking

You can use closures if you define your own sort method, but what you're actually asking for is some array splitting with a little normal sorting.
json.findAll{it = 'E'} + json.findAll{it = 'F'} + json.findAll{!(it in ['E', 'F'])}.sort()
If you're worried about the efficiency of looping through your json 3 times you can iterate through your json once, adding it to different arrays as you go.
The below example is a little fancier. The inject method will iterate over a collection, passing a value between each iteration (in our case a list of 3 lists. The first list will hold our E's, the second our F's, and the 3rd for everything else. After sorting our catchall list we use .flatten() to transform the 3 lists back into one list.
List organizedList = json.inject([[],[],[]]) {List<List> result, String jsonValue ->
select(jsonValue) {
case 'E':
result.get(0).add(jsonValue) // Could have just added 'E' but I like symmetry
break;
case 'F':
result.get(1).add(jsonValue)
break;
default:
result.get(2).add(jsonValue)
}
return result // Gets passed to each iteration over json
}
organizedList.get(2).sort() // sort on a list modifies the original list
organizedList.flatten()
It's also possible using sort with a closure where you define your own sorting; but as you can see, it doesn't flow quite as easily.
json.sort {String a, String b ->
if (a = b) return 0 // For efficiency's sake
def letterFirst = {String priority -> // Closure to help sort against a hardcoded value
if (a = priority) return 1
if (b = priority) return -1
return 0
}
def toReturn = letterFirst('E')
if (!toReturn) toReturn = letterFirst('F') // groovy evaluates 0 as false
if (!toReturn) toReturn = a <=> b
return toReturn
}

Related

Sort a list based on another list in Dart

I need to sort unsortedList based on the sorting of sortedList.
Example:
List<String> sortedList = ["x", "a", "c", "d", "w"];
// [unsortedList] items need to be also in [sortedList] and in the
// same range (eg. between 0 and 2 position)
List<String> unsortedList = ["a", "x", "c"];
You want the elements of sortedList which also appear in unsortedList, in their original order.
That's not about sorting.
No sorting is needed:
var result = sortedList.where({...unsortedList}.contains).toList();
(You can use unsortedList.contains directly, instead of creating a set, and it's probably even faster for small unsortedLists.)
Or, if your requirement is correct, and the n elements of unsortedList are the first n elements of sortedList in a different order, then maybe just var result = sortedList.sublist(0, unsortedList.length);.
If you actually care about getting the element from unsortedList, not the equal element from sortedList (which may matter if the elements are equal, but not identical), you can do:
var result = sortedList.map({...unsortedList}.lookup).whereNotNull().toList();
where whereNotNull is from package:collection.
Run the following code on DartPad
void main() async {
List<String> sortedList = ["x", "a", "c", "d", "w"];
// [unsortedList] items need to be also in [sortedList] and in the
// same range (eg. between 0 and 2 position)
List<String> unsortedList = ["a", "x", "c"];
final outcome = List<String>.generate(unsortedList.length,
(index) => unsortedList.firstWhere((item) => item == sortedList[index]));
print(outcome);
}

How do I find the index of any element from an array within another array?

I have an array:
["a", "b", "c", "d"]
How do I figure out the index of the first element of the above array to occur within a second array:
["next", "last", "d", "hello", "a"]
The index of the first element from the first array to occur within the above array would be 2; "d" belongs to the first array and occurs at position 2.
There's a couple of ways to do this, but the naive approach might work well enough to get going:
tests = ["a", "b", "c", "d"]
in_array = ["next", "last", "d", "hello", "a"]
in_array.each_with_index.find do |e, i|
tests.include?(e)
end
# => ["d", 2]
You can speed this up by making tests a Set which avoids a lot of O(N) lookups:
tests = Set.new([ ... ])
The same code will work with include? but that's now much faster on longer lists.
This approach, wrapped in a method, returns an array containing all indexes of common elements between two arrays.
def find_positions(original_array, look_up_array)
positions_array = []
original_array.each do |x|
if look_up_array.index(x) != nil
positions_array << look_up_array.index(x)
end
end
positions_array
# positions_array.first => for the first matched element
end
If you want only the first matched element you could return positions_array.first but this way you'll not avoid the extra lookups.
PS: you could also use #collect and avoid the extra array (positions_array)
You can iterate through the array you want to be compared and use .select or .find iterator method. .find will select the first element match in the arrays while .select will match all elements in the arrays. If you want to add the index in the selection you can add .each_with_index. '.index(a)' returns the element if present else it will return nil.
alphabet = %w(a b c d)
%w(next last d hello a).each_with_index.find {|a, _index| alphabet.index(a) }
=> ["d", 2]
%w(next last d hello a).each_with_index.select {|a, _index| alphabet.index(a) }[0]
=> ["d", 2]
# if you just need the index of the first match
%w(next last d hello a).index {|a| alphabet.index(a) }
=> 2

Filter array based on next object

I'm writing some code to filter out driving trips from the motion sensor. I figured out the best way to do this is to add a subarray to a nested array based on the following:
Detect the first occurrence of a confident automotive event
Add all the following motion events to the same array of the events until the first confident observation that says otherwise.
For example
automotive confidence 2 //Add
automotive confidence 2 //Add
automotive confidence 2 //Add
walking confidence 2 //Add the sub-array to the master array and start over on the next confident automotive event.
Currently i'm doing it this way:
//Remove all uncertain values.
let confidentActivities = activities!.filter{$0.confidence.rawValue == 2}
var needsNew = true
var automotiveActivities:Array<Array<CMMotionActivity>> = Array() //Master array to contain subarrays of automotiveactivity arrays
var automotiveActivitySession:Array<CMMotionActivity> = Array()
for activity in confidentActivities {
if activity.automotive && (!activity.cycling && !activity.running && !activity.walking){
if needsNew {
needsNew = false
}
automotiveActivitySession.append(activity)
} else {
if !needsNew {
//If user is no longer in car, store a cpoy of the session and reset the array
automotiveActivities.append(Array(automotiveActivitySession))
automotiveActivitySession = []
needsNew = true
}
}
}
This solution is not very elegant.
Is there any way to use Swift's Array.filter{} to make this sorting prettier?
Filter is not going to do it, but you can use reduce.
The example below shows how to collect runs of consecutive "A"s (denoting an automotive event) into arrays inside an array of arrays:
let data = ["A","A","A","B","A","A","B","A","A","A","A","B","B","B","A","B","A","A","A","A","A","A","B","A"]
var res = [[String]]()
_ = data.reduce("") { (last: String, current: String) in
if current == "A" {
if last != "A" {
res.append([String]())
}
res[res.count-1].append(current)
}
return current
}
print(res)
The prior value is passed to reduce's function as the first parameter. This makes it possible for the function to decide whether to append to the current list or to start a new list.
The result of this run is as follows:
[ ["A", "A", "A"]
, ["A", "A"]
, ["A", "A", "A", "A"]
, ["A"]
, ["A", "A", "A", "A", "A", "A"]
, ["A"]]
If you're after a pretty solution, you can use split to do this. You just have to provide it with a condition on what should be considered a separator. In your case, this will be any motion event that isn't an automotive one.
let arr = ["A","A","A","B","A","A","B","A","A","C","A","B","D","B","A","B","A","E","A","A","F","A","B","A","B"]
let split = arr.split {$0 != "A"} // insert your condition for whether the given element should be considered a 'seperator'
$0 here is the anonymous closure argument for an element in the array (as it iterates through). You can always expand the closure in order to make the namings more explicit, although it looks less elegant. For example:
let split = arr.split {element in
return element != "A"
}
This will return an array of ArraySlices like so:
[
ArraySlice(["A", "A", "A"]),
ArraySlice(["A", "A"]),
ArraySlice(["A", "A"]),
ArraySlice(["A"]),
ArraySlice(["A"]),
ArraySlice(["A"]),
ArraySlice(["A", "A"]),
ArraySlice(["A"]),
ArraySlice(["A"])
]
If you want them to be explicit Arrays, you can simply use map afterwards:
let split = arr.split {$0 != "A"}.map{Array($0)}
Returns:
[
["A", "A", "A"],
["A", "A"],
["A", "A"],
["A"], ["A"],
["A"],
["A", "A"],
["A"],
["A"]
]

Ruby on Rails - How to know how many time the same Object appears in Array using Active Record?

How to know how many times the same Object appears in Array?
I want to check how many times I found the object, like:
array = ['A','A','A','B','B','C','C','C','D']
So, A appeared three times, B twice, C three too, and only one for D.
I know that if I use "find_all", like:
array.find_all{ |e| array.count(e) > 1 }
I will get with answer
["A", "A", "A", "B", "B", "C", "C", "C"]
but, how I can count this? I want something like:
A = 3, B = 2, C = 3, D = 1.
You can use inject on the array to iterate over the array and pass a hash into each iteration to store data. So to retrieve the count of the array you gave you would do this:
array = ["A", "A", "A", "B", "B", "C", "C", "C"]
array.inject(Hash.new(0)) do |hash, array_item|
hash[array_item] += 1
hash # this will be passed into the next iteration as the hash parameter
end
=> {"A"=>3, "B"=>2, "C"=>3}
Passing in Hash.new(0) rather than {} will mean that the default value for each key when you first encounter it will be 0.

Most efficient way to count duplicated elements between two arrays

As part of a very basic program I am writing in Ruby, I am trying to find the total number of shared elements between two arrays of equal length, but
I need to include repeats.
My current example code for this situation is as follows:
array_a = ["B","A","A","A","B"]
array_b = ["A","B","A","B","B"]
counter = 0
array_a.each_index do |i|
array_a.sort[i] == array_b.sort[i]
counter += 1
end
end
puts counter
I want the return value of this comparison in this instance to be 4, and not 2, as the two arrays share 2 duplicate characters ("A" twice, and "B" twice). This seems to work, but I am wondering if there are any more efficient solutions for this issue. Specifically whether there are any methods you would suggest looking into. I spoke with someone who suggested a different method, inject, but I really don't understand how that applies and would like to understand. I did quite a bit of reading on uses for it, and it still isn't clear to me how it is appropriate. Thank you.
Looking at my code, I have realized that it doesn't seem to work for the situation that I am describing.
Allow me to reiterate and explain what I think the OP's original intent was:
Given arrays of equal size
array_a = ["B","A","A","A","B"]
array_b = ["A","B","A","B","B"]
We need to show the total number of matching pairs of elements between the two arrays. In other words, each B in array_a will "use up" a B in array_b, and the same will be true for each A. As there are two B's in array_a and three in array_b, this leaves us with a count of 2 for B, and following the same logic, 2 for A, for a sum of 4.
(array_a & array_b).map { |e| [array_a.count(e), array_b.count(e)].min }.reduce(:+)
If we get the intersection of the arrays with &, the result is a list of values that exist in both arrays. We then iterate over each match, and select the minimum number of times the element exists in either array --- this is the most number of times the element that can be "used". All that is left is to total the number of paired elements, with reduce(:+)
Changing array_a to ["B", "A", "A", "B", "B"] results in a total of 5, as there are now enough of B to exhaust the supply of B in array_b.
If I understand the question correctly, you could do the following.
Code
def count_shared(arr1, arr2)
arr1.group_by(&:itself).
merge(arr2.group_by(&:itself)) { |_,ov,nv| [ov.size, nv.size].min }.
values.
reduce(0) { |t,o| (o.is_a? Array) ? t : t + o }
end
Examples
arr1 = ["B","A","A","A","B"]
arr2 = ["A","B","A","B","B"]
count_shared(arr1, arr2)
#=> 4 (2 A's + 2 B's)
arr1 = ["B", "A", "C", "C", "A", "A", "B", "D", "E", "A"]
arr2 = ["C", "D", "F", "F", "A", "B", "A", "B", "B", "G"]
count_shared(arr1, arr2)
#=> 6 (2 A's + 2 B's + 1 C + 1 D + 0 E's + 0 F's + 0 G's)
Explanation
The steps are as follows for a slightly modified version of the first example.
arr1 = ["B","A","A","A","B","C","C"]
arr2 = ["A","B","A","B","B","D"]
First apply Enumerable#group_by to both arr1 and arr2:
h0 = arr1.group_by(&:itself)
#=> {"B"=>["B", "B"], "A"=>["A", "A", "A"], "C"=>["C", "C"]}
h1 = arr2.group_by(&:itself)
#=> {"A"=>["A", "A"], "B"=>["B", "B", "B"], "D"=>["D"]}
Prior to Ruby v.2.2, when Object#itself was introduced, you would have to write:
arr.group_by { |e| e }
Continuing,
h2 = h0.merge(h1) { |_,ov,nv| [ov.size, nv.size].min }
#=> {"B"=>2, "A"=>2, "C"=>["C", "C"], "D"=>["D"]}
I will return shortly to explain the above calculation.
a = h2.values
#=> [2, 2, ["C", "C"], ["D"]]
a.reduce(0) { |t,o| (o.is_a? Array) ? t : t + o }
#=> 4
Here Enumerable#reduce (aka inject) merely sums the values of a that are not arrays. The arrays correspond to elements of arr1 that do not appear in arr2 or vise-versa.
As promised, I will now explain how h2 is computed. I've used the form of Hash#merge that employs a block (here { |k,ov,nv| [ov.size, nv.size].min }) to compute the values of keys that are present in both hashes being merged. For example, when the first key-value pair of h1 ("A"=>["A", "A"]) is being merged into h0, since h0 also has a key "A", the array
["A", ["A", "A", "A"], ["A", "A"]]
is passed to the block and the three block variables are assigned values (using "parallel assignment", which is sometimes called "multiple assignment"):
k, ov, nv = ["A", ["A", "A", "A"], ["A", "A"]]
so we have
k #=> "A"
ov #=> ["A", "A", "A"]
nv #=> ["A", "A"]
k is the key, ov ("old value") is the value of "A" in h0 and nv ("new value") is the value of "A" in h1. The block calculation is
[ov.size, nv.size].min
#=> [3,2].min = 2
so the value of "A" is now 2.
Notice that the key, k, is not used in the block calculation (which is very common when using this form of merge). For that reason I've changed the block variable from k to _ (a legitimate local variable), both to reduce the chance of introducing a bug and to signal to the reader that the key is not used in the block. The other elements of h2 that use this block are computed similarly.
Another way
It would be quite simple if we had available an Array method I've proposed be added to the Ruby core:
array_a = ["B","A","A","A","B"]
array_b = ["A","B","A","B","B"]
array_a.size - (array_a.difference(array_b)).size
#=> 4
or
array_a.size - (array_b.difference(array_a)).size
#=> 4
I've cited other applications in my answer here.
This is a perfect job for Enumerable#zip and Enumerable#count:
array_a.zip(array_b).count do |a, b|
a == b
end
# => 2
The zip method pairs up elements, "zippering" them together, and the count method can take a block as to if the element should be counted.
The inject method is very powerful, but it's also the most low-level. Pretty much every other Enumerable method can be created with inject if you work at it, so it's quite flexible, but usually a more special-purpose method is better suited. It's still a useful tool if applied correctly.
In this case zip and count do a much better job and if you know what these methods do, this code is self explanatory.
Update:
If you need to count all overlapping letters regardless of order you need to do some grouping on them. Ruby on Rails provides the handy group_by method in ActiveSupport, but in pure Ruby you need to make your own.
Here's an approach that counts up all the unique letters, grouping them using chunk:
# Convert each array into a map like { "A" => 2, "B" => 3 }
# with a default count of 0.
counts = [ array_a, array_b ].collect do |a|
Hash.new(0).merge(
Hash[a.sort.chunk { |v| v }.collect { |k, a| [ k, a.length ] }]
)
end
# Iterate over one of the maps key by key and count the minimum
# overlap between the two.
counts[0].keys.inject(0) do |sum, key|
sum + [ counts[0][key], counts[1][key] ].min
end

Resources