急勾配の判定
Posted feedbacks - Nested
Flatten Hidden数列の長さに対して線形時間です。
1 2 3 4 | (defun rapidly-decreasing-p (numbers)
(loop for sum = (reduce #'+ (cdr numbers)) then (- sum x)
for x in numbers
always (< sum x)))
|
超増加列の反転かどうかを判定すればいいので、線形時間で出来ました。
1 2 3 4 5 6 7 | (define (super-decreasing? lis)
(let1 l (reverse lis)
(let loop ((l l) (s 0))
(cond ((null? l) #t)
((> (car l) s)
(loop (cdr l) (+ s (car l))))
(else #f)))))
|
OCaml学習中。
1 2 3 4 5 | let rec super_inc n lst = match lst with
[] -> true
| a::rst -> if n < a then (super_inc (n+a) rst) else false
let super_dec lst = f 0 (List.rev lst)
|
訂正。super_dec関数内で呼び出す関数名が間違ってました。 あと、計算量は O(n)です。
1 2 3 4 5 | let rec super_inc n lst = match lst with
[] -> true
| a::rst -> if n < a then (super_inc (n+a) rst) else false;;
let super_dec lst = super_inc 0 (List.rev lst);;
|
$ が多くてうっとうしいけど。
1 2 3 | import List
steep xs = and $ zipWith (>) xs $ map sum $ tail $ tails xs
|
1 | isSteelyList xs = and $ zipWith (>) xs $ tail $ scanl (-) (sum xs) xs
|
Squeak Smalltalk で。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | "ナイーブに"
| 急勾配か? |
急勾配か? := [:配列 |
(1 to: 配列 size - 1) allSatisfy: [:順 | (配列 at: 順) > (配列 allButFirst: 順) sum]].
急勾配か? value: #(). "=> true "
急勾配か? value: #(1). "=> true "
急勾配か? value: #(0). "=> true "
急勾配か? value: #(32 16 8 4 2 1). "=> true "
急勾配か? value: #(32 15 8 4 2 1). "=> false "
"効率に配慮して"
| 急勾配か? |
急勾配か? := [:配列 |
[:exit |
(配列 size to: 1 by: -1) inject: 0 into: [:和 :順 |
| 要素 |
要素 := 配列 at: 順.
要素 > 和 ifFalse: [exit value]. 和 + 要素]
] valueWithExit notNil].
急勾配か? value: #(). "=> true "
急勾配か? value: #(1). "=> true "
急勾配か? value: #(0). "=> false "
急勾配か? value: #(32 16 8 4 2 1). "=> true "
急勾配か? value: #(32 15 8 4 2 1). "=> false "
|
コードは素直に書いてみました。たぶんO(n)のはず。OO的述語関数の表現としてbooleanの読み取り専用プロパティにしてみました。
動作確認は以下のようにしています。
>|groovy|
assert [].is急勾配()
assert [0].is急勾配()
assert [1,0].is急勾配()
assert ! [0,1].is急勾配()
assert [4,2,1].is急勾配()
assert ! [3,2,1].is急勾配()
||<
1 2 3 4 5 | List.metaClass.is急勾配 = {->
delegate.size() <= 1?
true:
delegate[1..-1].is急勾配() && delegate[1..-1].sum() < delegate[0]
}
|
違った。毎回sum()してるから自乗オーダーでした。 もうちょっと手を入れなきゃ。
とりあえず急勾配な場合でもO(n)。かっこわるいです。 一般的にはもっと良いはず。ランダムな数列において後ろから見たときに急勾配の条件に合わない値が出てくる確率ってどのくらいなんでしょう。(考えるのめんどくさい)
1 2 3 4 5 6 7 8 9 10 | List.metaClass.is急勾配 {->
def sum
for (val in delegate.reverse()) {
if (sum != null && val <= sum) {
return false
}
sum = (sum?:0) + val
}
return true
}
|
仕様面ではっきりしないのは、
1.空リストは急勾配? not 急勾配?
2.問題文の「空列の和は0とします」をどう読むか。
[0] は 0 + 空列 なので、not 急勾配になる?
(sum の初期値を 0 とするか -∞ にするかというだけの話だけど)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | Array.prototype.isHeavySlope = function() {
var sum = 0;
for (var i = this.length-1; i >= 0; i--) {
if (this[i] <= sum) return false;
sum += this[i];
}
return true;
}
>>> [].isHeavySlope()
true
>>> [0].isHeavySlope()
false
>>> [1,0].isHeavySlope()
false
>>> [0,1].isHeavySlope()
false
>>> [4,2,1].isHeavySlope()
true
>>> [3,2,1].isHeavySlope()
false
|
てことは [0] は急勾配じゃないということでFA?
後ろから見るほうがシンプルだけど、リストの長さが未確定の場合はこちらが有利。
ちなみに [1,0].isHeavySlope() は true になりますが何か?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Array.prototype.isHeavySlope = function() {
if (this.length == 0) return true;
var pre, sub;
for each (var n in this) {
if (pre == undefined) {
sub = pre = n;
} else {
sub -= n;
if (pre <= n || sub <= 0) return false;
pre = n;
}
}
return true;
}
|
O(n)ですね。速くなりませんかね? でも、どうせ全部チェックしなきゃいけないんだから、O(n)が最速でしょうね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | using System;
static class P
{
static void Main(string[] args)
{
int[] array = { 1, 2, 7, 11, 25 };
Console.WriteLine("急勾配で{0}", IsAggressive(array) ? "す" : "はありません");
}
static bool IsAggressive(int[] array)
{
int sum = 0;
for (int i = 0; i < array.Length; i++)
{
if (sum >= array[i])
return false;
else
sum += array[i];
}
return true;
}
}
|
効率優先で。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | object SteepAngle {
def isSteep(l:List[Int]):Boolean = l match {
case List() => true
case _ => {
def loop(s:Int, r:List[Int]):Boolean = r match {
case List() => true
case x::xs if x > s => loop(s + x, xs)
case _ => false
}
loop(l.last, l.reverse.tail)
}
}
def main(args:Array[String]):Unit =
try {
isSteep(args.toList.map(_.toInt)) match {
case true => println("steep")
case _ => println("not steep")
}
} catch {
case e => println("usage: SteepAngle NUMBERS")
}
}
|
素直に書いてみました。
1 2 3 4 5 6 7 | class SteepAngle
def self.steep?(l)
(l.size < 2) || (0...l.size - 1).all? { |i| l[i] > l[i + 1...l.size].inject(0) { |r,e| r + e } }
end
end
puts SteepAngle.steep?(ARGV.map { |p| p.to_i }) ? "steep" : "not steep"
|
mapAccumの遅延評価が良くわかりませんが...
1 2 3 | import Data.List (mapAccumR)
isSteep = and . snd . mapAccumR (\acc x -> (acc+x, x > acc)) 0
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import Maybe
isKyukoubai xs = isJust $ foldr f (Just 0) xs where
f x b = do
s <- b
if (x > s) then Just (s+x) else Nothing
{-
> isKyukoubai [4,2,1]
True
> isKyukoubai [2,2,1]
False
> isKyukoubai [undefined,2,2,1]
False
-}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def super_decreasing?(a)
r = a.reverse
while r[1] && (w = r[0] < r[1])
r[1] += r[0]
r.shift
end
w ? "yes. super_decreasing." : "not super_decreasing."
end
p super_decreasing?([8, 4, 2, 1]) # => yes..
p super_decreasing?([8, 3, 2, 1]) # => not..
p super_decreasing?([1]) # => not..
p super_decreasing?([]) # => not..
p super_decreasing?([1, -4, -5]) # => yes..
|
Cで普通に。 O(n)です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #include <stdio.h>
static int is_kyuukoubai(int* a, int num)
{
int i;
int sum=0;
for(i=num-1; i>=0; i--)
{
if (a[i]<=sum) return 0;
sum+=a[i];
}
return 1;
}
int main()
{
int test[5] = { 20, 10, 5, 3, 1 };
if (is_kyuukoubai(test, 5)) printf("kyuukoubai.\n");
else printf("not kyuukoubai.\n");
return 0;
}
|
foreachの方が分かりやすかったかも。 計算量はΘ(n)のはずです。
1 2 3 4 5 | use List::MoreUtils qw/all/;
sub is_super_dec(@) {
my $total = 0;
all { ($_ > $total) and do { $total += $_; 1 } } reverse @_;
}
|
ありゃしまった、急勾配でなければもっと早く返るからO(n)ではあってもΘ(n)じゃないですね。
1 2 3 4 5 6 7 | isKyukoubai xs = f [] xs where
f ys (x:xs) = f (x:ys) xs
f ys [] = g 0 ys
g s (y:ys)
| y > s = g (s+y) ys
| otherwise = False
g _ [] = True
|
思いっきりナイーブに。
強いて特徴を言うなら,エラーチェックやオーバーフロー対策がしてあるくらい?
1 2 3 4 5 6 7 8 9 | public static boolean isSteep(int[] nums) {
if (nums == null || nums.length == 0) return false;
long sum = 0L;
for (int i = nums.length - 1; i >= 0; i--) {
if (nums[i] <= sum) return false;
sum += nums[i];
}
return true;
}
|
効率や末尾再帰や遅延評価や見易さやら考えてたら #8896 の reverse版になりました。
1 2 | isKyukoubai xs = and $ zipWith (>) ys $ scanl (+) 0 ys where
ys = reverse xs
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | #include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
template <typename T>
struct not_steep_slope {
T sum_;
not_steep_slope() : sum_(0) {}
bool operator()(T v) {
if (v <= sum_) return true;
sum_+=v;
return false;
}
};
template <typename T>
bool is_steep_slope(const std::vector<T>& v) {
return std::find_if(v.rbegin(), v.rend(), not_steep_slope<T>()) == v.rend();
}
template <typename T>
void check(const std::vector<T>& v) {
std::cout << "(";
std::copy(v.begin(), v.end(), std::ostream_iterator<T>(std::cout, ","));
std::cout << ") => " << is_steep_slope(v) << "\n";
}
int main() {
std::cout.setf( std::ios::boolalpha );
std::vector<int> v;
check(v);
v.push_back(1);
check(v);
v.front() = 0;
check(v);
v.clear();
v.push_back(32);
v.push_back(16);
v.push_back(8);
v.push_back(4);
v.push_back(2);
v.push_back(1);
check(v);
v[1] = 15;
check(v);
return 0;
}
|
組み込み関数reversedのオーダは分かりませんが、少なくともそれ以外の部分は要素数について、線形時間です。
1 2 3 4 5 6 7 8 9 10 11 12 13 | #!/usr/bin/python
def checkSteep(target):
li = reversed(target)
sum = 0
for x in li:
if x <= sum: return False
sum += x
return True
if __name__ == '__main__':
print 'Steep' if checkSteep([32, 16, 8, 4, 2, 1]) else 'not Steep' # => Steep
print 'Steep' if checkSteep([32, 15, 8, 4, 2, 1]) else 'not Steep' # => not Steep
|
1 2 | def super_decreasing(l):
return all(l[i] > sum(l[i+1:]) for i in xrange(0, len(l)-1))
|
cumsum の結果をキャッシュしておけば線形になります。
1 | is.superdecreasing <- function(l) all(l > rev(cumsum(rev(l)))-l)
|
1 2 3 | sd([],_) :- !.
sd([X|Xs],S) :- X > S, S1 is S + X, sd(Xs,S1).
super_decreasing(Xs) :- reverse(Xs,Ys), sd(Ys,0).
|
累積値のリストをつくって元のリストと比較しているので 効率は良くないと思います。 ]data1=: 2^i._15 16384 8192 4096 2048 1024 512 256 128 64 32 16 8 4 2 1 ]data2=: 100(0)}2^i._15 100 8192 4096 2048 1024 512 256 128 64 32 16 8 4 2 1 f data1 1 f data2 0
1 | f=:*/@(>(-~+/\.))
|
LINQを使って無難に判定。コレは 3n だから O(n)になるのかな。
Aggregateとかを使おうと思ったんですが、結局はsumを外出しにするのと、Selectで十分だと気づきました。
1 2 3 4 5 6 7 8 9 | static bool IsAggressive(int[] list)
{
int sum = list.Sum();
return list.Reverse().Select(num =>
{
sum -= num;
return sum - num;
}).Any(num => num < 0);
}
|
1 2 3 4 | let isSteep ls =
List.fold_right (fun x (result, sum) ->
if result && x > sum then (true, sum + x) else (false, sum)
) ls (true, 0) |> fst
|
1 2 3 4 5 6 7 8 9 10 11 12 13 | #! /usr/bin/ruby
class Array
def steep?
reverse.inject(0) do |sum, previous|
return false unless previous > sum
sum += previous
end
true
end
end
p [5, 3, 2].steep?
|
1 2 3 4 5 6 7 8 9 10 11 | #!/usr/bin/perl
use strict;
use warnings;
sub is_steep_array{
my @arr = reverse @_;
my $sum = 0;
$sum < $_ ? $sum += $_ : return () for @arr;
1;
}
print 'true' if is_steep_array(9,4,2,1);
|
後ろから見ていくことで、効率的になっていますね。 pop使えば、reverseする手間も省けていいんじゃないかなーと思います。
1 2 3 4 | sub isSteep2{
my($x,$q) = 0;
$x < ($q = pop||return 1) ? $x += $q : return 0 while 1;
}
|
#8973のボトルネックはreverseした値を配列にコピーしているところですね。
for(each)に直接渡すと最適化されるのでpopよりずっと速いです。以下ベンチマーク。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | use Benchmark qw/cmpthese/;
my @steep = map { 2 ** $_ } reverse 0 .. 49;
cmpthese(100_000,
{ pop => sub { is_steep0(@steep) },
rev_cp => sub { is_steep1(@steep) },
rev => sub { is_steep2(@steep) },
nk_pop => sub { is_steep3(@steep) },
sk_rev => sub { is_steep4(@steep) } });
sub is_steep0 {
my $total = 0;
while (my $i = pop) {
return unless $total < $i;
$total += $i;
}
return 1;
}
sub is_steep1 {
my $total = 0;
my @rev = reverse @_;
for my $i (@rev) {
return unless $i > $total;
$total += $i;
}
return 1;
}
sub is_steep2 {
my $total = 0;
for my $i (reverse @_) {
return unless $i > $total;
$total += $i;
}
return 1;
}
# http://ja.doukaku.org/comment/9251/
sub is_steep3 {
my ($x,$q) = 0;
$x < ($q = pop||return 1) ? $x += $q : return 0 while 1;
}
sub is_steep4 {
my $total = 0;
$total < $_ ? $total += $_ : return 0 for reverse @_;
return 1;
}
|
SQL Server 2008 で確認しました。 SQL で関数というと、ストアドファンクションとかになると思うんですけど、入力に対して出力がありさえすれば関数と言うことで、SELECT オンリーで書いてみました。
肝心のロジック部分は、もっといいやり方はあると思います。 3段階SELECTがネストしてるんで、効率は・・・
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | WITH
Input(i, n) AS (
SELECT 1, 100
UNION ALL
SELECT 2, 50
UNION ALL
SELECT 3, 10
)
, HeavySlopes(col) AS (
SELECT
1
FROM
Input P
WHERE
EXISTS(
SELECT * FROM Input C
WHERE P.i < C.i AND P.n > (SELECT SUM(GC.n) FROM Input GC WHERE P.i < GC.i)
)
)
, IsHeavySlope(result) AS (
SELECT
CASE (SELECT COUNT(*) FROM Input)
WHEN COUNT(*) + 1 THEN 1
ELSE 0
END
FROM
HeavySlopes
)
SELECT * FROM IsHeavySlope
|
畳み込みを使ったほうが綺麗ですが、Rubyの畳み込み(inject)は左から(foldl相当)なので、reverseする分重くなりそうですね。
というわけで、普通に実装してみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Array
def steep?
sum = 0
(self.size - 2).downto(0) do |index|
if self[index] <= (sum += self[index + 1])
return false
end
end
true
end
end
p [100, 50, 25, 12, 6, 3, 1].steep?
|
与えられたrangeの急勾配性を判定します.
rangeが後ろから手繰れる場合はO(n) time, O(1) space,そうでない場合はO(n) time, O(n) spaceです.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | module doukaku248;
import std.algorithm, std.array, std.range, std.traits;
bool isSteep(Range)(Range r) if(isInputRange!(Range)) {
static if(isInfinite!(Range)) {
static assert(false, "steepness of infinite ranges cannot be determined.");
} else static if(isBidirectionalRange!(Range)) {
if(!r.empty) {
auto sum = r.back;
r.popBack;
foreach_reverse(e; r) {
if(e <= sum)
return false;
sum += e;
}
}
return true;
} else {
return isSteep(toArray(r));
}
}
ElementType!(Range)[] toArray(Range)(Range r) if(isInputRange!(Range)) {
static if(isImplicitlyConvertible!(Range, typeof(return))) {
return r;
} else static if(isInfinite!(Range)) {
static assert(false, "infinite ranges cannot be converted to arrays.");
} else {
typeof(return) a;
static if(isForwardRange!(Range)) {
a.length = walkLength(r);
size_t i = 0;
foreach(e; r) {
a[i++] = e;
}
} else {
foreach(e; r) {
a ~= e;
}
}
return a;
}
}
|
計算量(という表現でいいかは別として、)は一律に要素数と一致するはず。たぶん。
Tabが一律4半角スペースなのでインデントがおかしいですけどそこはスルーでお願いします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #!/usr/local/bin/perl
use strict;
sub isSteep{
my $x = shift||return 1,0;
my @y = isSteep(@_);
return $y[0]&&$x>$y[1]?1:0,$y[1]+$x;
}
my @a1 = (\@{[100, 30, 10, 4, 1]},
\@{[10, 30, 100]},
\@{[10, 4, 2, 1]},
\@{[10, 5, 4, 2]});
print join(', ',@{$_}),' is',((isSteep(@{$_}))[0]?'':'n\'t'),' a steep array.',$/ for@a1;
|
後ろから反復してみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #module
#define true 1
#define false 0
#defcfunc IsSteepSlope array arr, \
local size, local nowSum, local bResult
size = length(arr)
nowSum = 0
bResult = true
repeat size, 1
if ( cnt == 1 || nowSum < arr(size - cnt) ) {
nowSum += arr(size - cnt)
} else {
bResult = false
break
}
loop
return bResult
#global
list = 128, 64, 32, 16, 8, 4
mes IsSteepSlope(list)
stop
|
Perlの方々の回答を参考にしました
1 2 3 4 5 6 7 8 9 10 11 12 | function is_super_dec( [array] $super_array )
{
$total = 0
foreach( $x in reverse $super_array )
{
if( $x -le $total ){ return 0 }
$total += $x
}
return 1
}
is_super_dec @( 512,256,128,64,32,16,8,4,2,1 )
|






nobsun
#8891()
Rating1/1=1.00
有限の長さの数列で,各要素の値が,その要素の後ろにある残りの列に含まれるすべての要素の和よりも大きい列を「急勾配の列」ということにします(空列の和は0とします).
任意の長さ(ただし有限の長さの)数列を与えられたとき,それが「急勾配の列」であるかどうかを判定する述語関数を定義してください.
必須ではありませんが,効率についてコメントがあれば面白いかもしれませんね.
[ reply ]