I've been searching for answer to this question for a couple of days i manages to somehow use a trick to just omit this Concatenation part and just use several seperate loops to re-insert different values into the same table...
but my question is
By default, table.sort uses < to compare array elements, so it can
only sort arrays of numbers or arrays of strings. Write a comparison
function that allows table.sort to sort arrays of mixed types. In the
sorted array, all values of a given type should be grouped together.
Within each such group, numbers and strings should be sorted as usual,
and other types should be sorted in some arbitrary but consistent way.
A = { {} , {} , {} , "" , "a", "b" , "c" , 1 , 2 , 3 , -100 , 1.1 , function() end , function() end , false , false , true }
as i said i solved this using different for loops but is there a way to just analyse every element of the table then assign it to a different Table??? Like : "Tables,Funcs,Nums,Strings,..." then after analysing finished just concatenate them together to have the same table just in sorted version.
My Inefficient Answer To This Was :
function Sep(val)
local NewA = {}
for i in pairs(val) do
if type(val[i]) == "string" then
table.insert(NewA,val[i])
end
end
for i in pairs(val) do
if type(val[i]) == "number" then
table.insert(NewA,val[i])
end
end
for i in pairs(val) do
if type(val[i]) == "function" then
table.insert(NewA,tostring(val[i]))
end
end
for i in pairs(val) do
if type(val[i]) == "table" then
table.insert(NewA,tostring(val[i]))
end
end
for i in pairs(val) do
if type(val[i]) == "boolean" then
table.insert(NewA,tostring(val[i]))
end
end
for i in pairs(NewA) do
print(NewA[i])
end
end
As far as I understand you want to sort them first by type and then by value:
You can write your own custom predicate and pass it to sort
-- there would be need of custom < function since tables cannot be compared
-- you can overload operator < as well I suppose.
function my_less (lhs, rhs)
if (type (lhs) ~= "number" or type (lhs) ~= "string") then
return tostring (lhs) < tostring (rhs)
else
return lhs < rhs;
end;
end;
-- the custom predicate I mentioned
function sort_predicate (a,b)
-- if the same type - compare variable, else compare types
return (type (a) == type (b) and my_less (a, b)) or type (a) < type (b);
end
table.sort (A, sort_predicate);
A = { {}, {}, {}, "", "a", "b", "c", "2", "12", 1, 2, 3, -100, 1.1, 12, 11,
function() end, function() end, false, false, true }
table.sort(A, function(a,b)
if type(a) == type(b) and type(a) == 'number' then return a < b end
return type(a)..tostring(a) < type(b)..tostring(b) end)
Will give you this:
{false, false, true, function() end, function() end,
-100, 1, 1.1, 2, 3, 11, 12, "", "12", "2", "a", "b", "c", {}, {}, {}}
[Updated based on #tozka's comments]
Related
I am trying to create a function that outputs the key and value for all entries of that JSON as described below so I can use it to send information similar to this:
key = "id", value = 1
key = "mem/stat1", value = 10
key = "more_stats/extra_stats/stat7", value = 5
Example JSON :
my_json =
{
"id": 1,
"system_name": "System_1",
"mem" : {
"stat1" : 10,
"stat2" : 1056,
"stat3" : 10563,
},
"other_stats" : {
"stat4" : 1,
"stat5" : 2,
"stat6" : 3,
},
"more_stats" : {
"name" : "jdlfjsdlfjs",
"os" : "fjsalfjsl",
"error_count": 3
"extra_stats" : {
"stat7" : 5,
"stat8" : 6,
},
}
}
I found an answer from this question (need help in getting nested ruby hash hierarchy) that was helpful but even with some alterations it isn't working how I would like it:
def hashkeys(json, keys = [], result = [])
if json.is_a?(Hash)
json.each do |key, value|
hashkeys(value, keys + [key], result)
end
else
result << keys
end
result.join("/")
end
It returns all the keys together as one string and doesn't include any of the respective values correctly as I would like.
Unwanted output of hashkeys currently:
id/system_name/mem/stat1/mem/stat2/...
Ideally I want something that takes in my_json:
find_nested_key_value(my_json)
some logic loop involving key and value:
if more logic needed
another_logic_loop_for_more_nested_info
else
send_info(final_key, final_value)
end
end
end
So if final_key = "mem/stat1" then the final_value = 10, then the next iteration would be final_key = "mem/stat2" and final_value = 1056 and so on
How do I achieve this? and is using a function like hashkeys the best way to achieve this
This is a recursive method that will create a "flattened hash", a hash without nesting and where the keys are the nested keys separated by slashes.
def flatten_hash(hash, result = {}, prefix = nil)
hash.each do |k,v|
if v.is_a? Hash
flatten_hash(v, result, [prefix, k].compact.join('/'))
else
result[[prefix, k].compact.join('/')] = v
end
end
result
end
my_hash = {'id': 1, 'system_name': 'Sysem_1', 'mem': {'stat1': 10, 'stat2': 1056, 'stat3': 10563}}
flatten_hash(my_hash)
=> {"id"=>1, "system_name"=>"Sysem_1", "mem/stat1"=>10, "mem/stat2"=>1056, "mem/stat3"=>10563}
def key_path_value(key_string, my_json)
value = nil
key_array = key_string.split("/")
return value if key_array.empty?
return my_json[key_array.last] if key_array.length == 1
value = my_json[key_array.first.to_sym]
key_array = key_array.drop(1)
key_array.each do |key|
break unless value.is_a? Hash
value = value[key.to_sym]
end
return value
end
I'm learning coding, and one of the assignments is to return keys is return the names of people who like the same TV show.
I have managed to get it working and to pass TDD, but I'm wondering if I've taken the 'long way around' and that maybe there is a simpler solution?
Here is the setup and test:
class TestFriends < MiniTest::Test
def setup
#person1 = {
name: "Rick",
age: 12,
monies: 1,
friends: ["Jay","Keith","Dave", "Val"],
favourites: {
tv_show: "Friends",
things_to_eat: ["charcuterie"]
}
}
#person2 = {
name: "Jay",
age: 15,
monies: 2,
friends: ["Keith"],
favourites: {
tv_show: "Friends",
things_to_eat: ["soup","bread"]
}
}
#person3 = {
name: "Val",
age: 18,
monies: 20,
friends: ["Rick", "Jay"],
favourites: {
tv_show: "Pokemon",
things_to_eat: ["ratatouille", "stew"]
}
}
#people = [#person1, #person2, #person3]
end
def test_shared_tv_shows
expected = ["Rick", "Jay"]
actual = tv_show(#people)
assert_equal(expected, actual)
end
end
And here is the solution that I found:
def tv_show(people_list)
tv_friends = {}
for person in people_list
if tv_friends.key?(person[:favourites][:tv_show]) == false
tv_friends[person[:favourites][:tv_show]] = [person[:name]]
else
tv_friends[person[:favourites][:tv_show]] << person[:name]
end
end
for array in tv_friends.values()
if array.length() > 1
return array
end
end
end
It passes, but is there a better way of doing this?
I think you could replace those for loops with the Array#each. But in your case, as you're creating a hash with the values in people_list, then you could use the Enumerable#each_with_object assigning a new Hash as its object argument, this way you have your own person hash from the people_list and also a new "empty" hash to start filling as you need.
To check if your inner hash has a key with the value person[:favourites][:tv_show] you can check for its value just as a boolean one, the comparison with false can be skipped, the value will be evaluated as false or true by your if statement.
You can create the variables tv_show and name to reduce a little bit the code, and then over your tv_friends hash to select among its values the one that has a length greater than 1. As this will give you an array inside an array you can get from this the first element with first (or [0]).
def tv_show(people_list)
tv_friends = people_list.each_with_object(Hash.new({})) do |person, hash|
tv_show = person[:favourites][:tv_show]
name = person[:name]
hash.key?(tv_show) ? hash[tv_show] << name : hash[tv_show] = [name]
end
tv_friends.values.select { |value| value.length > 1 }.first
end
Also you can omit parentheses when the method call doesn't have arguments.
I have two hashes:
a = {"0"=>"name", "1"=>"email"}
b = {"0"=>"source", "1"=>"info", "2"=>"extra", "3"=>"name"}
I want a hash created by doing the following:
1) When the two hashes contain identical values, keep value of original hash and discard value of second hash.
2) When the values of second hash are not in first hash, just add to the end of new hash, making sure that the key is ordered.
with this result:
{"0"=>"name", "1"=>"email", "2"=>"source", "3"=>"info", "4"=>"extra"}
I did it this ugly way:
l1 = a.keys.length
l2 = b.keys.length
max = l1 > l2 ? l1 : l2
counter = l1
result = {}
max.times do |i|
unless a.values.include? b[i.to_s]
result[counter.to_s] = b[i.to_s]
counter += 1
end
end
a.merge!(result)
Is there a built-in ruby method or utility that could achieve this same task in a cleaner fashion?
(a.values + b.values).uniq.map.with_index{|v, i| [i.to_s, v]}.to_h
# => {"0"=>"name", "1"=>"email", "2"=>"source", "3"=>"info", "4"=>"extra"}
First create an array containing the values in the hash. This can be accomplished with the concat method. Now that we have an array, we can call the uniq method to retrieve all unique values. This also preserves the order.
a = { "0" => "name", "1" => "email" }
b = { "0" => "source", "1" => "info", "2" => "extra", "3" => "name" }
values = a.values.concat(b.values).uniq
A shortcut to generating a hash in Ruby is with this trick.
Hash[[*0..values.length-1].zip(values)]
Output:
{0=>"name", 1=>"email", 2=>"source", 3=>"info", 4=>"extra"}
a = {"0"=>"name", "1"=>"email"}
b = {"0"=>"source", "1"=>"info", "2"=>"extra", "3"=>"name"}
key = (a.size-1).to_s
#=> "1"
b.each_value.with_object(a) { |v,h| (h[key.next!] = v) unless h.value?(v) }
#=> {"0"=>"name", "1"=>"email", "2"=>"source", "3"=>"info", "4"=>"extra"}
I'm trying to understand the sort_by method. Here's a script I'm experimenting with:
def test(x)
if x[:type] == 1
# move the hash to the first index of the array
end
end
values = [{value: "First", type: 0},{value: "Second", type: 1},{value: "1111", type: 0},{value: "2222", type: 1}]
values.sort_by! { |x| test(x) }
puts values
How can I explicitly state the index I wish the selected index to be moved to? I want the hashes with a type of 1 to all be moved to the first three indexes, and their order not changed.
To do what you want using sort_by (except that the original order is not preserved if two elements return same value but it shows how sort_by works)
values.sort_by! { |x| x[:type] == 1 ? 0 : 1 }
# => [{:value=>"2222", :type=>1}, {:value=>"Second", :type=>1},
# {:value=>"1111", :type=>0}, {:value=>"First", :type=>0}]
sort_by sorts the elements in ascending order based on the value returned by the block.
In this case, it iterates over the array the and passes each element to the following block as x
{ |x| x[:type] == 1 ? 0 : 1 }
The value returned from the above block is compared to each other and used to create the final ordered array.
In this case, the value returned is 0 if x[:type] == 1 and 1 otherwise. So all elements with x[:type] == 1 will be ordered first.
See more info on sort_by here
To use sort_by here, you need to find a way to keep the ordering correct. Here's one, though it's a bit of a hack:
values = [{value: "First", type: 0}, {value: "Second", type: 1},
{value: "1111", type: 0}, {value: "2222", type: 1}]
type = 1
n = values.size # => 4
values.each_with_index {|h,i| h[:type] = i-n if h[:type] == type}
# => [{:value=>"First", :type=>0}, {:value=>"Second", :type=>-3},
{:value=>"1111", :type=>0}, {:value=>"2222", :type=>-1}]
values.sort_by! {|h| h[:type]}
# => [{:value=>"Second", :type=>-3}, {:value=>"2222", :type=>-1},
{:value=>"1111", :type=>0}, {:value=>"First", :type=>0}]
values.map {|h| h[:type] = type if h[:type] < 0; h}
# => [{:value=>"Second", :type=>1}, {:value=>"2222", :type=>1},
{:value=>"1111", :type=>0}, {:value=>"First", :type=>0}]
values.sort_by{|x| x[:type]}
Cheers.
You can't, when using #sort or #sort_by, declare the index to which a item should be moved. You can, however, specify the order and let #sort_by do the rest.
The first problem is that sort_by is not stable: Equal items may be emitted in any order. You require a stable sort, so let's monkey-patch Enumerable to have a #stable_sort_by method:
module Enumerable
def stable_sort_by
map.each.with_index.sort_by do |e, i|
[yield(e), i]
end.map(&:first)
end
end
This sorts according to the values returned by the block, just as #sort_by does, but if the values are equal, it sorts by the items order. This preserves the relative order of equal items.
Now, using the newly defined #stable_sort_by:
values.sort_by! do |h|
if h[:type] == 1
0
else
1
end
end
This moves to the beginning all items having a type of 1, but otherwise leaves the relative order of items unchanged.
I have an array of hashes which look like:
ward = {id: id, name: record["Externalization"], mnemonic: record["Mnemonic"],
seqno: record["SeqNo"]}
All fields are strings.
Now I want to sort them first on seqno and then on name. seqno can be nil (if seqno is nil, then this ward must come after the ones having a seqno).
What I have so far is:
wardList.sort! do |a,b|
return (a[:name] <=> b[:name]) if (a[:seqno].nil? && b[:seqno].nil?)
return -1 if a[:seqno].nil?
return 1 if b[:seqno].nil?
(a[:seqno] <=> b[:seqno]).nonzero? ||
(a[:name] <=> b[:name])
end
But this gives me the error: can't convert Symbol into Integer
First, normalize your data, you can't work with integers as strings here:
wardList = wardList.map { |x| x.merge({:id => x[:id].to_i,
:seqno => x[:seqno].try(:to_i) }) }
Then you can use sort_by, which supports lexicographical sorting:
wardList.sort_by! { |x| [x[:seqno] || Float::INFINITY, x[:name]] }
Example:
irb(main):034:0> a = [{:seqno=>5, :name=>"xsd"},
{:seqno=>nil, :name=>"foo"},
{:seqno=>nil, :name=>"bar"},
{:seqno=>1, :name=>"meh"}]
irb(main):033:0> a.sort_by { |x| [x[:seqno] || Float::INFINITY, x[:name]] }
=> [{:seqno=>1, :name=>"meh"},
{:seqno=>5, :name=>"xsd"},
{:seqno=>nil, :name=>"bar"},
{:seqno=>nil, :name=>"foo"}]
This should work:
sorted = wardList.sort_by{|a| [a[:seqno] ? 0 : 1, a[:seqno], a[:name]] }
or for some rubies (e.g. 1.8.7):
sorted = wardList.sort_by{|a| [a[:seqno] ? 0 : 1, a[:seqno] || 0, a[:name]] }
I don't think you should use return here, it causes the block to return to the iterator, the iterator to return to the enclosing method and the enclosing method to return to its caller. Use next instead which only causes the block to return to the iterator (sort! in this case) and do something like:
wardList.sort! do |x,y|
next 1 if x[:seqno].nil?
next -1 if y[:seqno].nil?
comp = x[:seqno] <=> y[:seqno]
comp.zero? ? x[:name] <=> y[:name] : comp
end