ruby sorting hashes inside array - arrays

I have an array of hashes that I would like to sort based on the :reference values. But some of the elements within the array do not have a :reference key and therefore cannot be sorted. Is there a way to ignore this field and just sort the hash elements that contain this key?
I've tried the following approach but I'm getting an argument error
ArgumentError: comparison of NilClass with String failed
sort_by at org/jruby/RubyEnumerable.java:503
arr1 = [{:reference=> "F123",
:name=> "test4"
},
{
:reference=> "ZA4",
:name=> "test3"
},
{
:reference=> "A43",
:name=> "test2"
},
{
:name=> "test1"
},
{
:name=> "homework1"
}]
arr1 = arr1.sort_by { |hash| hash[:reference] }
puts arr1
The correct output should look like this :
=> arr1= [
{:reference=>"A43", :name=>"test2"},
{:reference=>"F123", :name=>"test4"},
{:reference=>"ZA4", :name=>"test3"},
{:name=> "test1"},
{:name=> "homework1"}
]

You can only sort on values that can be compared, so if you've got values of different types it's best to convert them to the same type first. A simple work-around is to convert to string:
arr1.sort_by { |hash| hash[:reference].to_s }
You can also assign a default:
arr1.sort_by { |hash| hash[:reference] || '' }
Edit: If you want the nil values sorted last:
arr1.sort_by { |hash| [ hash[:reference] ? 0 : 1, hash[:reference].to_s ] }

If you don't mind temporary variables, you could split the array into two arrays, one containing hashes with :reference and the other those without:
with_ref, without_ref = arr1.partition { |h| h[:reference] }
Then sort the first one:
with_ref.sort_by! { |h| h[:reference] }
And finally concatenate both arrays:
arr1 = with_ref + without_ref

Related

In Ruby, how can I sort an array of hashes

I am new to Ruby, could someone help?
I have some product data Json that I need to sort by the expiry date, however everything thing I have tried with .sort_by so far is erroring.
The Json is in this format
{"wh_Repeating":[
{
"wh": {
"Item_Number": "111166",
"Expiry_Date": "2023-05-05"
}
},
{
"wh": {
"Item_Number": "111167",
"Expiry_Date": "2023-05-01"
}
},
{
"wh": {
"Item_Number": "111168",
"Expiry_Date": "2023-05-09"
}
}]}
in Ruby that shows as
{:wh_Repeating=>[
{:wh=>{:Item_Number=>"111166", :Expiry_Date=>"2023-05-05"}},
{:wh=>{:Item_Number=>"111167", :Expiry_Date=>"2023-05-01"}},
{:wh=>{:Item_Number=>"111168", :Expiry_Date=>"2023-05-09"}}
]}
tried alsorts
latest attempt was
sorted = jsonIn["wh_Repeating"]
sorted.sort_by { |k,v| v[:"Expiry_Date"] }
puts sorted
which gave me
undefined method `sort_by' for nil:NilClass (NoMethodError)
(Exception)
Your hash keys are symbols not strings.
jsonIn["wh_Repeating"] should be jsonIn[:wh_Repeating]
Also, sorted.sort_by { |k,v| v[:"Expiry_Date"] } does not mutate sorted.
sort_by does not mutate the receiver. In other words, the value of sorted remains the same. There is a bang version (sort_by!) that does mutate (a side-effect) but the use of mutating functions is discouraged.
This does what you want to do.
jsonIn[:wh_Repeating].sort_by { |h| h.dig(:wh, :Expiry_Date) }
I would do it like this:
data = {:wh_Repeating=>
[{:wh=>{:Item_Number=>"111166", :Expiry_Date=>"2023-05-05"}},
{:wh=>{:Item_Number=>"111167", :Expiry_Date=>"2023-05-01"}},
{:wh=>{:Item_Number=>"111168", :Expiry_Date=>"2023-05-09"}}]}
data[:wh_Repeating].sort_by! { |hash| hash[:wh][:Expiry_Date] }
data
#=> {:wh_Repeating=>
[{:wh=>{:Item_Number=>"111167", :Expiry_Date=>"2023-05-01"}},
{:wh=>{:Item_Number=>"111166", :Expiry_Date=>"2023-05-05"}},
{:wh=>{:Item_Number=>"111168", :Expiry_Date=>"2023-05-09"}}]}
I have assumed the original hash (h below) is not to be mutated (modified).
h = { :wh_Repeating=>[
{:wh=>{:Item_Number=>"111166", :Expiry_Date=>"2023-05-05"}},
{:wh=>{:Item_Number=>"111167", :Expiry_Date=>"2023-05-01"}},
{:wh=>{:Item_Number=>"111168", :Expiry_Date=>"2023-05-09"}}
]
}
As this hash has a single key (:wh_Repeating), we can simply write the structure of the desired hash as
{ :wh_Repeating=>... }
and then compute the value of :wh_Repeating.
{ :wh_Repeating=>h[:wh_Repeating].sort_by { |g| g[:wh][:Expiry_Date] } }
#=> { :wh_Repeating=>[
# {:wh=>{:Item_Number=>"111167", :Expiry_Date=>"2023-05-01"}},
# {:wh=>{:Item_Number=>"111166", :Expiry_Date=>"2023-05-05"}},
# {:wh=>{:Item_Number=>"111168", :Expiry_Date=>"2023-05-09"}}
# ]
# }
The original hash h is unchanged, which can be easily verified.

How to merge two arrays of hashes

I have two arrays of hashes:
a = [
{
key: 1,
value: "foo"
},
{
key: 2,
value: "baz"
}
]
b = [
{
key: 1,
value: "bar"
},
{
key: 1000,
value: "something"
}
]
I want to merge them into one array of hashes, so essentially a + b except I want any duplicated key in b to overwrite those in a. In this case, both a and b contain a key 1 and I want the final result to have b's key value pair.
Here's the expected result:
expected = [
{
key: 1,
value: "bar"
},
{
key: 2,
value: "baz"
},
{
key: 1000,
value: "something"
}
]
I got it to work but I was wondering if there's a less wordy way of doing this:
hash_result = {}
a.each do |item|
hash_result[item[:key]] = item[:value]
end
b.each do |item|
hash_result[item[:key]] = item[:value]
end
result = []
hash_result.each do |k,v|
result << {:key => k, :value => v}
end
puts result
puts expected == result # prints true
uniq would work if you concatenate the arrays in reverse order:
(b + a).uniq { |h| h[:key] }
#=> [
# {:key=>1, :value=>"bar"},
# {:key=>1000, :value=>"something"},
# {:key=>2, :value=>"baz"}
# ]
It doesn't however preserve the order.
[a, b].map { |arr| arr.group_by { |e| e[:key] } }
.reduce(&:merge)
.flat_map(&:last)
Here we use hash[:key] as a key to build the new hash, then we merge them overriding everything with the last value and return values.
I would rebuild your data a bit, since there are redundant keys in hashes:
thin_b = b.map { |h| [h[:key], h[:value]] }.to_h
#=> {1=>"bar", 1000=>"something"}
thin_a = b.map { |h| [h[:key], h[:value]] }.to_h
#=> {1=>"bar", 1000=>"something"}
Then you can use just Hash#merge:
thin_a.merge(thin_b)
#=> {1=>"bar", 2=>"baz", 1000=>"something"}
But, if you want, you can get exactly result as mentioned in question:
result.map { |k, v| { key: k, value: v } }
#=> [{:key=>1, :value=>"bar"},
# {:key=>2, :value=>"baz"},
# {:key=>1000, :value=>"something"}]
using Enumerable#group_by and Enumerable#map
(b+a).group_by { |e| e[:key] }.values.map {|arr| arr.first}
If you need to merge two arrays of hashes that should be merged also and there is more than two keys, then next snippet should help:
[a, b].flatten
.compact
.group_by { |v| v[:key] }
.values
.map { |e| e.reduce(&:merge) }

How to get keys from anArray Object in reactjs

i have the following Array Object :
[
{
"key1": abc,
"key2":xyz
},
{
"key1": abc,
"key2":xyz
}
]
Now what i want is to print "key1" & "key2". I know we can iterate through values using map, but i also want to iterate through Array keys.
Assuming ArrayObj contains the key:value pairs, we can do the following:
let keys = Object.keys(ArrayObj);
for(index=0;index<keys.length;index++)
{
console.log(keys[index]);
}

How to arrange one dictionary array by ascending order and then reorder the other arrays?

I am trying to sort an dictionary (of type [String:[String]]) so that one key is in ascending order, once the key is sorted I would like to sort the other arrays too.
This is what I mean.
var dictionary = ["timeStamp":[String],"condition":[String]] //Dict to sort
dictionary["timeStamp"] = ["123","345","456","234"]
dictionary["condition"] = ["dry","wet","very wet","dry"]
dictionary["timeStamp"] = dictionary["timeStamp"]!.sort()
print("\(dictionary["timeStamp"]!)") //Returns["123","234","345","456"]
How would I be able to sort dictionary["condition"] to be ["dry","dry","wet","very wet"]?
I would make a simple struct so that your properties are associated and can be sorted together
struct condition {
var timeStamp: Int
var description: String
}
var conditionArray = [condition]()
conditionArray.append(condition(timeStamp: 123, description: "dry"))
conditionArray.append(condition(timeStamp: 345, description: "wet"))
conditionArray.append(condition(timeStamp: 456, description: "very wet"))
conditionArray.append(condition(timeStamp: 234, description: "dry"))
let sortedArray = conditionArray.sort() {return $0.timeStamp < $1.timeStamp}
The preferred way is give it a proper structure like #Russel Austin suggested in his answer. But you can also have some fun with Swift higher-order functions:
var dictionary = [
"timeStamp": ["123","345","456","234"],
"condition": ["dry","wet","very wet","dry"]
]
let sorted = Array(0..<dictionary["timeStamp"]!.count)
.map { (timeStamp: dictionary["timeStamp"]![$0], condition: dictionary["condition"]![$0]) }
.sort { $0.timeStamp < $1.timeStamp }
dictionary["timeStamp"] = sorted.map { $0.timeStamp }
dictionary["condition"] = sorted.map { $0.condition }
print(dictionary)
Array(0..<dictionary["timeStamp"]!.count) generate an array of ints, going 0, 1, 2, 3... up to the length of timeStamp
.map { ... } pulls data from the dictionary into tuples of timestamp and condition
.sort{ ... } sorts the array of tuples by the timestamp

Convert a ruby nested hash in array of hashes

I'm trying to flat a nested hash. My hash has this structure:
{
"errorDescription":"",
"message":"",
"resultCode":"OK",
"resultObj":{
"categoryList":[
{
"asNew":"N",
"categoryId":40000000,
"categoryList":[
{
"asNew":"N",
"categoryId":40000007,
"categoryList":[
],
"categoryName":"CATALOGO",
"categoryType":"TYPE_NODE",
"contentId":40000007,
"contentTitle":"CATALOGO",
"contentType":"CATEGORY_NODE",
"orderId":0,
"urlPictures":""
},
{
"asNew":"N",
"categoryId":40000018,
"categoryList":[
{
"asNew":"N",
"categoryId":40000019,
"categoryList":[
],
"categoryName":"CANALI CALCIO",
"categoryType":"TYPE_VOD",
"contentId":40000019,
"contentTitle":"CALCIO & SPORT",
"contentType":"CATEGORY_LEAF",
"orderId":0,
"urlPictures":""
},
{
"asNew":"N",
"categoryId":40000020,
"categoryList":[
],
"categoryName":"CANALI CINEMA",
"categoryType":"TYPE_VOD",
"contentId":40000020,
"contentTitle":"CINEMA & SERIE",
"contentType":"CATEGORY_LEAF",
"orderId":1,
"urlPictures":""
}
],
"categoryName":"CANALI TV",
"categoryType":"TYPE_NODE",
"contentId":40000018,
"contentTitle":"CANALI TV",
"contentType":"CATEGORY_NODE",
"orderId":1,
"urlPictures":""
}
],
"categoryName":"ROOT",
"categoryType":"TYPE_NODE",
"contentId":40000000,
"contentTitle":"ROOT",
"contentType":"",
"orderId":0,
"urlPictures":""
}
]
}
}
I must convert this hash in an array of objects, where every object has this structure:
{
"asNew":"N",
"categoryId":40000007,
"categoryName":"CATALOGO",
"categoryType":"TYPE_NODE",
"contentId":40000007,
"contentTitle":"CATALOGO",
"contentType":"CATEGORY_NODE",
"orderId":0,
"urlPictures":""
}
Basically every key "categoryList" has an array as value and this array has one or more hash with another key "categoryList". I must move all the objects in every categoryList array to another array without the categoryList 'key'.
edit
I add this method to Hash class
class Hash
def find_all_values_for(key)
result = []
result << self[key]
unless self.values.nil?
self.values.each do |values|
values = [values] unless values.is_a? Array
values.each do |value|
result += value.find_all_values_for(key) if value.is_a? Hash
end
end
end
result.flatten.compact
end
end
And I map the resulting array deleting the CategoryList key
h.map { |c| c.except!('categoryList') }
It works but meybe there is a more efficient way.
This code will return the requested Array of objects.
def flatten_categoryList(categoryList)
categoryList.inject([]) do |result, item|
result + flatten_categoryList(item.delete("categoryList")) << item
end
end
flatten_categoryList(h["resultObj"]["categoryList"])
I was unable to run yours to get a benchmark, so judging the solutions is currently subjective.

Resources