Splitting an array into subarrays - arrays

I have an array of chars that looks like this:
chars = ["x", "o", "o", "x", "x", "o", "o", "x", "x", "x", "o", "o"]
I need to get the number of consecutive chars and the index of that char. It should look like this:
[
{ index: 0, length: 1 }, # "x"
{ index: 1, length: 2 }, # "o", "o"
{ index: 3, length: 2 }, # "x", "x"
{ index: 5, length: 2 }, # "o", "o"
{ index: 7, length: 3 }, # "x", "x", "x"
{ index: 10, length: 2 } # "o", "o"
]
Is there an easy way to achieve this?

Not sure if you'd call this an easy way, but here's a one-line way of doing it. Resulting array is of the form [index, length].
chars.each_with_index.chunk {|i, _| i}.map {|_, y| [y.first.last, y.length]}
#=> [[0, 1], [1, 2], [3, 2], [5, 2], [7, 3], [10, 2]]

Two others ways of doing it:
Use Emumerable#slice_when (v.2.2+)
count = 0
arr = chars.slice_when { |a,b| a != b }.map do |arr|
sz = arr.size
h = { index: count, length: sz }
count += sz
h
end
#=> [{:index=>0, :length=>1}, {:index=>1, :length=>2}, {:index=>3, :length=>2},
# {:index=>5, :length=>2}, {:index=>7, :length=>3}, {:index=>10, :length=>2}]
Use a regex with a backreference
count = 0
arr = chars.join.scan(/((.)\2*)/).map do |run, _|
sz = run.size
h = { index: count, length: sz }
count += sz
h
end
#=> [{:index=>0, :length=>1}, {:index=>1, :length=>2}, {:index=>3, :length=>2},
# {:index=>5, :length=>2}, {:index=>7, :length=>3}, {:index=>10, :length=>2}]

Related

How to get sequence of array elements?

I have the following array that is "separated" by "blocks". Each invisible block begins with a letter having number 5 next to it and finishes when another letter with 5 next to it appears.
a = [
"F5","a4","g4","F5","a4","d4","F5","a4","g4","e3",
"H5","a4",
"Y5","a4","d4","Y5","g4","c3"
]
In this array there are 3 "blocks" like below.
Block1 --> begins with first "F5" (index 0) and ends in "e3" (index
9), just before the "H5".
Block2 --> begins with first "H5" (index 10) and ends in "a4" (index
11), just before the "Y5".
Block3 --> begins with first "Y5" (index 12) and ends in "c3" (index
17), when it reaches the end of array.
What I'd like to get, is a sequence (from 1 to N) of each element within each block (Not within the array itself), where the output would be:
b = [
["F5",1],["a4",1],["g4",1],["F5",2],["a4",2],["d4",1],["F5",3],["a4",3],["g4",2],["e3",1],
["H5",1],["a4",1],
["Y5",1],["a4",1],["d4",1],["Y5",2],["g4",1],["c3",1]
]
With my current attempt I'm getting only the count of each element and not the sequence. How can this be done? Thanks
a = [
"F5","a4","g4","F5","a4","d4","F5","a4","g4","e3",
"H5","a4",
"Y5","a4","d4","Y5","g4","c3"
]
b = []
a.each{|v|
b.push([v,a.count(v)])
}
=> [
["F5", 3], ["a4", 5], ["g4", 3], ["F5", 3], ["a4", 5], ["d4", 2], ["F5", 3], ["a4", 5], ["g4", 3], ["e3", 1],
["H5", 1], ["a4", 5],
["Y5", 2], ["a4", 5], ["d4", 2], ["Y5", 2], ["g4", 3], ["c3", 1]
]
We are given the array
a = ["F5","a4","g4","F5","a4","d4","F5","a4","g4","e3",
"H5","a4","Y5","a4","d4","Y5","g4","c3"]
The problem can be viewed as having two steps, the first being to convert a to
arr = [["F5","a4","g4","F5","a4","d4","F5","a4","g4","e3"],
["H5","a4"], ["Y5","a4","d4","Y5","g4","c3"]]
The second step is to construct the desired array from arr.
Step 1
I understand the rule for converting a to arr is as follows.
the first element of each array of arr is a string of the form YZ that matches the regular expression /[A-Z]\d/ (but the digit does not necessarily equal 5).
it is assumed that the first element of a always has the above property.
the last element of each array b of arr is the last element of a or the element of a that precedes the first element of a following the element of a that corresponds to the first element of b that matches /[A-Z]\d/, but when compared with the first element of b the capital letters differ and the digits are equal.
We may write
frst = a.first
arr = a.slice_before do |s|
(s[0].match?(/[A-Z]/) && s[0] != frst[0] && s[1] == frst[1]) ?
(frst = s) : false
end.to_a
#=> [["F5", "a4", "g4", "F5", "a4", "d4", "F5", "a4", "g4", "e3"]],
# ["H5", "a4"], ["Y5", "a4", "d4", "Y5", "g4", "c3"]]
See Enumerable#slice_before.
Step 2
We may now convert arr to the desired array as follows.
arr.map do |e|
h = Hash.new(0)
e.map { |s| [s, h[s] += 1] }
end
#=> [[["F5", 1], ["a4", 1], ["g4", 1], ["F5", 2], ["a4", 2], ["d4", 1],
# ["F5", 3], ["a4", 3], ["g4", 2], ["e3", 1]],
# [["H5", 1], ["a4", 1]],
# [["Y5", 1], ["a4", 1], ["d4", 1], ["Y5", 2], ["g4", 1], ["c3", 1]]]
Here Hash::new is used to create an empty hash h with a default value of zero. All that means is that if h does not have a key k, h[k] returns zero. As h[k] += 1 can be seen as
h[k] = h[k] + 1
h[k] on the right returns 1 when h does not have a key k. Another way of writing that is
h[k] = h.fetch(k,0) + 1
See Hash#fetch.
This is what I'd do:
Starting with:
a_ary = [
'F5', 'a4', 'g4', 'F5', 'a4', 'd4', 'F5', 'a4', 'g4', 'e3',
'H5', 'a4',
'Y5', 'a4', 'd4', 'Y5', 'g4', 'c3'
]
BREAK_REGEX = /^.5/
I'd group the array by matching blocks, then grab the resulting chunks and flatten them back to the collected arrays.
grouped_ary = a_ary.slice_before(BREAK_REGEX)
.group_by { |a| a.first } # => {"F5"=>[["F5", "a4", "g4"], ["F5", "a4", "d4"], ["F5", "a4", "g4", "e3"]], "H5"=>[["H5", "a4"]], "Y5"=>[["Y5", "a4", "d4"], ["Y5", "g4", "c3"]]}
.values # => [[["F5", "a4", "g4"], ["F5", "a4", "d4"], ["F5", "a4", "g4", "e3"]], [["H5", "a4"]], [["Y5", "a4", "d4"], ["Y5", "g4", "c3"]]]
.map(&:flatten) # => [["F5", "a4", "g4", "F5", "a4", "d4", "F5", "a4", "g4", "e3"], ["H5", "a4"], ["Y5", "a4", "d4", "Y5", "g4", "c3"]]
Then process those, counting the occurrences in each array, looping over them and output the element and the count, resetting the counter for each block:
b_ary = grouped_ary.flat_map{ |e|
element_count = Hash.new { |h, k| h[k] = 0 }
e.map { |i|
element_count[i] += 1
[
i,
element_count[i]
]
}
}
Which results in:
b_ary
# => [["F5", 1],
# ["a4", 1],
# ["g4", 1],
# ["F5", 2],
# ["a4", 2],
# ["d4", 1],
# ["F5", 3],
# ["a4", 3],
# ["g4", 2],
# ["e3", 1],
# ["H5", 1],
# ["a4", 1],
# ["Y5", 1],
# ["a4", 1],
# ["d4", 1],
# ["Y5", 2],
# ["g4", 1],
# ["c3", 1]]
# ]
Here's what the very first step using slice_before creates:
a_ary.slice_before(BREAK_REGEX).to_a
# => [["F5", "a4", "g4"],
# ["F5", "a4", "d4"],
# ["F5", "a4", "g4", "e3"],
# ["H5", "a4"],
# ["Y5", "a4", "d4"],
# ["Y5", "g4", "c3"]]
Enumerable is where the magic lies.
I'd break this into a few functions.
require 'minitest/autorun'
# Split array where the given block returns true
def group_by_sequence(x)
i = 0
x.each_with_object([]) { |e, a|
i += 1 if yield e
a[i] = [] if a[i].nil?
a[i] << e
}
end
# Split array at each new "five", eg. going from "A5" to "B5"
def group_by_fives(x)
li = nil
group_by_sequence(x) { |e|
l, n = e.chars
li = l if li.nil?
if n == '5' && li != l
li = l
true
else
false
end
}
end
# Given [A,B,C,B], returns [[A,1], [B,1], [C,1], [B,2]]
def add_counts_by_group(x)
group_by_fives(x).flat_map { |group|
count = {}
group.map { |e|
count[e] = count[e].to_i + 1
[e, count[e]]
}
}
end
class Test < Minitest::Test
def test_add_counts_by_group
assert_equal(
[
["F5",1],["a4",1],["g4",1],["F5",2],["a4",2],["d4",1],["F5",3],["a4",3],["g4",2],["e3",1],
["H5",1],["a4",1],
["Y5",1],["a4",1],["d4",1],["Y5",2],["g4",1],["c3",1]
],
add_counts_by_group([
"F5","a4","g4","F5","a4","d4","F5","a4","g4","e3",
"H5","a4",
"Y5","a4","d4","Y5","g4","c3"
])
)
end
end

Ruby each_slice with different sizes

I have:
stuff = [1, 2, "a", "b", "c", "d", 4, 5, "z", "l", "m", "l", 5, 4, 4, 77]
Numbers come in groups of multiples of two, and letters come in groups of multiples of four.
I want to group numbers in twos and letters in fours like this:
stuff_processed = [
[1, 2],
["a", "b", "c", "d"],
[4, 5],
["z", "l", "m", "l"],
[5, 4],
[4, 77]
]
The order inside of an array that holds numbers or letters is important, the order between the arrays I do not care about.
I know stuff.each_slice(2).to_a will take me part of the way. I can't figure out how to get all the way to what I need though.
stuff
.chunk(&:class)
.flat_map{|klass, a| a.each_slice(klass == Fixnum ? 2 : 4).to_a}
# => [[1, 2], ["a", "b", "c", "d"], [4, 5], ["z", "l", "m", "l"], [5, 4], [4, 77]]
arr = [1, 2, "a", "b", "c", "d", 4, 5, "z", "l", "m", "l", "s", "t",
"u", "v", 5, 4, 4, 77, 91, 65]
H = { Fixnum=>1, String=>3 }
count = 0
arr.slice_when do |a,b|
if a.class == b.class && count < H[a.class]
count += 1
false
else
count = 0
true
end
end.to_a
# => [[1, 2], ["a", "b", "c", "d"], [4, 5], ["z", "l", "m", "l"],
# ["s", "t", "u", "v"], [5, 4], [4, 77], [91, 65]]
See Enumerable#slice_when, which first appeared in Ruby v2.2.
This Array#conditional_slice method accepts a Block and returns an Enumerator :
stuff = [1, 2, "a", "b", "c", "d", 4, 5, "z", "l", "m", "l", 5, 4, 4, 77]
class Array
def conditional_slice(&block)
clone = self.dup
Enumerator.new do |yielder|
until clone.empty? do
yielder << clone.shift(block_given? ? block.call(clone.first) : 1)
end
end
end
end
sliced_stuff = stuff.conditional_slice{|x| x.is_a?(Numeric) ? 2 : 4}
puts sliced_stuff.to_a.inspect
# => [[1, 2], ["a", "b", "c", "d"], [4, 5], ["z", "l", "m", "l"], [5, 4], [4, 77]]

Find all combination of string array in swift

I have an Array of String and I want to find all the possible combinations of its element
For Example :
Array = [A,B,C,D]
should produce result as :
[A,AB,AC,AD,ABC,ABD,ACD,ABCD,B,BC,BD,BCD,C,CD,D]
Here is my Logic :
var array = ["A", "B", "C","D"]
var list = [String]()
for i in 0..<array.count{
let c = array[i]
list.append(c)
var d = c
for count in 1..<array.count{
if i+count < array.count{
for j in i+count..<array.count{
var a = d
a.appendContentsOf(array[j])
print("a : \(a)")
list.append(a)
}
}
d = c
d.appendContentsOf(array[count])
print("d : \(d)")
}
}
print(list.description)
Its Output is :
["A", "AB", "AC", "AD", "ABC", "ABD", "ACD", "B", "BC", "BD", "BBD",
"C", "CD", "D"]
This output is missing ABCD and wrongly printed BCD as BBD
Anyone Please Help me in this by Enhancing my code or suggesting your own logic for this.
#yannick's answer is very close.
By computing a Power Set of your set, you obtain all the possible subsets (including your original set and the empty set).
Once you have obtained the Power Set, all you have to do is join the subsets into a single string in order to obtain the result that you're looking for.
Here's the complete solution (along with updated code and plenty of comments):
extension Array {
var powerset: [[Element]] {
guard count > 0 else {
return [[]]
}
// tail contains the whole array BUT the first element
let tail = Array(self[1..<endIndex])
// head contains only the first element
let head = self[0]
// computing the tail's powerset
let withoutHead = tail.powerset
// mergin the head with the tail's powerset
let withHead = withoutHead.map { $0 + [head] }
// returning the tail's powerset and the just computed withHead array
return withHead + withoutHead
}
}
let myArray = ["A", "B", "C", "D"]
print(myArray.powerset) // -> [["D", "C", "B", "A"], ["C", "B", "A"], ["D", "B", "A"], ["B", "A"], ["D", "C", "A"], ["C", "A"], ["D", "A"], ["A"], ["D", "C", "B"], ["C", "B"], ["D", "B"], ["B"], ["D", "C"], ["C"], ["D"], []]
// joining the subsets
let myResult = myArray.powerset.map { $0.sort().joinWithSeparator("") }
print(myResult) // -> ["A", "AB", "ABC", "ABCD", "ABD", "AC", "ACD", "AD", "B", "BC", "BCD", "BD", "C", "CD", "D", ""]
PS
Note that this solution uses a recursive approach, while yours was using an iterative approach.
PPS
If you don't want the empty string "" in your solution, you can just filter it away:
let myResult = myArray.powerset.map({ $0.sort().joinWithSeparator("") }).filter({ $0 != "" })
print(myResult) // -> ["A", "AB", "ABC", "ABCD", "ABD", "AC", "ACD", "AD", "B", "BC", "BCD", "BD", "C", "CD", "D"]
It looks like you want to have the Power set of your array.
In mathematics, the power set (or powerset) of any set S is the set of
all subsets of S, including the empty set and S itself.
I found this Code on GitHub.
extension Array {
var powerset: [[Element]] {
if count == 0 {
return [self]
}
else {
let tail = Array(self[1..<endIndex])
let head = self[0]
let withoutHead = tail.powerset
let withHead = withoutHead.map { $0 + [head] }
return withHead + withoutHead
}
}
}
println([1,2,3,4].powerset) -> [[4, 3, 2, 1], [3, 2, 1], [4, 2, 1], [2, 1], [4, 3, 1], [3, 1], [4, 1], [1], [4, 3, 2], [3, 2], [4, 2], [2], [4, 3], [3], [4], []]
I find a neater answer for it.Power set of Collection.
The principle is using induction on the size of a collection, as showed on that link.
Here is the copy of code from that link. And all credits to its author.
extension Collection {
public var powerSet: [[Element]] {
guard let fisrt = self.first else {return [[]]}
return self.dropFirst().powerSet.flatMap{[$0, [fisrt] + $0]}
}
}
let s: Set<Int> = [1,2,3]
s.powerSet //[[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
let a: Array<Int> = [1,2,3]
a.powerSet //[[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
I know some good answers have been given already, but coming from a Java background, I just wanted to drop some insights using bitwise operators (which surprisingly still work in Swift).
You can try this out:
let len = stringArr.count
for i in 0 ..< (1<<len){
print("{", terminator: "")
for j in 0 ..< len {
if ((i & (1<<j)) > 0) {
print(stringArr[j], terminator: "")
}
}
print("}")
}
You can find more information on bitwise operators here
I will take a shot also using this logic as reference:
extension RangeReplaceableCollection {
var subSets : [SubSequence] {
guard !isEmpty else { return [] }
let count = self.count
let n = 1 << count - 1
var subSequences: [SubSequence] = .init(repeating: SubSequence(), count: n-1)
(0 ..< n).forEach { x in
autoreleasepool {
var counter = 0
for element in self {
if x & 1 << counter > 0 {
subSequences[x-1].append(element)
}
counter += 1
}
}
}
return subSequences + [self[...]]
}
}
Playground Testing:
["A", "B", "C","D"].subSets // [["A"], ["B"], ["A", "B"], ["C"], ["A", "C"], ["B", "C"], ["A", "B", "C"], ["D"], ["A", "D"], ["B", "D"], ["A", "B", "D"], ["C", "D"], ["A", "C", "D"], ["B", "C", "D"], ["A", "B", "C", "D"]]
"ABCD".subSets // ["A", "B", "AB", "C", "AC", "BC", "ABC", "D", "AD", "BD", "ABD", "CD", "ACD", "BCD", "ABCD"]

How to sort three arrays based on the order of two arrays. Swift

The only way I can think of is after the first sorting based on rating, I group all the names which have same rating together and sort that group based on quantity. It is a little complicated and I guess I need to implement a function to do it. Is there any shorter way to do it? Thanks.
// **Expected result**
// sortedNameArray = ["b", "e", "a", "f", "c", "g", "d", "h"]
// sortedRatingArray = [ 5, 5, 4, 3, 3, 3, 2, 2 ]
// sortedQuantityArray = [ 4, 3, 3, 5, 3, 2, 4, 3 ]
let nameArray = ["a", "b", "c", "d", "e", "f", "g", "h"]
let ratingArray = [ 4, 5, 3, 2, 5, 3, 3, 2]
let quantityArray = [ 3, 4, 3, 4, 3, 5, 2, 3]
let firstNameArray = Array(zip(nameArray, ratingArray)).sort { $0.1 > $1.1 }.map { $0.0 }
let firstRatingArray = Array(zip(ratingArray, ratingArray)).sort { $0.1 > $1.1 }.map { $0.0 }
let firstQuantityArray = Array(zip(quantityArray, ratingArray)).sort { $0.1 > $1.1 }.map { $0.0 }
// first sorting based on rating
firstNameArray // = ["b", "e", "a", "c", "f", "g", "d", "h"]
firstRatingArray // = [5, 5, 4, 3, 3, 3, 2, 2]
firstQuantityArray // = [4, 3, 3, 3, 5, 2, 4, 3]
// second sorting based on quantity.
Rather than maintaining three arrays that require synchronization, just maintain a single array of a simple struct.
struct Entry {
let name: String
let rating: Int
let quantity: Int
}
let entries = [ Entry(name: "a", rating: 4, quantity: 3),
Entry(name: "b", rating: 5, quantity: 4),
Entry(name: "c", rating: 3, quantity: 3),
Entry(name: "d", rating: 2, quantity: 4),
Entry(name: "e", rating: 5, quantity: 3),
Entry(name: "f", rating: 3, quantity: 5),
Entry(name: "g", rating: 3, quantity: 2),
Entry(name: "h", rating: 2, quantity: 3),
]
let sortedEntries = entries.sort { lhs, rhs in
if lhs.rating != rhs.rating {
return lhs.rating > rhs.rating
}
return lhs.quantity > rhs.quantity
}
With this, your algorithms should become trivial and require less computation.
If you require separate arrays at some point, it's trivial to unzip it (though I seldom find this necessary in practice):
let nameArray = entries.map { $0.name }

sort a array according to another array, swift

I am wondering is there any build-in function I can sort a array according to another array. For example: sort testStringArray according to testIntArray
var testStringArray = ["a", "b", "c", "d", "e"]
var testIntArray = [21, 3, 43, 5, 1]
After the function, testStringArray will be
testIntArray.sort // [1, 3, 5, 21, 43]
testStringArray // ["e", "b", "d", "a", "c"]
var array1 = ["a", "b", "c", "d", "e"]
var array2 = [21, 3, 43, 5, 1]
let sorted = zip(array1, array2).sort { $0.1 < $1.1 }
array1 = sorted.map { $0.0 }
array2 = sorted.map { $0.1 }
print(array1) // ["e", "b", "d", "a", "c"]
print(array2) // [1, 3, 5, 21, 43]
Something like this? I feel like it can be done better...
EDIT:
This doesn't feel like it's much better...
zip(array1, array2).sort { $0.1 < $1.1 }.forEach {
array1.removeAtIndex(array1.indexOf($0.0)!)
array1.insert($0.0, atIndex: array1.count)
array2.removeAtIndex(array2.indexOf($0.1)!)
array2.insert($0.1, atIndex: array2.count)
}
print(array1) // ["e", "b", "d", "a", "c"]
print(array2) // [1, 3, 5, 21, 43]

Resources