Comparing more than one object properties in separate arrays - ruby

I am trying to figure out a way to iterate through and remove duplicate records from four different sources.
first_source = [#<Customer:0x007f911e307ad0 #id="123", #name="Whitehall">,# <Customer:0x007f911e307ad0 #id="124", #name="Whitehall">#<Customer:0x007f911e307ad0 #id="125", #name="Whitehall">]
second_source = [#<Customer:0x007f911e307ad0 #id="5000", #name="Whitehall">,#<Customer:0x007f911e307ad0 #id="5500", #name="Whitehall">#<Customer:0x007f911e307ad0 #id="123", #name="Whitehall">]
third_source = [#<Customer:0x007f911e307ad0 #id="800", #name="Whitehall">,#<Customer:0x007f911e307ad0 #id="5000", #name="Whitehall">#<Customer:0x007f911e307ad0 #id="124", #name="Whitehall">]
fourth_source = [#<Customer:0x007f911e307ad0 #id="4300", #name="Whitehall">,#<Customer:0x007f911e307ad0 #id="800", #name="Whitehall">#<Customer:0x007f911e307ad0 #id="125", #name="Whitehall">]
I tried
customers = []
dup_customers = first_source + second_source + third_source + fourth_source
dup_customers.combination(2).each do |cs1, cs2|
customers << cs1 unless cs1.id != cs2.id
end
But this really did not work.
Can someone help me suggest a way/strategy for traversing through these four collections and finding the Customer id's that are equal and then doing something with it?

How about Array#uniq?
customers = (first_source + second_source + third_source + fourth_source).uniq
uniq discards duplicates by element-wise comparison using Object#eql?, so for this method to work, you would need to implement Customer#eql?.
class Customer
def eql?(other)
id == other.id #or however you define equality
end
end

dup_customers =
[first_source, second_source, third_source, fourth_source]
.combination(2).flat_map{|s1, s2| s1 & s2}

Overriding eql as #pje does is not necessary. uniq takes a block (last example):
customers = [first_source ,second_source, third_source, fourth_source ].flatten
p customers.uniq{|c| c.id}

You can use Array#| (union operator):
customers = first_source | second_source | third_source | fourth_source
It returns the result of the merging of the two arrays while removing duplicates:
["a", "b", "c" ] | [ "c", "d", "a" ]
#=> [ "a", "b", "c", "d" ]

Related

Sorting both ascending and descending based on keys in array of hashes

I have the following array:
[
{:string=>"2:aa/", :count=>2, :char=>"a"},
{:string=>"2:dd/", :count=>2, :char=>"d"},
{:string=>"2:ee/", :count=>2, :char=>"e"},
{:string=>"=:gg/", :count=>2, :char=>"g"},
{:string=>"1:ii/", :count=>2, :char=>"i"},
{:string=>"=:nnn/", :count=>3, :char=>"n"},
{:string=>"1:ooo/", :count=>3, :char=>"o"},
{:string=>"2:sss/", :count=>3, :char=>"s"},
{:string=>"1:uuu/", :count=>3, :char=>"u"}
]
I want this array of hashes to be sorted descending by count, and if count is equal, then I need to sort it ascending based on the char value.
Is there any direct way I can do this?
Instead of negating the value via -, you could also use sort with two arrays and switch the elements as needed.
To sort ascending / ascending you'd use: (see Array#<=>)
ary.sort { |a, b| [a[:count], a[:char]] <=> [b[:count], b[:char]] }
to sort descending / ascending, you switch the first elements:
ary.sort { |a, b| [b[:count], a[:char]] <=> [a[:count], b[:char]] }
# ^ ^
# | |
# +-------------------------+
to sort ascending / descending, you switch the second elements: (you get the idea)
ary.sort { |a, b| [a[:count], b[:char]] <=> [b[:count], a[:char]] }
# ^ ^
# | |
# +-------------------------+
Try this one
a.sort_by { |item| [-item[:count], item[:char]] }
a is your array
You asked for a direct way, so here's a way to remove even another indirection--your intermediate data structure. This assumes your hash is just a means to an end and you intend to sort the strings:
strings = ["2:aa/", "2:dd/", "2:ee/", "=:gg/", "1:ii/", "=:nnn/", "1:ooo/", "2:sss/", "1:uuu/"]
strings.sort_by{ |s| x = s[/\w+/]; [-x.size, x] }
The regex isolates the part you need to count and sort; it may need to be adjusted if your real data differs from the example.
(I would replace x with a more meaningful variable name based on what the letters represent)

Redis: sorting hash "fields" in alpha

I am trying to sort the "fields" in a hash.
For example,
mykey, cde, firstone
mykey, abcde, secondone
mykey, bcde, thirdone
I want to sort the fields(cde, abcde, bcde) in alphabet order, but there is no way to do so.. If anyone knows about this, please help me.
If there is no way to solve this, I am thinking about changing names of key&values.. and use zadd instead of hash. If you have a better solution, please give me an advice here.
Hash field names are not sortable-by easily - there is no native command to do so and the order in which fields are returned (e.g. with HGETALL) is for all intents and purposes random.
While Sorted Sets are preferable when it comes to sorting, you could work around this with use of a Lua script that will perform lexical sorting the Hash's fields. For example:
$ cat hashsort.lua
local r = redis.call('HGETALL',KEYS[1])
local t = {}
for i=1,#r,2 do
t[#t+1] = { field = r[i], value = r[i+1] }
end
table.sort(t, function(a,b) return a.field < b.field end)
r = {}
for _, v in pairs(t) do
r[#r+1] = v.field
r[#r+1] = v.value
end
return r
$ redis-cli HMSET hash z 99 ee 55 e 5 a 1 b 2 ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ justsomethinglongsohashmaxzipwillbeexceeded
OK
$ redis-cli --eval hashsort.lua hash
1) "a"
2) "1"
3) "b"
4) "2"
5) "e"
6) "5"
7) "ee"
8) "55"
9) "z"
10) "99"
11) "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
12) "justsomethinglongsohashmaxzipwillbeexceeded"

Simple way to check for membership in a list?

For use in a closure (though I suppose it will be useful elsewhere as well), what is the cleanest way to check to see whether or not a given element is a member of a list (or any character is a member of a string)?
For example, to check whether or not the string "abcde" contains any of the characters in "aeiou", or whether the list ["bill", "sam" , "suzy"] contains any of the names in ["abe", "bill", "charlie"]?
1 ) Common chars in 2 words
You easily check the intersection of the common chars in 2 strings using the Set struct:
let set0 = Set("abcde".characters)
let set1 = Set("aeiou".characters)
let intersection = set0.intersect(set1) // {"e", "a"}
2) Common words in 2 lists
Similarly you can find the intersection of 2 arrays of strings:
let set0 : Set = ["bill", "sam", "suzy"]
let set1 : Set = ["abe", "bill", "charlie"]
let intersection = set0.intersect(set1) // {"bill"}
3) Back to Array
Please note that in both examples, the intersection constant is a Set. You can transform it into an Array writing:
let list = Array(intersection)
Hope this helps.
P.S. This code is for Swift 2.0
Update
More specifically if you want to find out whether an intersection exists you can simply write:
!intersection.isEmpty
Swift arrays have built in functions for this exact functionality.
I recommend checking out the official documentation for Collection Types from Apple for a starting point.
Here is an example that follows your question:
// With [Character]
let vowels: [Character] = ["a", "e", "i", "o", "u"]
let chars: [Character] = ["a", "b", "c", "d", "e"]
let resultVowels = vowels.filter { chars.contains($0) }
resultVowels.count // == 2
resultVowels.description // == "[a, e]"
// With [String]
let people = ["Bill", "Sam", "Suzy"]
let peeps = ["Abe", "Bill", "Charlie"]
let resultPeople = people.filter { peeps.contains($0) }
resultPeople.count // == 1
resultPeople.description // == "[Bill]"
The result will be the names (or numbers, characters, etc.) that are matching up. So you can not only get the count, but also the contents of the comparison in this procedure. So long as the types follow Equatable, this will work for you.
If you want to return a Boolean, just simply:
return filteredArray.count != 0
Now, if you want to compare say, a [Character] to a String then:
let vowels: [Character] = ["a", "e", "i", "o", "u"]
let someString = "abcde"
let result = vowels.filter { someString.characters.contains($0) }
result.count // == 2
result.description // == "[a, e]"
The String.characters property is a new feature brought in to Swift 2.
I hope I was able to assist you.

How to sort a hash with duplicate key?

I have a hash
h = {}
h.compare_by_identity
h[2.51] = 1
h1[2.51] = 2
Edit: h1[2.51] = 2 should be h[2.51] = 2
it is ok with duplicate key. But when i use
Hash[h.sort]
it return only one value with key like
{2.51=>2}
is there any way to get the two values from the hash in sorted order?
Starting with ruby version 2.0 the key 2.51 is actually the same object (because of ruby internal caching) in both assignments. Try to output 2.51.object_id for both cases and it will output the same id.
Since it can't be done with floats, turn them into strings:
h = {}
h.compare_by_identity
h[2.51.to_s] = 2
h[2.51.to_s] = 1
p h.sort # => [["2.51", 1], ["2.51", 2]]

Ruby - producing a combination from two arrays *produces headache*

What I'm trying to do is take an array of service names and apply a service response of either true of false to each.
Basically I'm getting an xml with a set of booleans for each of the services checked. So for this example they all came back as true.
I put them into an array using Nokogiri like so:
doc = Nokogiri::XML.parse(xml)
service_state = doc.css("HeartBeat Status").map(&:text)
This results in an array with 3 ["true"] items.
What I need to is apply each of them sequentially with an array I have in the code.
name = ['svc1', 'svc2', 'svc3']
To do this I used the following code:
status = [] ; service_state.each {|n| name.each {|l| status << [l,n]}}
status.each {|state| print state.to_s + "\n"}
This does what I want... sort of...
I do get an output of:
["svc1", "true"]
["svc2", "true"]
["svc3", "true"]
However, it repeats all the possible combinations.
When applying this to the actual array I have a total of 13 services that have a response 17 times so I end up with an array with 221 items.
Question: How do I do what I'm doing now but without repeating for each item in both arrays?
Thanks!
Sounds like you want to use Array#zip
name = ['svc1', 'svc2', 'svc3']
status = ['true', 'true', 'true']
name.zip(status)
#=> [['svc1','true'], ['svc2','true'], ['svc3','true']]
name.zip service_state
I was looking for a much more complicated answer than I needed.
This snippet gave me what I needed. :P
status = [] ; service_state.each {|n| name.each {|l| status << [l,n]}}
status = status.uniq
status.each {|state| print state.to_s + "\n"}

Resources