Related
Is there any method to split an array like this?
[1, 2, 3, 4, 5, 6, 7, 8, 9].split(3, 4, 2)
#=> [[1, 2, 3],[4, 5, 6, 7],[8, 9]]
Immutable version with λ:
▶ splitter = ->(array, *parts) do
parts.reduce([[], 0]) do |acc, i|
right = acc.last + i
[acc.first << (acc.last...right), right]
end.first.map { |r| array[r] }
end
#⇒ #<Proc:0x0055ae3d9ae7c8#(pry):18 (lambda)>
▶ splitter.((1..9).to_a, 3, 4, 2)
#⇒ [[1, 2, 3], [4, 5, 6, 7], [8, 9]]
No, there is none, but you can easily write one yourself.
class Array
def in_groups_of_n(*sizes)
sizes.map(&method(:shift))
end
end
Example:
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
arr.in_groups_of_n(3, 4, 2)
# => [[1, 2, 3], [4, 5, 6, 7], [8, 9]]
Demonstration
In case you want a none-destructive version, you can use a dup method:
class Array
def in_groups_of_n(*sizes)
duplicate = dup
sizes.map { |size| duplicate.shift(size) }
end
end
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
arr.in_groups_of_n(3,4,2)
# => [[1, 2, 3], [4, 5, 6, 7], [8, 9]
arr
# => [1, 2, 3, 4, 5, 6, 7, 8, 9]
Demonstration
Here's a naive Array implementation:
class Array
def multi_split(*sizes)
r = []
e = self.each
sizes.each do |size|
t = []
size.times do
t << e.next
end
r << t
end
r
end
end
p [1, 2, 3, 4, 5, 6, 7, 8, 9].multi_split(3, 4, 2)
# [[1, 2, 3], [4, 5, 6, 7], [8, 9]]
#Stefan mentioned it might make sense to implement it on Enumerable:
module Enumerable
def multi_split(*sizes)
Enumerator.new do |yielder|
e = self.each
sizes.each do |size|
yielder << Array.new(size){ e.next }
end
end
end
end
p [1, 2, 3, 4, 5, 6, 7, 8, 9].multi_split(3, 4, 2).to_a
# [[1, 2, 3], [4, 5, 6, 7], [8, 9]]
Another option (lossless in event the splits are not equal to the array size
def split_at(arr,splits)
rest = arr.last(arr.size - splits.reduce(:+))
enum = arr.to_enum
splits.map do |n|
n.times.map { enum.next }
end.concat(rest.empty? ? [] : [rest])
end
Then called as
split_at (1..9), [3,4,2]
#=> [[1, 2, 3], [4, 5, 6, 7], [8, 9]]
split_at (1..22), [3,4,2]
#=> [[1, 2, 3], [4, 5, 6, 7], [8, 9], [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]]
Example
class Array
def split_by_number(*sizes)
sizes.each_with_object([]) { |n,a| a << [a.empty? ? 0 : a.last.sum, n] }.
map { |start, nbr| self[start, nbr] }
end
end
[1, 2, 3, 4, 5, 6, 7, 8, 9].split_by_number 3, 4, 2
#=> [[1, 2, 3], [4, 5, 6, 7], [8, 9]]
Note that
[3, 4, 2].each_with_object([]) { |n,a| a << [a.empty? ? 0 : a.last.sum, n] }
#=> [[0, 3], [3, 4], [7, 2]]
I'm using Ruby 2.4. I'm having trouble creating an array of arrays. I have this
data_cols = [[], []]
lines.each do |line|
parts = [0, *shared_space_indexes, line.size].each_cons(2).map { |a, b| line[a...b].strip }
parts.each_with_index do |part, index|
data_cols[index][data_cols[index] ? data_cols[index].size : 0] = part
end
end
But I get a "NoMethodError: undefined method `[]=' for nil:NilClass" error on the "data_cols[index][data_cols[index] ? data_cols[index].size : 0] = part" line. What I'm trying to do is for each parts array, push each item in "parts" onto its own array correspoding to the index of that element in parts. So, for instance, if the first iteration of the loop has parts equal to
[1, 5, 8, 12]
I would have a data_cols array that looks like
[[1], [5], [8], [12]]
and if the next iteration of the loop had a parts array that looked like
[19, 20, 21, 22]
the data_cols array would then be changed to
[[1, 19], [5, 20], [8, 21], [12, 22]]
It's hard to test without knowing what lines and shared_space_indexes look like.
2 columns → a matrix
zip
For the second part of your question you can use :
[1, 5, 8, 12].zip([19, 20, 21, 22])
#=> [[1, 19], [5, 20], [8, 21], [12, 22]]
transpose
[[1, 5, 8, 12],[19, 20, 21, 22]].transpose
#=> [[1, 19], [5, 20], [8, 21], [12, 22]]
With transpose, both arrays need to have the same size.
3 columns → matrix
array1 = [1, 5, 8, 12]
array2 = [19, 20, 21, 22]
array3 = [99, 88, 77, 66]
p [array1, array2, array3].transpose
#=> [[1, 19, 99], [5, 20, 88], [8, 21, 77], [12, 22, 66]]
columns → rows
If you have an array of arrays :
arrays = [
[1, 5, 8, 12],
[19, 20, 21, 22],
[99, 88, 77, 66]
]
p arrays.transpose
#=> [[1, 19, 99], [5, 20, 88], [8, 21, 77], [12, 22, 66]]
So in your case, I'd just concentrate on creating an array of arrays with the same length, and use transpose at the end.
matrix + column → matrix
As you noticed, if you have
data_cols = [[1, 19], [5, 20], [8, 21], [12, 22]]
arr = [99, 88, 77, 66]
data_cols.zip(arr) would produce a weird half-nested array :
[[[1, 19], 99], [[5, 20], 88], [[8, 21], 77], [[12, 22], 66]]
You can use :
p data_cols.zip(arr).map(&:flatten)
#=> [[1, 19, 99], [5, 20, 88], [8, 21, 77], [12, 22, 66]]
a = [[5, 6], [6, 5], [7, 4], [1, 0, 0], [9, 9], [6, 8], [8, 6], [1, 8, 0], [9, 0]]
How can I sum the integers inside the inner array and return an array of sum of the inner array?
I need it to return [(5+6), (6+5), (7+4), (1+0+0), ...], thus [11, 11, 11, 1, 18, 14, 14, 9, 9]
a.map{|a| a.inject(:+)}
# => [11, 11, 11, 1, 18, 14, 14, 9, 9]
Try
sum_arry = a.map { |sub_arry| sub_arry.inject(&:+) }
Then sum_arry should be the array you want.
Use map + reduce
a.map { |a| a.reduce(:+) }
#board = {1=>0, 2=>0, 3=>0, 4=>0, 5=>0, 6=>0, 7=>0, 8=>"b", 9=>"r", 10=>"u", 11=>"c", 12=>0, 13=>0, 14=>"d", 15=>"h", 16=>"s", 17=>"l", 18=>0, 19=>0, 20=>"o", 21=>"i", 22=>"l", 23=>"b", 24=>0, 25=>0, 26=>"g", 27=>"t", 28=>"f", 29=>"e", 30=>0, 31=>0, 32=>0, 33=>0, 34=>0, 35=>0, 36=>0}
#words = {"shift"=>[16, 15, 21, 28, 27], "bell"=>[23, 29, 22, 17], "curb"=>[11, 10, 9, 8], "dog"=>[14, 20, 26]}
#col1 = [8, 14, 20, 26]
#col2 = [9, 15, 21, 27]
#col3 = [10, 16, 22, 28]
#col4 = [11, 17, 23, 29]
#rcol1 = Array.new
#rcol2 = Array.new
#rcol3 = Array.new
#rcol4 = Array.new
#cols = [#col1, #col2, #col3, #col4]
#rcols0 = [#rcol1, #rcol2, #rcol3, #rcol4]
out = #words["dog"]
out.each do |remove|
#board[remove] = 100
end
def colclone
nums = 0
#rcols0.each do |fixy|
a = #cols[nums]
a.each do |stick|
fixy[nums].push #board[stick]
end
nums += 1
end
end
Here's the actual code. I tried to simplify but I think I must have left something out.
I am working on a board game. What I want is
--> #rcol1 = [0, 1, 2, 3]
what I am getting is those results in my reference array instead
--> #rcols0 = [[0, 1, 2, 3], ...]
Any help would be greatly appreciated!
You have the wrong idea, or the wrong vocabulary, about how it works. names does not store the names of the arrays. names stores a reference to the Array objects. It doesn't matter what they're named.
array1 = [0, 1, 2]
array2 = [10,20,30]
names = [array1, array2]
# names[1] contains a reference to array2
# So this is the same as array2[2]
puts names[1][2] # 30
Your code works fine.
names.each do |update|
update.push 3
update.push 4
end
puts array1.inspect # [0, 1, 2, 3, 4]
Your solution seems to be working:
array1 = [1,2,3]
=> [1, 2, 3]
array2 = [4,5,6]
=> [4, 5, 6]
names = [array1, array2]
=> [[1, 2, 3], [4, 5, 6]]
names.each do |update|
update.push 8
update.push 9
end
=> [[1, 2, 3, 8, 9], [4, 5, 6, 8, 9]]
array1
=> [1, 2, 3, 8, 9]
array2
=> [4, 5, 6, 8, 9]
I think something like this is what you're looking for:
array1 = [0, 1, 2]
array2 = [10, 11, 12]
array3 = [20, 21, 22]
names = [array1, array2, array3]
names.each do |array|
i = array[array.length-1] + 1
array.push(i,i+1)
end
p array1
p array2
p array3
p names
Output for the above is:
[0, 1, 2, 3, 4] #array1
[10, 11, 12 ,13, 14] #array2
[20, 21, 22, 23, 24] #array3
[[0, 1, 2, 3, 4], [10, 11, 12, 13, 14], [20, 21, 22, 23, 24]] #names array
Modify and/or extend the code to suit your needs.
Is there an efficient way to remove subsets from an array of sets
E.g. array of arrays
[[2, 3, 4, 7, 8, 9, 10], [1, 5, 6], [3, 7, 10], [4, 8, 9], [5, 6], [7, 10], [8, 9], [6], [9]]
to output an array
[[2, 3, 4, 7, 8, 9, 10], [1, 5, 6]]
The key is guaranteeing the source sets are sorted in descending order of size. That way all supersets precede their subsets.
Here’s a generic function to do it. You could adapt it to take any kind of sequence of sequence of hashable and convert them to an array of sets on the way in:
func removeSubsets<T: Hashable>(source: [Set<T>]) -> [Set<T>] {
let sets = source.sorted { $0.count > $1.count }
var supersets: [Set<T>] = []
for set in sets {
if !contains(supersets, { set.isSubsetOf($0) }) {
supersets.append(set)
}
}
return supersets
}
removeSubsets([[2, 3, 4, 7, 8, 9, 10], [1, 5, 6], [3, 7, 10], [4, 8, 9], [5, 6], [7, 10], [8, 9], [6], [9]])
// returns [{10, 2, 9, 4, 7, 3, 8}, {5, 6, 1}]
It's still cubic unfortunately since contains is linear and so is isSubsetOf.
EDIT: here's the fully generic version:
func removeSubsets
<S0: SequenceType, S1: SequenceType
where S0.Generator.Element == S1,
S1.Generator.Element: Hashable>
(source: S0) -> [Set<S1.Generator.Element>]
{
let sets = map(source) { Set($0) }.sorted { $0.count > $1.count }
var supersets: [Set<S1.Generator.Element>] = []
for set in sets {
if !contains(supersets, { set.isSubsetOf($0) }) {
supersets.append(set)
}
}
return supersets
}
let a: [[Int]] = [
[2, 3, 4, 7, 8, 9, 10],
[1, 5, 6], [3, 7, 10],
[4, 8, 9], [5, 6],
[7, 10], [8, 9],
[6], [9]]
removeSubsets(a) // returns [{10, 2, 9, 4, 7, 3, 8}, {5, 6, 1}]
EDIT2: if you want the result to be an array of the original arrays (since converting them to sets loses their ordering), you could make the following change, which takes more space but is also slightly more efficient since it only converts the supersets to sets, not the subsets:
func removeSubsets<T: Hashable>(source: [[T]]) -> [[T]] {
// note, this is quite efficient since arrays are copy-on-write,
// so it is only really creating a new array of pointers
let sets = source.sorted { $0.count > $1.count }
var supersets: [Set<T>] = []
var result: [[T]] = []
for set in sets {
if !contains(supersets, { $0.isSupersetOf(set) }) {
supersets.append(Set(set))
result.append(set)
}
}
return result
}
removeSubsets([[2, 3, 4, 7, 8, 9, 10], [1, 5, 6], [3, 7, 10], [4, 8, 9], [5, 6], [7, 10], [8, 9], [6], [9]])
// returns [[2, 3, 4, 7, 8, 9, 10], [1, 5, 6]]
EDIT3: if you want to keep the original order of the sets (just with the subsets removed), you could tag them with a number on the way in before sorting, then re-sort them using it and strip it off the result at the end:
func removeSubsets<T: Hashable>(source: [[T]]) -> [[T]] {
let sets = sorted(enumerate(source)) { $0.1.count > $1.1.count }
var supersets: [Set<T>] = []
var result: [(Int,[T])] = []
for (n,set) in sets {
if !contains(supersets, { $0.isSupersetOf(set) }) {
supersets.append(Set(set))
result.append(n,set)
}
}
return result.sorted { $0.0 < $1.0 }.map { $1 }
}
// note, input not sorted in order of length
removeSubsets([[1, 5, 6], [2, 3, 4, 7, 8, 9, 10], [3, 7, 10], [4, 8, 9], [5, 6], [7, 10], [8, 9], [6], [9]])
// returns [[1, 5, 6], [2, 3, 4, 7, 8, 9, 10]]
Just like with any other (non-2D/set) array you could use an array extension like this ...
extension Array
{
func slice(indices:Int...) -> Array
{
var s = indices[0];
var e = self.count - 1;
if (indices.count > 1)
{
e = indices[1];
}
if (e < 0)
{
e += self.count;
}
if (s < 0)
{
s += self.count;
}
let count = (s < e ? e - s : s - e) + 1;
let inc = s < e ? 1 : -1;
var result = Array();
var idx = s;
for i in 0 ..< count
{
result.append(self[idx]);
idx += inc;
}
return result;
}
}
Usage:
let a = [[2, 3, 4, 7, 8, 9, 10], [1, 5, 6], [3, 7, 10], [4, 8, 9], [5, 6], [7, 10], [8, 9], [6], [9]];
let b = a.slice(0, 1);
let c = a.slice(3);
If your array does not contain duplicated int values, you can convert to Set to use some feature from Swift:
(Take a look at Performing Set Operations)
https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html
Here is my code to get another Array which does not contain subsets. This method is not optimized, it works however.
//let arrayOfArray = [[2, 3, 4, 7, 8, 9, 10], [1, 5, 6], [3, 7, 10], [4, 8, 9], [5, 6], [7, 10], [8, 9], [6], [9]]
//use set instead
var setArray : [Set<Int>] = [[2, 3, 4, 7, 8, 9, 10], [1, 5, 6], [3, 7, 10], [4, 8, 9], [5, 6], [7, 10], [8, 9], [6], [9]]
setArray.sort({$0.count > $1.count}) //sort to have ordered array (biggest set at first)
var result = [Set<Int>]() //you will get your result in this variable.
for _aSet in setArray {
var isSubSet = false
for _exitSet in result {
if _aSet.isSubsetOf(_exitSet) {
isSubSet = true
break;
}
}
if (!isSubSet) {
result.append(_aSet)
}
}
This is the most efficient way I could think of:
let nArrays = [[2, 3, 4, 7, 8, 9, 10], [1, 5, 6], [3, 7, 10], [4, 8, 9], [5, 6], [7, 10], [8, 9], [6], [9]]
nArrays
.reduce([Set<Int>]()) {
accu, el in let setEl = Set(el)
return contains(accu) {setEl.isSubsetOf($0)} ? accu : accu + [setEl]
}
//[{10, 2, 9, 4, 7, 3, 8}, {5, 6, 1}]
Instead of checking if every array is a subset of every other array, you just check if they're a subset of the already checked arrays. Of course, that returns an array of Sets, not an array of Arrays, but you can map() over it to convert it back:
let nArrays = [[2, 3, 4, 7, 8, 9, 10], [1, 5, 6], [3, 7, 10], [4, 8, 9], [5, 6], [7, 10], [8, 9], [6], [9]]
nArrays
.reduce([Set<Int>]()) {
accu, el in let setEl = Set(el)
return contains(accu) {setEl.isSubsetOf($0)} ? accu : accu + [setEl]
}
.map{Array($0)}
//[[10, 2, 9, 4, 7, 3, 8], [5, 6, 1]]
You could do this:
let arrayOfArray = [[2, 3, 4, 7, 8, 9, 10], [1, 5, 6], [3, 7, 10], [4, 8, 9], [5, 6], [7, 10], [8, 9], [6], [9]]
let output = arrayOfArray[0...1]